Skip to content

Commit 8f380e7

Browse files
feat: Refactor commands to use viper for configuration management and remove deprecated config file
1 parent 550bd6b commit 8f380e7

File tree

7 files changed

+219
-172
lines changed

7 files changed

+219
-172
lines changed

cmd/completeness.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"strings"
66

77
"github.com/spf13/cobra"
8+
"github.com/spf13/viper"
89

910
"github.com/idlab-discover/AIBoMGen-cli/internal/completeness"
1011
bomio "github.com/idlab-discover/AIBoMGen-cli/internal/io"
@@ -17,16 +18,16 @@ var completenessCmd = &cobra.Command{
1718
Long: "Reads an existing CycloneDX AIBOM (json/xml) and scores it against the configured field registry.",
1819
RunE: func(cmd *cobra.Command, args []string) error {
1920

20-
// Resolve effective log level.
21-
level := strings.ToLower(strings.TrimSpace(completenessLogLevel))
21+
// Get log level from viper (respects config file and CLI flag)
22+
level := strings.ToLower(strings.TrimSpace(viper.GetString("completeness.log-level")))
2223
if level == "" {
2324
level = "standard"
2425
}
2526
switch level {
2627
case "quiet", "standard", "debug":
2728
// ok
2829
default:
29-
return fmt.Errorf("invalid --log-level %q (expected quiet|standard|debug)", completenessLogLevel)
30+
return fmt.Errorf("invalid --log-level %q (expected quiet|standard|debug)", level)
3031
}
3132

3233
// Wire internal package logging based on log level.
@@ -38,7 +39,14 @@ var completenessCmd = &cobra.Command{
3839
}
3940
}
4041

41-
bom, err := bomio.ReadBOM(inPath, inFormat)
42+
// Get input path and format from viper
43+
inputPath := viper.GetString("completeness.input")
44+
inputFormat := viper.GetString("completeness.format")
45+
if inputFormat == "" {
46+
inputFormat = "auto"
47+
}
48+
49+
bom, err := bomio.ReadBOM(inputPath, inputFormat)
4250
if err != nil {
4351
return err
4452
}
@@ -59,8 +67,13 @@ func init() {
5967
rootCmd.AddCommand(completenessCmd)
6068

6169
completenessCmd.Flags().StringVarP(&inPath, "input", "i", "", "Path to existing AIBOM file (required)")
62-
completenessCmd.Flags().StringVarP(&inFormat, "format", "f", "auto", "Input BOM format: json|xml|auto (default: auto)")
63-
completenessCmd.Flags().StringVar(&completenessLogLevel, "log-level", "standard", "Log level: quiet|standard|debug (default: standard)")
70+
completenessCmd.Flags().StringVarP(&inFormat, "format", "f", "", "Input BOM format: json|xml|auto")
71+
completenessCmd.Flags().StringVar(&completenessLogLevel, "log-level", "", "Log level: quiet|standard|debug")
6472

6573
_ = completenessCmd.MarkFlagRequired("input")
74+
75+
// Bind all flags to viper for config file support
76+
viper.BindPFlag("completeness.input", completenessCmd.Flags().Lookup("input"))
77+
viper.BindPFlag("completeness.format", completenessCmd.Flags().Lookup("format"))
78+
viper.BindPFlag("completeness.log-level", completenessCmd.Flags().Lookup("log-level"))
6679
}

cmd/enrich.go

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ from Hugging Face API and README before enrichment.`,
3434
}
3535

3636
// Get log level from viper
37-
level := strings.ToLower(strings.TrimSpace(viper.GetString("log.level")))
37+
level := strings.ToLower(strings.TrimSpace(viper.GetString("enrich.log-level")))
3838
if level == "" {
3939
level = "standard"
4040
}
4141
switch level {
4242
case "quiet", "standard", "debug":
4343
// ok
4444
default:
45-
return fmt.Errorf("invalid --log-level %q (expected quiet|standard|debug)", enrichLogLevel)
45+
return fmt.Errorf("invalid --log-level %q (expected quiet|standard|debug)", level)
4646
}
4747

4848
// Wire internal package logging
@@ -57,36 +57,41 @@ from Hugging Face API and README before enrichment.`,
5757
}
5858

