feat(commands): more commands (#30)
- I wrote a scripts to copy and generate all commands missing in iris from fig repo: https://github.com/withfig/autocomplete - Add a new script to generate commands docs about which commands currently are inside commands folder 377d4aac663590e504fe39a92eb4dac4cbd333b2 - Add more icons e64d4e70a1db61e62053460a882e093bd6373ab1
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SpecInfo struct {
|
||||
Name string
|
||||
Description string
|
||||
File string
|
||||
}
|
||||
|
||||
type Section struct {
|
||||
Cat string
|
||||
Title string
|
||||
Specs []SpecInfo
|
||||
}
|
||||
|
||||
type CategoryMeta struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
type CategoriesConfig struct {
|
||||
Categories []CategoryMeta `json:"categories"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
commandsDir := "commands"
|
||||
if len(os.Args) > 1 {
|
||||
commandsDir = os.Args[1]
|
||||
} else if _, err := os.Stat(commandsDir); os.IsNotExist(err) {
|
||||
if _, err2 := os.Stat("../commands"); err2 == nil {
|
||||
commandsDir = "../commands"
|
||||
}
|
||||
}
|
||||
|
||||
metaFile := filepath.Join(commandsDir, "categories.json")
|
||||
metaData, err := os.ReadFile(metaFile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading %s: %v\n", metaFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var config CategoriesConfig
|
||||
if unmarshalErr := json.Unmarshal(metaData, &config); unmarshalErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error parsing %s: %v\n", metaFile, unmarshalErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
orderMap := make(map[string]int)
|
||||
titleMap := make(map[string]string)
|
||||
for idx, cat := range config.Categories {
|
||||
orderMap[cat.ID] = idx
|
||||
titleMap[cat.ID] = cat.Title
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(commandsDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading commands dir %s: %v\n", commandsDir, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
nameRe := regexp.MustCompile(`Name:\s*["'` + "`" + `]([^"'` + "`" + `]+)["'` + "`" + `]`)
|
||||
descRe := regexp.MustCompile(`Description:\s*["'` + "`" + `]([^"'` + "`" + `]*)["'` + "`" + `]`)
|
||||
|
||||
var sections []Section
|
||||
totalCommands := 0
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
cat := entry.Name()
|
||||
if cat == "core" || strings.HasPrefix(cat, ".") || strings.HasPrefix(cat, "_") {
|
||||
continue
|
||||
}
|
||||
|
||||
title, ok := titleMap[cat]
|
||||
if !ok {
|
||||
if len(cat) <= 3 {
|
||||
title = strings.ToUpper(cat) + " Tools"
|
||||
} else {
|
||||
title = strings.ToUpper(cat[:1]) + cat[1:] + " Tools"
|
||||
}
|
||||
}
|
||||
|
||||
dirPath := filepath.Join(commandsDir, cat)
|
||||
files, readDirErr := os.ReadDir(dirPath)
|
||||
if readDirErr != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var specs []SpecInfo
|
||||
seen := make(map[string]bool)
|
||||
|
||||
for _, f := range files {
|
||||
if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || strings.HasSuffix(f.Name(), "_test.go") {
|
||||
continue
|
||||
}
|
||||
content, readFileErr := os.ReadFile(filepath.Join(dirPath, f.Name()))
|
||||
if readFileErr != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
blocks := strings.Split(string(content), "core.Register(")
|
||||
for i := 1; i < len(blocks); i++ {
|
||||
block := blocks[i]
|
||||
nameMatch := nameRe.FindStringSubmatch(block)
|
||||
if nameMatch == nil {
|
||||
continue
|
||||
}
|
||||
name := nameMatch[1]
|
||||
if seen[name] {
|
||||
continue
|
||||
}
|
||||
seen[name] = true
|
||||
|
||||
desc := "(no description)"
|
||||
descMatch := descRe.FindStringSubmatch(block)
|
||||
if descMatch != nil && descMatch[1] != "" {
|
||||
desc = descMatch[1]
|
||||
}
|
||||
desc = strings.ReplaceAll(desc, "\n", " ")
|
||||
desc = strings.ReplaceAll(desc, "|", "\\|")
|
||||
|
||||
specs = append(specs, SpecInfo{
|
||||
Name: name,
|
||||
Description: desc,
|
||||
File: f.Name(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(specs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
sort.Slice(specs, func(i, j int) bool {
|
||||
return strings.Compare(specs[i].Name, specs[j].Name) < 0
|
||||
})
|
||||
|
||||
totalCommands += len(specs)
|
||||
sections = append(sections, Section{
|
||||
Cat: cat,
|
||||
Title: title,
|
||||
Specs: specs,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(sections, func(i, j int) bool {
|
||||
idxI, okI := orderMap[sections[i].Cat]
|
||||
idxJ, okJ := orderMap[sections[j].Cat]
|
||||
if okI && okJ {
|
||||
return idxI < idxJ
|
||||
}
|
||||
if okI {
|
||||
return true
|
||||
}
|
||||
if okJ {
|
||||
return false
|
||||
}
|
||||
return strings.Compare(sections[i].Cat, sections[j].Cat) < 0
|
||||
})
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString("# Iris commands\n\n")
|
||||
sb.WriteString("This directory contains all modular CLI command specifications and autocompletion definitions supported by **Iris**. Every command is defined as a `core.Spec` registered via package `init()` functions and grouped into category subdirectories.\n\n")
|
||||
sb.WriteString("The **[`core/`](./core)** subdirectory is the primary engine package. It implements the underlying command registry (`core.Registry`), data structures (`Spec`, `Arg`, `Flag`), dynamic generators, and autocompletion matching logic. The top-level **[`all.go`](./all.go)** file anonymously imports all category subpackages to trigger their initialization and register all available commands at startup.\n\n")
|
||||
sb.WriteString("## Overview\n\n")
|
||||
fmt.Fprintf(&sb, "Currently, Iris natively supports **%d** top-level CLI commands across **%d** categories:\n\n", totalCommands, len(sections))
|
||||
|
||||
for _, sec := range sections {
|
||||
fmt.Fprintf(&sb, "- [%s (`%s/`)](#%s): **%d** commands\n", sec.Title, sec.Cat, sec.Cat, len(sec.Specs))
|
||||
}
|
||||
sb.WriteString("\n---\n\n")
|
||||
|
||||
for _, sec := range sections {
|
||||
fmt.Fprintf(&sb, "<a id=\"%s\"></a>\n", sec.Cat)
|
||||
fmt.Fprintf(&sb, "## %s (`%s/`)\n\n", sec.Title, sec.Cat)
|
||||
sb.WriteString("| Command | Description | Source File |\n")
|
||||
sb.WriteString("| :--- | :--- | :--- |\n")
|
||||
for _, spec := range sec.Specs {
|
||||
fmt.Fprintf(&sb, "| **`%s`** | %s | [`%s`](./%s/%s) |\n", spec.Name, spec.Description, spec.File, sec.Cat, spec.File)
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
|
||||
outputFile := filepath.Join(commandsDir, "README.md")
|
||||
if writeErr := os.WriteFile(outputFile, []byte(sb.String()), 0644); writeErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", outputFile, writeErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Generated %s with %d commands across %d categories.\n", outputFile, totalCommands, len(sections))
|
||||
}
|
||||
Reference in New Issue
Block a user