Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement toml config for fmt command. #205

Merged
merged 6 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
.vscode
*.profdata
*.profraw
*.swp
target
macros/target
site/node_modules
site/public
site/resources
site/resources
34 changes: 34 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ dsa = "0.6.3"
ecdsa = "0.16.9"
enable-ansi-support = "0.2.1"
env_logger = "0.11.3"
figment = "0.10.19"
fmmap = "0.3.3"
globwalk = "0.9.1"
goldenfile = "1.6.1"
home = "0.5.9"
ihex = "3.0.0"
indenter = "0.3.3"
indexmap = "2.2.6"
Expand Down
3 changes: 3 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,17 @@ ascii_tree = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true, features = ["cargo", "derive"] }
clap_complete = { workspace = true }
figment = { workspace = true, features = ["toml"] }
globwalk = { workspace = true }
home = { workspace = true }
itertools = { workspace = true }
enable-ansi-support = { workspace = true }
env_logger = { workspace = true, optional = true, features = ["auto-color"] }
log = { workspace = true, optional = true }
protobuf = { workspace = true }
protobuf-json-mapping = { workspace = true }
serde_json = { workspace = true, features = ["preserve_order"] }
serde = { workspace = true }
yansi = { workspace = true }
yara-x = { workspace = true, features = ["parallel-compilation"] }
yara-x-parser = { workspace = true }
Expand Down
28 changes: 25 additions & 3 deletions cli/src/commands/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ use std::fs::File;
use std::path::PathBuf;
use std::{fs, io, process};

use crate::help::FMT_CHECK_MODE;
use clap::{arg, value_parser, ArgAction, ArgMatches, Command};
use yara_x_fmt::Formatter;

use crate::config::{load_config_from_file, FormatConfig};
use crate::help::{CONFIG_FILE, FMT_CHECK_MODE};

pub fn fmt() -> Command {
super::command("fmt")
.about("Format YARA source files")
Expand All @@ -17,13 +19,33 @@ pub fn fmt() -> Command {
.action(ArgAction::Append),
)
.arg(arg!(-c --check "Run in 'check' mode").long_help(FMT_CHECK_MODE))
.arg(
arg!(-C --config <CONFIG_FILE> "Config file")
.value_parser(value_parser!(PathBuf))
.long_help(CONFIG_FILE),
)
}

