Skip to content

Commit f43e747

Browse files
vojtabiberleclaude
andcommitted
DMD-921 - Move SanitizeFilename to strhelper package
Move the function to internal/pkg/utils/strhelper so it can be reused across packages without refactoring. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 0c70189 commit f43e747

4 files changed

Lines changed: 52 additions & 50 deletions

File tree

internal/pkg/llm/twinformat/generator.go

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/keboola/keboola-as-code/internal/pkg/llm/twinformat/writer"
1313
"github.com/keboola/keboola-as-code/internal/pkg/log"
1414
"github.com/keboola/keboola-as-code/internal/pkg/utils/errors"
15+
"github.com/keboola/keboola-as-code/internal/pkg/utils/strhelper"
1516
"github.com/keboola/keboola-as-code/internal/pkg/utils/timeutils"
1617
)
1718

@@ -387,7 +388,7 @@ func (g *Generator) buildTransformationIndex(data *ProcessedData) map[string]any
387388
// generateTransformationMetadata generates a transformation metadata.json file.
388389
func (g *Generator) generateTransformationMetadata(ctx context.Context, transform *ProcessedTransformation) error {
389390
// Create transformation directory with sanitized name for filesystem safety.
390-
transformDir := filesystem.Join(g.outputDir, "transformations", sanitizeFilename(transform.Name))
391+
transformDir := filesystem.Join(g.outputDir, "transformations", strhelper.SanitizeFilename(transform.Name))
391392
if err := g.fs.Mkdir(ctx, transformDir); err != nil {
392393
return errors.Errorf("failed to create transformation directory %s: %w", transformDir, err)
393394
}
@@ -484,7 +485,7 @@ func (g *Generator) generateTransformationCode(ctx context.Context, transformDir
484485
blockNum++
485486
// Create filename: 01-block-name.sql
486487
ext := languageToExtension(block.Language)
487-
filename := fmt.Sprintf("%02d-%s%s", blockNum, sanitizeFilename(code.Name), ext)
488+
filename := fmt.Sprintf("%02d-%s%s", blockNum, strhelper.SanitizeFilename(code.Name), ext)
488489
filePath := filesystem.Join(codeDir, filename)
489490

490491
if err := g.mdWriter.Write(ctx, filePath, code.Script); err != nil {
@@ -529,25 +530,6 @@ func buildJobExecutionMap(exec *JobExecution) map[string]any {
529530
return result
530531
}
531532

532-
// sanitizeFilename removes or replaces characters that are not safe for filenames.
533-
// Returns "unnamed" if the result would be empty.
534-
func sanitizeFilename(name string) string {
535-
// Replace spaces and special characters with hyphens
536-
result := make([]byte, 0, len(name))
537-
for i := range len(name) {
538-
c := name[i]
539-
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' {
540-
result = append(result, c)
541-
} else if c == ' ' {
542-
result = append(result, '-')
543-
}
544-
}
545-
if len(result) == 0 {
546-
return "unnamed"
547-
}
548-
return string(result)
549-
}
550-
551533
// generateJobs generates jobs/index.json and job detail files.
552534
func (g *Generator) generateJobs(ctx context.Context, data *ProcessedData) error {
553535
g.logger.Debugf(ctx, "Generating jobs index and details")

internal/pkg/llm/twinformat/generator_test.go

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -39,35 +39,6 @@ func newTestGenerator(t *testing.T) (*Generator, filesystem.Fs) {
3939
// Helper Function Tests
4040
// =============================================================================
4141

42-
func TestSanitizeFilename(t *testing.T) {
43-
t.Parallel()
44-
45-
tests := []struct {
46-
name string
47-
input string
48-
expected string
49-
}{
50-
{name: "simple name", input: "orders", expected: "orders"},
51-
{name: "with spaces", input: "my file", expected: "my-file"},
52-
{name: "with special chars", input: "file@#$%", expected: "file"},
53-
{name: "with dashes", input: "my-file", expected: "my-file"},
54-
{name: "with underscores", input: "my_file", expected: "my_file"},
55-
{name: "mixed", input: "My File (v2)", expected: "My-File-v2"},
56-
{name: "numbers", input: "file123", expected: "file123"},
57-
{name: "empty string", input: "", expected: "unnamed"},
58-
{name: "only special chars", input: "@#$%", expected: "unnamed"},
59-
{name: "multiple spaces", input: "a b", expected: "a---b"},
60-
}
61-
62-
for _, tc := range tests {
63-
t.Run(tc.name, func(t *testing.T) {
64-
t.Parallel()
65-
result := sanitizeFilename(tc.input)
66-
assert.Equal(t, tc.expected, result)
67-
})
68-
}
69-
}
70-
7142
func TestLanguageToExtension(t *testing.T) {
7243
t.Parallel()
7344

internal/pkg/utils/strhelper/strhelper.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,23 @@ func FilterLines(keep, lines string) string {
187187
}
188188
return strings.TrimRight(out.String(), "\n")
189189
}
190+
191+
// SanitizeFilename removes or replaces characters that are not safe for filenames.
192+
// Only alphanumeric characters, hyphens, and underscores are kept.
193+
// Spaces are replaced with hyphens.
194+
// Returns "unnamed" if the result would be empty.
195+
func SanitizeFilename(name string) string {
196+
result := make([]byte, 0, len(name))
197+
for i := range len(name) {
198+
c := name[i]
199+
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' {
200+
result = append(result, c)
201+
} else if c == ' ' {
202+
result = append(result, '-')
203+
}
204+
}
205+
if len(result) == 0 {
206+
return "unnamed"
207+
}
208+
return string(result)
209+
}

internal/pkg/utils/strhelper/strhelper_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,32 @@ func TestFilterLines(t *testing.T) {
235235
assert.Equal(t, c.expected, FilterLines("^<KEEP>", c.in), "case "+cast.ToString(i))
236236
}
237237
}
238+
239+
func TestSanitizeFilename(t *testing.T) {
240+
t.Parallel()
241+
242+
tests := []struct {
243+
name string
244+
input string
245+
expected string
246+
}{
247+
{name: "simple name", input: "orders", expected: "orders"},
248+
{name: "with spaces", input: "my file", expected: "my-file"},
249+
{name: "with special chars", input: "file@#$%", expected: "file"},
250+
{name: "with dashes", input: "my-file", expected: "my-file"},
251+
{name: "with underscores", input: "my_file", expected: "my_file"},
252+
{name: "mixed", input: "My File (v2)", expected: "My-File-v2"},
253+
{name: "numbers", input: "file123", expected: "file123"},
254+
{name: "empty string", input: "", expected: "unnamed"},
255+
{name: "only special chars", input: "@#$%", expected: "unnamed"},
256+
{name: "multiple spaces", input: "a b", expected: "a---b"},
257+
}
258+
259+
for _, tc := range tests {
260+
t.Run(tc.name, func(t *testing.T) {
261+
t.Parallel()
262+
result := SanitizeFilename(tc.input)
263+
assert.Equal(t, tc.expected, result)
264+
})
265+
}
266+
}

0 commit comments

Comments
 (0)