diff --git a/README.md b/README.md index 3f1f75a..d5b265e 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ second { key = "value"; global_reference = sub_settings.sub_float; local_reference = .key; # References second.key + + include "/path/to/other/settings/*.cfg"; } ``` @@ -43,6 +45,11 @@ Sections (basically a map) is formatted as the section name with the section's s Comments start with a pound sign `#` and end with a newline. A comment can exist on the same line as settings/sections, but the comment must end the line. +Includes are allowed simply by using the directive `include` followed by a string pointing to the location of the file(s) you want to include. +`include` uses go's [filepath.Match](http://golang.org/pkg/path/filepath/#Glob) functionality to find all files matching the provided pattern. +Each file is included directly where it's `include` statement is called from. +`include "/etc/app/*.cfg";` + ## Data types ### Boolean diff --git a/config/section.go b/config/section.go index b3fd8ea..1d129cd 100644 --- a/config/section.go +++ b/config/section.go @@ -6,6 +6,7 @@ type SectionValue struct { Name string Value map[string]ConfigValue Comments []string + Includes []string } func NewNamedSection(name string) SectionValue { @@ -13,6 +14,7 @@ func NewNamedSection(name string) SectionValue { Name: name, Value: make(map[string]ConfigValue), Comments: make([]string, 0), + Includes: make([]string, 0), } } @@ -20,6 +22,7 @@ func NewAnonymousSection() SectionValue { return SectionValue{ Value: make(map[string]ConfigValue), Comments: make([]string, 0), + Includes: make([]string, 0), } } @@ -30,6 +33,10 @@ func (this SectionValue) AddComment(comment string) { this.Comments = append(this.Comments, comment) } +func (this SectionValue) AddInclude(include string) { + this.Includes = append(this.Includes, include) +} + func (this SectionValue) Set(name string, value ConfigValue) { this.Value[name] = value } diff --git a/example/example.cfg b/example/example.cfg index 362a18b..f5bb5d0 100644 --- a/example/example.cfg +++ b/example/example.cfg @@ -8,6 +8,8 @@ primary { boolean = true; negative = FALSE; nothing = NULL; + # Include external files + include "./include*.cfg"; # Primary-sub stuff sub { key = "primary sub key value"; diff --git a/parser/parser.go b/parser/parser.go index 3c99ad9..87102aa 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "path/filepath" "strconv" "strings" @@ -167,7 +168,7 @@ func (this *Parser) parseSetting(name string) error { read_next = false default: return this.SyntaxError( - fmt.Sprintf("expected STRING, INTEGER or FLOAT, instead found %s", this.cur_tok.ID), + fmt.Sprintf("expected STRING, INTEGER, FLOAT, BOOLEAN or IDENTIFIER, instead found %s", this.cur_tok.ID), ) } @@ -184,6 +185,39 @@ func (this *Parser) parseSetting(name string) error { return nil } +func (this *Parser) parseInclude() error { + if this.cur_tok.ID != token.STRING { + msg := fmt.Sprintf("expected STRING instead found '%s'", this.cur_tok.ID) + return this.SyntaxError(msg) + } + pattern := this.cur_tok.Literal + + this.readToken() + if this.cur_tok.ID != token.SEMICOLON { + msg := fmt.Sprintf("expected ';' instead found '%s'", this.cur_tok.Literal) + return this.SyntaxError(msg) + } + + filenames, err := filepath.Glob(pattern) + if err != nil { + return err + } + old_tokenizer := this.tokenizer + for _, filename := range filenames { + reader, err := os.Open(filename) + if err != nil { + return err + } + this.cur_section.AddInclude(filename) + this.tokenizer = token.NewTokenizer(reader) + this.Parse() + } + fmt.Println(this.cur_section.Includes) + this.tokenizer = old_tokenizer + this.readToken() + return nil +} + func (this *Parser) parseSection(name string) error { section := config.NewNamedSection(name) this.cur_section.Set(name, section) @@ -215,6 +249,8 @@ func (this *Parser) Parse() error { switch tok.ID { case token.COMMENT: this.cur_section.AddComment(tok.Literal) + case token.INCLUDE: + this.parseInclude() case token.IDENTIFIER: if this.cur_tok.ID == token.LBRACKET { err := this.parseSection(tok.Literal) diff --git a/token/tokenid.go b/token/tokenid.go index a2b711f..8144eb0 100644 --- a/token/tokenid.go +++ b/token/tokenid.go @@ -19,6 +19,7 @@ const ( STRING NULL COMMENT + INCLUDE ) var tokenNames = [...]string{ @@ -36,6 +37,7 @@ var tokenNames = [...]string{ STRING: "STRING", NULL: "NULL", COMMENT: "COMMENT", + INCLUDE: "INCLUDE", } func (this TokenID) String() string { diff --git a/token/tokenizer.go b/token/tokenizer.go index 7ad2ef4..e6f5405 100644 --- a/token/tokenizer.go +++ b/token/tokenizer.go @@ -29,6 +29,10 @@ func isNull(str string) bool { return strings.ToLower(str) == "null" } +func isInclude(str string) bool { + return strings.ToLower(str) == "include" +} + type Tokenizer struct { cur_line int cur_col int @@ -86,6 +90,8 @@ func (this *Tokenizer) parseIdentifier() { this.cur_tok.ID = BOOLEAN } else if isNull(this.cur_tok.Literal) { this.cur_tok.ID = NULL + } else if isInclude(this.cur_tok.Literal) { + this.cur_tok.ID = INCLUDE } }