pub fn exec_fmt(args: &ArgMatches) -> anyhow::Result<()> {
pub fn exec_fmt(
args: &ArgMatches,
main_config: FormatConfig,
) -> anyhow::Result<()> {
let files = args.get_many::<PathBuf>("FILE").unwrap();
let check = args.get_flag("check");
let config_file = args.get_one::<PathBuf>("config");

let config: FormatConfig = if config_file.is_some() {
load_config_from_file(config_file.unwrap())?.fmt
} else {
main_config
};

let formatter = Formatter::new();
let formatter = Formatter::new()
.align_metadata(config.meta.align_values)
.align_patterns(config.patterns.align_values)
.indent_section_headers(config.rule.indent_section_headers)
.indent_section_contents(config.rule.indent_section_contents)
.indent_spaces(config.rule.indent_spaces);
let mut changed = false;

for file in files {
Expand Down
79 changes: 79 additions & 0 deletions cli/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use std::path::Path;

use figment::{
providers::{Format, Serialized, Toml},
Figment,
};
use serde::{Deserialize, Serialize};

/// Configuration structure for "yr" commands.
#[derive(Deserialize, Serialize, Debug)]
pub struct Config {
/// Format specific configuration information.
pub fmt: FormatConfig,
}

/// Format specific configuration information.
#[derive(Deserialize, Serialize, Debug)]
pub struct FormatConfig {
/// Rule specific formatting information.
pub rule: Rule,
/// Meta specific formatting information.
pub meta: Meta,
/// Pattern specific formatting information.
pub patterns: Patterns,
}

/// Rule specific formatting information.
#[derive(Deserialize, Serialize, Debug)]
pub struct Rule {
/// Indent section headers (meta, strings, condition).
pub indent_section_headers: bool,
/// Indent section contents one level past section headers.
pub indent_section_contents: bool,
/// Number of spaces for indent. Set to 0 to use tabs.
pub indent_spaces: u8,
}

/// Meta specific formatting information.
#[derive(Deserialize, Serialize, Debug)]
pub struct Meta {
/// Align values to longest key.
pub align_values: bool,
}

/// Pattern specific formatting information.
#[derive(Deserialize, Serialize, Debug)]
pub struct Patterns {
/// Align patterns to the longest name.
pub align_values: bool,
}

impl Default for Config {
fn default() -> Config {
Config {
fmt: FormatConfig {
rule: Rule {
indent_section_headers: true,
indent_section_contents: true,
indent_spaces: 2,
},
meta: Meta { align_values: true },
patterns: Patterns { align_values: true },
},
}
}
}

/// Load config file from a given path. Path must contain a valid TOML file or
/// this function will propagate the error. For structure of the config file
/// see "YARA-X Config Guide.md".
pub fn load_config_from_file(
config_file: &Path,
) -> Result<Config, figment::Error> {
let config: Config =
Figment::from(Serialized::defaults(Config::default()))
.merge(Toml::file_exact(config_file))
.extract()?;
Ok(config)
}
8 changes: 8 additions & 0 deletions cli/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,11 @@ pub const FMT_CHECK_MODE: &str = r#"Run in 'check' mode

Doesn't modify the files. Exits with 0 if files are formatted correctly. Exits
with 1 if formatting is required."#;

pub const CONFIG_FILE: &str = r#"Config file for YARA-X

Config file which controls the behavior of YARA-X. See XXX (FILL IN URL
ONCE DOCS ARE WRITTEN) for supported options.

If config file is not specified, ${HOME}/.yara-x.toml is used. If that does not
exist the default options are applied."#;
20 changes: 19 additions & 1 deletion cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod commands;
mod config;
mod help;
mod walk;

use config::{load_config_from_file, Config};
use crossterm::tty::IsTty;
use std::{io, panic, process};
use yansi::Color::Red;
Expand All @@ -19,6 +21,7 @@ const APP_HELP_TEMPLATE: &str = r#"YARA-X {version}, the pattern matching swiss
"#;

const EXIT_ERROR: i32 = 1;
const CONFIG_FILE: &str = ".yara-x.toml";

fn main() -> anyhow::Result<()> {
// Enable support for ANSI escape codes in Windows. In other platforms
Expand Down Expand Up @@ -58,12 +61,27 @@ fn main() -> anyhow::Result<()> {
process::exit(EXIT_ERROR);
}));

let config: Config = match home::home_dir() {
Some(home_path) if !home_path.as_os_str().is_empty() => {
load_config_from_file(&home_path.join(CONFIG_FILE)).unwrap_or_else(
|err| {
println!("Error parsing config, using defaults: {}", err);
Config::default()
},
)
}
_ => {
println!("Unable to find home directory, using defaults.");
Config::default()
}
};

let result = match args.subcommand() {
#[cfg(feature = "debug-cmd")]
Some(("debug", args)) => commands::exec_debug(args),
Some(("check", args)) => commands::exec_check(args),
Some(("fix", args)) => commands::exec_fix(args),
Some(("fmt", args)) => commands::exec_fmt(args),
Some(("fmt", args)) => commands::exec_fmt(args, config.fmt),
Some(("scan", args)) => commands::exec_scan(args),
Some(("dump", args)) => commands::exec_dump(args),
Some(("compile", args)) => commands::exec_compile(args),
Expand Down
85 changes: 85 additions & 0 deletions docs/YARA-X Config Guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
YARA-X Config Guide
===================

YARA-X uses a configuration file for controlling the behavior of different
commands. It currently supports the fmt command, but others will be added in the
future.

The `yr` command looks in `${HOME}/.yara-x.toml` when starting up. If that file
does not exist the default values are used.

An example `.yara-x.toml` file is below, with comments that explain each option.
This is the definitive list of supported configuration options, and will be
updated as more are added.

```toml
# Config file for YARA-X.

# Any options that are not valid are ignored. However, valid keys with an
# invalid type will cause a parsing error. For example, if you set
# rule.indent_spaces to false, it will result in a parsing error.
pants = false # Invalid keys are ignored.

# The configuration of the "fmt" subcommand can be controlled by options in the
# "fmt" section. Each line is a key-value pair where the key uses a dot notation
# to deliniate different options. The "rule" namespace are for options that
# apply to the rule as a whole, while the "meta" and "patterns" namespaces are
# for options that only apply to those sections in a rule.
[fmt]
# Indent section headers so that:
#
# rule a {
# condition:
# true
# }
#
# Becomes:
#
# rule a {
# condition:
# true
# }
rule.indent_section_headers = true

# Indent section contents so that:
# rule a {
# condition:
# true
# }
#
# Becomes:
#
# rule a {
# condition:
# true
# }
rule.indent_section_contents = true

# Number of spaces to use for indentation. Setting this to 0 will use one tab
# character per level of indentation. To disable indentation entirely use
# rule.indent_section_headers and rule.indent_section_contents
rule.indent_spaces = 2

# Align metadata values so that:
#
# rule a {
# meta:
# key = "a"
# long_key = "b"
# }
#
# Becomes:
#
# rule a {
# meta:
# key = "a"
# long_key = "b"
# }
#
# Note that alignment is done with spaces, regardless of rule.indent_spaces
# setting.
meta.align_values = false

# Same as meta.align_values but applies to patterns.
patterns.align_values = false
```
Loading
Loading