From 30c24998be1cad9d3c709bb734d1d478734686bd Mon Sep 17 00:00:00 2001 From: Gregory Eremin Date: Sun, 18 Jan 2015 17:50:03 +0700 Subject: [PATCH] Initial commit --- .gitignore | 0 README.md | 1 + config.go | 121 +++++++++++++++++++++++++++++++++++++++++++++++++ config_file.go | 60 ++++++++++++++++++++++++ demo/demo.go | 35 ++++++++++++++ manager.go | 46 +++++++++++++++++++ server.go | 33 ++++++++++++++ 7 files changed, 296 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 config.go create mode 100644 config_file.go create mode 100644 demo/demo.go create mode 100644 manager.go create mode 100644 server.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..236ab23 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Confection diff --git a/config.go b/config.go new file mode 100644 index 0000000..59aff06 --- /dev/null +++ b/config.go @@ -0,0 +1,121 @@ +package confection + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +type ( + config struct { + config interface{} + } + configField struct { + Path string `json:"path"` + IsRequired bool `json:"is_required"` + IsReadonly bool `json:"is_readonly"` + Title string `json:"title"` + Description string `json:"description"` + Options []string `json:"options"` + } +) + +const ( + tJson = "json" + tTitle = "title" + tDescription = "description" + tAttrs = "attrs" + tOptions = "options" + aRequired = "required" + aReadonly = "readonly" + sep = "," +) + +func (c *config) dump() ([]byte, error) { + var ( + out bytes.Buffer + b []byte + err error + ) + + if b, err = json.Marshal(c.config); err != nil { + return nil, err + } + // Indent with empty prefix and four spaces + if err = json.Indent(&out, b, "", " "); err != nil { + return nil, err + } + + return out.Bytes(), nil +} + +func (c *config) load(b []byte) (err error) { + return +} + +// TODO: function draft, needs refactor +func (c *config) meta(prefix string) []*configField { + var ( + fields = []*configField{} + cval = reflect.ValueOf(c.config) + typ = reflect.TypeOf(c.config) + kind = cval.Kind() + ) + + if kind != reflect.Struct { + panic(fmt.Errorf("Config is expected to be a Struct, not %s", kind.String())) + } + + for i := 0; i < cval.NumField(); i++ { + var ( + field = typ.Field(i) + val = cval.Field(i) + + jsonKey = field.Tag.Get(tJson) + path = strings.Join([]string{prefix, jsonKey}, "/") + title = field.Tag.Get(tTitle) + description = field.Tag.Get(tDescription) + attrs = strings.Split(field.Tag.Get(tAttrs), sep) + options = strings.Split(field.Tag.Get(tOptions), sep) + + cf = &configField{ + Path: path, + Title: title, + Description: description, + } + ) + + // Skip field if no tags are set + if title == "" && len(attrs) == 0 && len(options) == 0 { + continue + } + + // Substitute field name for title if none set + if title == "" { + cf.Title = field.Name + } + + for _, attr := range attrs { + if attr == aRequired { + cf.IsRequired = true + } + if attr == aReadonly { + cf.IsReadonly = true + } + } + + fields = append(fields, cf) + + // Recursion here + if val.Kind() == reflect.Struct { + subconf := &config{ + config: val.Interface(), + } + fields = append(fields, subconf.meta(path)...) + } + } + + return fields +} diff --git a/config_file.go b/config_file.go new file mode 100644 index 0000000..92c060a --- /dev/null +++ b/config_file.go @@ -0,0 +1,60 @@ +package confection + +import ( + "fmt" + "io/ioutil" + "os" + "path" +) + +type ( + configFile struct { + path string + } +) + +func (cf *configFile) write(b []byte) (err error) { + var ( + fd *os.File + n int // Bytes written successfully + ) + + if cf.isExist() { + if err = cf.mkdirp(); err != nil { + return + } + + fd, err = os.Create(cf.path) + } else { + fd, err = os.Open(cf.path) + } + if err != nil { + return + } + defer fd.Close() + + n, err = fd.Write(b) + if err == nil && n != len(b) { + return fmt.Errorf("Failed to write config file: written %d/%d bytes", n, len(b)) + } + + return +} + +func (cf *configFile) read() ([]byte, error) { + if cf.isExist() { + return ioutil.ReadFile(cf.path) + } else { + return nil, fmt.Errorf("Config file does not exist") + } +} + +func (cf *configFile) isExist() bool { + _, err := os.Stat(cf.path) + return os.IsExist(err) +} + +func (cf *configFile) mkdirp() error { + dir := path.Dir(cf.path) + return os.MkdirAll(dir, 0755) +} diff --git a/demo/demo.go b/demo/demo.go new file mode 100644 index 0000000..f2cb5af --- /dev/null +++ b/demo/demo.go @@ -0,0 +1,35 @@ +package main + +import ( + "flag" + + "github.com/localhots/confection" +) + +type ( + Config struct { + AppName string `json:"app_name" attrs:"required" title:"Application Name"` + BuildNumber int `json:"build_number" attrs:"readonly" title:"Build Number"` + EnableSignIn bool `json:"enable_sign_in" title:"Enable Sign-In"` + DatabaseDriver string `json:"database_driver" title:"Database Driver" options:"mysql,postgresql,mssql"` + DatabaseConfig DatabaseConfig `json:"database_config"` + } + DatabaseConfig struct { + Hostname string `json:"hostname"` + Port int `json:"port"` + Username string `json:"username"` + Password string `json:"password"` + Database string `json:"database" attrs:"required"` + } +) + +func main() { + confection.Setup() + flag.Parse() + + conf := Config{ + DatabaseConfig: DatabaseConfig{}, + } + manager := confection.New(conf) + manager.StartServer() +} diff --git a/manager.go b/manager.go new file mode 100644 index 0000000..be3aa45 --- /dev/null +++ b/manager.go @@ -0,0 +1,46 @@ +package confection + +import ( + "flag" + "sync" +) + +type ( + Manager struct { + mux *sync.Mutex + conf *config + file *configFile + } +) + +var ( + configPath string + serverPort int +) + +func Setup() { + flag.StringVar(&configPath, "config", "config.json", "Path to config file") + flag.IntVar(&serverPort, "config-port", 5050, "Config manager http port") +} + +func New(conf interface{}) *Manager { + mgr := &Manager{ + mux: &sync.Mutex{}, + conf: &config{ + config: conf, + }, + file: &configFile{ + path: configPath, + }, + } + + return mgr +} + +func (m *Manager) StartServer() { + srv := &server{ + manager: m, + port: serverPort, + } + srv.start() +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..1d8773b --- /dev/null +++ b/server.go @@ -0,0 +1,33 @@ +package confection + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" +) + +type ( + server struct { + manager *Manager + port int + } +) + +func (s *server) start() { + portStr := ":" + strconv.Itoa(s.port) + + fmt.Println("Configuration server is available at http://127.0.0.1" + portStr) + if err := http.ListenAndServe(portStr, s); err != nil { + panic(err) + } +} + +func (s *server) ServeHTTP(w http.ResponseWriter, req *http.Request) { + jsn, err := json.Marshal(s.manager.conf.meta("")) + if err != nil { + panic(err) + } + + w.Write(jsn) +}