Skip to content
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
13 changes: 8 additions & 5 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,20 @@ risundle [OPTIONS] <FILE> [-- <COMPILER OPTIONS>...]
| オプション | 説明 |
| --- | --- |
| `-c`, `--compiler <PATH>` | 使用するコンパイラ (既定: `g++`) |
| `-k`, `--keep <ID>` | tree-shaking の対象外にするライブラリ ID (繰り返し可。既定: `std`) |
| `-k`, `--keep <ID>` | 展開せず `#include` のまま残すライブラリを追加指定する (繰り返し可。既定: `std`) |
| `--no-keep <ID>` | 維持指定 (keep) からライブラリを外す (繰り返し可。`--keep` より優先) |
| `--no-tree-shaking` | tree-shaking を無効化し、keep 指定以外をすべて展開する (フォールバック用) |
| `-e`, `--embed` | 元のソースを先頭にコメントとして埋め込む |
| `--no-embed` | 元のソースを埋め込まない (設定の `embed = true` を打ち消す) |
| `-n`, `--no-check` | ライブラリ更新のハッシュ検証をスキップする |
| `-- <OPTIONS>...` | `--` 以降をコンパイラへそのまま渡す |
| `--no-config` | `.risundlerc.toml` を無視する (設定ファイルが無い時と同じ挙動) |
| `-- <OPTIONS>...` | `--` 以降をコンパイラへそのまま渡す (設定の options への追記) |

`--keep` はライブラリを展開せず `#include` のまま残しますが、`--no-tree-shaking` は keep 指定を除く全ライブラリを展開した上で tree-shaking を行いません。両者は別物で、併用もできます。なお `--no-tree-shaking` は識別子情報を使わないため、ライブラリ更新のハッシュ検証も行いません。

```bash
# clang++ を使い、AC Library も展開せず #include のまま残す
risundle -c clang++ -k std -k ac-library main.cpp > submission.cpp
# clang++ を使い、AC Library も展開せず #include のまま残す (std は既定で維持される)
risundle -c clang++ -k ac-library main.cpp > submission.cpp

# コンパイラに追加オプションを渡す
risundle main.cpp -- -std=gnu++20 -O2
Expand All @@ -94,7 +97,7 @@ risundle library <SUBCOMMAND>

## 設定ファイル

解答ファイルのあるディレクトリから親方向に探索し、最も近い `.risundlerc.toml` を 1 つ採用します (複数ファイルのマージはしません)。CLI オプションが設定ファイルより優先されます
解答ファイルのあるディレクトリから親方向に探索し、最も近い `.risundlerc.toml` を 1 つ採用します (複数ファイルのマージはしません)。CLI で明示したオプションが設定ファイルより優先されます: スカラーと bool は上書き、維持指定 (keep) は `--keep` で追加・`--no-keep` で除外、`--` のコンパイラオプションは設定への追記です。`--no-config` を指定すると設定ファイルを無視できます

```toml
[compiler]
Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,20 @@ Bundles `<FILE>` and writes the result to standard output.
| Option | Description |
| --- | --- |
| `-c`, `--compiler <PATH>` | Compiler to use (default: `g++`) |
| `-k`, `--keep <ID>` | Library ID to exclude from tree-shaking (repeatable; default: `std`) |
| `-k`, `--keep <ID>` | Also keep a library unexpanded, out of tree-shaking (repeatable; default: `std`) |
| `--no-keep <ID>` | Stop keeping a library (repeatable; beats `--keep`) |
| `--no-tree-shaking` | Disable tree-shaking and expand everything except kept libraries (useful as a fallback) |
| `-e`, `--embed` | Embed the original source as a comment at the top |
| `--no-embed` | Do not embed the original source (cancels a configured `embed = true`) |
| `-n`, `--no-check` | Skip the hash verification of library updates |
| `-- <OPTIONS>...` | Pass everything after `--` straight to the compiler |
| `--no-config` | Ignore any `.risundlerc.toml`, behaving as if none exists |
| `-- <OPTIONS>...` | Pass everything after `--` straight to the compiler, appended to the configured options |

`--keep` leaves a library unexpanded as an `#include`, whereas `--no-tree-shaking` expands every library except the kept ones but performs no tree-shaking. The two are different and can be combined. Note that `--no-tree-shaking` also skips the hash verification of library updates, since it uses no identifier information.

