Add trim CLI command
This commit is contained in:
@@ -47,6 +47,17 @@ type MergeOptions struct {
|
||||
CoalesceGap string
|
||||
}
|
||||
|
||||
// TrimOptions captures raw CLI option values before validation.
|
||||
type TrimOptions struct {
|
||||
InputFile string
|
||||
OutputFile string
|
||||
ReportFile string
|
||||
Keep string
|
||||
Remove string
|
||||
OutputSchema string
|
||||
AllowEmpty bool
|
||||
}
|
||||
|
||||
// Config is the validated runtime configuration for a merge invocation.
|
||||
type Config struct {
|
||||
InputFiles []string
|
||||
@@ -66,6 +77,17 @@ type Config struct {
|
||||
FillerMaxDuration float64
|
||||
}
|
||||
|
||||
// TrimConfig is the validated runtime configuration for a trim invocation.
|
||||
type TrimConfig struct {
|
||||
InputFile string
|
||||
OutputFile string
|
||||
ReportFile string
|
||||
Mode string
|
||||
Selector string
|
||||
OutputSchema string
|
||||
AllowEmpty bool
|
||||
}
|
||||
|
||||
// NewMergeConfig validates raw merge options and returns normalized config.
|
||||
func NewMergeConfig(opts MergeOptions) (Config, error) {
|
||||
cfg := Config{
|
||||
@@ -168,6 +190,63 @@ func NewMergeConfig(opts MergeOptions) (Config, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// NewTrimConfig validates raw trim options and returns normalized config.
|
||||
func NewTrimConfig(opts TrimOptions) (TrimConfig, error) {
|
||||
inputFile := filepath.Clean(strings.TrimSpace(opts.InputFile))
|
||||
if strings.TrimSpace(opts.InputFile) == "" {
|
||||
return TrimConfig{}, errors.New("--input-file is required")
|
||||
}
|
||||
if err := requireFile(inputFile, "--input-file"); err != nil {
|
||||
return TrimConfig{}, err
|
||||
}
|
||||
|
||||
outputFile, err := normalizeOutputPath(opts.OutputFile, "--output-file")
|
||||
if err != nil {
|
||||
return TrimConfig{}, err
|
||||
}
|
||||
|
||||
reportFile := ""
|
||||
if strings.TrimSpace(opts.ReportFile) != "" {
|
||||
reportFile, err = normalizeOutputPath(opts.ReportFile, "--report-file")
|
||||
if err != nil {
|
||||
return TrimConfig{}, err
|
||||
}
|
||||
}
|
||||
|
||||
keep := strings.TrimSpace(opts.Keep)
|
||||
remove := strings.TrimSpace(opts.Remove)
|
||||
if keep == "" && remove == "" {
|
||||
return TrimConfig{}, errors.New("exactly one of --keep or --remove is required")
|
||||
}
|
||||
if keep != "" && remove != "" {
|
||||
return TrimConfig{}, errors.New("--keep and --remove are mutually exclusive")
|
||||
}
|
||||
|
||||
mode := "keep"
|
||||
selector := keep
|
||||
if remove != "" {
|
||||
mode = "remove"
|
||||
selector = remove
|
||||
}
|
||||
|
||||
outputSchema := strings.TrimSpace(opts.OutputSchema)
|
||||
if outputSchema != "" {
|
||||
if err := validateOutputSchema(outputSchema); err != nil {
|
||||
return TrimConfig{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return TrimConfig{
|
||||
InputFile: inputFile,
|
||||
OutputFile: outputFile,
|
||||
ReportFile: reportFile,
|
||||
Mode: mode,
|
||||
Selector: selector,
|
||||
OutputSchema: outputSchema,
|
||||
AllowEmpty: opts.AllowEmpty,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseModuleList(value string) ([]string, error) {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
|
||||
@@ -612,6 +612,105 @@ func TestCoalesceGapRejectsInvalidOverride(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTrimConfigRequiresInputAndOutput(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
input := writeTempFile(t, dir, "input.json")
|
||||
output := filepath.Join(dir, "trimmed.json")
|
||||
|
||||
_, err := NewTrimConfig(TrimOptions{
|
||||
OutputFile: output,
|
||||
Keep: "1",
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "--input-file is required") {
|
||||
t.Fatalf("expected input-file required error, got %v", err)
|
||||
}
|
||||
|
||||
_, err = NewTrimConfig(TrimOptions{
|
||||
InputFile: input,
|
||||
Keep: "1",
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "--output-file is required") {
|
||||
t.Fatalf("expected output-file required error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTrimConfigRequiresExactlyOneSelectorFlag(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
input := writeTempFile(t, dir, "input.json")
|
||||
output := filepath.Join(dir, "trimmed.json")
|
||||
|
||||
_, err := NewTrimConfig(TrimOptions{
|
||||
InputFile: input,
|
||||
OutputFile: output,
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "exactly one of --keep or --remove is required") {
|
||||
t.Fatalf("expected missing selector error, got %v", err)
|
||||
}
|
||||
|
||||
_, err = NewTrimConfig(TrimOptions{
|
||||
InputFile: input,
|
||||
OutputFile: output,
|
||||
Keep: "1",
|
||||
Remove: "2",
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "mutually exclusive") {
|
||||
t.Fatalf("expected mutually exclusive selector error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTrimConfigAcceptsOutputSchemaOverride(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
input := writeTempFile(t, dir, "input.json")
|
||||
output := filepath.Join(dir, "trimmed.json")
|
||||
reportPath := filepath.Join(dir, "report.json")
|
||||
|
||||
cfg, err := NewTrimConfig(TrimOptions{
|
||||
InputFile: input,
|
||||
OutputFile: output,
|
||||
ReportFile: reportPath,
|
||||
Remove: "3-5",
|
||||
OutputSchema: OutputSchemaMinimal,
|
||||
AllowEmpty: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("config failed: %v", err)
|
||||
}
|
||||
if cfg.Mode != "remove" {
|
||||
t.Fatalf("mode = %q, want remove", cfg.Mode)
|
||||
}
|
||||
if cfg.Selector != "3-5" {
|
||||
t.Fatalf("selector = %q, want 3-5", cfg.Selector)
|
||||
}
|
||||
if cfg.OutputSchema != OutputSchemaMinimal {
|
||||
t.Fatalf("output schema = %q, want %q", cfg.OutputSchema, OutputSchemaMinimal)
|
||||
}
|
||||
if !cfg.AllowEmpty {
|
||||
t.Fatal("allow empty should be true")
|
||||
}
|
||||
if cfg.ReportFile != reportPath {
|
||||
t.Fatalf("report file = %q, want %q", cfg.ReportFile, reportPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTrimConfigRejectsInvalidOutputSchemaOverride(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
input := writeTempFile(t, dir, "input.json")
|
||||
output := filepath.Join(dir, "trimmed.json")
|
||||
|
||||
_, err := NewTrimConfig(TrimOptions{
|
||||
InputFile: input,
|
||||
OutputFile: output,
|
||||
Keep: "1",
|
||||
OutputSchema: "compact",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected output schema validation error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "--output-schema must be one of") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertPositiveFloatEnvValidation(t *testing.T, envName string) {
|
||||
t.Helper()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user