diff --git a/README.md b/README.md index e771d06..0a7a8fa 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ This project is no longer maintained by Segment. Instead, [Martin Etmajer](https Options: -h, --help show help information --no-required omit "Required" column when generating markdown + --no-sort omit sorted rendering of inputs and ouputs --version print version ``` diff --git a/internal/pkg/doc/doc.go b/internal/pkg/doc/doc.go index 704e201..b20b30a 100644 --- a/internal/pkg/doc/doc.go +++ b/internal/pkg/doc/doc.go @@ -6,7 +6,6 @@ import ( "log" "path" "path/filepath" - "sort" "strconv" "strings" @@ -43,18 +42,6 @@ func (d *Doc) HasOutputs() bool { return len(d.Outputs) > 0 } -type inputsByName []Input - -func (a inputsByName) Len() int { return len(a) } -func (a inputsByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a inputsByName) Less(i, j int) bool { return a[i].Name < a[j].Name } - -type outputsByName []Output - -func (a outputsByName) Len() int { return len(a) } -func (a outputsByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a outputsByName) Less(i, j int) bool { return a[i].Name < a[j].Name } - // CreateFromPaths creates a new document from a list of file or directory paths. func CreateFromPaths(paths []string) (*Doc, error) { names := make([]string, 0) @@ -110,8 +97,6 @@ func Create(files map[string]*ast.File) *Doc { } } - sort.Sort(inputsByName(doc.Inputs)) - sort.Sort(outputsByName(doc.Outputs)) return doc } diff --git a/internal/pkg/doc/doc_test.go b/internal/pkg/doc/doc_test.go index 8cf15c1..a07311e 100644 --- a/internal/pkg/doc/doc_test.go +++ b/internal/pkg/doc/doc_test.go @@ -78,34 +78,10 @@ func TestInputs(t *testing.T) { expected := []doc.Input{ doc.Input{ - Name: "list-1", - Description: "It's list number one.", - Default: &doc.Value{ - Type: "list", - Literal: "", - }, - Type: "list", - }, - doc.Input{ - Name: "list-2", - Description: "It's list number two.", + Name: "string-2", + Description: "It's string number two.", Default: nil, - Type: "list", - }, - doc.Input{ - Name: "map-1", - Description: "It's map number one.", - Default: &doc.Value{ - Type: "map", - Literal: "", - }, - Type: "map", - }, - doc.Input{ - Name: "map-2", - Description: "It's map number two.", - Default: nil, - Type: "map", + Type: "string", }, doc.Input{ Name: "string-1", @@ -117,10 +93,34 @@ func TestInputs(t *testing.T) { Type: "string", }, doc.Input{ - Name: "string-2", - Description: "It's string number two.", + Name: "map-2", + Description: "It's map number two.", Default: nil, - Type: "string", + Type: "map", + }, + doc.Input{ + Name: "map-1", + Description: "It's map number one.", + Default: &doc.Value{ + Type: "map", + Literal: "", + }, + Type: "map", + }, + doc.Input{ + Name: "list-2", + Description: "It's list number two.", + Default: nil, + Type: "list", + }, + doc.Input{ + Name: "list-1", + Description: "It's list number one.", + Default: &doc.Value{ + Type: "list", + Literal: "", + }, + Type: "list", }, } @@ -142,34 +142,10 @@ func TestInputsFromVariablesTf(t *testing.T) { expected := []doc.Input{ doc.Input{ - Name: "list-1", - Description: "It's list number one.", - Default: &doc.Value{ - Type: "list", - Literal: "", - }, - Type: "list", - }, - doc.Input{ - Name: "list-2", - Description: "It's list number two.", + Name: "string-2", + Description: "It's string number two.", Default: nil, - Type: "list", - }, - doc.Input{ - Name: "map-1", - Description: "It's map number one.", - Default: &doc.Value{ - Type: "map", - Literal: "", - }, - Type: "map", - }, - doc.Input{ - Name: "map-2", - Description: "It's map number two.", - Default: nil, - Type: "map", + Type: "string", }, doc.Input{ Name: "string-1", @@ -181,10 +157,34 @@ func TestInputsFromVariablesTf(t *testing.T) { Type: "string", }, doc.Input{ - Name: "string-2", - Description: "It's string number two.", + Name: "map-2", + Description: "It's map number two.", Default: nil, - Type: "string", + Type: "map", + }, + doc.Input{ + Name: "map-1", + Description: "It's map number one.", + Default: &doc.Value{ + Type: "map", + Literal: "", + }, + Type: "map", + }, + doc.Input{ + Name: "list-2", + Description: "It's list number two.", + Default: nil, + Type: "list", + }, + doc.Input{ + Name: "list-1", + Description: "It's list number one.", + Default: &doc.Value{ + Type: "list", + Literal: "", + }, + Type: "list", }, } @@ -195,14 +195,14 @@ func TestOutputs(t *testing.T) { actual := doc.TestDoc(t, ".").Outputs expected := []doc.Output{ - doc.Output{ - Name: "output-1", - Description: "It's output number one.", - }, doc.Output{ Name: "output-2", Description: "It's output number two.", }, + doc.Output{ + Name: "output-1", + Description: "It's output number one.", + }, } assert.Equal(t, expected, actual) @@ -217,14 +217,14 @@ func TestOutputsFromOutputsTf(t *testing.T) { actual := doc.TestDocFromFile(t, ".", "outputs.tf").Outputs expected := []doc.Output{ - doc.Output{ - Name: "output-1", - Description: "It's output number one.", - }, doc.Output{ Name: "output-2", Description: "It's output number two.", }, + doc.Output{ + Name: "output-1", + Description: "It's output number one.", + }, } assert.Equal(t, expected, actual) diff --git a/internal/pkg/doc/input.go b/internal/pkg/doc/input.go index 99382a8..759bc89 100644 --- a/internal/pkg/doc/input.go +++ b/internal/pkg/doc/input.go @@ -1,6 +1,8 @@ package doc -// Input represents a Terraform input variable. +import "sort" + +// Input represents a Terraform input. type Input struct { Name string Description string @@ -22,3 +24,22 @@ func (i *Input) IsOptional() bool { func (i *Input) IsRequired() bool { return i.Default == nil } + +type inputsSortedByName []Input + +func (a inputsSortedByName) Len() int { + return len(a) +} + +func (a inputsSortedByName) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} + +func (a inputsSortedByName) Less(i, j int) bool { + return a[i].Name < a[j].Name +} + +// SortInputsByName sorts a list of inputs by name. +func SortInputsByName(inputs []Input) { + sort.Sort(inputsSortedByName(inputs)) +} diff --git a/internal/pkg/doc/output.go b/internal/pkg/doc/output.go index 8c59ff9..a8fc60d 100644 --- a/internal/pkg/doc/output.go +++ b/internal/pkg/doc/output.go @@ -1,5 +1,7 @@ package doc +import "sort" + // Output represents a Terraform output. type Output struct { Name string @@ -10,3 +12,22 @@ type Output struct { func (o *Output) HasDescription() bool { return o.Description != "" } + +type outputsSortedByName []Output + +func (a outputsSortedByName) Len() int { + return len(a) +} + +func (a outputsSortedByName) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} + +func (a outputsSortedByName) Less(i, j int) bool { + return a[i].Name < a[j].Name +} + +// SortOutputsByName sorts a list of outputs by name. +func SortOutputsByName(outputs []Output) { + sort.Sort(outputsSortedByName(outputs)) +} diff --git a/internal/pkg/doc/testdata/outputs.tf b/internal/pkg/doc/testdata/outputs.tf index 2307feb..4d12225 100644 --- a/internal/pkg/doc/testdata/outputs.tf +++ b/internal/pkg/doc/testdata/outputs.tf @@ -1,9 +1,9 @@ -// It's output number one. -output "output-1" { - value = "1" -} - output "output-2" { description = "It's output number two." value = "2" } + +// It's output number one. +output "output-1" { + value = "1" +} diff --git a/internal/pkg/doc/testdata/variables.tf b/internal/pkg/doc/testdata/variables.tf index be1ec2a..b6978de 100644 --- a/internal/pkg/doc/testdata/variables.tf +++ b/internal/pkg/doc/testdata/variables.tf @@ -1,12 +1,16 @@ -// It's list number one. -variable "list-1" { - default = ["a", "b", "c"] - type = "list" +variable "string-2" { + description = "It's string number two." + type = "string" } -variable "list-2" { - description = "It's list number two." - type = "list" +// It's string number one. +variable "string-1" { + default = "bar" +} + +variable "map-2" { + description = "It's map number two." + type = "map" } // It's map number one. @@ -19,17 +23,13 @@ variable "map-1" { type = "map" } -variable "map-2" { - description = "It's map number two." - type = "map" +variable "list-2" { + description = "It's list number two." + type = "list" } -// It's string number one. -variable "string-1" { - default = "bar" -} - -variable "string-2" { - description = "It's string number two." - type = "string" +// It's list number one. +variable "list-1" { + default = ["a", "b", "c"] + type = "list" } diff --git a/internal/pkg/print/json/json.go b/internal/pkg/print/json/json.go index ca8e4e6..29ee1ab 100644 --- a/internal/pkg/print/json/json.go +++ b/internal/pkg/print/json/json.go @@ -4,6 +4,7 @@ import ( "encoding/json" "github.com/segmentio/terraform-docs/internal/pkg/doc" + "github.com/segmentio/terraform-docs/internal/pkg/print" "github.com/segmentio/terraform-docs/internal/pkg/settings" ) @@ -14,6 +15,18 @@ const ( // Print prints a document as json. func Print(document *doc.Doc, settings settings.Settings) (string, error) { + if document.HasInputs() { + if settings.Has(print.WithSorting) { + doc.SortInputsByName(document.Inputs) + } + } + + if document.HasOutputs() { + if settings.Has(print.WithSorting) { + doc.SortOutputsByName(document.Outputs) + } + } + buffer, err := json.MarshalIndent(document, prefix, indent) if err != nil { return "", err diff --git a/internal/pkg/print/json/json_test.go b/internal/pkg/print/json/json_test.go index b4f40cc..ee3b39e 100644 --- a/internal/pkg/print/json/json_test.go +++ b/internal/pkg/print/json/json_test.go @@ -12,6 +12,7 @@ import ( func TestPrint(t *testing.T) { doc := doc.TestDoc(t, "..") + var settings settings.Settings actual, err := json.Print(doc, settings) @@ -26,3 +27,22 @@ func TestPrint(t *testing.T) { assert.Equal(t, expected, actual) } + +func TestPrintWithSorting(t *testing.T) { + doc := doc.TestDoc(t, "..") + + var settings settings.Settings + settings.Add(print.WithSorting) + + actual, err := json.Print(doc, settings) + if err != nil { + t.Fatal(err) + } + + expected, err := print.ReadGoldenFile("json-WithSorting") + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, expected, actual) +} diff --git a/internal/pkg/print/json/testdata/json-WithSorting.golden b/internal/pkg/print/json/testdata/json-WithSorting.golden new file mode 100644 index 0000000..ab306de --- /dev/null +++ b/internal/pkg/print/json/testdata/json-WithSorting.golden @@ -0,0 +1,60 @@ +{ + "Comment": "Usage:\n\nmodule \"foo\" {\n source = \"github.com/foo/bar\"\n\n id = \"1234567890\"\n name = \"baz\"\n\n zones = [\"us-east-1\", \"us-west-1\"]\n\n tags = {\n Name = \"baz\"\n Created-By = \"first.last@email.com\"\n Date-Created = \"20180101\"\n }\n}\n\n", + "Inputs": [ + { + "Name": "list-1", + "Description": "It's list number one.", + "Default": { + "Type": "list", + "Literal": "" + }, + "Type": "list" + }, + { + "Name": "list-2", + "Description": "It's list number two.", + "Default": null, + "Type": "list" + }, + { + "Name": "map-1", + "Description": "It's map number one.", + "Default": { + "Type": "map", + "Literal": "" + }, + "Type": "map" + }, + { + "Name": "map-2", + "Description": "It's map number two.", + "Default": null, + "Type": "map" + }, + { + "Name": "string-1", + "Description": "It's string number one.", + "Default": { + "Type": "string", + "Literal": "bar" + }, + "Type": "string" + }, + { + "Name": "string-2", + "Description": "It's string number two.", + "Default": null, + "Type": "string" + } + ], + "Outputs": [ + { + "Name": "output-1", + "Description": "It's output number one." + }, + { + "Name": "output-2", + "Description": "It's output number two." + } + ] +} \ No newline at end of file diff --git a/internal/pkg/print/json/testdata/json.golden b/internal/pkg/print/json/testdata/json.golden index ab306de..7c71224 100644 --- a/internal/pkg/print/json/testdata/json.golden +++ b/internal/pkg/print/json/testdata/json.golden @@ -2,34 +2,10 @@ "Comment": "Usage:\n\nmodule \"foo\" {\n source = \"github.com/foo/bar\"\n\n id = \"1234567890\"\n name = \"baz\"\n\n zones = [\"us-east-1\", \"us-west-1\"]\n\n tags = {\n Name = \"baz\"\n Created-By = \"first.last@email.com\"\n Date-Created = \"20180101\"\n }\n}\n\n", "Inputs": [ { - "Name": "list-1", - "Description": "It's list number one.", - "Default": { - "Type": "list", - "Literal": "" - }, - "Type": "list" - }, - { - "Name": "list-2", - "Description": "It's list number two.", + "Name": "string-2", + "Description": "It's string number two.", "Default": null, - "Type": "list" - }, - { - "Name": "map-1", - "Description": "It's map number one.", - "Default": { - "Type": "map", - "Literal": "" - }, - "Type": "map" - }, - { - "Name": "map-2", - "Description": "It's map number two.", - "Default": null, - "Type": "map" + "Type": "string" }, { "Name": "string-1", @@ -41,20 +17,44 @@ "Type": "string" }, { - "Name": "string-2", - "Description": "It's string number two.", + "Name": "map-2", + "Description": "It's map number two.", "Default": null, - "Type": "string" + "Type": "map" + }, + { + "Name": "map-1", + "Description": "It's map number one.", + "Default": { + "Type": "map", + "Literal": "" + }, + "Type": "map" + }, + { + "Name": "list-2", + "Description": "It's list number two.", + "Default": null, + "Type": "list" + }, + { + "Name": "list-1", + "Description": "It's list number one.", + "Default": { + "Type": "list", + "Literal": "" + }, + "Type": "list" } ], "Outputs": [ - { - "Name": "output-1", - "Description": "It's output number one." - }, { "Name": "output-2", "Description": "It's output number two." + }, + { + "Name": "output-1", + "Description": "It's output number one." } ] } \ No newline at end of file diff --git a/internal/pkg/print/markdown/markdown.go b/internal/pkg/print/markdown/markdown.go index 029b95b..c18c55b 100644 --- a/internal/pkg/print/markdown/markdown.go +++ b/internal/pkg/print/markdown/markdown.go @@ -19,10 +19,18 @@ func Print(document *doc.Doc, settings settings.Settings) (string, error) { } if document.HasInputs() { + if settings.Has(print.WithSorting) { + doc.SortInputsByName(document.Inputs) + } + printInputs(&buffer, document.Inputs, settings) } if document.HasOutputs() { + if settings.Has(print.WithSorting) { + doc.SortOutputsByName(document.Outputs) + } + printOutputs(&buffer, document.Outputs, settings) } diff --git a/internal/pkg/print/markdown/markdown_test.go b/internal/pkg/print/markdown/markdown_test.go index 4cbcd25..0c6168e 100644 --- a/internal/pkg/print/markdown/markdown_test.go +++ b/internal/pkg/print/markdown/markdown_test.go @@ -45,3 +45,22 @@ func TestPrintWithRequired(t *testing.T) { assert.Equal(t, expected, actual) } + +func TestPrintWithSorting(t *testing.T) { + doc := doc.TestDoc(t, "..") + + var settings settings.Settings + settings.Add(print.WithSorting) + + actual, err := markdown.Print(doc, settings) + if err != nil { + t.Fatal(err) + } + + expected, err := print.ReadGoldenFile("markdown-WithSorting") + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, expected, actual) +} diff --git a/internal/pkg/print/markdown/testdata/markdown-WithRequired.golden b/internal/pkg/print/markdown/testdata/markdown-WithRequired.golden index 5add3e3..da301ad 100644 --- a/internal/pkg/print/markdown/testdata/markdown-WithRequired.golden +++ b/internal/pkg/print/markdown/testdata/markdown-WithRequired.golden @@ -21,16 +21,16 @@ module "foo" { | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| -| list-1 | It's list number one. | list | `` | no | -| list-2 | It's list number two. | list | - | yes | -| map-1 | It's map number one. | map | `` | no | -| map-2 | It's map number two. | map | - | yes | -| string-1 | It's string number one. | string | `bar` | no | | string-2 | It's string number two. | string | - | yes | +| string-1 | It's string number one. | string | `bar` | no | +| map-2 | It's map number two. | map | - | yes | +| map-1 | It's map number one. | map | `` | no | +| list-2 | It's list number two. | list | - | yes | +| list-1 | It's list number one. | list | `` | no | ## Outputs | Name | Description | |------|-------------| -| output-1 | It's output number one. | | output-2 | It's output number two. | +| output-1 | It's output number one. | diff --git a/internal/pkg/print/markdown/testdata/markdown-WithSorting.golden b/internal/pkg/print/markdown/testdata/markdown-WithSorting.golden new file mode 100644 index 0000000..6ee0ffe --- /dev/null +++ b/internal/pkg/print/markdown/testdata/markdown-WithSorting.golden @@ -0,0 +1,36 @@ +Usage: + +module "foo" { + source = "github.com/foo/bar" + + id = "1234567890" + name = "baz" + + zones = ["us-east-1", "us-west-1"] + + tags = { + Name = "baz" + Created-By = "first.last@email.com" + Date-Created = "20180101" + } +} + + + +## Inputs + +| Name | Description | Type | Default | +|------|-------------|:----:|:-----:| +| list-1 | It's list number one. | list | `` | +| list-2 | It's list number two. | list | - | +| map-1 | It's map number one. | map | `` | +| map-2 | It's map number two. | map | - | +| string-1 | It's string number one. | string | `bar` | +| string-2 | It's string number two. | string | - | + +## Outputs + +| Name | Description | +|------|-------------| +| output-1 | It's output number one. | +| output-2 | It's output number two. | diff --git a/internal/pkg/print/markdown/testdata/markdown.golden b/internal/pkg/print/markdown/testdata/markdown.golden index 6ee0ffe..f608362 100644 --- a/internal/pkg/print/markdown/testdata/markdown.golden +++ b/internal/pkg/print/markdown/testdata/markdown.golden @@ -21,16 +21,16 @@ module "foo" { | Name | Description | Type | Default | |------|-------------|:----:|:-----:| -| list-1 | It's list number one. | list | `` | -| list-2 | It's list number two. | list | - | -| map-1 | It's map number one. | map | `` | -| map-2 | It's map number two. | map | - | -| string-1 | It's string number one. | string | `bar` | | string-2 | It's string number two. | string | - | +| string-1 | It's string number one. | string | `bar` | +| map-2 | It's map number two. | map | - | +| map-1 | It's map number one. | map | `` | +| list-2 | It's list number two. | list | - | +| list-1 | It's list number one. | list | `` | ## Outputs | Name | Description | |------|-------------| -| output-1 | It's output number one. | | output-2 | It's output number two. | +| output-1 | It's output number one. | diff --git a/internal/pkg/print/pretty/pretty.go b/internal/pkg/print/pretty/pretty.go index 7c4ac77..055632a 100644 --- a/internal/pkg/print/pretty/pretty.go +++ b/internal/pkg/print/pretty/pretty.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/segmentio/terraform-docs/internal/pkg/doc" + "github.com/segmentio/terraform-docs/internal/pkg/print" "github.com/segmentio/terraform-docs/internal/pkg/settings" ) @@ -17,10 +18,18 @@ func Print(document *doc.Doc, settings settings.Settings) (string, error) { } if document.HasInputs() { + if settings.Has(print.WithSorting) { + doc.SortInputsByName(document.Inputs) + } + printInputs(&buffer, document.Inputs, settings) } if document.HasOutputs() { + if settings.Has(print.WithSorting) { + doc.SortOutputsByName(document.Outputs) + } + printOutputs(&buffer, document.Outputs, settings) } diff --git a/internal/pkg/print/pretty/pretty_test.go b/internal/pkg/print/pretty/pretty_test.go index bbf87f3..76c7da9 100644 --- a/internal/pkg/print/pretty/pretty_test.go +++ b/internal/pkg/print/pretty/pretty_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/segmentio/terraform-docs/internal/pkg/doc" + "github.com/segmentio/terraform-docs/internal/pkg/print" "github.com/segmentio/terraform-docs/internal/pkg/print/pretty" "github.com/segmentio/terraform-docs/internal/pkg/settings" "github.com/stretchr/testify/assert" @@ -22,6 +23,72 @@ func TestPretty(t *testing.T) { sgr_color_2 := "\x1b[90m" sgr_reset := "\x1b[0m" + expected := + "\nUsage:\n" + + "\n" + + "module \"foo\" {\n" + + " source = \"github.com/foo/bar\"\n" + + "\n" + + " id = \"1234567890\"\n" + + " name = \"baz\"\n" + + "\n" + + " zones = [\"us-east-1\", \"us-west-1\"]\n" + + "\n" + + " tags = {\n" + + " Name = \"baz\"\n" + + " Created-By = \"first.last@email.com\"\n" + + " Date-Created = \"20180101\"\n" + + " }\n" + + "}\n" + + "\n" + + "\n" + + "\n" + + " " + sgr_color_1 + "var.string-2" + sgr_reset + " (required)\n" + + " " + sgr_color_2 + "It's string number two." + sgr_reset + "\n" + + "\n" + + " " + sgr_color_1 + "var.string-1" + sgr_reset + " (bar)\n" + + " " + sgr_color_2 + "It's string number one." + sgr_reset + "\n" + + "\n" + + " " + sgr_color_1 + "var.map-2" + sgr_reset + " (required)\n" + + " " + sgr_color_2 + "It's map number two." + sgr_reset + "\n" + + "\n" + + " " + sgr_color_1 + "var.map-1" + sgr_reset + " ()\n" + + " " + sgr_color_2 + "It's map number one." + sgr_reset + "\n" + + "\n" + + " " + sgr_color_1 + "var.list-2" + sgr_reset + " (required)\n" + + " " + sgr_color_2 + "It's list number two." + sgr_reset + "\n" + + "\n" + + " " + sgr_color_1 + "var.list-1" + sgr_reset + " ()\n" + + " " + sgr_color_2 + "It's list number one." + sgr_reset + "\n" + + "\n" + + "\n" + + "\n" + + " " + sgr_color_1 + "output.output-2" + sgr_reset + "\n" + + " " + sgr_color_2 + "It's output number two." + sgr_reset + "\n" + + "\n" + + " " + sgr_color_1 + "output.output-1" + sgr_reset + "\n" + + " " + sgr_color_2 + "It's output number one." + sgr_reset + "\n" + + "\n" + + "\n" + + assert.Equal(t, expected, actual) +} + +func TestPrettyWithSorting(t *testing.T) { + doc := doc.TestDoc(t, "..") + + var settings settings.Settings + settings.Add(print.WithSorting) + + actual, err := pretty.Print(doc, settings) + if err != nil { + t.Fatal(err) + } + + sgr_color_1 := "\x1b[36m" + sgr_color_2 := "\x1b[90m" + sgr_reset := "\x1b[0m" + expected := "\nUsage:\n" + "\n" + diff --git a/internal/pkg/print/print.go b/internal/pkg/print/print.go index 1c0693b..2eaf15d 100644 --- a/internal/pkg/print/print.go +++ b/internal/pkg/print/print.go @@ -8,4 +8,6 @@ const ( _ settings.Setting = iota // WithRequired prints if inputs are required WithRequired + // WithSorting prints sorted inputs and outputs + WithSorting ) diff --git a/internal/pkg/settings/settings.go b/internal/pkg/settings/settings.go index 667ff4b..b55db51 100644 --- a/internal/pkg/settings/settings.go +++ b/internal/pkg/settings/settings.go @@ -15,23 +15,3 @@ func (s *Settings) Add(setting Setting) { func (s *Settings) Has(setting Setting) bool { return *s&Settings(1<