Skip to content

Commit bc444d8

Browse files
committed
update render logic to support other formats
1 parent d6ab963 commit bc444d8

7 files changed

Lines changed: 664 additions & 102 deletions

File tree

cmd/render.go

Lines changed: 7 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@ package cmd
22

33
import (
44
"fmt"
5-
"io"
65
"os"
7-
"path/filepath"
8-
"strings"
9-
106
"rules-cli/internal/formats"
117

128
"github.com/spf13/cobra"
@@ -17,8 +13,8 @@ var renderCmd = &cobra.Command{
1713
Use: "render [format]",
1814
Short: "Render rules to a specific format",
1915
Long: `Renders existing rules to a specified format.
20-
Creates .{format}/rules/ directory and copies all rules from the default location
21-
(.rules/) to the target format location. Preserves directory structure of rule sets.`,
16+
Copies all rules from the default location (.rules/) to the target format
17+
as described in render-formats.md.`,
2218
Example: ` rules render cursor
2319
rules render continue`,
2420
RunE: func(cmd *cobra.Command, args []string) error {
@@ -44,20 +40,10 @@ Creates .{format}/rules/ directory and copies all rules from the default locatio
4440

4541
fmt.Printf("Rendering rules to %s format...\n", formatName)
4642

47-
// Get target directory
48-
targetDir, err := formats.GetRulesDirectory(formatName)
49-
if err != nil {
50-
return fmt.Errorf("failed to get target directory for format %s: %w", formatName, err)
51-
}
52-
53-
// Create target directory if it doesn't exist
54-
if err := os.MkdirAll(targetDir, 0755); err != nil {
55-
return fmt.Errorf("failed to create target directory %s: %w", targetDir, err)
56-
}
57-
58-
// Copy rules from source to target
59-
if err := copyDir(sourceDir, targetDir); err != nil {
60-
return fmt.Errorf("failed to copy rules to target directory: %w", err)
43+
// Use the formats package to handle the rendering based on the target format
44+
verbose, _ := cmd.Flags().GetBool("verbose")
45+
if err := formats.RenderRulesToFormat(sourceDir, formatName, verbose); err != nil {
46+
return fmt.Errorf("failed to render rules to %s format: %w", formatName, err)
6147
}
6248

6349
fmt.Printf("Successfully rendered rules to %s format\n", formatName)
@@ -66,76 +52,7 @@ Creates .{format}/rules/ directory and copies all rules from the default locatio
6652
},
6753
}
6854

69-
// copyDir recursively copies a directory tree, preserving directory structure
70-
func copyDir(src, dst string) error {
71-
// Get properties of source directory
72-
srcInfo, err := os.Stat(src)
73-
if err != nil {
74-
return err
75-
}
76-
77-
// Create the destination directory
78-
if err = os.MkdirAll(dst, srcInfo.Mode()); err != nil {
79-
return err
80-
}
81-
82-
// Get directory contents
83-
entries, err := os.ReadDir(src)
84-
if err != nil {
85-
return err
86-
}
87-
88-
for _, entry := range entries {
89-
srcPath := filepath.Join(src, entry.Name())
90-
dstPath := filepath.Join(dst, entry.Name())
91-
92-
if entry.IsDir() {
93-
// Recursively copy subdirectory
94-
if err = copyDir(srcPath, dstPath); err != nil {
95-
return err
96-
}
97-
} else {
98-
// Copy the file
99-
if err = copyFile(srcPath, dstPath); err != nil {
100-
return err
101-
}
102-
}
103-
}
104-
105-
return nil
106-
}
107-
108-
// copyFile copies a single file from src to dst
109-
func copyFile(src, dst string) error {
110-
// Skip if not a markdown file
111-
if !strings.HasSuffix(src, ".md") {
112-
return nil
113-
}
114-
115-
// Open source file
116-
in, err := os.Open(src)
117-
if err != nil {
118-
return err
119-
}
120-
defer in.Close()
121-
122-
// Create destination file
123-
out, err := os.Create(dst)
124-
if err != nil {
125-
return err
126-
}
127-
defer out.Close()
128-
129-
// Copy contents
130-
_, err = io.Copy(out, in)
131-
if err != nil {
132-
return err
133-
}
134-
135-
// Flush to disk
136-
return out.Sync()
137-
}
138-
13955
func init() {
14056
rootCmd.AddCommand(renderCmd)
57+
renderCmd.Flags().BoolP("verbose", "v", false, "Enable verbose output")
14158
}

internal/formats/formats.go

Lines changed: 92 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import (
1111
type Format struct {
1212
Name string
1313
DirectoryPrefix string
14+
FileExtension string
15+
IsSingleFile bool
16+
SingleFilePath string
1417
}
1518

1619
// GetFormat returns a Format for the given format name
@@ -20,23 +23,99 @@ func GetFormat(formatName string) Format {
2023
return Format{
2124
Name: "default",
2225
DirectoryPrefix: ".rules",
26+
FileExtension: ".md",
27+
IsSingleFile: false,
2328
}
2429
}
2530

26-
// Otherwise use .<format>/rules
27-
return Format{
28-
Name: formatName,
29-
DirectoryPrefix: fmt.Sprintf(".%s/rules", formatName),
31+
// Define formats based on render-formats.md
32+
switch formatName {
33+
case "continue":
34+
return Format{
35+
Name: "continue",
36+
DirectoryPrefix: ".continue/rules",
37+
FileExtension: ".md",
38+
IsSingleFile: false,
39+
}
40+
case "cursor":
41+
return Format{
42+
Name: "cursor",
43+
DirectoryPrefix: ".cursor/rules",
44+
FileExtension: ".mdc",
45+
IsSingleFile: false,
46+
}
47+
case "windsurf":
48+
return Format{
49+
Name: "windsurf",
50+
DirectoryPrefix: ".windsurf/rules",
51+
FileExtension: ".md",
52+
IsSingleFile: false,
53+
}
54+
case "claude":
55+
return Format{
56+
Name: "claude",
57+
DirectoryPrefix: "",
58+
FileExtension: ".md",
59+
IsSingleFile: true,
60+
SingleFilePath: "CLAUDE.md",
61+
}
62+
case "copilot":
63+
return Format{
64+
Name: "copilot",
65+
DirectoryPrefix: ".github/instructions",
66+
FileExtension: ".instructions.md",
67+
IsSingleFile: false,
68+
}
69+
case "codex":
70+
return Format{
71+
Name: "codex",
72+
DirectoryPrefix: "",
73+
FileExtension: ".md",
74+
IsSingleFile: true,
75+
SingleFilePath: "AGENT.md",
76+
}
77+
case "cline":
78+
return Format{
79+
Name: "cline",
80+
DirectoryPrefix: ".clinerules",
81+
FileExtension: ".md",
82+
IsSingleFile: false,
83+
}
84+
case "cody":
85+
return Format{
86+
Name: "cody",
87+
DirectoryPrefix: ".sourcegraph",
88+
FileExtension: ".rule.md",
89+
IsSingleFile: false,
90+
}
91+
case "amp":
92+
return Format{
93+
Name: "amp",
94+
DirectoryPrefix: "",
95+
FileExtension: ".md",
96+
IsSingleFile: true,
97+
SingleFilePath: "AGENT.md",
98+
}
99+
default:
100+
// For any other format, use .<format>/rules
101+
return Format{
102+
Name: formatName,
103+
DirectoryPrefix: fmt.Sprintf(".%s/rules", formatName),
104+
FileExtension: ".md",
105+
IsSingleFile: false,
106+
}
30107
}
31108
}
32109

33110
// InitializeFormat creates the directory structure for a format
34111
func InitializeFormat(formatName string) error {
35112
format := GetFormat(formatName)
36113

37-
dirPath := format.DirectoryPrefix
38-
if err := os.MkdirAll(dirPath, 0755); err != nil {
39-
return fmt.Errorf("failed to create directory %s: %w", dirPath, err)
114+
if !format.IsSingleFile {
115+
dirPath := format.DirectoryPrefix
116+
if err := os.MkdirAll(dirPath, 0755); err != nil {
117+
return fmt.Errorf("failed to create directory %s: %w", dirPath, err)
118+
}
40119
}
41120

42121
// Create rules.json in the root directory if it doesn't exist
@@ -111,4 +190,10 @@ func GetFormatSuggestionMessage() (string, error) {
111190
}
112191

113192
return "", nil
193+
}
194+
195+
// RenderRules renders rules from the source directory to the target format
196+
func RenderRules(sourceDir string, targetFormat Format) error {
197+
// Use the transformer to process the rule files
198+
return ProcessRuleFiles(sourceDir, targetFormat)
114199
}

internal/formats/render.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package formats
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
)
8+
9+
// RenderOptions holds configuration options for the rendering process
10+
type RenderOptions struct {
11+
SourceDir string
12+
TargetFormat Format
13+
Verbose bool
14+
}
15+
16+
// RenderRulesToFormat renders rules from the source directory to the target format
17+
// This is a higher-level function that sets up the rendering process
18+
func RenderRulesToFormat(sourceDir string, targetFormatName string, verbose bool) error {
19+
// Get the target format
20+
targetFormat := GetFormat(targetFormatName)
21+
22+
// Check if source directory exists
23+
if _, err := os.Stat(sourceDir); os.IsNotExist(err) {
24+
return fmt.Errorf("source directory %s does not exist", sourceDir)
25+
}
26+
27+
// For single file formats, make sure the parent directory exists
28+
if targetFormat.IsSingleFile {
29+
parentDir := filepath.Dir(targetFormat.SingleFilePath)
30+
if parentDir != "." && parentDir != "" {
31+
if err := os.MkdirAll(parentDir, 0755); err != nil {
32+
return fmt.Errorf("failed to create parent directory for single file format: %w", err)
33+
}
34+
}
35+
} else {
36+
// For directory-based formats, create the target directory
37+
if err := os.MkdirAll(targetFormat.DirectoryPrefix, 0755); err != nil {
38+
return fmt.Errorf("failed to create target directory %s: %w", targetFormat.DirectoryPrefix, err)
39+
}
40+
}
41+
42+
// Process the rule files
43+
return ProcessRuleFiles(sourceDir, targetFormat)
44+
}

0 commit comments

Comments
 (0)