Metadata-Version: 2.4
Name: ffmt
Version: 0.1.0
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Fortran
Classifier: Programming Language :: Rust
Classifier: Topic :: Software Development :: Quality Assurance
License-File: LICENSE
Summary: An opinionated Fortran formatter with Fypp and OpenACC support
Keywords: fortran,formatter,fypp,openacc,openmp
License: MIT
Requires-Python: >=3.8
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM

# ffmt

An opinionated, idempotent Fortran formatter with first-class [Fypp](https://github.com/aradi/fypp) preprocessor and OpenACC/OpenMP directive support.

`ffmt` is a single-pass formatter written in Rust.
It replaces [fprettify](https://github.com/pseewald/fprettify) and produces consistent output on the first run -- no multi-pass convergence needed.

## Installation

### From PyPI

```bash
pip install ffmt
```

### From crates.io

```bash
cargo install ffmt
```

### From source

```bash
git clone https://github.com/sbryngelson/ffmt
cd ffmt
cargo install --path .
```

## Usage

```bash
# Format files in-place
ffmt file.fpp
ffmt src/

# Check mode (CI) -- exit 1 if any file would change
ffmt --check src/

# Show what would change (with color)
ffmt --diff src/

# Both
ffmt --check --diff src/

# Parallel formatting
ffmt -j 8 src/

# Read from stdin, write to stdout (for editor integration)
cat file.fpp | ffmt -
ffmt --stdin-filepath file.fpp - < file.fpp

# Exclude patterns
ffmt --exclude "autogen/**" src/
```

### Stdin/stdout mode

Use `-` as the path to read from stdin and write formatted output to stdout. This enables editor integration (format-on-save) and piping:

```bash
# Format stdin to stdout
echo "INTEGER::x" | ffmt -
# Output: integer :: x

# Check stdin (exit 1 if it would change)
cat file.fpp | ffmt --check --stdin-filepath file.fpp -

# Diff stdin
cat file.fpp | ffmt --diff --stdin-filepath file.fpp -
```

The `--stdin-filepath` flag provides a filename for diagnostics.

### .gitignore support

`ffmt` automatically respects `.gitignore`, `.ignore`, `.fdignore`, and `.git/info/exclude` when walking directories. Files ignored by git are skipped.

## Editor integration

### Vim/Neovim

```vim
" Format on save
autocmd BufWritePost *.fpp,*.f90 silent !ffmt %

" Or use as formatprg
set formatprg=ffmt\ -
```

### VS Code

Add to `settings.json`:
```json
{
    "[fortran]": {
        "editor.formatOnSave": true
    },
    "fortran.formatting.formatter": "ffmt",
    "fortran.formatting.path": "ffmt",
    "fortran.formatting.args": ["--stdin-filepath", "${file}", "-"]
}
```

## CI integration

### GitHub Actions

```yaml
- uses: sbryngelson/ffmt@v1
  with:
    args: "--check src/"
```

Or install directly:

```yaml
- run: pip install ffmt
- run: ffmt --check src/
```

### pre-commit

Add to `.pre-commit-config.yaml`:

```yaml
repos:
  - repo: https://github.com/sbryngelson/ffmt
    rev: v0.1.0
    hooks:
      - id: ffmt
```

Use `ffmt-check` instead of `ffmt` for check-only mode (no modifications).

## What it does

`ffmt` formats Fortran free-form source files (`.fpp`, `.f90`, `.f95`, `.f03`) through a single-pass pipeline:

1. **Indentation** -- 4-space indentation based on scope tracking (`if/do/subroutine/module/type/...`)
2. **Fypp block indentation** -- `#:if`, `#:for`, `#:call`, `#:def`, etc. are indented as nested blocks
3. **Directive indentation** -- `!$acc`, `!$omp`, `!DIR$` lines indented to match surrounding Fortran scope
4. **Whitespace normalization** -- consistent spacing around operators, commas, colons, parentheses
5. **Case normalization** -- Fortran keywords lowercased (`IF` -> `if`, `END DO` -> `end do`)

### Formatting rules

**Indentation:**
- 4 spaces per scope level
- Fypp blocks (`#:if`/`#:for`/`#:call`/`#:def`/`#:block`/`#:mute`) create scope
- `#ifdef`/`#endif` preprocessor blocks do **not** change indentation
- Continuation lines (`&`) preserve their original spacing
- `!!` Doxygen continuation comments preserve their original alignment

**Operators -- space each side:**
`==`, `/=`, `<=`, `>=`, `<`, `>`, `.and.`, `.or.`, `.not.`, `=` (assignment), `=>`, `+`, `-` (binary), `//` (concatenation), `::`

**Operators -- no spaces:**
`*`, `/`, `**`

**Other:**
- Commas: one space after, none before
- Array slices: no spaces around `:` -- `a(1:n)`
- Keyword arguments: no spaces around `=` -- `call foo(bar=1)`
- Parentheses: no internal padding -- `f(x)` not `f( x )`
- Trailing whitespace: always stripped
- Blank lines: 3+ consecutive collapsed to 2
- Blank lines after `!$acc loop` directives: removed

**Preserved as-is:**
- String literal contents (`'...'`, `"..."`)
- Inline Fypp expressions (`${...}$`, `@{...}@`)
- Comment contents
- Doxygen comment alignment (`!<`, `!>`, `!!`)
- Continuation line structure and alignment

## Design

`ffmt` is opinionated like `gofmt` -- one style, no configuration file, minimal CLI flags.

### Why not fprettify?

- **Not idempotent** -- fprettify requires 2-4 passes to converge
- **No directive awareness** -- requires a separate indenter script for `!$acc`/`!$omp` lines
- **Fragile Fypp support** -- treats Fypp lines as "not Fortran," causing inconsistent indentation
- **Unmaintained** -- last release circa 2020

### Architecture

```
Source lines -> Join continuations -> Classify (LineKind enum) ->
Track scope depth -> Apply indentation -> Normalize whitespace ->
Normalize case -> Emit formatted lines
```

| Module | Responsibility |
|--------|---------------|
| `reader.rs` | Join `&` continuations, mark opaque regions (strings, `${...}$`, comments) |
| `classifier.rs` | Classify lines into `LineKind` enum with disambiguation rules |
| `scope.rs` | Scope tracking state machine (push/pop on block open/close) |
| `indent.rs` | Apply `4 * depth` leading spaces |
| `whitespace.rs` | Operator/comma/colon/paren spacing normalization |
| `case_norm.rs` | Lowercase Fortran keywords outside strings/Fypp/comments |
| `formatter.rs` | Pipeline orchestrator |
| `cli.rs` | CLI with stdin/stdout, .gitignore, color, exclude patterns |

## Exit codes

| Code | Meaning |
|------|---------|
| 0 | Success (all files formatted, or no changes needed in `--check` mode) |
| 1 | Files would change (`--check` mode only) |
| 2 | Usage error or I/O failure |

## License

MIT

