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

Add options to exclude files #238

Merged
merged 11 commits into from
Dec 20, 2024
1 change: 0 additions & 1 deletion Cargo.lock

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

12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ fortitude check
```

You can also call `check` on individual files, globs, and
directories. You can configure what extensions `fortitude` searches
directories. You can configure what extensions Fortitude searches
for in directories with `--file-extensions`:

```bash
fortitude check src --file-extensions=f90,fpp
fortitude check --file-extensions=f90,fpp
```

You can select or ignore individual rules or whole groups with
Expand All @@ -77,7 +77,7 @@ fortitude check --select=typing --ignore=superfluous-implicit-none
Use `--output-format=concise` to get shorter output:

```bash
$ fortitude check --concise
$ fortitude check --output-format=concise
test.f90:2:1: M001 function not contained within (sub)module or program
test.f90:5:1: S061 end statement should read 'end function double'
test.f90:7:1: M001 subroutine not contained within (sub)module or program
Expand All @@ -90,11 +90,11 @@ The `explain` command can be used to get extra information about any rules:
# Print extra information for all rules
fortitude explain
# Only get information for selected rules
fortitude explain T001,T011
fortitude explain T001 T011
# Print information on all style rules
fortitude explain S
# Rules and categories can also be referred to by name
fortitude explain style,superfluous-implicit-none
fortitude explain style superfluous-implicit-none
```

To see further commands and optional arguments, try using `--help`:
Expand Down Expand Up @@ -152,6 +152,8 @@ preview = true

Run `fortitude explain` to see which rules are in preview mode.



## Configuration

Fortitude will look for either a `fortitude.toml` or `fpm.toml` file in the
Expand Down
126 changes: 121 additions & 5 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,128 @@ Written in Rust :crab: and installable with Python :snake:.

Fortitude can be installed directly into your Python environment:

```console
$ pip install fortitude-lint
```bash
pip install fortitude-lint
```

Run Fortitude on your project:
You can then lint your whole project under the current working directory
using the `check` command:

```console
$ fortitude check
```bash
fortitude check
```

You can also call `check` on individual files, directories, and globs:

```bash
fortitude check main.f90 src/ extra/*.f90
```

Some rule violations can even be fixed automatically:

```bash
fortitude check --fix
```

The `explain` command can be used to get extra information about any rules:

```bash
# Print extra information for all rules
fortitude explain
# Only get information for selected rules, by code or by name
fortitude explain T001 trailing-whitespace
# Print information on all style rules
fortitude explain style
```

New rules and other features may be in 'preview' mode while they undergo further review
and testing. To activate them, use the `--preview` flag:

```bash
fortitude check --preview
```

To see further commands and optional arguments, try using `--help`:

```bash
fortitude --help
fortitude check --help
```

### Rule Selection

You can select or ignore individual rules or whole groups with
`--select` and `--ignore`:

```bash
# Just check for missing `implicit none`
fortitude check --select=T001
# Also check for missing `implicit none` in interfaces
fortitude check --select=T001,T002
# Ignore all styling rules
fortitude check --ignore=S
# Only check for typing rules, but ignore superfluous implicit none
fortitude check --select=T --ignore=T003
# Rules and categories can also be referred to by name
fortitude check --select=typing --ignore=superfluous-implicit-none
```

It is also possible to switch off individual rules or rule categories for specific
files using `--per-file-ignores`:

```bash
fortitude check --per-file-ignores=**/*.f95:non-standard-file-extension
```

### Filtering Files

Fortitude will automatically ignore files in some directories (`build/`, `.git/`,
`.venv/`, etc.), and this behaviour can be extended using the `--exclude` option. For
example, to ignore all files in the directory `benchmarks/`:

```bash
fortitude check --exclude=benchmarks
```

You can also configure what extensions Fortitude searches for in directories with
`--file-extensions`:

```bash
fortitude check --file-extensions=f90,fpp
```

### Configuration

Fortitude will look for either a `fortitude.toml` or `fpm.toml` file in the
current directory or one of its parents. If using `fortitude.toml`, settings
should be under the command name:

```toml
[check]
select = ["S", "T"]
ignore = ["S001", "S051"]
line-length = 132
```

For `fpm.toml` files, this has to be additionally nested under the
`extra.fortitude` table:

```toml
[extra.fortitude.check]
select = ["S", "T"]
ignore = ["S001", "S051"]
line-length = 132
```

Arguments on the command line take precedence over those in the configuration file,
so using `--select` will override the choices shown above. You should instead use
`--extend-select` from the command line to select additional rules on top of those in
the configuration file:

```bash
# Selects S, T, and M categories
fortitude check --extend-select=M
```

Similar options include `--extend-exclude`, `--extend-ignore`, and
`--extend-per-file-ignores`.
1 change: 0 additions & 1 deletion fortitude/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ unicode-width = "0.2.0"
url = { version = "2.5.0" }
walkdir = "2.4.0"
globset = "0.4.15"
glob = "0.3.1"

[build-dependencies]
shadow-rs = { version = "0.36.0", default-features = false }
Expand Down
74 changes: 60 additions & 14 deletions fortitude/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use crate::rule_selector::{
use crate::rules::Rule;
use crate::rules::{error::ioerror::IoError, AstRuleEnum, PathRuleEnum, TextRuleEnum};
use crate::settings::{
FixMode, OutputFormat, PatternPrefixPair, PreviewMode, ProgressBar, Settings, UnsafeFixes,
DEFAULT_SELECTORS,
ExcludeMode, FilePattern, FilePatternSet, FixMode, OutputFormat, PatternPrefixPair,
PreviewMode, ProgressBar, Settings, UnsafeFixes, DEFAULT_SELECTORS,
};

use anyhow::{anyhow, Context, Result};
Expand Down Expand Up @@ -55,6 +55,25 @@ struct CheckSection {
check: Option<CheckArgs>,
}

// Default paths to exclude when searching paths
pub(crate) static EXCLUDE_BUILTINS: &[FilePattern] = &[
FilePattern::Builtin(".git"),
FilePattern::Builtin(".git-rewrite"),
FilePattern::Builtin(".hg"),
FilePattern::Builtin(".svn"),
FilePattern::Builtin("venv"),
FilePattern::Builtin(".venv"),
FilePattern::Builtin("pyenv"),
FilePattern::Builtin(".pyenv"),
FilePattern::Builtin(".eggs"),
FilePattern::Builtin("site-packages"),
FilePattern::Builtin(".vscode"),
FilePattern::Builtin("build"),
FilePattern::Builtin("_build"),
FilePattern::Builtin("dist"),
FilePattern::Builtin("_dist"),
];

// Adapted from ruff
fn parse_fpm_toml<P: AsRef<Path>>(path: P) -> Result<Fpm> {
let contents = std::fs::read_to_string(path.as_ref())
Expand Down Expand Up @@ -154,6 +173,9 @@ pub struct CheckSettings {
pub output_format: OutputFormat,
pub progress_bar: ProgressBar,
pub preview: PreviewMode,
pub exclude: Option<Vec<FilePattern>>,
pub extend_exclude: Vec<FilePattern>,
pub exclude_mode: ExcludeMode,
}

/// Read either fpm.toml or fortitude.toml into our "known good" file
Expand Down Expand Up @@ -192,6 +214,11 @@ fn parse_config_file(config_file: &Option<PathBuf>) -> Result<CheckSettings> {
preview: resolve_bool_arg(value.preview, value.no_preview)
.map(PreviewMode::from)
.unwrap_or_default(),
exclude: value.exclude,
extend_exclude: value.extend_exclude.unwrap_or_default(),
exclude_mode: resolve_bool_arg(value.force_exclude, value.no_force_exclude)
.map(ExcludeMode::from)
.unwrap_or_default(),
},
None => CheckSettings::default(),
};
Expand Down Expand Up @@ -241,9 +268,8 @@ fn ruleset(args: RuleSelection, preview: &PreviewMode) -> anyhow::Result<Vec<Rul
Ok(rules)
}

/// Helper function used with `filter` to select only paths that end in a Fortran extension.
/// Includes non-standard extensions, as these should be reported.
fn filter_fortran_extensions<S: AsRef<str>>(path: &Path, extensions: &[S]) -> bool {
/// Helper function used with `get_files` to select only paths that end in a Fortran extension.
fn is_valid_extension<S: AsRef<str>>(path: &Path, extensions: &[S]) -> bool {
if let Some(ext) = path.extension() {
// Can't use '&[&str].contains()', as extensions are of type OsStr
extensions.iter().any(|x| x.as_ref() == ext)
Expand All @@ -256,24 +282,28 @@ fn filter_fortran_extensions<S: AsRef<str>>(path: &Path, extensions: &[S]) -> bo
fn get_files<P: AsRef<Path>, S: AsRef<str>>(
paths: &[P],
extensions: &[S],
) -> anyhow::Result<Vec<PathBuf>> {
excludes: &FilePatternSet,
exclude_mode: ExcludeMode,
) -> Vec<PathBuf> {
paths
.iter()
.flat_map(|path| {
if path.as_ref().is_dir() {
if matches!(exclude_mode, ExcludeMode::Force) && excludes.matches(path) {
vec![]
} else if path.as_ref().is_dir() {
WalkDir::new(path)
.min_depth(1)
.into_iter()
.filter_map(|x| x.ok()) // skip dirs if user doesn't have permission
.filter(|x| filter_fortran_extensions(x.path(), extensions))
.map(|x| std::path::absolute(x.path()))
.filter_entry(|e| !excludes.matches(e.path()))
.filter_map(|p| p.ok()) // skip dirs if user doesn't have permission
.filter(|p| is_valid_extension(p.path(), extensions))
.map(|p| fs::normalize_path(p.path()))
.collect::<Vec<_>>()
} else {
vec![std::path::absolute(path)]
vec![fs::normalize_path(path)]
}
})
.collect::<Result<Vec<_>, _>>()
.map_err(anyhow::Error::new)
.collect()
}

/// Parse a file, check it for issues, and return the report.
Expand Down Expand Up @@ -718,6 +748,22 @@ pub fn check(args: CheckArgs, global_options: &GlobalConfigArgs) -> Result<ExitC
.collect::<Vec<_>>(),
))?;

let file_excludes = FilePatternSet::try_from_iter(
EXCLUDE_BUILTINS
.iter()
.cloned()
.chain(
args.exclude
.unwrap_or(file_settings.exclude.unwrap_or_default())
.into_iter(),
)
.chain(args.extend_exclude.unwrap_or_default().into_iter())
.chain(file_settings.extend_exclude.into_iter()),
)?;
let exclude_mode = resolve_bool_arg(args.force_exclude, args.no_force_exclude)
.map(ExcludeMode::from)
.unwrap_or(file_settings.exclude_mode);

let output_format = args.output_format.unwrap_or(file_settings.output_format);
let preview_mode = resolve_bool_arg(args.preview, args.no_preview)
.map(PreviewMode::from)
Expand Down Expand Up @@ -762,7 +808,7 @@ pub fn check(args: CheckArgs, global_options: &GlobalConfigArgs) -> Result<ExitC
let text_rules = rules_to_text_rules(&rules);
let ast_entrypoints = ast_entrypoint_map(&rules);

let files = get_files(files, file_extensions)?;
let files = get_files(files, file_extensions, &file_excludes, exclude_mode);
let file_digits = files.len().to_string().len();
let progress_bar_style = match progress_bar {
ProgressBar::Fancy => {
Expand Down
Loading
Loading