5959
// Read existing BOM
60-
bom, err := bomio.ReadBOM(enrichInput, enrichInputFormat)
60+
inputPath := viper.GetString("enrich.input")
61+
inputFormat := viper.GetString("enrich.format")
62+
if inputFormat == "" {
63+
inputFormat = "auto"
64+
}
65+
bom, err := bomio.ReadBOM(inputPath, inputFormat)
6166
if err != nil {
6267
return fmt.Errorf("failed to read input BOM: %w", err)
6368
}
6469

6570
// Determine output path
66-
outPath := enrichOutput
71+
outPath := viper.GetString("enrich.output")
6772
if outPath == "" {
68-
outPath = enrichInput // overwrite by default
73+
outPath = inputPath // overwrite by default
6974
}
7075

7176
// Get settings from viper (respects config file)
72-
specVersion := strings.TrimSpace(viper.GetString("output.specVersion"))
73-
outputFormat := viper.GetString("output.format")
77+
specVersion := strings.TrimSpace(viper.GetString("enrich.spec"))
78+
outputFormat := viper.GetString("enrich.output-format")
7479
if outputFormat == "" {
7580
outputFormat = "auto"
7681
}
7782

7883
// Build enricher configuration
7984
cfg := enricher.Config{
8085
Strategy: strategy,
81-
ConfigFile: viper.GetString("enrich.configFile"),
82-
RequiredOnly: viper.GetBool("enrich.requiredOnly"),
83-
MinWeight: viper.GetFloat64("enrich.minWeight"),
86+
ConfigFile: viper.GetString("enrich.file"),
87+
RequiredOnly: viper.GetBool("enrich.required-only"),
88+
MinWeight: viper.GetFloat64("enrich.min-weight"),
8489
Refetch: viper.GetBool("enrich.refetch"),
85-
NoPreview: viper.GetBool("enrich.noPreview"),
90+
NoPreview: viper.GetBool("enrich.no-preview"),
8691
SpecVersion: specVersion,
87-
HFToken: viper.GetString("huggingface.token"),
88-
HFBaseURL: viper.GetString("huggingface.baseURL"),
89-
HFTimeout: viper.GetInt("huggingface.timeout"),
92+
HFToken: viper.GetString("enrich.hf-token"),
93+
HFBaseURL: viper.GetString("enrich.hf-base-url"),
94+
HFTimeout: viper.GetInt("enrich.hf-timeout"),
9095
}
9196