```bash
# Use clang++ and leave AC Library unexpanded, keeping it as #include
risundle -c clang++ -k std -k ac-library main.cpp > submission.cpp
# Use clang++ and leave AC Library unexpanded too (std stays kept by default)
risundle -c clang++ -k ac-library main.cpp > submission.cpp

# Pass extra options to the compiler
risundle main.cpp -- -std=gnu++20 -O2
Expand All @@ -94,7 +97,7 @@ risundle library <SUBCOMMAND>

## Configuration file

risundle searches from the directory of the solution file toward its parents and adopts the single nearest `.risundlerc.toml` (it does not merge multiple files). CLI options take precedence over the configuration file.
risundle searches from the directory of the solution file toward its parents and adopts the single nearest `.risundlerc.toml` (it does not merge multiple files). Explicit CLI options take precedence over the configuration file: scalars and booleans are overridden, `--keep` and `--no-keep` add and remove kept libraries, and options after `--` are appended to the configured ones. `--no-config` ignores the file entirely.

```toml
[compiler]
Expand Down
22 changes: 16 additions & 6 deletions docs/cheatsheet.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,18 @@ oj t && risundle main.cpp > submission.cpp && oj s submission.cpp
例: AC Library がジャッジ側にあるので埋め込みたくない。

```bash
risundle -k std -k ac-library main.cpp > submission.cpp
risundle -k ac-library main.cpp > submission.cpp
```

`-k` に渡すのは登録時の ID です。`-k` を 1 つでも指定すると既定の `keep = ["std"]` は上書きされるため、`-k std` も一緒に指定してください (忘れると標準ライブラリまで展開されます)。このとき解答側の include は `#include "..."` ではなく `#include <atcoder/dsu>` のような山括弧で書いてください (`"..."` だと keep が効かないことがあります)。
`-k` に渡すのは登録時の ID です。`-k` は「追加」なので、既定で維持される std はそのまま残ります。このとき解答側の include は `#include "..."` ではなく `#include <atcoder/dsu>` のような山括弧で書いてください (`"..."` だと keep が効かないことがあります)。

### keep しているライブラリを今回だけ展開したい

```bash
risundle --no-keep ac-library main.cpp > submission.cpp
```

設定ファイルや `-k` で維持指定していても、`--no-keep` が優先されて展開されます。

### ジャッジと同じコンパイラ・同じフラグで処理したい

