diff --git a/cato.go b/cato.go index 7b18ebf..c5dd0f1 100644 --- a/cato.go +++ b/cato.go @@ -14,10 +14,10 @@ import ( "regexp" "strings" + "github.com/cs3org/cato/exporter" + _ "github.com/cs3org/cato/exporter/drivers/loader" + "github.com/cs3org/cato/exporter/drivers/registry" "github.com/cs3org/cato/resources" - "github.com/cs3org/cato/writer" - _ "github.com/cs3org/cato/writer/drivers/loader" - "github.com/cs3org/cato/writer/drivers/registry" ) type structInfo struct { @@ -195,17 +195,17 @@ func getConfigsToDocument(filePath, catoTag string) (map[string][]*resources.Fie return configs, nil } -func getDriver(c *resources.CatoConfig) (writer.ConfigWriter, error) { +func getDriver(c *resources.CatoConfig) (exporter.ConfigExporter, error) { if f, ok := registry.NewFuncs[c.Driver]; ok { return f(c.DriverConfig[c.Driver]) } return nil, fmt.Errorf("driver not found: %s", c.Driver) } -func GenerateDocumentation(rootPath string, conf *resources.CatoConfig) error { +func GenerateDocumentation(rootPath string, conf *resources.CatoConfig) (map[string]map[string][]*resources.FieldInfo, error) { if rootPath == "" { - return fmt.Errorf("cato: root path can't be empty") + return nil, fmt.Errorf("cato: root path can't be empty") } if conf.CustomTag == "" { @@ -214,27 +214,30 @@ func GenerateDocumentation(rootPath string, conf *resources.CatoConfig) error { fileList, err := listGoFiles(rootPath) if err != nil { - return fmt.Errorf("cato: error listing root path: %w", err) + return nil, fmt.Errorf("cato: error listing root path: %w", err) } - writerDriver, err := getDriver(conf) + exporterDriver, err := getDriver(conf) + exportConfigs := true if err != nil { - return fmt.Errorf("cato: error getting driver: %w", err) + // We don't export configs in this case + exportConfigs = false } + filesConfigs := map[string]map[string][]*resources.FieldInfo{} for _, file := range fileList { - configs, err := getConfigsToDocument(file, conf.CustomTag) if err != nil { - return fmt.Errorf("cato: error parsing go file: %w", err) + return nil, fmt.Errorf("cato: error parsing go file: %w", err) } - if len(configs) > 0 { - err = writerDriver.WriteConfigs(configs, file, rootPath) + if exportConfigs && len(configs) > 0 { + err = exporterDriver.ExportConfigs(configs, file, rootPath) if err != nil { - return fmt.Errorf("cato: error writing documentation: %w", err) + return nil, fmt.Errorf("cato: error writing documentation: %w", err) } + filesConfigs[file] = configs } } - return nil + return filesConfigs, nil } diff --git a/cato_html_test.go b/cato_html_test.go new file mode 100644 index 0000000..08fb3cf --- /dev/null +++ b/cato_html_test.go @@ -0,0 +1,19 @@ +package cato + +import ( + "testing" + + "github.com/cs3org/cato/resources" +) + +func TestHTML(t *testing.T) { + + rootPath := "examples/" + conf := &resources.CatoConfig{ + Driver: "html", + } + + if _, err := GenerateDocumentation(rootPath, conf); err != nil { + t.Errorf("GenerateDocumentation(): %w", err) + } +} diff --git a/cato_markdown_test.go b/cato_markdown_test.go new file mode 100644 index 0000000..a2a57c8 --- /dev/null +++ b/cato_markdown_test.go @@ -0,0 +1,24 @@ +package cato + +import ( + "testing" + + "github.com/cs3org/cato/resources" +) + +func TestMarkdown(t *testing.T) { + + rootPath := "examples/" + conf := &resources.CatoConfig{ + Driver: "markdown", + DriverConfig: map[string]map[string]interface{}{ + "markdown": map[string]interface{}{ + "ReferenceBase": "https://github.com/cs3org/cato/tree/master/examples", + }, + }, + } + + if _, err := GenerateDocumentation(rootPath, conf); err != nil { + t.Errorf("GenerateDocumentation(): %w", err) + } +} diff --git a/cato_test.go b/cato_test.go deleted file mode 100644 index 8416b89..0000000 --- a/cato_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package cato - -import ( - "testing" - - "github.com/cs3org/cato/resources" -) - -func TestCato(t *testing.T) { - rootPath := "/path/to/reva/" - conf := &resources.CatoConfig{ - Driver: "reva", - DriverConfig: map[string]map[string]interface{}{ - "reva": map[string]interface{}{ - "DocPaths": map[string]string{ - "internal/": "docs/content/en/docs/config/", - "pkg/": "docs/content/en/docs/config/packages/", - }, - "ReferenceBase": "https://github.com/cs3org/reva/tree/master", - }, - }, - } - if err := GenerateDocumentation(rootPath, conf); err != nil { - t.Errorf("GenerateDocumentation(): %w", err) - } -} diff --git a/examples/filesystem.go b/examples/filesystem.go new file mode 100644 index 0000000..d6358a9 --- /dev/null +++ b/examples/filesystem.go @@ -0,0 +1,51 @@ +package main + +import "fmt" + +type FileSystem struct { + CacheDirectory string `docs:"/var/tmp/;Path of cache directory"` + EnableLogging bool `docs:"false;Whether to enable logging"` + AvailableChecksums []string `docs:"[adler, rabin];The list of checksums provided by the file system"` + // Configs for various metadata drivers + DriverConfig map[string]map[string]interface{} `docs:"{json:{encoding: UTF8}, xml:{encoding: ASCII}}"` + // Config for the HTTP uploads service + Uploads *UploadConfig `docs:"&UploadConfig{HTTPPrefix: uploads, DisableTus: false}"` +} + +type UploadConfig struct { + // Whether to disable TUS protocol for uploads. + DisableTus bool `json:"disable_tus" docs:"false"` + // The prefix at which the uploads service should be exposed. + HTTPPrefix string `json:"http_prefix" docs:"uploads"` +} + +func (fs FileSystem) init() { + if fs.CacheDirectory == "" { + fs.CacheDirectory = "/var/tmp/" + } + if len(fs.AvailableChecksums) == 0 { + fs.AvailableChecksums = []string{"adler", "rabin"} + } + if fs.DriverConfig == nil { + fs.DriverConfig = map[string]map[string]interface{}{ + "json": map[string]interface{}{ + "encoding": "UTF8", + }, + "xml": map[string]interface{}{ + "encoding": "ASCII", + }, + } + } + if fs.Uploads == nil { + fs.Uploads = &UploadConfig{ + HTTPPrefix: "uploads", + } + } +} + +func main() { + fs := FileSystem{ + AvailableChecksums: []string{"adler32"}, + } + fmt.Printf("FileSystem: %+v", fs) +} diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 0000000..f95ba76 --- /dev/null +++ b/examples/index.html @@ -0,0 +1,43 @@ + +

struct: FileSystem

+ + +

struct: UploadConfig

+ diff --git a/examples/index.md b/examples/index.md new file mode 100644 index 0000000..0454433 --- /dev/null +++ b/examples/index.md @@ -0,0 +1,25 @@ + +## struct: FileSystem +- **CacheDirectory** - string + - Path of cache directory [[Ref]](https://github.com/cs3org/cato/tree/master/examples/filesystem.go#L6) + - Default: "/var/tmp/" +- **EnableLogging** - bool + - Whether to enable logging [[Ref]](https://github.com/cs3org/cato/tree/master/examples/filesystem.go#L7) + - Default: false +- **AvailableChecksums** - []string + - The list of checksums provided by the file system [[Ref]](https://github.com/cs3org/cato/tree/master/examples/filesystem.go#L8) + - Default: [adler, rabin] +- **DriverConfig** - map[string]map[string]interface{} + - Configs for various metadata drivers [[Ref]](https://github.com/cs3org/cato/tree/master/examples/filesystem.go#L10) + - Default: {json:{encoding: UTF8}, xml:{encoding: ASCII}} +- **Uploads** - *UploadConfig + - Config for the HTTP uploads service [[Ref]](https://github.com/cs3org/cato/tree/master/examples/filesystem.go#L12) + - Default: &UploadConfig{HTTPPrefix: uploads, DisableTus: false} + +## struct: UploadConfig +- **disable_tus** - bool + - Whether to disable TUS protocol for uploads. [[Ref]](https://github.com/cs3org/cato/tree/master/examples/filesystem.go#L17) + - Default: false +- **http_prefix** - string + - The prefix at which the uploads service should be exposed. [[Ref]](https://github.com/cs3org/cato/tree/master/examples/filesystem.go#L19) + - Default: "uploads" diff --git a/exporter/drivers/html/html.go b/exporter/drivers/html/html.go new file mode 100644 index 0000000..4ec6d9c --- /dev/null +++ b/exporter/drivers/html/html.go @@ -0,0 +1,160 @@ +package html + +import ( + "bufio" + "bytes" + "fmt" + "os" + "path" + "path/filepath" + "strings" + "text/template" + + "github.com/cs3org/cato/exporter" + "github.com/cs3org/cato/exporter/drivers/registry" + "github.com/cs3org/cato/resources" + "github.com/mitchellh/mapstructure" +) + +const ( + htmlFile = "index.html" + + configDefaultTemplate = "
  • {{ .Config.FieldName}} - {{ .Config.DataType}}
  • \n" + + " " +) + +func init() { + registry.Register("html", New) +} + +type mgr struct { + c *config +} + +type config struct { + DocPaths map[string]string + ReferenceBase string +} + +type templateParameters struct { + Config *resources.FieldInfo + EscapedDefaultValue string + ReferenceURL string +} + +func parseConfig(m map[string]interface{}) (*config, error) { + c := &config{} + if err := mapstructure.Decode(m, c); err != nil { + return nil, err + } + return c, nil +} + +func New(m map[string]interface{}) (exporter.ConfigExporter, error) { + conf, err := parseConfig(m) + if err != nil { + return nil, fmt.Errorf("error parsing conf: %w", err) + } + + mgr := &mgr{ + c: conf, + } + return mgr, nil +} + +func (m mgr) ExportConfigs(configs map[string][]*resources.FieldInfo, filePath, rootPath string) error { + + td, err := template.New("htmlDefault").Parse(configDefaultTemplate) + if err != nil { + return err + } + + docFileSuffix, err := filepath.Rel(rootPath, path.Dir(filePath)) + if err != nil { + return err + } + + var match string + for k := range m.c.DocPaths { + if strings.HasPrefix(docFileSuffix, k) && len(k) > len(match) { + match = k + } + } + + configName, err := filepath.Rel(match, docFileSuffix) + if err != nil { + return err + } + + docsRoot := path.Join(rootPath, m.c.DocPaths[match]) + mdDir := path.Join(docsRoot, configName) + err = os.MkdirAll(mdDir, 0700) + if err != nil { + return err + } + + lines := []string{} + + for s, fields := range configs { + lines = append(lines, fmt.Sprintf("\n

    struct: %s

    ", s)) + lines = append(lines, "") + } + + docFile := path.Join(mdDir, htmlFile) + fo, err := os.Create(docFile) + if err != nil { + return err + } + defer fo.Close() + w := bufio.NewWriter(fo) + for _, line := range lines { + fmt.Fprintln(w, line) + } + return w.Flush() +} diff --git a/exporter/drivers/loader/loader.go b/exporter/drivers/loader/loader.go new file mode 100644 index 0000000..b2f5c99 --- /dev/null +++ b/exporter/drivers/loader/loader.go @@ -0,0 +1,7 @@ +package loader + +import ( + _ "github.com/cs3org/cato/exporter/drivers/html" + _ "github.com/cs3org/cato/exporter/drivers/markdown" + _ "github.com/cs3org/cato/exporter/drivers/reva" +) diff --git a/exporter/drivers/markdown/markdown.go b/exporter/drivers/markdown/markdown.go new file mode 100644 index 0000000..6b5ce88 --- /dev/null +++ b/exporter/drivers/markdown/markdown.go @@ -0,0 +1,156 @@ +package markdown + +import ( + "bufio" + "bytes" + "fmt" + "os" + "path" + "path/filepath" + "strings" + "text/template" + + "github.com/cs3org/cato/exporter" + "github.com/cs3org/cato/exporter/drivers/registry" + "github.com/cs3org/cato/resources" + "github.com/mitchellh/mapstructure" +) + +const ( + mdFile = "index.md" + + configDefaultTemplate = "- **{{ .Config.FieldName}}** - {{ .Config.DataType}}\n" + + " - {{ .Config.Description}} {{ .ReferenceURL}}\n" + + " - Default: {{ .EscapedDefaultValue}}" +) + +func init() { + registry.Register("markdown", New) +} + +type mgr struct { + c *config +} + +type config struct { + DocPaths map[string]string + ReferenceBase string +} + +type templateParameters struct { + Config *resources.FieldInfo + EscapedDefaultValue string + ReferenceURL string +} + +func parseConfig(m map[string]interface{}) (*config, error) { + c := &config{} + if err := mapstructure.Decode(m, c); err != nil { + return nil, err + } + return c, nil +} + +func New(m map[string]interface{}) (exporter.ConfigExporter, error) { + conf, err := parseConfig(m) + if err != nil { + return nil, fmt.Errorf("error parsing conf: %w", err) + } + + mgr := &mgr{ + c: conf, + } + return mgr, nil +} + +func (m mgr) ExportConfigs(configs map[string][]*resources.FieldInfo, filePath, rootPath string) error { + + td, err := template.New("markdownDefault").Parse(configDefaultTemplate) + if err != nil { + return err + } + + docFileSuffix, err := filepath.Rel(rootPath, path.Dir(filePath)) + if err != nil { + return err + } + + var match string + for k := range m.c.DocPaths { + if strings.HasPrefix(docFileSuffix, k) && len(k) > len(match) { + match = k + } + } + + configName, err := filepath.Rel(match, docFileSuffix) + if err != nil { + return err + } + + docsRoot := path.Join(rootPath, m.c.DocPaths[match]) + mdDir := path.Join(docsRoot, configName) + err = os.MkdirAll(mdDir, 0700) + if err != nil { + return err + } + + lines := []string{} + + for s, fields := range configs { + lines = append(lines, fmt.Sprintf("\n## struct: %s", s)) + + for _, f := range fields { + var escapedDefaultValue string + var isPointer bool + if strings.HasPrefix(f.DefaultValue, "url:") { + decodedVal := strings.TrimPrefix(f.DefaultValue, "url:") + escapedDefaultValue = fmt.Sprintf("[%s](%s)", decodedVal, decodedVal) + f.DefaultValue = decodedVal + isPointer = true + } else { + escapedDefaultValue = f.DefaultValue + } + + var refURL string + if m.c.ReferenceBase != "" { + reference, err := filepath.Rel(rootPath, filePath) + if err != nil { + return err + } + refURL = fmt.Sprintf("[[Ref]](%s/%s#L%d)", m.c.ReferenceBase, reference, f.LineNumber) + } + + params := templateParameters{ + Config: f, + EscapedDefaultValue: escapedDefaultValue, + ReferenceURL: refURL, + } + + b := bytes.Buffer{} + if isPointer { + err = td.Execute(&b, params) + if err != nil { + return err + } + } else { + err = td.Execute(&b, params) + if err != nil { + return err + } + } + lines = append(lines, b.String()) + } + } + + docFile := path.Join(mdDir, mdFile) + fo, err := os.Create(docFile) + if err != nil { + return err + } + defer fo.Close() + w := bufio.NewWriter(fo) + for _, line := range lines { + fmt.Fprintln(w, line) + } + return w.Flush() +} diff --git a/writer/drivers/registry/registry.go b/exporter/drivers/registry/registry.go similarity index 75% rename from writer/drivers/registry/registry.go rename to exporter/drivers/registry/registry.go index dc09ccc..670627a 100644 --- a/writer/drivers/registry/registry.go +++ b/exporter/drivers/registry/registry.go @@ -1,9 +1,9 @@ package registry -import "github.com/cs3org/cato/writer" +import "github.com/cs3org/cato/exporter" // NewFunc is the function prototype that drivers should register at init. -type NewFunc func(map[string]interface{}) (writer.ConfigWriter, error) +type NewFunc func(map[string]interface{}) (exporter.ConfigExporter, error) // NewFuncs is a map containing all the registered drivers. var NewFuncs = map[string]NewFunc{} diff --git a/writer/drivers/reva/reva.go b/exporter/drivers/reva/reva.go similarity index 88% rename from writer/drivers/reva/reva.go rename to exporter/drivers/reva/reva.go index 95707fa..af5cb4a 100644 --- a/writer/drivers/reva/reva.go +++ b/exporter/drivers/reva/reva.go @@ -10,9 +10,9 @@ import ( "strings" "text/template" + "github.com/cs3org/cato/exporter" + "github.com/cs3org/cato/exporter/drivers/registry" "github.com/cs3org/cato/resources" - "github.com/cs3org/cato/writer" - "github.com/cs3org/cato/writer/drivers/registry" "github.com/mitchellh/mapstructure" ) @@ -120,7 +120,7 @@ func createMDFiles(root, mdDir string) error { return nil } -func New(m map[string]interface{}) (writer.ConfigWriter, error) { +func New(m map[string]interface{}) (exporter.ConfigExporter, error) { conf, err := parseConfig(m) if err != nil { @@ -133,7 +133,7 @@ func New(m map[string]interface{}) (writer.ConfigWriter, error) { return mgr, nil } -func (m mgr) WriteConfigs(configs map[string][]*resources.FieldInfo, filePath, rootPath string) error { +func (m mgr) ExportConfigs(configs map[string][]*resources.FieldInfo, filePath, rootPath string) error { td, err := template.New("revaDefault").Parse(configDefaultTemplate) if err != nil { @@ -176,7 +176,7 @@ func (m mgr) WriteConfigs(configs map[string][]*resources.FieldInfo, filePath, r } defer fi.Close() - mdCount := 0 + configLineCount := 0 lines := []string{} scanner := bufio.NewScanner(fi) @@ -184,15 +184,16 @@ func (m mgr) WriteConfigs(configs map[string][]*resources.FieldInfo, filePath, r currLine := scanner.Text() lines = append(lines, currLine) if strings.TrimSpace(currLine) == "---" { - mdCount = mdCount + 1 + configLineCount = configLineCount + 1 } - if mdCount == 2 { + if configLineCount == 2 { break } } if err := scanner.Err(); err != nil { return err } + fi.Close() lines = append(lines, "") @@ -200,10 +201,6 @@ func (m mgr) WriteConfigs(configs map[string][]*resources.FieldInfo, filePath, r lines = append(lines, fmt.Sprintf("# _struct: %s_\n", s)) for _, f := range fields { - reference, err := filepath.Rel(rootPath, filePath) - if err != nil { - return err - } var escapedDefaultValue, tomlPath string var isPointer bool if strings.HasPrefix(f.DefaultValue, "url:") { @@ -216,11 +213,21 @@ func (m mgr) WriteConfigs(configs map[string][]*resources.FieldInfo, filePath, r escapedDefaultValue = f.DefaultValue tomlPath = strings.ReplaceAll(configName, "/", ".") } + + var refURL string + if m.c.ReferenceBase != "" { + reference, err := filepath.Rel(rootPath, filePath) + if err != nil { + return err + } + refURL = fmt.Sprintf("[[Ref]](%s/%s#L%d)", m.c.ReferenceBase, reference, f.LineNumber) + } + params := templateParameters{ Config: f, TomlPath: tomlPath, EscapedDefaultValue: escapedDefaultValue, - ReferenceURL: fmt.Sprintf("[[Ref]](%s/%s#L%d)", m.c.ReferenceBase, reference, f.LineNumber), + ReferenceURL: refURL, } b := bytes.Buffer{} diff --git a/exporter/exporter.go b/exporter/exporter.go new file mode 100644 index 0000000..4910556 --- /dev/null +++ b/exporter/exporter.go @@ -0,0 +1,7 @@ +package exporter + +import "github.com/cs3org/cato/resources" + +type ConfigExporter interface { + ExportConfigs(configs map[string][]*resources.FieldInfo, filePath, rootPath string) error +} diff --git a/writer/drivers/html/html.go b/writer/drivers/html/html.go deleted file mode 100644 index 4d320f5..0000000 --- a/writer/drivers/html/html.go +++ /dev/null @@ -1,28 +0,0 @@ -package html - -import ( - "github.com/cs3org/cato/resources" - "github.com/cs3org/cato/writer" - "github.com/cs3org/cato/writer/drivers/registry" -) - -func init() { - registry.Register("html", New) -} - -type mgr struct { - c *config -} - -type config struct { -} - -func New(m map[string]interface{}) (writer.ConfigWriter, error) { - return &mgr{ - c: nil, - }, nil -} - -func (m mgr) WriteConfigs(configs map[string][]*resources.FieldInfo, filePath, rootPath string) error { - return nil -} diff --git a/writer/drivers/loader/loader.go b/writer/drivers/loader/loader.go deleted file mode 100644 index 3100bbb..0000000 --- a/writer/drivers/loader/loader.go +++ /dev/null @@ -1,7 +0,0 @@ -package loader - -import ( - _ "github.com/cs3org/cato/writer/drivers/html" - _ "github.com/cs3org/cato/writer/drivers/markdown" - _ "github.com/cs3org/cato/writer/drivers/reva" -) diff --git a/writer/drivers/markdown/markdown.go b/writer/drivers/markdown/markdown.go deleted file mode 100644 index 09dc833..0000000 --- a/writer/drivers/markdown/markdown.go +++ /dev/null @@ -1,28 +0,0 @@ -package markdown - -import ( - "github.com/cs3org/cato/resources" - "github.com/cs3org/cato/writer" - "github.com/cs3org/cato/writer/drivers/registry" -) - -func init() { - registry.Register("markdown", New) -} - -type mgr struct { - c *config -} - -type config struct { -} - -func New(m map[string]interface{}) (writer.ConfigWriter, error) { - return &mgr{ - c: nil, - }, nil -} - -func (m mgr) WriteConfigs(configs map[string][]*resources.FieldInfo, filePath, rootPath string) error { - return nil -} diff --git a/writer/writer.go b/writer/writer.go deleted file mode 100644 index 8db96e0..0000000 --- a/writer/writer.go +++ /dev/null @@ -1,7 +0,0 @@ -package writer - -import "github.com/cs3org/cato/resources" - -type ConfigWriter interface { - WriteConfigs(configs map[string][]*resources.FieldInfo, filePath, rootPath string) error -}