From 84dae7b457b9f32d07b96f64a89e8b0f12b0a057 Mon Sep 17 00:00:00 2001 From: Indra Gunawan Date: Fri, 13 Mar 2026 21:22:47 +0800 Subject: [PATCH 1/3] add overwrite-readme flag to extension-init command --- caddy/extinit.go | 29 +++++++++-------- internal/extgen/docs.go | 7 ++++ internal/extgen/docs_test.go | 63 ++++++++++++++++++++++++++++++++++++ internal/extgen/generator.go | 20 +++++++----- 4 files changed, 98 insertions(+), 21 deletions(-) diff --git a/caddy/extinit.go b/caddy/extinit.go index c990b99f80..333c0bd952 100644 --- a/caddy/extinit.go +++ b/caddy/extinit.go @@ -3,46 +3,49 @@ package caddy import ( "errors" "log" - "os" "path/filepath" "strings" - "github.com/dunglas/frankenphp/internal/extgen" - caddycmd "github.com/caddyserver/caddy/v2/cmd" + "github.com/dunglas/frankenphp/internal/extgen" "github.com/spf13/cobra" ) func init() { caddycmd.RegisterCommand(caddycmd.Command{ Name: "extension-init", - Usage: "go_extension.go [--verbose]", + Usage: "go_extension.go [--overwrite-readme]", Short: "Initializes a PHP extension from a Go file (EXPERIMENTAL)", Long: ` Initializes a PHP extension from a Go file. This command generates the necessary C files for the extension, including the header and source files, as well as the arginfo file.`, CobraFunc: func(cmd *cobra.Command) { - cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs") + cmd.Flags().BoolP("overwrite-readme", "r", false, "Overwrite README.md if it exists") - cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdInitExtension) + cmd.RunE = cmdInitExtension }, }) } -func cmdInitExtension(_ caddycmd.Flags) (int, error) { - if len(os.Args) < 3 { - return 1, errors.New("the path to the Go source is required") +func cmdInitExtension(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("the path to the Go source is required") + } + + overwriteReadme, err := cmd.Flags().GetBool("overwrite-readme") + if err != nil { + return err } - sourceFile := os.Args[2] + sourceFile := args[0] baseName := extgen.SanitizePackageName(strings.TrimSuffix(filepath.Base(sourceFile), ".go")) - generator := extgen.Generator{BaseName: baseName, SourceFile: sourceFile, BuildDir: filepath.Dir(sourceFile)} + generator := extgen.Generator{BaseName: baseName, SourceFile: sourceFile, BuildDir: filepath.Dir(sourceFile), OverwriteReadme: overwriteReadme} if err := generator.Generate(); err != nil { - return 1, err + return err } log.Printf("PHP extension %q initialized successfully in directory %q", baseName, generator.BuildDir) - return 0, nil + return nil } diff --git a/internal/extgen/docs.go b/internal/extgen/docs.go index 428507ef13..dd49145f17 100644 --- a/internal/extgen/docs.go +++ b/internal/extgen/docs.go @@ -3,6 +3,7 @@ package extgen import ( "bytes" _ "embed" + "os" "path/filepath" "text/template" ) @@ -12,6 +13,7 @@ var docFileContent string type DocumentationGenerator struct { generator *Generator + overwrite bool } type DocTemplateData struct { @@ -22,6 +24,11 @@ type DocTemplateData struct { func (dg *DocumentationGenerator) generate() error { filename := filepath.Join(dg.generator.BuildDir, "README.md") + + if _, err := os.Stat(filename); err == nil && !dg.overwrite { + return nil + } + content, err := dg.generateMarkdown() if err != nil { return err diff --git a/internal/extgen/docs_test.go b/internal/extgen/docs_test.go index 27b2b6af78..a93e45b9c6 100644 --- a/internal/extgen/docs_test.go +++ b/internal/extgen/docs_test.go @@ -383,3 +383,66 @@ func BenchmarkDocumentationGenerator_GenerateMarkdown(b *testing.B) { assert.NoError(b, err) } } + +func TestDocumentationGenerator_OverwriteReadme(t *testing.T) { + tempDir := t.TempDir() + readmePath := filepath.Join(tempDir, "README.md") + + err := os.WriteFile(readmePath, []byte("hello"), 0644) + require.NoError(t, err) + + generator := &Generator{ + BaseName: "testextension", + BuildDir: tempDir, + Functions: []phpFunction{ + { + Name: "greet", + ReturnType: phpString, + Params: []phpParameter{ + {Name: "name", PhpType: phpString}, + }, + Signature: "greet(string $name): string", + }, + }, + Classes: []phpClass{}, + } + + docGen := &DocumentationGenerator{ + generator: generator, + } + + err = docGen.generate() + assert.NoError(t, err, "generate() unexpected error") + + content, err := os.ReadFile(readmePath) + require.NoError(t, err, "Failed to read generated README.md") + + assert.Equal(t, string(content), "hello") + + // regenerate + docGen = &DocumentationGenerator{ + generator: generator, + overwrite: true, + } + + err = docGen.generate() + assert.NoError(t, err, "generate() unexpected error") + + content, err = os.ReadFile(readmePath) + require.NoError(t, err, "Failed to read generated README.md") + + contentStr := string(content) + + assert.Contains(t, contentStr, "# testextension Extension", "README should contain extension title") + assert.Contains(t, contentStr, "Auto-generated PHP extension from Go code.", "README should contain description") + + if len(generator.Functions) > 0 { + assert.Contains(t, contentStr, "## Functions", "README should contain functions section when functions exist") + + for _, fn := range generator.Functions { + assert.Contains(t, contentStr, "### "+fn.Name, "README should contain function %s", fn.Name) + assert.Contains(t, contentStr, fn.Signature, "README should contain function signature for %s", fn.Name) + } + } + +} diff --git a/internal/extgen/generator.go b/internal/extgen/generator.go index 40cd3365a2..d7702a10a5 100644 --- a/internal/extgen/generator.go +++ b/internal/extgen/generator.go @@ -6,13 +6,14 @@ import ( ) type Generator struct { - BaseName string - SourceFile string - BuildDir string - Functions []phpFunction - Classes []phpClass - Constants []phpConstant - Namespace string + BaseName string + SourceFile string + BuildDir string + Functions []phpFunction + Classes []phpClass + Constants []phpConstant + Namespace string + OverwriteReadme bool } // EXPERIMENTAL @@ -129,7 +130,10 @@ func (g *Generator) generateGoFile() error { } func (g *Generator) generateDocumentation() error { - docGen := DocumentationGenerator{g} + docGen := DocumentationGenerator{ + generator: g, + overwrite: g.OverwriteReadme, + } if err := docGen.generate(); err != nil { return &GeneratorError{"documentation generation", "failed to generate documentation", err} } From c03d11734e1c5d7d8734c6e8b316d3af144d9ef3 Mon Sep 17 00:00:00 2001 From: Indra Gunawan Date: Sun, 15 Mar 2026 16:39:25 +0800 Subject: [PATCH 2/3] skip README.md generation in extension-init command if it exists --- caddy/extinit.go | 11 ++--------- internal/extgen/docs.go | 3 +-- internal/extgen/docs_test.go | 29 +---------------------------- internal/extgen/generator.go | 20 ++++++++------------ 4 files changed, 12 insertions(+), 51 deletions(-) diff --git a/caddy/extinit.go b/caddy/extinit.go index 333c0bd952..087bf82fbc 100644 --- a/caddy/extinit.go +++ b/caddy/extinit.go @@ -14,13 +14,11 @@ import ( func init() { caddycmd.RegisterCommand(caddycmd.Command{ Name: "extension-init", - Usage: "go_extension.go [--overwrite-readme]", + Usage: "go_extension.go", Short: "Initializes a PHP extension from a Go file (EXPERIMENTAL)", Long: ` Initializes a PHP extension from a Go file. This command generates the necessary C files for the extension, including the header and source files, as well as the arginfo file.`, CobraFunc: func(cmd *cobra.Command) { - cmd.Flags().BoolP("overwrite-readme", "r", false, "Overwrite README.md if it exists") - cmd.RunE = cmdInitExtension }, }) @@ -31,15 +29,10 @@ func cmdInitExtension(cmd *cobra.Command, args []string) error { return errors.New("the path to the Go source is required") } - overwriteReadme, err := cmd.Flags().GetBool("overwrite-readme") - if err != nil { - return err - } - sourceFile := args[0] baseName := extgen.SanitizePackageName(strings.TrimSuffix(filepath.Base(sourceFile), ".go")) - generator := extgen.Generator{BaseName: baseName, SourceFile: sourceFile, BuildDir: filepath.Dir(sourceFile), OverwriteReadme: overwriteReadme} + generator := extgen.Generator{BaseName: baseName, SourceFile: sourceFile, BuildDir: filepath.Dir(sourceFile)} if err := generator.Generate(); err != nil { return err diff --git a/internal/extgen/docs.go b/internal/extgen/docs.go index dd49145f17..434f3e78c7 100644 --- a/internal/extgen/docs.go +++ b/internal/extgen/docs.go @@ -13,7 +13,6 @@ var docFileContent string type DocumentationGenerator struct { generator *Generator - overwrite bool } type DocTemplateData struct { @@ -25,7 +24,7 @@ type DocTemplateData struct { func (dg *DocumentationGenerator) generate() error { filename := filepath.Join(dg.generator.BuildDir, "README.md") - if _, err := os.Stat(filename); err == nil && !dg.overwrite { + if _, err := os.Stat(filename); err == nil { return nil } diff --git a/internal/extgen/docs_test.go b/internal/extgen/docs_test.go index a93e45b9c6..7d8272526a 100644 --- a/internal/extgen/docs_test.go +++ b/internal/extgen/docs_test.go @@ -384,7 +384,7 @@ func BenchmarkDocumentationGenerator_GenerateMarkdown(b *testing.B) { } } -func TestDocumentationGenerator_OverwriteReadme(t *testing.T) { +func TestDocumentationGenerator_SkipExistingReadme(t *testing.T) { tempDir := t.TempDir() readmePath := filepath.Join(tempDir, "README.md") @@ -418,31 +418,4 @@ func TestDocumentationGenerator_OverwriteReadme(t *testing.T) { require.NoError(t, err, "Failed to read generated README.md") assert.Equal(t, string(content), "hello") - - // regenerate - docGen = &DocumentationGenerator{ - generator: generator, - overwrite: true, - } - - err = docGen.generate() - assert.NoError(t, err, "generate() unexpected error") - - content, err = os.ReadFile(readmePath) - require.NoError(t, err, "Failed to read generated README.md") - - contentStr := string(content) - - assert.Contains(t, contentStr, "# testextension Extension", "README should contain extension title") - assert.Contains(t, contentStr, "Auto-generated PHP extension from Go code.", "README should contain description") - - if len(generator.Functions) > 0 { - assert.Contains(t, contentStr, "## Functions", "README should contain functions section when functions exist") - - for _, fn := range generator.Functions { - assert.Contains(t, contentStr, "### "+fn.Name, "README should contain function %s", fn.Name) - assert.Contains(t, contentStr, fn.Signature, "README should contain function signature for %s", fn.Name) - } - } - } diff --git a/internal/extgen/generator.go b/internal/extgen/generator.go index d7702a10a5..40cd3365a2 100644 --- a/internal/extgen/generator.go +++ b/internal/extgen/generator.go @@ -6,14 +6,13 @@ import ( ) type Generator struct { - BaseName string - SourceFile string - BuildDir string - Functions []phpFunction - Classes []phpClass - Constants []phpConstant - Namespace string - OverwriteReadme bool + BaseName string + SourceFile string + BuildDir string + Functions []phpFunction + Classes []phpClass + Constants []phpConstant + Namespace string } // EXPERIMENTAL @@ -130,10 +129,7 @@ func (g *Generator) generateGoFile() error { } func (g *Generator) generateDocumentation() error { - docGen := DocumentationGenerator{ - generator: g, - overwrite: g.OverwriteReadme, - } + docGen := DocumentationGenerator{g} if err := docGen.generate(); err != nil { return &GeneratorError{"documentation generation", "failed to generate documentation", err} } From ed3170ddd89a355f097c8efc7b3dec318cf77552 Mon Sep 17 00:00:00 2001 From: Indra Gunawan Date: Tue, 17 Mar 2026 18:01:49 +0800 Subject: [PATCH 3/3] revert extinit.go --- caddy/extinit.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/caddy/extinit.go b/caddy/extinit.go index 087bf82fbc..c990b99f80 100644 --- a/caddy/extinit.go +++ b/caddy/extinit.go @@ -3,42 +3,46 @@ package caddy import ( "errors" "log" + "os" "path/filepath" "strings" - caddycmd "github.com/caddyserver/caddy/v2/cmd" "github.com/dunglas/frankenphp/internal/extgen" + + caddycmd "github.com/caddyserver/caddy/v2/cmd" "github.com/spf13/cobra" ) func init() { caddycmd.RegisterCommand(caddycmd.Command{ Name: "extension-init", - Usage: "go_extension.go", + Usage: "go_extension.go [--verbose]", Short: "Initializes a PHP extension from a Go file (EXPERIMENTAL)", Long: ` Initializes a PHP extension from a Go file. This command generates the necessary C files for the extension, including the header and source files, as well as the arginfo file.`, CobraFunc: func(cmd *cobra.Command) { - cmd.RunE = cmdInitExtension + cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs") + + cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdInitExtension) }, }) } -func cmdInitExtension(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return errors.New("the path to the Go source is required") +func cmdInitExtension(_ caddycmd.Flags) (int, error) { + if len(os.Args) < 3 { + return 1, errors.New("the path to the Go source is required") } - sourceFile := args[0] + sourceFile := os.Args[2] baseName := extgen.SanitizePackageName(strings.TrimSuffix(filepath.Base(sourceFile), ".go")) generator := extgen.Generator{BaseName: baseName, SourceFile: sourceFile, BuildDir: filepath.Dir(sourceFile)} if err := generator.Generate(); err != nil { - return err + return 1, err } log.Printf("PHP extension %q initialized successfully in directory %q", baseName, generator.BuildDir) - return nil + return 0, nil }