diff --git a/README.md b/README.md index d1112f1..cc11db8 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ * [x] Middleware support. All [alice](https://github.com/justinas/alice) compatible Middleware works out of the box * [x] Gopher spirit (write golang, use all the golang libraries you like) * [x] Lightweight. Only MVC -* [x] Multiple configuration files support (currently json, yaml and toml) +* [x] Multiple configuration files support (currently json, yaml, toml and hcl) @@ -88,7 +88,7 @@ I have included three configuration files to show how they work, but you are bet ## Configurations utron support yaml, json and toml configurations files. In our todo app, we put the configuration files in the config directory. I have included all three formats for clarity, you can be just fine with either one of them. -`utron` searches for a file named `app.json`, or `app.yml` or `app.toml` in the config directory. The first to be found is the one to be used. +`utron` searches for a file named `app.json`, or `app.yml`, `app.toml`, `app.hcl` in the config directory. The first to be found is the one to be used. This is the content of `config/app.json` file: diff --git a/config.go b/config.go index 27e1b3c..597630d 100644 --- a/config.go +++ b/config.go @@ -14,20 +14,21 @@ import ( "github.com/BurntSushi/toml" "github.com/fatih/camelcase" + "github.com/hashicorp/hcl" "gopkg.in/yaml.v2" ) // Config stores configurations values type Config struct { - AppName string `json:"app_name" yaml:"app_name" toml:"app_name"` - BaseURL string `json:"base_url" yaml:"base_url" toml:"base_url"` - Port int `json:"port" yaml:"port" toml:"port"` - Verbose bool `json:"verbose" yaml:"verbose" toml:"verbose"` - StaticDir string `json:"static_dir" yaml:"static_dir" toml:"static_dir"` - ViewsDir string `json:"view_dir" yaml:"view_dir" toml:"view_dir"` - Database string `json:"database" yaml:"database" toml:"database"` - DatabaseConn string `json:"database_conn" yaml:"database_conn" toml:"database_conn"` - Automigrate bool `json:"automigrate" yaml:"automigrate" toml:"automigrate"` + AppName string `json:"app_name" yaml:"app_name" toml:"app_name" hcl:"app_name"` + BaseURL string `json:"base_url" yaml:"base_url" toml:"base_url" hcl:"base_url"` + Port int `json:"port" yaml:"port" toml:"port" hcl:"port"` + Verbose bool `json:"verbose" yaml:"verbose" toml:"verbose" hcl:"verbose"` + StaticDir string `json:"static_dir" yaml:"static_dir" toml:"static_dir" hcl:"static_dir"` + ViewsDir string `json:"view_dir" yaml:"view_dir" toml:"view_dir" hcl:"view_dir"` + Database string `json:"database" yaml:"database" toml:"database" hcl:"database"` + DatabaseConn string `json:"database_conn" yaml:"database_conn" toml:"database_conn" hcl:"database_conn"` + Automigrate bool `json:"automigrate" yaml:"automigrate" toml:"automigrate" hcl:"automigrate"` } // DefaultConfig returns the default configuration settings. @@ -47,6 +48,7 @@ func DefaultConfig() *Config { // * .json - is decoded as json // * .yml - is decoded as yaml // * .toml - is decoded as toml +// * .hcl - is decoded as hcl func NewConfig(path string) (*Config, error) { _, err := os.Stat(path) if err != nil { @@ -75,7 +77,14 @@ func NewConfig(path string) (*Config, error) { if yerr != nil { return nil, yerr } - + case ".hcl": + obj, herr := hcl.Parse(string(data)) + if herr != nil { + return nil, herr + } + if herr = hcl.DecodeObject(&cfg, obj); herr != nil { + return nil, herr + } default: return nil, errors.New("utron: config file format not supported") } @@ -110,7 +119,6 @@ func (c *Config) saveToFile(path string) error { return err } data = b.Bytes() - } return ioutil.WriteFile(path, data, 0600) } diff --git a/config_test.go b/config_test.go index ffbe490..0f65e4c 100644 --- a/config_test.go +++ b/config_test.go @@ -10,6 +10,7 @@ func TestConfig(t *testing.T) { "fixtures/config/app.json", "fixtures/config/app.yml", "fixtures/config/app.toml", + "fixtures/config/app.hcl", } cfg := DefaultConfig() diff --git a/fixtures/config/app.hcl b/fixtures/config/app.hcl new file mode 100644 index 0000000..55ab6d9 --- /dev/null +++ b/fixtures/config/app.hcl @@ -0,0 +1,9 @@ +app_name = "utron web app" +base_url = "http://localhost:8090" +port = 8090 +verbose = false +static_dir = "fixtures/static" +view_dir = "fixtures/view" +database = "" +database_conn = "" +automigrate = true \ No newline at end of file diff --git a/fixtures/config/routes.hcl b/fixtures/config/routes.hcl new file mode 100644 index 0000000..88874c8 --- /dev/null +++ b/fixtures/config/routes.hcl @@ -0,0 +1,4 @@ +routes = [ + "get,post;/hello;Sample.Hello", + "get,post;/about;Hello.About" +] \ No newline at end of file diff --git a/routes.go b/routes.go index dadddd2..3dc2707 100644 --- a/routes.go +++ b/routes.go @@ -14,6 +14,7 @@ import ( "github.com/BurntSushi/toml" "github.com/gernest/ita" "github.com/gorilla/mux" + "github.com/hashicorp/hcl" "github.com/justinas/alice" "gopkg.in/yaml.v2" ) @@ -415,7 +416,7 @@ type routeFile struct { // ] // } // -// supported formats are json,toml and yaml with extension .json, .toml and .yml respectively. +// supported formats are json, toml, yaml and hcl with extension .json, .toml, .yml and .hcl respectively. // //TODO refactor the decoding part to a separate function? This part shares the same logic as the // one found in NewConfig() @@ -441,6 +442,14 @@ func (r *Router) LoadRoutesFile(file string) error { if err != nil { return err } + case ".hcl": + obj, err := hcl.Parse(string(data)) + if err != nil { + return err + } + if err = hcl.DecodeObject(&rFile, obj); err != nil { + return err + } default: return errors.New("utron: unsupported file format") } @@ -461,8 +470,9 @@ func (r *Router) LoadRoutesFile(file string) error { // * routes.json // * routes.toml // * routes.yml +// * routes.hcl func (r *Router) loadRoutes(cfgPath string) { - exts := []string{".json", ".toml", ".yml"} + exts := []string{".json", ".toml", ".yml", ".hcl"} rFile := "routes" for _, ext := range exts { file := filepath.Join(cfgPath, rFile+ext) diff --git a/routes_test.go b/routes_test.go index bc03ec5..8c8cf93 100644 --- a/routes_test.go +++ b/routes_test.go @@ -92,31 +92,39 @@ func TestRouteField(t *testing.T) { } func TestRoutesFile(t *testing.T) { - file := "fixtures/config/routes.json" - r := NewRouter() - err := r.LoadRoutesFile(file) - if err != nil { - t.Error(err) + routeFiles := []string{ + "fixtures/config/routes.json", + "fixtures/config/routes.hcl", } - if len(r.routes) != 2 { - t.Errorf("expcted 2 got %d", len(r.routes)) - } - _ = r.Add(NewSample()) - req, err := http.NewRequest("GET", "/hello", nil) - if err != nil { - t.Error(err) - } - w := httptest.NewRecorder() - r.ServeHTTP(w, req) + for _, file := range routeFiles { + r := NewRouter() - if w.Code != http.StatusOK { - t.Errorf("expected %d got %d", http.StatusOK, w.Code) - } - if w.Body.String() != msg { - t.Errorf("expected %s got %s", msg, w.Body.String()) + err := r.LoadRoutesFile(file) + if err != nil { + t.Error(err) + } + if len(r.routes) != 2 { + t.Errorf("expcted 2 got %d", len(r.routes)) + } + _ = r.Add(NewSample()) + + req, err := http.NewRequest("GET", "/hello", nil) + if err != nil { + t.Error(err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Errorf("expected %d got %d", http.StatusOK, w.Code) + } + if w.Body.String() != msg { + t.Errorf("expected %s got %s", msg, w.Body.String()) + } } + } func TestSplitRoutes(t *testing.T) { diff --git a/utron.go b/utron.go index 08c2c5a..d637404 100644 --- a/utron.go +++ b/utron.go @@ -155,7 +155,7 @@ func loadConfig(cfg ...string) (*Config, error) { // findConfigFile finds the configuration file name in the directory dir. func findConfigFile(dir string, name string) (file string, err error) { - extensions := []string{".json", ".toml", ".yml"} + extensions := []string{".json", ".toml", ".yml", ".hcl"} for _, ext := range extensions { file = filepath.Join(dir, name)