9297
// Load config file values if using file strategy
@@ -149,37 +154,40 @@ var (
149154
func init() {
150155
enrichCmd.Flags().StringVarP(&enrichInput, "input", "i", "", "Path to existing AIBOM (required)")
151156
enrichCmd.Flags().StringVarP(&enrichOutput, "output", "o", "", "Output file path (default: overwrite input)")
152-
enrichCmd.Flags().StringVarP(&enrichInputFormat, "format", "f", "auto", "Input BOM format: json|xml|auto")
153-
enrichCmd.Flags().StringVar(&enrichOutputFormat, "output-format", "auto", "Output BOM format: json|xml|auto")
157+
enrichCmd.Flags().StringVarP(&enrichInputFormat, "format", "f", "", "Input BOM format: json|xml|auto")
158+
enrichCmd.Flags().StringVar(&enrichOutputFormat, "output-format", "", "Output BOM format: json|xml|auto")
154159
enrichCmd.Flags().StringVar(&enrichSpecVersion, "spec", "", "CycloneDX spec version for output (default: same as input)")
155160

156-
enrichCmd.Flags().StringVar(&enrichStrategy, "strategy", "interactive", "Enrichment strategy: interactive|file")
157-
enrichCmd.Flags().StringVar(&enrichConfigFile, "file", "./config/enrichment.yaml", "Path to enrichment config file (YAML)")
161+
enrichCmd.Flags().StringVar(&enrichStrategy, "strategy", "", "Enrichment strategy: interactive|file")
162+
enrichCmd.Flags().StringVar(&enrichConfigFile, "file", "", "Path to enrichment config file (YAML)")
158163
enrichCmd.Flags().BoolVar(&enrichRequiredOnly, "required-only", false, "Only prompt for required fields")
159164
enrichCmd.Flags().Float64Var(&enrichMinWeight, "min-weight", 0.0, "Only prompt for fields with weight >= this value")
160165
enrichCmd.Flags().BoolVar(&enrichRefetch, "refetch", false, "Refetch model metadata from Hugging Face before enrichment")
161166
enrichCmd.Flags().BoolVar(&enrichNoPreview, "no-preview", false, "Skip preview before saving")
162167

163-
enrichCmd.Flags().StringVar(&enrichLogLevel, "log-level", "standard", "Log level: quiet|standard|debug")
168+
enrichCmd.Flags().StringVar(&enrichLogLevel, "log-level", "", "Log level: quiet|standard|debug")
164169
enrichCmd.Flags().StringVar(&enrichHFToken, "hf-token", "", "Hugging Face API token (for refetch)")
165170
enrichCmd.Flags().StringVar(&enrichHFBaseURL, "hf-base-url", "", "Hugging Face base URL (for refetch)")
166-
enrichCmd.Flags().IntVar(&enrichHFTimeout, "hf-timeout", 30, "Hugging Face API timeout in seconds (for refetch)")
171+
enrichCmd.Flags().IntVar(&enrichHFTimeout, "hf-timeout", 0, "Hugging Face API timeout in seconds (for refetch)")
167172

168173
_ = enrichCmd.MarkFlagRequired("input")
169174

170-
// Bind flags to viper for config file support
175+
// Bind all flags to viper for config file support
176+
viper.BindPFlag("enrich.input", enrichCmd.Flags().Lookup("input"))
177+
viper.BindPFlag("enrich.output", enrichCmd.Flags().Lookup("output"))
178+
viper.BindPFlag("enrich.format", enrichCmd.Flags().Lookup("format"))
179+
viper.BindPFlag("enrich.output-format", enrichCmd.Flags().Lookup("output-format"))
180+
viper.BindPFlag("enrich.spec", enrichCmd.Flags().Lookup("spec"))
171181
viper.BindPFlag("enrich.strategy", enrichCmd.Flags().Lookup("strategy"))
172-
viper.BindPFlag("enrich.configFile", enrichCmd.Flags().Lookup("file"))
173-
viper.BindPFlag("enrich.requiredOnly", enrichCmd.Flags().Lookup("required-only"))
174-
viper.BindPFlag("enrich.minWeight", enrichCmd.Flags().Lookup("min-weight"))
182+
viper.BindPFlag("enrich.file", enrichCmd.Flags().Lookup("file"))
183+
viper.BindPFlag("enrich.required-only", enrichCmd.Flags().Lookup("required-only"))
184+
viper.BindPFlag("enrich.min-weight", enrichCmd.Flags().Lookup("min-weight"))
175185
viper.BindPFlag("enrich.refetch", enrichCmd.Flags().Lookup("refetch"))
176-
viper.BindPFlag("enrich.noPreview", enrichCmd.Flags().Lookup("no-preview"))
177-
viper.BindPFlag("log.level", enrichCmd.Flags().Lookup("log-level"))
178-
viper.BindPFlag("huggingface.token", enrichCmd.Flags().Lookup("hf-token"))
179-
viper.BindPFlag("huggingface.baseURL", enrichCmd.Flags().Lookup("hf-base-url"))
180-
viper.BindPFlag("huggingface.timeout", enrichCmd.Flags().Lookup("hf-timeout"))
181-
viper.BindPFlag("output.format", enrichCmd.Flags().Lookup("output-format"))
182-
viper.BindPFlag("output.specVersion", enrichCmd.Flags().Lookup("spec"))
186+
viper.BindPFlag("enrich.no-preview", enrichCmd.Flags().Lookup("no-preview"))
187+
viper.BindPFlag("enrich.log-level", enrichCmd.Flags().Lookup("log-level"))
188+
viper.BindPFlag("enrich.hf-token", enrichCmd.Flags().Lookup("hf-token"))
189+
viper.BindPFlag("enrich.hf-base-url", enrichCmd.Flags().Lookup("hf-base-url"))
190+
viper.BindPFlag("enrich.hf-timeout", enrichCmd.Flags().Lookup("hf-timeout"))
183191
}
184192

185193
// loadEnrichmentConfig loads enrichment values from a YAML config file

cmd/generate.go

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ var generateCmd = &cobra.Command{
4242
Short: "Generate an AI-aware BOM (AIBOM)",
4343
Long: "Scans the target path for AI Hugginface imports and produces a CycloneDX AIBOM JSON.",
4444
RunE: func(cmd *cobra.Command, args []string) error {
45-
target := generatePath
45+
// Get input path from viper (respects config file and CLI flag)
46+
target := viper.GetString("generate.input")
4647
if target == "" {
4748
target = "."
4849
}
@@ -52,7 +53,7 @@ var generateCmd = &cobra.Command{
5253
}
5354

5455
// Resolve effective log level (from config, env, or flag).
55-
level := strings.ToLower(strings.TrimSpace(viper.GetString("log.level")))
56+
level := strings.ToLower(strings.TrimSpace(viper.GetString("generate.log-level")))
5657
if level == "" {
5758
level = "standard"
5859
}
@@ -64,33 +65,36 @@ var generateCmd = &cobra.Command{
6465
}
6566

6667
// Resolve effective HF mode (from config, env, or flag).
67-
mode := strings.ToLower(strings.TrimSpace(viper.GetString("huggingface.mode")))
68+
mode := strings.ToLower(strings.TrimSpace(viper.GetString("generate.hf-mode")))
6869
if mode == "" {
6970
mode = "online"
7071
}
7172
switch mode {
7273
case "online", "dummy":
7374
// ok
7475
default:
75-
return fmt.Errorf("invalid --hf-mode %q (expected online|dummy)", hfMode)
76+
return fmt.Errorf("invalid --hf-mode %q (expected online|dummy)", mode)
7677
}
7778
// Get format from viper (respects config file)
78-
outputFormat := viper.GetString("output.format")
79+
outputFormat := viper.GetString("generate.format")
7980
if outputFormat == "" {
8081
outputFormat = "auto"
8182
}
8283

8384
// Get spec version from viper
84-
specVersion := viper.GetString("output.specVersion")
85+
specVersion := viper.GetString("generate.spec")
86+
87+
// Get output path from viper for early validation
88+
outputPath := viper.GetString("generate.output")
8589

8690
// Fail fast on explicit format/extension mismatch before scanning
87-
if generateOutput != "" && outputFormat != "" && outputFormat != "auto" {
88-
ext := filepath.Ext(generateOutput)
91+
if outputPath != "" && outputFormat != "" && outputFormat != "auto" {
92+
ext := filepath.Ext(outputPath)
8993
if outputFormat == "xml" && ext == ".json" {
90-
return fmt.Errorf("output path extension %q does not match format %q", ext, generateOutputFormat)
94+
return fmt.Errorf("output path extension %q does not match format %q", ext, outputFormat)
9195
}
92-
if generateOutputFormat == "json" && ext == ".xml" {
93-
return fmt.Errorf("output path extension %q does not match format %q", ext, generateOutputFormat)
96+
if outputFormat == "json" && ext == ".xml" {
97+
return fmt.Errorf("output path extension %q does not match format %q", ext, outputFormat)
9498
}
9599
}
96100

@@ -106,15 +110,9 @@ var generateCmd = &cobra.Command{
106110
}
107111
}
108112

109-
// Scan for AI components
110-
discoveries, err := scanner.Scan(absTarget)
111-
if err != nil {
112-
return err
113-
}
114-
115113
// Get HF settings from viper
116-
hfToken := viper.GetString("huggingface.token")
117-
hfTimeout := viper.GetInt("huggingface.timeout")
114+
hfToken := viper.GetString("generate.hf-token")
115+
hfTimeout := viper.GetInt("generate.hf-timeout")
118116
if hfTimeout <= 0 {
119117
hfTimeout = 10
120118
}
@@ -130,6 +128,11 @@ var generateCmd = &cobra.Command{
130128
return err
131129
}
132130
} else {
131+
// Scan for AI components (only in non-dummy mode)
132+
discoveries, err := scanner.Scan(absTarget)
133+
if err != nil {
134+
return err
135+
}
133136
// Online mode: per discovery: store + fetch + map + build (inside generator).
134137
discoveredBOMs, err = generator.BuildPerDiscovery(discoveries, hfToken, timeout)
135138
if err != nil {
@@ -139,14 +142,15 @@ var generateCmd = &cobra.Command{
139142

140143
// Optional enrichment: only run when requested.
141144
// The enricher is now a separate command; this flag is deprecated.
142-
if enrich {
145+
if viper.GetBool("generate.enrich") {
143146
fmt.Fprintf(cmd.ErrOrStderr(), "[warn] --enrich flag is deprecated. Use 'aibomgen-cli enrich' command instead.\n")
144147
}
145148

146-
output := generateOutput
149+
// Get output path from viper
150+
output := viper.GetString("generate.output")
147151
if output == "" {
148152
// Default extension based on requested format (json unless explicitly xml)
149-
if generateOutputFormat == "xml" {
153+
if outputFormat == "xml" {
150154
output = "dist/aibom.xml"
151155
} else {
152156
output = "dist/aibom.json"
@@ -213,23 +217,26 @@ var generateCmd = &cobra.Command{
213217
}
214218

215219
func init() {
216-
generateCmd.Flags().StringVarP(&generatePath, "input", "i", "", "Path to scan (default: current directory)")
217-
generateCmd.Flags().StringVarP(&generateOutput, "output", "o", "", "Output file path (directory is used; default: dist/aibom.json)")
218-
generateCmd.Flags().StringVarP(&generateOutputFormat, "format", "f", "auto", "Output BOM format: json|xml|auto (default: auto)")
220+
generateCmd.Flags().StringVarP(&generatePath, "input", "i", "", "Path to scan")
221+
generateCmd.Flags().StringVarP(&generateOutput, "output", "o", "", "Output file path (directory is used)")
222+
generateCmd.Flags().StringVarP(&generateOutputFormat, "format", "f", "", "Output BOM format: json|xml|auto")
219223
generateCmd.Flags().StringVar(&generateSpecVersion, "spec", "", "CycloneDX spec version for output (e.g., 1.4, 1.5, 1.6)")
220-
generateCmd.Flags().StringVar(&hfMode, "hf-mode", "online", "Hugging Face metadata mode: online|dummy (default: online)")
221-
generateCmd.Flags().IntVar(&hfTimeoutSec, "hf-timeout", 10, "HTTP timeout in seconds for Hugging Face API")
222-
generateCmd.Flags().StringVar(&hfToken, "hf-token", "", "Hugging Face access token (string)")
223-
generateCmd.Flags().BoolVar(&enrich, "enrich", false, "Prompt for missing fields and compute completeness")
224-
generateCmd.Flags().StringVar(&generateLogLevel, "log-level", "standard", "Log level: quiet|standard|debug (default: standard)")
224+
generateCmd.Flags().StringVar(&hfMode, "hf-mode", "", "Hugging Face metadata mode: online|dummy")
225+
generateCmd.Flags().IntVar(&hfTimeoutSec, "hf-timeout", 0, "HTTP timeout in seconds for Hugging Face API")
226+
generateCmd.Flags().StringVar(&hfToken, "hf-token", "", "Hugging Face access token")
227+
generateCmd.Flags().BoolVar(&enrich, "enrich", false, "Prompt for missing fields and compute completeness (deprecated)")
228+
generateCmd.Flags().StringVar(&generateLogLevel, "log-level", "", "Log level: quiet|standard|debug")
225229

226-
// Bind flags to viper for config file support
227-
viper.BindPFlag("output.format", generateCmd.Flags().Lookup("format"))
228-
viper.BindPFlag("output.specVersion", generateCmd.Flags().Lookup("spec"))
229-
viper.BindPFlag("huggingface.mode", generateCmd.Flags().Lookup("hf-mode"))
230-
viper.BindPFlag("huggingface.timeout", generateCmd.Flags().Lookup("hf-timeout"))
231-
viper.BindPFlag("huggingface.token", generateCmd.Flags().Lookup("hf-token"))
232-
viper.BindPFlag("log.level", generateCmd.Flags().Lookup("log-level"))
230+
// Bind all flags to viper for config file support
231+
viper.BindPFlag("generate.input", generateCmd.Flags().Lookup("input"))
232+
viper.BindPFlag("generate.output", generateCmd.Flags().Lookup("output"))
233+
viper.BindPFlag("generate.format", generateCmd.Flags().Lookup("format"))
234+
viper.BindPFlag("generate.spec", generateCmd.Flags().Lookup("spec"))
235+
viper.BindPFlag("generate.hf-mode", generateCmd.Flags().Lookup("hf-mode"))
236+
viper.BindPFlag("generate.hf-timeout", generateCmd.Flags().Lookup("hf-timeout"))
237+
viper.BindPFlag("generate.hf-token", generateCmd.Flags().Lookup("hf-token"))
238+
viper.BindPFlag("generate.enrich", generateCmd.Flags().Lookup("enrich"))
239+
viper.BindPFlag("generate.log-level", generateCmd.Flags().Lookup("log-level"))
233240
}
234241

235242
func bomMetadataComponentName(bom *cdx.BOM) string {

cmd/root.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func init() {
4545

4646
cobra.OnInitialize(initConfig)
4747

48-
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.aibomgen-cli.yaml)")
48+
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.aibomgen-cli.yaml or ./config/defaults.yaml)")
4949
rootCmd.PersistentFlags().BoolVar(&noColor, "no-color", false, "Disable colored output")
5050

5151
// Ensure `--help` (and help subcommands) show a green banner consistently.
@@ -70,12 +70,12 @@ func initConfig() {
7070

7171
// Search for config in multiple locations (in order of priority):
7272
// 1. $HOME/.aibomgen-cli.yaml
73-
// 2. ./config/config.yaml (project local)
73+
// 2. ./config/defaults.yaml (project local)
7474
viper.AddConfigPath(home)
7575
viper.AddConfigPath("./config")
7676
viper.SetConfigType("yaml")
7777
viper.SetConfigName(".aibomgen-cli") // for $HOME/.aibomgen-cli.yaml
78-
viper.SetConfigName("config") // for ./config/config.yaml
78+
viper.SetConfigName("defaults") // for ./config/defaults.yaml
7979
}
8080

8181
// Enable environment variable support (e.g., AIBOMGEN_HUGGINGFACE_TOKEN)

0 commit comments

Comments
 (0)