Expand Down Expand Up @@ -120,7 +128,7 @@ bundle() {
risundle -e main.cpp > submission.cpp
```

コードを公開するジャッジで、tree-shaking 前の読みやすいコードを見せたいときに
コードを公開するジャッジで、自分が書いたままの元ソースを見せたいときに。設定ファイルで `embed = true` を常用している場合は、`--no-embed` で今回だけ打ち消せます

## ライブラリを触ったらやること

Expand Down Expand Up @@ -170,7 +178,9 @@ options = ["-std=gnu++20", "-O2", "-DONLINE_JUDGE", "-DATCODER"]
keep = ["std", "ac-library"]
```

CLI オプションを併用した場合はそちらが優先されます。
CLI オプションを併用した場合はそちらが優先されます (`-k` は設定への追加、`--` のコンパイラオプションは設定への追記になります)。設定ファイルを無視したいときは `--no-config` を付けてください。

なお `keep` を自分で書くときは `"std"` も一緒に並べてください。書き忘れると標準ライブラリまで展開されます (その場合は risundle が警告を出します)。

## うまくいかないとき

Expand All @@ -197,9 +207,9 @@ risundle --no-tree-shaking main.cpp > submission.cpp

解答側の include を `#include "atcoder/dsu"` のような `"..."` 形式で書いていませんか。`#include <atcoder/dsu>` の山括弧形式に変えてください。

### `-k` を付けたら標準ライブラリまで展開されるようになった
### 標準ライブラリまで展開されてしまう

`-k` の指定は既定の `keep = ["std"]` を上書きします。`-k std` を並べて指定してください
維持指定 (keep) に std が入っていません。典型は設定ファイルの `keep` に `"std"` を書き忘れているケースです (`keep` を自分で書くと既定値は使われません)。`.risundlerc.toml` の `keep` に `"std"` を足してください。この状態では risundle も警告を出します

### エラーメッセージの行番号が元のファイルと合わない

Expand Down
22 changes: 16 additions & 6 deletions docs/cheatsheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,18 @@ oj t && risundle main.cpp > submission.cpp && oj s submission.cpp
Example: the judge provides AC Library, so you don't want it embedded.

```bash
risundle -k std -k ac-library main.cpp > submission.cpp
risundle -k ac-library main.cpp > submission.cpp
```

`-k` takes the ID you used at registration. Specifying `-k` even once overrides the default `keep = ["std"]`, so pass `-k std` alongside it (forget it and the standard library gets expanded too). Also write the includes in your solution with angle brackets like `#include <atcoder/dsu>`, not `#include "..."` (with `"..."` the keep may not take effect).
`-k` takes the ID you used at registration. `-k` is additive, so std stays kept by default. Also write the includes in your solution with angle brackets like `#include <atcoder/dsu>`, not `#include "..."` (with `"..."` the keep may not take effect).

### Expand a kept library just this once

```bash
risundle --no-keep ac-library main.cpp > submission.cpp
```

`--no-keep` beats both the config file and `-k`, so the library gets expanded.

### Process with the same compiler and flags as the judge

Expand Down Expand Up @@ -121,7 +129,7 @@ From then on, `bundle main.cpp` is all you need. When the fallback fires, check
risundle -e main.cpp > submission.cpp
```

For judges that publish submissions, when you want readers to see the readable pre-tree-shaking code.
For judges that publish submissions, when you want readers to see the source exactly as you wrote it. If your config file sets `embed = true`, cancel it for one run with `--no-embed`.

## After touching a library

Expand Down Expand Up @@ -171,7 +179,9 @@ options = ["-std=gnu++20", "-O2", "-DONLINE_JUDGE", "-DATCODER"]
keep = ["std", "ac-library"]
```

CLI options take precedence when both are given.
CLI options take precedence when both are given (`-k` adds to the configured keeps, and options after `--` are appended to the configured ones). Add `--no-config` to ignore the file entirely.

When you write `keep` yourself, list `"std"` alongside the rest. Forget it and the standard library gets expanded too (risundle prints a warning when that happens).

## When things go wrong

Expand All @@ -198,9 +208,9 @@ If this fixes it, tree-shaking dropped a needed definition (a report in the [iss

Are you writing the include as `#include "atcoder/dsu"`? Switch to the angle-bracket form `#include <atcoder/dsu>`.

### Adding `-k` made the standard library get expanded too
### The standard library got expanded too

Specifying `-k` overrides the default `keep = ["std"]`. Pass `-k std` alongside it.
std is missing from the effective keeps. The typical cause is a config file whose `keep` forgets to list `"std"` (writing `keep` yourself replaces the default). Add `"std"` to `keep` in `.risundlerc.toml`. risundle also prints a warning in this situation.

### Error line numbers don't match the original file

Expand Down
13 changes: 12 additions & 1 deletion docs/spec.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,18 @@ README より踏み込んだ、各コマンドの挙動・エラー条件・出

### 設定の解決

`<file>` のあるディレクトリから親方向に `.risundlerc.toml` を探し、最も近い 1 つだけを既定値に使う (複数あってもマージしない)。CLI オプションは設定ファイルより優先される。どちらにも無い項目は組み込みの既定値 (`compiler = g++`、`options = ["-std=gnu++17", "-O2", "-DONLINE_JUDGE", "-DATCODER"]`、`keep = ["std"]`、`embed = false`) で補う。
`<file>` のあるディレクトリから親方向に `.risundlerc.toml` を探し、最も近い 1 つだけを既定値に使う (複数あってもマージしない)。設定ファイルで省略された項目は組み込みの既定値 (`compiler = g++`、`options = ["-std=gnu++17", "-O2", "-DONLINE_JUDGE", "-DATCODER"]`、`keep = ["std"]`、`embed = false`) で補う。設定ファイル内の各項目は宣言的な全量指定であり、既定値へのマージはしない (`keep = ["ac-library"]` と書けば既定の `std` は含まれない)。

CLI オプションの重ね方は、項目の型ごとに決まる。

- `compiler` (スカラー) — CLI の `--compiler` が設定を上書きする。
- `embed` (bool) — `--embed` / `--no-embed` は後勝ちのペアで、CLI で明示された場合のみ設定を上書きする。
- `keep` (集合) — 実効 keep = (設定の keep ∪ `--keep`) − `--no-keep`。同じ ID が両方に指定された場合は、順序に依らず `--no-keep` が勝つ。
- `options` (順序付きリスト) — 実効 options = 設定の options + `--` 以降の CLI オプション。同種フラグの上書きはコンパイラの後勝ち (`-std` 等) や `-U` に委ね、risundle 側では解釈しない。

`--no-config` を指定すると `.risundlerc.toml` を一切読まず、設定ファイルが 1 つも見つからない環境と完全に同一の挙動になる。これにより、CLI だけで組み込み既定から実効設定を組み立て直せることが常に保証される。

実効 keep に `std` が含まれない場合は警告を出す (std の全展開はほぼ常に事故で、巨大な出力が提出時まで顕在化しないため)。ただし CLI で `--no-keep std` と明示された場合は、意図とみなして警告しない。

### ライブラリの変更検知

Expand Down
13 changes: 12 additions & 1 deletion docs/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,18 @@ See the README for the full list of options. The following are the behavioral de

### Resolving configuration

Searches from the directory of `<file>` toward its parents for `.risundlerc.toml` and uses only the single nearest one as defaults (no merging even if multiple exist). CLI options take precedence over the configuration file. Items present in neither are filled in with the built-in defaults (`compiler = g++`, `options = ["-std=gnu++17", "-O2", "-DONLINE_JUDGE", "-DATCODER"]`, `keep = ["std"]`, `embed = false`).
Searches from the directory of `<file>` toward its parents for `.risundlerc.toml` and uses only the single nearest one as defaults (no merging even if multiple exist). Items omitted in the configuration file are filled in with the built-in defaults (`compiler = g++`, `options = ["-std=gnu++17", "-O2", "-DONLINE_JUDGE", "-DATCODER"]`, `keep = ["std"]`, `embed = false`). Each item in the configuration file is a declarative, complete value and is not merged with the defaults (writing `keep = ["ac-library"]` does not include the default `std`).

How CLI options are layered on top depends on the type of the item.

- `compiler` (scalar) — the CLI `--compiler` overrides the configuration.
- `embed` (bool) — `--embed` / `--no-embed` are a last-wins pair, overriding the configuration only when given explicitly.
- `keep` (set) — effective keep = (configured keep ∪ `--keep`) − `--no-keep`. When the same ID appears in both, `--no-keep` wins regardless of order.
- `options` (ordered list) — effective options = configured options + the CLI options after `--`. Overriding between flags of the same kind is delegated to the compiler's own last-wins rules (`-std` etc.) and `-U`; risundle does not interpret them.

With `--no-config`, no `.risundlerc.toml` is read at all and the behavior is exactly identical to an environment where no configuration file is found. This guarantees that the effective settings can always be rebuilt from the built-in defaults through the CLI alone.

When `std` is not in the effective keep, a warning is printed (expanding `std` is almost always an accident whose huge output only surfaces at submission time). The warning is suppressed when `--no-keep std` was passed explicitly on the CLI, which is taken as intent.

### Library change detection

Expand Down
50 changes: 47 additions & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,31 @@ use std::path::PathBuf;
version,
after_help = "For library management, see `risundle library --help`."
)]
#[expect(
clippy::struct_excessive_bools,
reason = "CLI フラグの列挙であり、bool の数は引数の数を映しているだけで状態機械の複雑さではない"
)]
pub struct BundleArgs {
/// Path to the compiler to use
#[arg(short, long)]
pub compiler: Option<PathBuf>,

/// Library ID to keep out of tree-shaking (can be repeated)
#[arg(short, long = "keep")]
/// Also keep a library unexpanded, out of tree-shaking (can be repeated)
#[arg(short, long = "keep", value_name = "ID")]
pub keep: Vec<String>,

/// Stop keeping a library (can be repeated; beats --keep)
#[arg(long = "no-keep", value_name = "ID")]
pub no_keep: Vec<String>,

/// Embed the original source as a comment at the top
#[arg(short, long)]
#[arg(short, long, overrides_with = "no_embed")]
pub embed: bool,

/// Do not embed the original source (cancels a configured embed)
#[arg(long, overrides_with = "embed")]
pub no_embed: bool,
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/// Skip hash verification of library updates
#[arg(short = 'n', long = "no-check")]
pub no_check: bool,
Expand All @@ -34,6 +46,10 @@ pub struct BundleArgs {
#[arg(long = "no-tree-shaking")]
pub no_tree_shaking: bool,

/// Ignore any .risundlerc.toml, behaving as if none exists
#[arg(long = "no-config")]
pub no_config: bool,

/// C++ source file to bundle
pub file: PathBuf,

Expand All @@ -42,6 +58,20 @@ pub struct BundleArgs {
pub options: Vec<String>,
}

impl BundleArgs {
/// `--embed` / `--no-embed` は後勝ちのペア。どちらも指定されなければ `None` を返し、
/// 設定ファイル (なければ組み込み既定) に委ねる。
pub fn embed_override(&self) -> Option<bool> {
if self.embed {
Some(true)
} else if self.no_embed {
Some(false)
} else {
None
}
}
Comment thread
TwoSquirrels marked this conversation as resolved.
}

// エントリポイントで argv の先頭を `risundle library` に差し替えて渡すため、
// help・usage 上のコマンド名が `risundle library` として表示される。

Expand Down Expand Up @@ -114,4 +144,18 @@ mod tests {
fn hyphen_arguments_before_double_dash_are_rejected() {
assert!(BundleArgs::try_parse_from(["risundle", "main.cpp", "-O2"]).is_err());
}

#[test]
fn embed_pair_resolves_to_the_last_flag() {
let parse = |argv: &[&str]| {
let argv = [&["risundle", "main.cpp"], argv].concat();
BundleArgs::try_parse_from(argv).unwrap().embed_override()
};
assert_eq!(parse(&[]), None);
assert_eq!(parse(&["-e"]), Some(true));
assert_eq!(parse(&["--no-embed"]), Some(false));
// 後勝ち: 同時指定はコマンドライン上で後にある方が有効。
assert_eq!(parse(&["--embed", "--no-embed"]), Some(false));
assert_eq!(parse(&["--no-embed", "--embed"]), Some(true));
}
}
Loading
Loading