diff --git a/HISTORY.md b/HISTORY.md index df24ee28..b5986a14 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -36,7 +36,7 @@ Iris now supports static paths and dynamic paths for the same path prefix with z `app.Get("/profile/{id:int}", handler)` and `app.Get("/profile/create", createHandler)` are not in conflict anymore. -The the rest of the special Iris' routing features, including static & wildcard subdomains are still work like a charm. +The rest of the special Iris' routing features, including static & wildcard subdomains are still work like a charm. > This was one of the most popular community's feature requests. Click [here](https://github.com/kataras/iris/blob/master/_examples/beginner/routing/overview/main.go) to see a trivial example. diff --git a/_examples/beginner/file-logger/main.go b/_examples/beginner/file-logger/main.go index 738e9ded..02d362f4 100644 --- a/_examples/beginner/file-logger/main.go +++ b/_examples/beginner/file-logger/main.go @@ -43,7 +43,7 @@ func main() { // navigate to http://localhost:8080 // and open the ./logs.txt file if err := app.Run(iris.Addr(":8080"), iris.WithoutBanner); err != nil { - app.Log("Shutdown with error: %v", err) + app.Log("Shutdown with error: %v\n", err) } } diff --git a/context/response_recorder.go b/context/response_recorder.go index 3cef4984..03820325 100644 --- a/context/response_recorder.go +++ b/context/response_recorder.go @@ -28,6 +28,12 @@ func releaseResponseRecorder(w *ResponseRecorder) { rrpool.Put(w) } +// A ResponseRecorder is used mostly by context's transactions +// in order to record and change if needed the body, status code and headers. +// +// Developers are not limited to manually ask to record a response. +// To turn on the recorder from a Handler, +// rec := context.Recorder() type ResponseRecorder struct { ResponseWriter // keep track of the body in order to be @@ -39,12 +45,16 @@ type ResponseRecorder struct { var _ ResponseWriter = &ResponseRecorder{} +// BeginRecord accepts its parent ResponseWriter and +// prepares itself, the response recorder, to record and send response to the client. func (w *ResponseRecorder) BeginRecord(underline ResponseWriter) { w.ResponseWriter = underline w.headers = underline.Header() w.ResetBody() } +// EndResponse is auto-called when the whole client's request is done, +// releases the response recorder and its underline ResponseWriter. func (w *ResponseRecorder) EndResponse() { releaseResponseRecorder(w) w.ResponseWriter.EndResponse() diff --git a/core/logger/dev.go b/core/logger/dev.go index 3bbbfc1f..4e479996 100644 --- a/core/logger/dev.go +++ b/core/logger/dev.go @@ -12,6 +12,9 @@ import ( "time" ) +// NewDevLogger returns a new logger of io.Writer which +// formats its log message input and writes it +// to the os.Stdout. func NewDevLogger(omitTimeFor ...string) io.Writer { mu := &sync.Mutex{} // for now and last log lastLog := time.Now() diff --git a/core/logger/logger.go b/core/logger/logger.go index 0aacee98..5e2a4ff0 100644 --- a/core/logger/logger.go +++ b/core/logger/logger.go @@ -26,7 +26,7 @@ type stringWriter interface { WriteString(string) (int, error) } -// Log sends a message to the defined io.Writer logger, it's +// Log sends a message to the defined logger of io.Writer logger, it's // just a help function for internal use but it can be used to a cusotom middleware too. // // See AttachLogger too. diff --git a/core/logger/noop.go b/core/logger/noop.go index 1a737aca..58333ffa 100644 --- a/core/logger/noop.go +++ b/core/logger/noop.go @@ -4,4 +4,6 @@ package logger +// NoOpLogger returns a new, non-operational logger of io.Writer, +// it does nothing any form of input. var NoOpLogger = writerFunc(func([]byte) (int, error) { return -1, nil }) diff --git a/core/router/handler.go b/core/router/handler.go index 60144009..086154ec 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -5,7 +5,6 @@ package router import ( - "fmt" "html" "net/http" "sort" @@ -53,10 +52,6 @@ func (h *routerHandler) getTree(method, subdomain string) *tree { } func (h *routerHandler) addRoute(method, subdomain, path string, handlers context.Handlers) error { - if len(path) == 0 || path[0] != '/' { - return fmt.Errorf("router: path %q must begin with %q", path, "/") - } - t := h.getTree(method, subdomain) if t == nil { diff --git a/core/router/macro/interpreter/lexer/lexer.go b/core/router/macro/interpreter/lexer/lexer.go index ce313498..e57822f3 100644 --- a/core/router/macro/interpreter/lexer/lexer.go +++ b/core/router/macro/interpreter/lexer/lexer.go @@ -8,6 +8,7 @@ import ( "github.com/kataras/iris/core/router/macro/interpreter/token" ) +// Lexer helps us to read/scan characters of a source and resolve their token types. type Lexer struct { input string pos int // current pos in input, current char @@ -15,6 +16,8 @@ type Lexer struct { ch byte // current char under examination } +// New takes a source, series of chars, and returns +// a new, ready to read from the first letter, lexer. func New(src string) *Lexer { l := &Lexer{ input: src, @@ -35,11 +38,13 @@ func (l *Lexer) readChar() { } const ( + // Begin is the symbol which lexer should scan forward to. Begin = '{' // token.LBRACE - End = '}' // token.RBRACE + // End is the symbol which lexer should stop scanning. + End = '}' // token.RBRACE ) -func resolveTokenType(ch byte) token.TokenType { +func resolveTokenType(ch byte) token.Type { switch ch { case Begin: return token.LBRACE @@ -71,6 +76,11 @@ func resolveTokenType(ch byte) token.TokenType { } +// NextToken returns the next token in the series of characters. +// It can be a single symbol, a token type or a literal. +// It's able to return an EOF token too. +// +// It moves the cursor forward. func (l *Lexer) NextToken() (t token.Token) { l.skipWhitespace() typ := resolveTokenType(l.ch) @@ -101,6 +111,13 @@ func (l *Lexer) NextToken() (t token.Token) { return } +// NextDynamicToken doesn't cares about the grammar. +// It reads numbers or any unknown symbol, +// it's being used by parser to skip all characters +// between parameter function's arguemnts inside parenthesis, +// in order to allow custom regexp on the end-language too. +// +// It moves the cursor forward. func (l *Lexer) NextDynamicToken() (t token.Token) { // calculate anything, even spaces. @@ -124,8 +141,11 @@ func (l *Lexer) readIdentifierFuncArgument() string { return l.input[pos:l.pos] } -// useful when we want to peek but no continue, i.e empty param functions 'even()' -func (l *Lexer) PeekNextTokenType() token.TokenType { +// PeekNextTokenType returns only the token type +// of the next character and it does not move forward the cursor. +// It's being used by parser to recognise empty functions, i.e `even()` +// as valid functions with zero input arguments. +func (l *Lexer) PeekNextTokenType() token.Type { if len(l.input)-1 > l.pos { ch := l.input[l.pos] return resolveTokenType(ch) @@ -133,7 +153,7 @@ func (l *Lexer) PeekNextTokenType() token.TokenType { return resolveTokenType(0) // EOF } -func (l *Lexer) newToken(tokenType token.TokenType, lit string) token.Token { +func (l *Lexer) newToken(tokenType token.Type, lit string) token.Token { t := token.Token{ Type: tokenType, Literal: lit, @@ -151,7 +171,7 @@ func (l *Lexer) newToken(tokenType token.TokenType, lit string) token.Token { return t } -func (l *Lexer) newTokenRune(tokenType token.TokenType, ch byte) token.Token { +func (l *Lexer) newTokenRune(tokenType token.Type, ch byte) token.Token { return l.newToken(tokenType, string(ch)) } diff --git a/core/router/macro/interpreter/lexer/lexer_test.go b/core/router/macro/interpreter/lexer/lexer_test.go index dc1750a9..39aa709c 100644 --- a/core/router/macro/interpreter/lexer/lexer_test.go +++ b/core/router/macro/interpreter/lexer/lexer_test.go @@ -14,7 +14,7 @@ func TestNextToken(t *testing.T) { input := `{id:int min(1) max(5) else 404}` tests := []struct { - expectedType token.TokenType + expectedType token.Type expectedLiteral string }{ {token.LBRACE, "{"}, // 0 diff --git a/core/router/macro/interpreter/token/token.go b/core/router/macro/interpreter/token/token.go index a7d595e8..29c6b3f5 100644 --- a/core/router/macro/interpreter/token/token.go +++ b/core/router/macro/interpreter/token/token.go @@ -4,10 +4,12 @@ package token -type TokenType int +// Type is a specific type of int which describes the symbols. +type Type int +// Token describes the letter(s) or symbol, is a result of the lexer. type Token struct { - Type TokenType + Type Type Literal string Start int // including the first char End int // including the last char @@ -33,19 +35,21 @@ const ( COMMA IDENT // string or keyword // Keywords - keywords_start + // keywords_start ELSE // else - keywords_end + // keywords_end INT // 42 ) const eof rune = 0 -var keywords = map[string]TokenType{ +var keywords = map[string]Type{ "else": ELSE, } -func LookupIdent(ident string) TokenType { +// LookupIdent receives a series of chars +// and tries to resolves the token type. +func LookupIdent(ident string) Type { if tok, ok := keywords[ident]; ok { return tok } diff --git a/internal/cmd/README.md b/internal/cmd/README.md deleted file mode 100644 index a31807dc..00000000 --- a/internal/cmd/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Internal CLI - -This folder contains the internal Iris cli program. It's internal because -it will contain generators for the website, maybe versioning for the github branches etc... -So it's useless for the end-developers, but it will be a good place to learn how you can create your own -cli and dynamic-generators programs. - - -Click [here](https://github.com/iris-contrib/community-board/issues/2) to learn its future. - -> When I use the word "generator" I don't mean the go1.4+ generate feature. - -## Don't waste your time, it is not ready yet. - diff --git a/internal/cmd/gen/website/recipe/example/example.go b/internal/cmd/gen/website/recipe/example/example.go deleted file mode 100644 index 0eb68b41..00000000 --- a/internal/cmd/gen/website/recipe/example/example.go +++ /dev/null @@ -1,11 +0,0 @@ -package example - -// Example defines the example link. -type Example struct { - Name string // i.e: Hello World - DataSource string // i.e: https://raw.githubusercontent.com/iris-contrib/examples/master/hello-world.go - Children []Example // if has children the data source is not a source file, it's just a folder, its the template's H2 tag. - // needed for the raw templates, we can do a simple func but lets keep it simple, it's a small template file. - HasChildren bool - HasNotChildren bool -} diff --git a/internal/cmd/gen/website/recipe/example/parser.go b/internal/cmd/gen/website/recipe/example/parser.go deleted file mode 100644 index 5404b6de..00000000 --- a/internal/cmd/gen/website/recipe/example/parser.go +++ /dev/null @@ -1,129 +0,0 @@ -package example - -import ( - "bytes" - "io/ioutil" - "net/http" - "strings" - - "github.com/PuerkitoBio/goquery" - "github.com/kataras/iris/core/errors" - "github.com/microcosm-cc/bluemonday" - "github.com/russross/blackfriday" -) - -// Parse will try to parse and return all examples. -// The input parameter "branch" is used to build -// the raw..iris-contrib/examples/$branch/ -// but it should be the same with -// the kataras/iris/$branch/ for consistency. -func Parse(branch string) (examples []Example, err error) { - var ( - contentsURL = "https://raw.githubusercontent.com/iris-contrib/examples/" + branch - tableOfContents = "Table of contents" - sanitizeMarkdown = true - ) - - // get the raw markdown - readmeURL := contentsURL + "/README.md" - res, err := http.Get(readmeURL) - if err != nil { - return nil, err - } - markdownContents, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - - // convert it to html - htmlContents := &bytes.Buffer{} - htmlContentsFromMarkdown := blackfriday.MarkdownCommon(markdownContents) - - if len(htmlContentsFromMarkdown) == 0 { - return nil, errors.New("empty html") - } - - if sanitizeMarkdown { - markdownContents = bluemonday.UGCPolicy().SanitizeBytes(markdownContents) - } - - htmlContents.Write(htmlContentsFromMarkdown) - // println("html contents: " + htmlContents.String()) - // get the document from the html - readme, err := goquery.NewDocumentFromReader(htmlContents) - if err != nil { - return nil, err - } - - // or with just one line (but may break if we add another h2, - // so I will do it with the hard and un-readable way for now) - // readme.Find("h2").First().NextAllFiltered("ul").Children().Text() - - // find the header of Table Of Contents, we will need it to take its - // next ul, which should be the examples list. - var tableOfContentsHeader *goquery.Selection - readme.Find("h2").EachWithBreak(func(_ int, n *goquery.Selection) bool { - if nodeContents := n.Text(); nodeContents == tableOfContents { - tableOfContentsHeader = n - return false // break - } - return true - }) - - if tableOfContentsHeader == nil { - return nil, errors.New("table of contents not found using: " + tableOfContents) - } - - // get the list of the examples - tableOfContentsUL := tableOfContentsHeader.NextFiltered("ul") - if tableOfContentsUL == nil { - return nil, errors.New("table of contents list not found") - } - - // iterate over categories example's <a href ...>...</a> - tableOfContentsUL.Children().EachWithBreak(func(_ int, li *goquery.Selection) bool { - exampleHrefLink := li.Children().First() - if exampleHrefLink == nil { - err = errors.New("example link href is nil, source: " + li.Text()) - return false // break on first failure - } - - name := exampleHrefLink.Text() - - sourcelink, _ := li.Find("a").First().Attr("href") - hasChildren := !strings.HasSuffix(sourcelink, ".go") - - example := Example{ - Name: name, - DataSource: contentsURL + "/" + sourcelink, - HasChildren: hasChildren, - HasNotChildren: !hasChildren, - } - - // search for sub examples - if hasChildren { - li.Find("ul").Children().EachWithBreak(func(_ int, liExample *goquery.Selection) bool { - name := liExample.Text() - liHref := liExample.Find("a").First() - sourcelink, ok := liHref.Attr("href") - if !ok { - err = errors.New(name + "'s source couldn't be found") - return false - } - - subExample := Example{ - Name: name, - DataSource: contentsURL + "/" + sourcelink, - } - - example.Children = append(example.Children, subExample) - return true - }) - - } - - examples = append(examples, example) - return true - }) - return examples, err -} diff --git a/internal/cmd/gen/website/recipe/recipe.go b/internal/cmd/gen/website/recipe/recipe.go deleted file mode 100644 index 9f99ca4c..00000000 --- a/internal/cmd/gen/website/recipe/recipe.go +++ /dev/null @@ -1,30 +0,0 @@ -package recipe - -import ( - "github.com/kataras/iris/internal/cmd/gen/website/recipe/example" -) - -type Recipe struct { - Branch string // i.e "master", "v6"... - Examples []example.Example -} - -// NewRecipe accepts the "branch", i.e: "master", "v6", "v7"... -// and returns a new Recipe pointer with its generated and parsed examples. -func NewRecipe(branch string) (*Recipe, error) { - if branch == "" { - branch = "master" - } - - examples, err := example.Parse(branch) - if err != nil { - return nil, err - } - - r := &Recipe{ - Branch: branch, - Examples: examples, - } - - return r, nil -} diff --git a/internal/cmd/gen/website/recipe/recipe.html b/internal/cmd/gen/website/recipe/recipe.html deleted file mode 100644 index 8256b577..00000000 --- a/internal/cmd/gen/website/recipe/recipe.html +++ /dev/null @@ -1,149 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - -<head> - <title>Iris</title> - <meta charset="utf-8"> - <meta name="description" content="Iris-go - The fastest backend web framework for Go."> - <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> - - <meta property="og:type" content="article"> - <meta property="og:title" content="Iris"> - <meta property="og:description" content="Iris-go - The fastest backend web framework for Go."> - <meta property="og:image" content="http://iris-go.com/images/icon.svg"> - - <meta name="twitter:card" content="summary"> - <meta name="twitter:title" content="Iris"> - <meta name="twitter:description" content="Iris-go - The fastest backend web framework for Go."> - <meta name="twitter:image" content="http://iris-go.com/images/icon.svg"> - - <link rel="icon" href="/images/favicon.ico" type="image/x-icon"> - - <link href='//fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600|Roboto Mono' rel='stylesheet' type='text/css'> - <link href='//fonts.googleapis.com/css?family=Dosis:500&text=Iris' rel='stylesheet' type='text/css'> - <link href='/css/prism.css' rel='stylesheet' type='text/css'> - <link href='/css/terminal.css' rel='stylesheet' type='text/css'> - - <!-- main page styles --> - <link rel="stylesheet" href="/css/page.css"> - - <!-- this needs to be loaded before guide's inline scripts --> - <script> - window.PAGE_TYPE = "Recipe" - </script> - - <!-- ga --> - <script> - (function (i, s, o, g, r, a, m) { - i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () { - (i[r].q = i[r].q || []).push(arguments) - }, i[r].l = 1 * new Date(); a = s.createElement(o), - m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m) - })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga'); - - ga('create', 'UA-46045347-6', 'iris-go.com'); - ga('send', 'pageview'); - </script> -</head> - -<body class="docs"> - <div id="mobile-bar"> - <a class="menu-button"></a> - <a class="logo" href="/"></a> - </div> - <div id="header"> - <a id="logo" href="/"> - <img src="/images/logo.png"> - <span>IRIS</span> - </a> - <ul id="nav"> - <li><a href="/{{.Branch}}/start/" class="nav-link">Start</a></li> - <li><a href="/{{.Branch}}/recipe/" class="nav-link current">Recipe</a></li> - <li><a href="/{{.Branch}}/blogs/" class="nav-link">Blogs</a></li> - <li><a target="_blank" href="https://kataras.rocket.chat/channel/iris" class="nav-link">Chat</a></li> - <li><a class="donate" style="color:#ff6666;" target="_blank" href="https://github.com/kataras/iris#buy-me-a-cup-of-coffee">Donate</a></li> - <!-- -<li> - <form id="search-form"> - <input type="text" id="search-query-nav" - class="search-query st-default-search-input"/> - </form> -</li> ---> - </ul> - </div> - - - <div id="main" class="fix-sidebar"> - - - <div class="sidebar"> - <ul class="main-menu"> - <li><a href="/{{.Branch}}/start/" class="nav-link">Start</a></li> - <li><a href="/{{.Branch}}/recipe/" class="nav-link current">Recipe</a></li> - <li><a href="/{{.Branch}}/blogs/" class="nav-link">Blogs</a></li> - <li><a target="_blank" href="https://kataras.rocket.chat/channel/iris" class="nav-link">Chat</a></li> - <li><a target="_blank" class="donate" style="color:#ff6666;" href="https://github.com/kataras/iris#buy-me-a-cup-of-coffee">Donate</a></li> - </ul> - <div class="list"> - <h2> - Recipe - </h2> - <ul class="menu-root"> - <li> - <a href="/{{.Branch}}/recipe/index.html" class="sidebar-link current"></a> - </li> - </ul> - </div> - </div> - - <div class="content Recipe with-sidebar "> - {{ range $key, $example := .Examples -}} - {{ if $example.HasChildren }} - <h2 id="{{$example.Name}}"> - <a href="#{{$example.Name}}" class="headerlink" title="{{$example.Name}}"></a> - {{$example.Name}} - </h2> - {{ range $key, $child := $example.Children -}} - <h3 id="{{ $child.Name }}"> - <a href="#{{ $child.Name }}" class="headerlink" title="{{ $child.Name }}"></a> - {{ $child.Name }} - </h3> - <pre data-src="{{ $child.DataSource }}" data-visible="true" class="line-numbers codepre"></pre> - {{- end }} {{- end }} - - {{ if $example.HasNotChildren }} - <h2 id="{{ $example.Name }}"> - <a href="#{{ $example.Name }}" class="headerlink" title="{{ $example.Name }}"></a> - {{ $example.Name }} - </h2> - <pre data-src="{{ $example.DataSource }}" data-visible="true" class="line-numbers codepre"></pre> - {{- end }} - {{- end }} - - <div class="footer"> - Contribute to the documentation? - <a target="_blank" href="https://github.com/iris-contrib/website/blob/gh-pages/{{.Branch}}/recipe/index.html" > - Edit this page on Github! - </a> - </div> - </div> - - </div> - <script src="/js/smooth-scroll.min.js"></script> - - <script async defer src="https://buttons.github.io/buttons.js"></script> - <!-- main custom script for sidebars, version selects etc. --> - <script src="/js/css.escape.js"></script> - <script src="/js/common.js"></script> - <script src="/js/prism.js"></script> - <!-- fastclick --> - <script src="//cdnjs.cloudflare.com/ajax/libs/fastclick/1.0.6/fastclick.min.js"></script> - <script> - document.addEventListener('DOMContentLoaded', function () { - FastClick.attach(document.body) - }, false) - </script> -</body> - -</html> \ No newline at end of file diff --git a/internal/cmd/main.go b/internal/cmd/main.go deleted file mode 100644 index f68b06fa..00000000 --- a/internal/cmd/main.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "os" - "text/template" - - "github.com/kataras/iris/internal/cmd/gen/website/recipe/example" -) - -const tmpl = ` -{{ range $key, $example := . -}} -{{ if $example.HasChildren }} -<h2 id="{{$example.Name}}"><a href="#{{$example.Name}}" class="headerlink" title="{{$example.Name}}"></a>{{$example.Name}}</h2> -{{ range $key, $child := $example.Children -}} - <h3 id="{{ $child.Name }}"> - <a href="#{{ $child.Name }}" class="headerlink" title="{{ $child.Name }}"></a> - {{ $child.Name }} - </h3> - <pre data-src="{{ $child.DataSource }}" - data-visible="true" class ="line-numbers codepre"></pre> -{{- end }} -{{- end }} -{{ if .HasNotChildren }} -<h2 id="{{ $example.Name }}"> - <a href="#{{ $example.Name }}" class="headerlink" title="{{ $example.Name }}"></a> - {{ $example.Name }} -</h2> -<pre data-src="{{ $example.DataSource }}" - data-visible="true" class ="line-numbers codepre"></pre> -{{- end }} -{{- end }} -` - -func main() { - // just for testing, the cli will be coded when I finish at least with this one command. - examples, err := example.Parse("master") - if err != nil { - println(err.Error()) - return - } - - text, err := template.New("").Parse(tmpl) - if err != nil { - println(err.Error()) - } - - if err := text.Execute(os.Stdout, examples); err != nil { - println("err in template : " + err.Error()) - } -} diff --git a/iris.go b/iris.go index 1179c305..ec0c837d 100644 --- a/iris.go +++ b/iris.go @@ -91,7 +91,7 @@ func New() *Application { app := &Application{ config: &config, - logger: logger.NewDevLogger(), + logger: logger.NewDevLogger(banner), APIBuilder: router.NewAPIBuilder(), Router: router.NewRouter(), } diff --git a/middleware/logger/config.go b/middleware/logger/config.go index 53f20543..cfe8bca3 100644 --- a/middleware/logger/config.go +++ b/middleware/logger/config.go @@ -19,7 +19,7 @@ type Config struct { Path bool } -// DefaultConfig returns an options which all properties are true except EnableColors -func DefaultConfigurationReadOnly() Config { +// DefaultConfiguration returns an options which all properties are true except EnableColors +func DefaultConfiguration() Config { return Config{true, true, true, true} } diff --git a/middleware/logger/logger.go b/middleware/logger/logger.go index 0123ff90..c6591ae9 100644 --- a/middleware/logger/logger.go +++ b/middleware/logger/logger.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package loger provides request logging via middleware. See _examples/beginner/request-logger +// Package logger provides request logging via middleware. See _examples/beginner/request-logger package logger import ( @@ -58,7 +58,7 @@ func (l *requestLoggerMiddleware) ServeHTTP(ctx context.Context) { // // Receives an optional configuation. func New(cfg ...Config) context.Handler { - c := DefaultConfigurationReadOnly() + c := DefaultConfiguration() if len(cfg) > 0 { c = cfg[0] }