Function bodies 391 total
parse_key_value function · rust · L314-L334 (21 LOC)src/commands/import.rs
fn parse_key_value(&self, line: &str, secrets: &mut HashMap<String, String>) -> Result<()> {
if let Some((key, value)) = line.split_once('=') {
let key = key.trim();
let value = value.trim();
// Handle quoted values
let value = if (value.starts_with('"') && value.ends_with('"'))
|| (value.starts_with('\'') && value.ends_with('\''))
{
value[1..value.len() - 1].to_string()
} else {
value.to_string()
};
if !key.is_empty() {
secrets.insert(key.to_string(), value);
}
}
Ok(())
}parse_json function · rust · L335-L348 (14 LOC)src/commands/import.rs
fn parse_json(&self, input: &str, source_name: &str) -> Result<HashMap<String, String>> {
let data: serde_json::Value = serde_json::from_str(input).map_err(|e| {
// serde_json provides line and column
let offset = self.offset_from_line_col(input, e.line(), e.column());
FnoxError::ImportParseErrorWithSource {
format: "JSON".to_string(),
details: e.to_string(),
src: Arc::new(NamedSource::new(source_name, Arc::new(input.to_string()))),
span: SourceSpan::new(offset.into(), 1usize),
}
})?;
self.extract_string_values(&data)
}parse_yaml function · rust · L349-L367 (19 LOC)src/commands/import.rs
fn parse_yaml(&self, input: &str, source_name: &str) -> Result<HashMap<String, String>> {
let data: serde_yaml::Value = serde_yaml::from_str(input).map_err(|e| {
// serde_yaml provides location via e.location()
// Note: serde_yaml uses 0-indexed line/column, so we add 1 for our 1-indexed function
if let Some(loc) = e.location() {
let offset = self.offset_from_line_col(input, loc.line() + 1, loc.column() + 1);
FnoxError::ImportParseErrorWithSource {
format: "YAML".to_string(),
details: e.to_string(),
src: Arc::new(NamedSource::new(source_name, Arc::new(input.to_string()))),
span: SourceSpan::new(offset.into(), 1usize),
}
} else {
FnoxError::Config(format!("Failed to parse YAML: {}", e))
}
})?;
self.extract_string_values(&data)
}parse_toml function · rust · L368-L384 (17 LOC)src/commands/import.rs
fn parse_toml(&self, input: &str, source_name: &str) -> Result<HashMap<String, String>> {
let data: serde_json::Value = toml_edit::de::from_str(input).map_err(|e| {
// toml_edit provides span via e.span()
if let Some(span) = e.span() {
FnoxError::ImportParseErrorWithSource {
format: "TOML".to_string(),
details: e.to_string(),
src: Arc::new(NamedSource::new(source_name, Arc::new(input.to_string()))),
span: SourceSpan::new(span.start.into(), span.end - span.start),
}
} else {
FnoxError::Config(format!("Failed to parse TOML: {}", e))
}
})?;
self.extract_string_values(&data)
}offset_from_line_col function · rust · L395-L437 (43 LOC)src/commands/import.rs
fn offset_from_line_col(&self, input: &str, line: usize, col: usize) -> usize {
// Handle invalid 0-indexed values (serde_json can return 0,0 for some errors)
if line == 0 || col == 0 {
return 0;
}
let mut current_line = 1;
let mut line_start_byte = 0;
// Find the byte offset of the target line by scanning for newlines
for (byte_idx, c) in input.char_indices() {
if current_line == line {
// Found the start of target line
line_start_byte = byte_idx;
break;
}
if c == '\n' {
current_line += 1;
// Set line_start_byte to byte after newline for next iteration
line_start_byte = byte_idx + 1;
}
}
// If requested line is beyond the file, return end of input
if current_line < line {
return input.len();
}
// Defensive: clamp lineextract_string_values function · rust · L438-L466 (29 LOC)src/commands/import.rs
fn extract_string_values<V>(&self, data: &V) -> Result<HashMap<String, String>>
where
V: serde::Serialize,
{
let json_value = serde_json::to_value(data)?;
let mut secrets = HashMap::new();
if let serde_json::Value::Object(map) = json_value {
for (key, value) in map {
match value {
serde_json::Value::String(s) => {
secrets.insert(key, s);
}
serde_json::Value::Null
| serde_json::Value::Bool(_)
| serde_json::Value::Number(_) => {
secrets.insert(key, value.to_string());
}
_ => {
tracing::warn!("Skipping non-string value for key '{}'", key);
}
}
}
}
Ok(secrets)
}run function · rust · L26-L85 (60 LOC)src/commands/init.rs
pub async fn run(&self, cli: &Cli) -> Result<()> {
// Determine the target config path
let config_path = if self.global {
Config::global_config_path()
} else {
cli.config.clone()
};
tracing::debug!(
"Initializing new fnox configuration at '{}'",
config_path.display()
);
if config_path.exists() && !self.force {
return Err(FnoxError::Config(format!(
"Configuration file '{}' already exists. Use --force to overwrite.",
config_path.display()
)));
}
// Create parent directory if it doesn't exist (for global config)
if self.global
&& let Some(parent) = config_path.parent()
{
std::fs::create_dir_all(parent).map_err(|e| {
FnoxError::Config(format!(
"Failed to create config directory '{}': {}",
parent.display(),
Repobility — same analyzer, your code, free for public repos · /scan/
run_wizard function · rust · L86-L161 (76 LOC)src/commands/init.rs
async fn run_wizard(&self) -> Result<Config> {
println!("\n🔐 Welcome to fnox setup wizard!\n");
println!("This will help you configure your first secret provider.\n");
// Ask if they want to set up a provider
let setup_provider = Confirm::new("Would you like to set up a provider now?")
.affirmative("Yes")
.negative("No, I'll configure it later")
.run()
.map_err(|e| FnoxError::Config(format!("Wizard cancelled: {}", e)))?;
if !setup_provider {
println!("\n✓ Creating minimal configuration file.");
return Ok(Config::new());
}
// Select provider category
let category = self.select_category()?;
// Get providers for that category
let providers = ProviderConfig::wizard_info_by_category(category);
// Select specific provider
let provider_info = self.select_provider(&providers)?;
// Print setup instructions
select_category function · rust · L164-L189 (26 LOC)src/commands/init.rs
fn select_category(&self) -> Result<WizardCategory> {
let mut select = Select::new("What type of provider do you want to use?")
.description("Choose a category based on your security and convenience needs")
.filterable(false);
for category in WizardCategory::all() {
select = select.option(
DemandOption::new(category.display_name())
.label(category.display_name())
.description(category.description()),
);
}
let selected = select
.run()
.map_err(|e| FnoxError::Config(format!("Wizard cancelled: {}", e)))?;
// Map the display name back to the category
for category in WizardCategory::all() {
if category.display_name() == selected {
return Ok(*category);
}
}
Err(FnoxError::Config("Unknown provider category".to_string()))
}select_provider function · rust · L192-L215 (24 LOC)src/commands/init.rs
fn select_provider(&self, providers: &[&'static WizardInfo]) -> Result<&'static WizardInfo> {
let mut select = Select::new("Select provider:").filterable(false);
for info in providers {
select = select.option(
DemandOption::new(info.provider_type)
.label(info.display_name)
.description(info.description),
);
}
let selected = select
.run()
.map_err(|e| FnoxError::Config(format!("Wizard cancelled: {}", e)))?;
// Find the selected provider info
for info in providers {
if info.provider_type == selected {
return Ok(info);
}
}
Err(FnoxError::Config("Unknown provider".to_string()))
}collect_fields function · rust · L218-L238 (21 LOC)src/commands/init.rs
fn collect_fields(&self, info: &WizardInfo) -> Result<HashMap<String, String>> {
let mut fields = HashMap::new();
for field in info.fields {
let result = Input::new(field.label).placeholder(field.placeholder).run();
match result {
Ok(value) => {
if value.is_empty() && field.required {
return Err(FnoxError::Config(format!("{} is required", field.name)));
}
fields.insert(field.name.to_string(), value);
}
Err(e) => {
return Err(FnoxError::Config(format!("Wizard cancelled: {}", e)));
}
}
}
Ok(fields)
}get_provider_name function · rust · L241-L253 (13 LOC)src/commands/init.rs
fn get_provider_name(&self, default: &str) -> Result<String> {
Input::new("Provider name:")
.placeholder(default)
.run()
.map(|name| {
if name.is_empty() {
default.to_string()
} else {
name
}
})
.map_err(|e| FnoxError::Config(format!("Wizard cancelled: {}", e)))
}test_provider_connection function · rust · L256-L283 (28 LOC)src/commands/init.rs
async fn test_provider_connection(&self, provider_config: &ProviderConfig) {
println!("\n🔍 Testing provider connection...");
// Wizard-created configs always have literal values, so we can use try_to_resolved
match provider_config.try_to_resolved() {
Ok(resolved) => match get_provider_from_resolved(&resolved) {
Ok(provider) => match provider.test_connection().await {
Ok(()) => {
println!("✓ Provider connection successful!\n");
}
Err(e) => {
println!("⚠️ Provider connection test failed: {}", e);
println!(
" You can still save the configuration and fix the issue later.\n"
);
}
},
Err(e) => {
println!("⚠️ Could not create provider: {}", e);
println!(" You run function · rust · L90-L137 (48 LOC)src/commands/list.rs
pub async fn run(&self, cli: &Cli, config: Config) -> Result<()> {
let profile = Config::get_profile(cli.profile.as_deref());
tracing::debug!("Listing secrets in profile '{}'", profile);
// Get the profile secrets
let profile_secrets = config.get_secrets(&profile)?;
if profile_secrets.is_empty() {
if !self.complete {
println!("No secrets defined in profile '{}'", profile);
}
return Ok(());
}
// Preserve insertion order from IndexMap
let keys: Vec<_> = profile_secrets.keys().collect();
// Handle completion mode
if self.complete {
for key in keys {
println!("{}", key);
}
return Ok(());
}
// Resolve secrets if values are requested
let resolved_values = if self.values {
Some(resolve_secrets_batch(&config, &profile, &profile_secrets).await?)
} else {
get_source_type_and_provider_key function · rust · L138-L166 (29 LOC)src/commands/list.rs
fn get_source_type_and_provider_key(
&self,
secret_config: &crate::config::SecretConfig,
) -> (String, String) {
let (base_type, provider_key) = if let Some(provider) = secret_config.provider() {
let pk = secret_config.value().unwrap_or("");
let pk_display = if !self.full && pk.len() > 40 {
format!("{}...", &pk[..37])
} else {
pk.to_string()
};
(format!("provider ({})", provider), pk_display)
} else if secret_config.value().is_some() {
("stored value".to_string(), String::new())
} else if secret_config.default.is_some() {
("default value".to_string(), String::new())
} else {
("env var".to_string(), String::new())
};
let source_type = if secret_config.as_file {
format!("{} [file]", base_type)
} else {
base_type
};
(source_type, provider_keySame scanner, your repo: https://repobility.com — Repobility
display_basic function · rust · L167-L193 (27 LOC)src/commands/list.rs
fn display_basic(
&self,
keys: &[&String],
profile_secrets: &indexmap::IndexMap<String, crate::config::SecretConfig>,
) -> Result<()> {
let mut rows = Vec::new();
for key in keys {
let secret_config = &profile_secrets[*key];
let (source_type, provider_key_str) =
self.get_source_type_and_provider_key(secret_config);
let description_str = secret_config
.description
.as_deref()
.unwrap_or("")
.to_string();
rows.push(SecretRow {
key: (*key).clone(),
source_type,
provider_key: provider_key_str,
description: description_str,
});
}
self.display_table(rows)
}display_with_sources function · rust · L194-L226 (33 LOC)src/commands/list.rs
fn display_with_sources(
&self,
keys: &[&String],
profile_secrets: &indexmap::IndexMap<String, crate::config::SecretConfig>,
) -> Result<()> {
let mut rows = Vec::new();
for key in keys {
let secret_config = &profile_secrets[*key];
let (source_type, provider_key_str) =
self.get_source_type_and_provider_key(secret_config);
let description_str = secret_config
.description
.as_deref()
.unwrap_or("")
.to_string();
let source_file = secret_config
.source_path
.as_ref()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "unknown".to_string());
rows.push(SecretRowWithSources {
key: (*key).clone(),
source_type,
source_file,
provider_key: provider_key_str,
descriptdisplay_with_values function · rust · L227-L262 (36 LOC)src/commands/list.rs
fn display_with_values(
&self,
keys: &[&String],
profile_secrets: &indexmap::IndexMap<String, crate::config::SecretConfig>,
resolved_values: &IndexMap<String, Option<String>>,
) -> Result<()> {
let mut rows = Vec::new();
for key in keys {
let secret_config = &profile_secrets[*key];
let (source_type, provider_key_str) =
self.get_source_type_and_provider_key(secret_config);
let description_str = secret_config
.description
.as_deref()
.unwrap_or("")
.to_string();
// Use the resolved value if available, otherwise show placeholder
let value_str = resolved_values
.get(*key)
.and_then(|v| v.as_ref())
.cloned()
.unwrap_or_else(|| "<not available>".to_string());
rows.push(SecretRowWithValues {
key: (*key).clodisplay_with_values_and_sources function · rust · L263-L304 (42 LOC)src/commands/list.rs
fn display_with_values_and_sources(
&self,
keys: &[&String],
profile_secrets: &indexmap::IndexMap<String, crate::config::SecretConfig>,
resolved_values: &IndexMap<String, Option<String>>,
) -> Result<()> {
let mut rows = Vec::new();
for key in keys {
let secret_config = &profile_secrets[*key];
let (source_type, provider_key_str) =
self.get_source_type_and_provider_key(secret_config);
let description_str = secret_config
.description
.as_deref()
.unwrap_or("")
.to_string();
let source_file = secret_config
.source_path
.as_ref()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "unknown".to_string());
// Use the resolved value if available, otherwise show placeholder
let value_str = resolved_values
.gdisplay_table function · rust · L305-L326 (22 LOC)src/commands/list.rs
fn display_table<T: tabled::Tabled>(&self, rows: Vec<T>) -> Result<()> {
let mut table = Table::new(rows);
table.with(Style::empty());
// Apply colors only if enabled
if console::colors_enabled() {
table.with(
Modify::new(Rows::first())
.with(Color::FG_BRIGHT_BLUE)
.with(Format::content(|s| format!("\x1b[1m{}\x1b[0m", s))),
);
}
if !self.full {
// Apply width constraints for description and provider key columns
table.with(Modify::new(Columns::last()).with(Width::wrap(40)));
}
println!("{}", table);
Ok(())
}run function · rust · L150-L189 (40 LOC)src/commands/mod.rs
pub async fn run(&self, cli: &Cli) -> Result<()> {
match self {
// Commands that don't need config
Commands::Version(cmd) => cmd.run(cli).await,
Commands::Init(cmd) => cmd.run(cli).await,
Commands::Completion(cmd) => cmd.run(cli).await,
Commands::ConfigFiles(cmd) => cmd.run(cli).await,
Commands::Schema(cmd) => cmd.run(cli).await,
Commands::Usage(cmd) => cmd.run(cli).await,
Commands::Activate(cmd) => cmd
.run()
.await
.map_err(|e| FnoxError::Config(e.to_string())),
Commands::Deactivate(cmd) => cmd
.run(cli, Config::new())
.await
.map_err(|e| FnoxError::Config(e.to_string())),
Commands::HookEnv(cmd) => cmd
.run()
.await
.map_err(|e| FnoxError::Config(e.to_string())),
// Commands that need config
run function · rust · L15-L35 (21 LOC)src/commands/profiles.rs
pub async fn run(&self, _cli: &Cli, config: Config) -> Result<()> {
let mut profile_names = vec!["default".to_string()];
profile_names.extend(config.profiles.keys().cloned());
profile_names.sort();
profile_names.dedup();
if self.complete {
// Output for completion
for name in profile_names {
println!("{}", name);
}
} else {
// Normal output
println!("Available profiles:");
for name in profile_names {
let secret_count = config.get_secrets(&name).map(|s| s.len()).unwrap_or(0);
println!(" {} ({} secrets)", name, secret_count);
}
}
Ok(())
}run function · rust · L25-L145 (121 LOC)src/commands/provider/add.rs
pub async fn run(&self, cli: &Cli) -> Result<()> {
tracing::debug!(
"Adding provider '{}' of type '{}'",
self.provider,
self.provider_type
);
// Determine the target config file
let target_path = if self.global {
let global_path = Config::global_config_path();
// Create parent directory if it doesn't exist
if let Some(parent) = global_path.parent() {
std::fs::create_dir_all(parent).map_err(|e| {
FnoxError::Config(format!(
"Failed to create config directory '{}': {}",
parent.display(),
e
))
})?;
}
global_path
} else {
let current_dir = std::env::current_dir().map_err(|e| {
FnoxError::Config(format!("Failed to get current directory: {}", e))
})?;
current_diOpen data scored by Repobility · https://repobility.com
run function · rust · L15-L30 (16 LOC)src/commands/provider/list.rs
pub async fn run(&self, _cli: &Cli, config: Config) -> Result<()> {
tracing::debug!("Listing providers");
if config.providers.is_empty() {
return Ok(());
}
// Always just output provider names, one per line
let mut names: Vec<_> = config.providers.keys().collect();
names.sort();
for name in names {
println!("{}", name);
}
Ok(())
}run function · rust · L87-L95 (9 LOC)src/commands/provider/mod.rs
pub async fn run(&self, cli: &Cli, config: Config) -> Result<()> {
match &self.action {
None => ListCommand { complete: false }.run(cli, config).await,
Some(ProviderAction::List(cmd)) => cmd.run(cli, config).await,
Some(ProviderAction::Add(cmd)) => cmd.run(cli).await,
Some(ProviderAction::Remove(cmd)) => cmd.run(cli).await,
Some(ProviderAction::Test(cmd)) => cmd.run(cli, config).await,
}
}run function · rust · L18-L53 (36 LOC)src/commands/provider/remove.rs
pub async fn run(&self, cli: &Cli) -> Result<()> {
tracing::debug!("Removing provider '{}'", self.provider);
// Determine the target config file
let target_path = if self.global {
Config::global_config_path()
} else {
let current_dir = std::env::current_dir().map_err(|e| {
FnoxError::Config(format!("Failed to get current directory: {}", e))
})?;
current_dir.join(&cli.config)
};
// Load the target config file directly
if !target_path.exists() {
return Err(FnoxError::Config(format!(
"Config file '{}' not found",
target_path.display()
)));
}
let mut config = Config::load(&target_path)?;
if config.providers.shift_remove(&self.provider).is_some() {
config.save(&target_path)?;
let global_suffix = if self.global { " (global)" } else { "" };
println!("✓ run function · rust · L18-L31 (14 LOC)src/commands/provider/test.rs
pub async fn run(&self, cli: &Cli, config: Config) -> Result<()> {
let profile = Config::get_profile(cli.profile.as_deref());
if self.all {
self.test_all_providers(cli, &config, &profile).await
} else if let Some(ref provider_name) = self.provider {
self.test_single_provider(&config, &profile, provider_name)
.await
} else {
Err(FnoxError::Config(
"Please specify a provider name or use --all to test all providers".to_string(),
))?
}
}test_single_provider function · rust · L32-L62 (31 LOC)src/commands/provider/test.rs
async fn test_single_provider(
&self,
config: &Config,
profile: &str,
provider_name: &str,
) -> Result<()> {
tracing::debug!("Testing provider '{}'", provider_name);
let provider_config = config
.providers
.get(provider_name)
.ok_or_else(|| FnoxError::Config(format!("Provider '{}' not found", provider_name)))?;
// Create the provider instance (resolving any secret refs in config)
let provider = crate::providers::get_provider_resolved(
config,
profile,
provider_name,
provider_config,
)
.await?;
// Test the connection
provider.test_connection().await?;
let check = console::style("✓").green();
let styled_provider = console::style(provider_name).cyan();
println!("{check} Provider {styled_provider} connection successful");
Ok(())
}test_all_providers function · rust · L63-L158 (96 LOC)src/commands/provider/test.rs
async fn test_all_providers(&self, cli: &Cli, config: &Config, profile: &str) -> Result<()> {
let providers = config.get_providers(profile);
if providers.is_empty() {
println!("No providers configured");
return Ok(());
}
println!(
"Testing {} provider{}...\n",
providers.len(),
if providers.len() == 1 { "" } else { "s" }
);
let mut passed = 0;
let mut failed = 0;
let mut errors: Vec<(String, String)> = Vec::new();
for (provider_name, provider_config) in providers {
let styled_provider = console::style(&provider_name).cyan();
print!(" {} ", styled_provider);
match crate::providers::get_provider_resolved(
config,
profile,
&provider_name,
&provider_config,
)
.await
{
Ok(provider) => match provider.terun function · rust · L22-L96 (75 LOC)src/commands/remove.rs
pub async fn run(&self, cli: &Cli) -> Result<()> {
let profile = Config::get_profile(cli.profile.as_deref());
tracing::debug!("Removing secret '{}' from profile '{}'", self.key, profile);
// Determine the target config file
let target_path = if self.global {
Config::global_config_path()
} else {
let current_dir = std::env::current_dir().map_err(|e| {
FnoxError::Config(format!("Failed to get current directory: {}", e))
})?;
current_dir.join(&cli.config)
};
// Load the target config file directly (not the merged config)
if !target_path.exists() {
return Err(FnoxError::ConfigFileNotFound {
path: target_path.clone(),
});
}
// Check the secret exists before attempting removal
let config = Config::load(&target_path)?;
let profile_secrets = config.get_secrets(&profile)?;
if !profirun function · rust · L25-L42 (18 LOC)src/commands/scan.rs
pub async fn run(&self, _cli: &Cli, _config: Config) -> Result<()> {
println!("Scanning directory: {}", self.dir.display());
// TODO: Implement file scanning logic
// - Walk through directory recursively
// - Skip .git directory and hidden files
// - Check for secret patterns in text files
// - Use regex patterns to detect potential secrets
// - Report findings
if self.quiet {
println!("Scan completed (quiet mode).");
} else {
println!("Scan completed. No implementation yet.");
}
Ok(())
}Repobility · severity-and-effort ranking · https://repobility.com
run function · rust · L10-L15 (6 LOC)src/commands/schema.rs
pub async fn run(&self, _cli: &Cli) -> Result<()> {
let schema = schemars::schema_for!(Config);
let json = serde_json::to_string_pretty(&schema)?;
println!("{json}");
Ok(())
}run function · rust · L28-L76 (49 LOC)src/commands/tui.rs
pub async fn run(&self, cli: &Cli, config: Config) -> Result<()> {
let profile = Config::get_profile(cli.profile.as_deref());
// Install panic hook to restore terminal on panic
install_panic_hook();
// Initialize terminal
let mut terminal = enter_terminal().map_err(|e| {
crate::error::FnoxError::Config(format!("Failed to initialize terminal: {}", e))
})?;
// Create guard to ensure terminal cleanup on any exit (error or success)
let _guard = TerminalGuard;
// Create app state
let mut app = App::new(config, profile)?;
// Create event handler
let mut events = EventHandler::new(Duration::from_millis(250));
// Store event tx for refresh operations
app.set_event_tx(events.message_tx());
// Spawn initial secret resolution
app.spawn_resolve_secrets(events.message_tx());
// Main event loop
while app.running {
// Renderun function · rust · L9-L19 (11 LOC)src/commands/usage.rs
pub async fn run(&self, _cli: &Cli) -> Result<()> {
use clap::CommandFactory;
let cmd = Cli::command();
let spec: usage::Spec = cmd.into();
let min_version = r#"min_usage_version "1.3""#;
let extra = include_str!("../assets/fnox-extras.usage.kdl").trim();
println!("{min_version}\n{}\n{extra}", spec.to_string().trim());
Ok(())
}all_config_filenames function · rust · L21-L30 (10 LOC)src/config.rs
pub fn all_config_filenames(profile: Option<&str>) -> Vec<String> {
let mut files = vec!["fnox.toml".to_string(), ".fnox.toml".to_string()];
if let Some(p) = profile.filter(|p| *p != "default") {
files.push(format!("fnox.{p}.toml"));
files.push(format!(".fnox.{p}.toml"));
}
files.push("fnox.local.toml".to_string());
files.push(".fnox.local.toml".to_string());
files
}load_smart function · rust · L165-L186 (22 LOC)src/config.rs
pub fn load_smart<P: AsRef<Path>>(path: P) -> Result<Self> {
let path_ref = path.as_ref();
// If the path is one of the default config filenames, use recursive loading
let default_filenames = all_config_filenames(None);
if default_filenames.iter().any(|f| path_ref == Path::new(f)) {
Self::load_with_recursion(path_ref)
} else {
// For explicit paths, resolve relative paths against current directory first
let resolved_path = if path_ref.is_relative() {
env::current_dir()
.map_err(|e| {
FnoxError::Config(format!("Failed to get current directory: {}", e))
})?
.join(path_ref)
} else {
path_ref.to_path_buf()
};
// For explicit paths, use direct loading
Self::load(resolved_path)
}
}load function · rust · L189-L222 (34 LOC)src/config.rs
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
use miette::{NamedSource, SourceSpan};
let path = path.as_ref();
let content = fs::read_to_string(path).map_err(|source| FnoxError::ConfigReadFailed {
path: path.to_path_buf(),
source,
})?;
// Register the source for error reporting
source_registry::register(path, content.clone());
let mut config: Config = toml_edit::de::from_str(&content).map_err(|e| {
// Try to create a source-aware error with span highlighting
if let Some(span) = e.span() {
FnoxError::ConfigParseErrorWithSource {
message: e.message().to_string(),
src: Arc::new(NamedSource::new(
path.display().to_string(),
Arc::new(content),
)),
span: SourceSpan::new(span.start.into(), span.end - span.start),
}
load_with_recursion function · rust · L225-L244 (20 LOC)src/config.rs
fn load_with_recursion<P: AsRef<Path>>(_start_path: P) -> Result<Self> {
// Start from current working directory and search upwards
let current_dir = env::current_dir()
.map_err(|e| FnoxError::Config(format!("Failed to get current directory: {}", e)))?;
match Self::load_recursive(¤t_dir, false) {
Ok((_config, found)) if !found => {
// No config file was found anywhere in the directory tree
Err(FnoxError::ConfigNotFound {
message: format!(
"No configuration file found in {} or any parent directory",
current_dir.display()
),
help: "Run 'fnox init' to create a configuration file".to_string(),
})
}
Ok((config, _)) => Ok(config),
Err(e) => Err(e),
}
}load_recursive function · rust · L248-L303 (56 LOC)src/config.rs
fn load_recursive(dir: &Path, found_any: bool) -> Result<(Self, bool)> {
// Get current profile from Settings (respects: CLI flag > Env var > Default)
let profile = crate::settings::Settings::get().profile.clone();
let filenames = all_config_filenames(Some(&profile));
// Load all existing config files in order (later files override earlier ones)
let mut config = Self::new();
let mut found = found_any;
for filename in &filenames {
let path = dir.join(filename);
if path.exists() {
let file_config = Self::load(&path)?;
config = Self::merge_configs(config, file_config)?;
found = true;
}
}
// If this config marks root, stop recursion but still load global config
if config.root {
// Load imports if any
for import_path in &config.import.clone() {
let import_config = Self::load_import(iRepobility — same analyzer, your code, free for public repos · /scan/
load_global function · rust · L312-L325 (14 LOC)src/config.rs
fn load_global() -> Result<(Self, bool)> {
let global_config_path = Self::global_config_path();
if global_config_path.exists() {
tracing::debug!(
"Loading global config from {}",
global_config_path.display()
);
let config = Self::load(&global_config_path)?;
Ok((config, true))
} else {
Ok((Self::new(), false))
}
}load_import function · rust · L328-L346 (19 LOC)src/config.rs
fn load_import(import_path: &str, base_dir: &Path) -> Result<Self> {
let path = PathBuf::from(import_path);
// Handle relative paths - they're relative to the base config's directory
let absolute_path = if path.is_absolute() {
path
} else {
base_dir.join(path)
};
if !absolute_path.exists() {
return Err(FnoxError::Config(format!(
"Import file not found: {}",
absolute_path.display()
)));
}
Self::load(&absolute_path)
}merge_configs function · rust · L349-L434 (86 LOC)src/config.rs
fn merge_configs(base: Config, overlay: Config) -> Result<Config> {
let mut merged = base;
// Merge imports (overlay takes precedence, but keep unique paths)
for import_path in overlay.import {
if !merged.import.contains(&import_path) {
merged.import.push(import_path);
}
}
// root flag: if either is true, result is true
merged.root = merged.root || overlay.root;
// Merge age_key_file (overlay takes precedence)
if overlay.age_key_file.is_some() {
merged.age_key_file = overlay.age_key_file;
}
// Merge if_missing (overlay takes precedence)
if overlay.if_missing.is_some() {
merged.if_missing = overlay.if_missing;
}
// Merge prompt_auth (overlay takes precedence)
if overlay.prompt_auth.is_some() {
merged.prompt_auth = overlay.prompt_auth;
}
// Merge default_provider and its sourcesave function · rust · L439-L464 (26 LOC)src/config.rs
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
// Clone and clean up empty profiles before saving
let mut clean_config = self.clone();
clean_config
.profiles
.retain(|_, profile| !profile.is_empty());
// First serialize with to_string_pretty to get proper structure
let pretty_string = toml_edit::ser::to_string_pretty(&clean_config)?;
// Parse it back as a document so we can modify it
let mut doc = pretty_string
.parse::<toml_edit::DocumentMut>()
.map_err(|e| FnoxError::Config(format!("Failed to parse TOML: {}", e)))?;
// Convert secrets to inline tables
Self::convert_secrets_to_inline(&mut doc)?;
fs::write(path.as_ref(), doc.to_string()).map_err(|source| {
FnoxError::ConfigWriteFailed {
path: path.as_ref().to_path_buf(),
source,
}
})?;
Ok(())
}convert_secrets_to_inline function · rust · L467-L524 (58 LOC)src/config.rs
fn convert_secrets_to_inline(doc: &mut toml_edit::DocumentMut) -> Result<()> {
use toml_edit::{InlineTable, Item};
// Convert top-level [secrets]
if let Some(secrets_item) = doc.get_mut("secrets")
&& let Some(secrets_table) = secrets_item.as_table_mut()
{
let keys: Vec<String> = secrets_table.iter().map(|(k, _)| k.to_string()).collect();
for key in keys {
if let Some(item) = secrets_table.get_mut(&key)
&& let Some(table) = item.as_table()
{
let mut inline = InlineTable::new();
for (k, v) in table.iter() {
if let Some(value) = v.as_value() {
inline.insert(k, value.clone());
}
}
inline.fmt();
*item = Item::Value(toml_edit::Value::InlineTable(inline));
}
}
}save_secret_to_source function · rust · L533-L601 (69 LOC)src/config.rs
pub fn save_secret_to_source(
&self,
secret_name: &str,
secret_config: &SecretConfig,
profile: &str,
default_target: &Path,
) -> Result<()> {
use toml_edit::{DocumentMut, Item, Value};
let target_file = default_target.to_path_buf();
// Load existing document or create new one (preserves comments)
let mut doc = if target_file.exists() {
let content =
fs::read_to_string(&target_file).map_err(|source| FnoxError::ConfigReadFailed {
path: target_file.clone(),
source,
})?;
content.parse::<DocumentMut>().map_err(|e| {
FnoxError::Config(format!(
"Failed to parse TOML in {}: {}",
target_file.display(),
e
))
})?
} else {
DocumentMut::new()
};
// Get or create the secrets table
remove_secret_from_source function · rust · L607-L654 (48 LOC)src/config.rs
pub fn remove_secret_from_source(
secret_name: &str,
profile: &str,
target_file: &Path,
) -> Result<bool> {
use toml_edit::DocumentMut;
let content =
fs::read_to_string(target_file).map_err(|source| FnoxError::ConfigReadFailed {
path: target_file.to_path_buf(),
source,
})?;
let mut doc = content.parse::<DocumentMut>().map_err(|e| {
FnoxError::Config(format!(
"Failed to parse TOML in {}: {}",
target_file.display(),
e
))
})?;
// Navigate to the secrets table
let removed = if profile == "default" {
doc.get_mut("secrets")
.and_then(|s| s.as_table_mut())
.map(|t| t.remove(secret_name).is_some())
.unwrap_or(false)
} else {
doc.get_mut("profiles")
.and_then(|p| p.as_table_mut())
save_secrets_to_source function · rust · L659-L732 (74 LOC)src/config.rs
pub fn save_secrets_to_source(
secrets: &IndexMap<String, SecretConfig>,
profile: &str,
target_file: &Path,
) -> Result<()> {
use toml_edit::{DocumentMut, Item, Value};
// Load existing document or create new one (preserves comments)
let mut doc = if target_file.exists() {
let content =
fs::read_to_string(target_file).map_err(|source| FnoxError::ConfigReadFailed {
path: target_file.to_path_buf(),
source,
})?;
content.parse::<DocumentMut>().map_err(|e| {
FnoxError::Config(format!(
"Failed to parse TOML in {}: {}",
target_file.display(),
e
))
})?
} else {
DocumentMut::new()
};
// Get or create the secrets table
let secrets_table = if profile == "default" {
if doc.get("secrets").iSame scanner, your repo: https://repobility.com — Repobility
new function · rust · L735-L750 (16 LOC)src/config.rs
pub fn new() -> Self {
Self {
import: Vec::new(),
root: false,
providers: IndexMap::new(),
default_provider: None,
secrets: IndexMap::new(),
profiles: IndexMap::new(),
age_key_file: None,
if_missing: None,
prompt_auth: None,
provider_sources: HashMap::new(),
secret_sources: HashMap::new(),
default_provider_source: None,
}
}get_profile function · rust · L753-L758 (6 LOC)src/config.rs
pub fn get_profile(profile_flag: Option<&str>) -> String {
profile_flag
.map(String::from)
.or_else(|| (*env::FNOX_PROFILE).clone())
.unwrap_or_else(|| "default".to_string())
}should_prompt_auth function · rust · L763-L771 (9 LOC)src/config.rs
pub fn should_prompt_auth(&self) -> bool {
// Check env var first
let enabled = (*env::FNOX_PROMPT_AUTH)
.or(self.prompt_auth)
.unwrap_or(true);
// Only prompt if enabled AND we're in a TTY
enabled && atty::is(atty::Stream::Stdin)
}