cifter-cli 0.1.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cifter_cli-0.1.0 → cifter_cli-0.2.1}/PKG-INFO +81 -17
- {cifter_cli-0.1.0 → cifter_cli-0.2.1}/README.md +79 -15
- {cifter_cli-0.1.0 → cifter_cli-0.2.1}/pyproject.toml +2 -2
- cifter_cli-0.2.1/src/cifter/__init__.py +6 -0
- {cifter_cli-0.1.0 → cifter_cli-0.2.1}/src/cifter/cli.py +50 -11
- cifter_cli-0.2.1/src/cifter/render.py +54 -0
- cifter_cli-0.2.1/src/cifter/version.py +31 -0
- cifter_cli-0.1.0/src/cifter/__init__.py +0 -3
- cifter_cli-0.1.0/src/cifter/render.py +0 -8
- {cifter_cli-0.1.0 → cifter_cli-0.2.1}/LICENSE +0 -0
- {cifter_cli-0.1.0 → cifter_cli-0.2.1}/src/cifter/__main__.py +0 -0
- {cifter_cli-0.1.0 → cifter_cli-0.2.1}/src/cifter/errors.py +0 -0
- {cifter_cli-0.1.0 → cifter_cli-0.2.1}/src/cifter/extract_flow.py +0 -0
- {cifter_cli-0.1.0 → cifter_cli-0.2.1}/src/cifter/extract_function.py +0 -0
- {cifter_cli-0.1.0 → cifter_cli-0.2.1}/src/cifter/extract_path.py +0 -0
- {cifter_cli-0.1.0 → cifter_cli-0.2.1}/src/cifter/model.py +0 -0
- {cifter_cli-0.1.0 → cifter_cli-0.2.1}/src/cifter/parser.py +0 -0
- {cifter_cli-0.1.0 → cifter_cli-0.2.1}/src/cifter/preprocessor.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cifter-cli
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: C/C++ の関数実装を抽出する軽量 CLI
|
|
5
5
|
Keywords: c,c++,cpp,cli,tree-sitter,source-extraction,static-analysis
|
|
6
6
|
Author: t-kenji
|
|
@@ -10,7 +10,6 @@ License-File: LICENSE
|
|
|
10
10
|
Classifier: Development Status :: 3 - Alpha
|
|
11
11
|
Classifier: Environment :: Console
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
13
|
Classifier: Operating System :: OS Independent
|
|
15
14
|
Classifier: Programming Language :: Python :: 3
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -18,6 +17,7 @@ Classifier: Programming Language :: C
|
|
|
18
17
|
Classifier: Programming Language :: C++
|
|
19
18
|
Classifier: Topic :: Software Development
|
|
20
19
|
Requires-Dist: pcpp>=1.30
|
|
20
|
+
Requires-Dist: rich>=14.3.3
|
|
21
21
|
Requires-Dist: tree-sitter>=0.25.2
|
|
22
22
|
Requires-Dist: tree-sitter-c>=0.24.1
|
|
23
23
|
Requires-Dist: tree-sitter-cpp>=0.23.4
|
|
@@ -40,6 +40,7 @@ Description-Content-Type: text/markdown
|
|
|
40
40
|
- 公開サブコマンドは `function` / `flow` / `path` の 3 つです
|
|
41
41
|
- 出力は元ソースと対応付け可能な行番号付き text です
|
|
42
42
|
- `-D NAME[=VALUE]` により条件分岐前処理を評価できます
|
|
43
|
+
- 出力先が TTY の場合は既定でシンタックスハイライトします
|
|
43
44
|
|
|
44
45
|
## Why cifter
|
|
45
46
|
|
|
@@ -59,14 +60,16 @@ python -m pip install cifter-cli
|
|
|
59
60
|
最小確認:
|
|
60
61
|
|
|
61
62
|
```sh
|
|
63
|
+
cift --version
|
|
62
64
|
cift --help
|
|
65
|
+
python -m cifter --version
|
|
63
66
|
python -m cifter --help
|
|
64
67
|
```
|
|
65
68
|
|
|
66
69
|
GitHub Release の `wheel` / `sdist` から install することもできます。
|
|
67
70
|
|
|
68
71
|
```sh
|
|
69
|
-
python -m pip install ./cifter_cli-0.1
|
|
72
|
+
python -m pip install ./cifter_cli-0.2.1-py3-none-any.whl
|
|
70
73
|
```
|
|
71
74
|
|
|
72
75
|
開発用:
|
|
@@ -78,40 +81,99 @@ uv run cift --help
|
|
|
78
81
|
|
|
79
82
|
## Quick Start
|
|
80
83
|
|
|
81
|
-
|
|
84
|
+
サンプルソース `foo.c`:
|
|
82
85
|
|
|
83
86
|
```c
|
|
84
|
-
int
|
|
87
|
+
int DecideState(int x)
|
|
85
88
|
{
|
|
89
|
+
int state = 0;
|
|
90
|
+
LogStart();
|
|
91
|
+
|
|
86
92
|
if (x > 0) {
|
|
87
|
-
|
|
93
|
+
Prepare();
|
|
94
|
+
state = 1;
|
|
95
|
+
} else {
|
|
96
|
+
PrepareFallback();
|
|
97
|
+
state = 2;
|
|
88
98
|
}
|
|
89
99
|
|
|
90
|
-
|
|
100
|
+
Finalize();
|
|
101
|
+
return state;
|
|
91
102
|
}
|
|
92
103
|
```
|
|
93
104
|
|
|
94
|
-
|
|
105
|
+
まず全体を確認したいときは、関数をそのまま抜きます。
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
cift function --name DecideState --source foo.c
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
1: int DecideState(int x)
|
|
113
|
+
2: {
|
|
114
|
+
3: int state = 0;
|
|
115
|
+
4: LogStart();
|
|
116
|
+
5:
|
|
117
|
+
6: if (x > 0) {
|
|
118
|
+
7: Prepare();
|
|
119
|
+
8: state = 1;
|
|
120
|
+
9: } else {
|
|
121
|
+
10: PrepareFallback();
|
|
122
|
+
11: state = 2;
|
|
123
|
+
12: }
|
|
124
|
+
13:
|
|
125
|
+
14: Finalize();
|
|
126
|
+
15: return state;
|
|
127
|
+
16: }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
分岐の骨格と、見たい値更新だけを薄く追いたいときは `flow` を使います。
|
|
95
131
|
|
|
96
132
|
```sh
|
|
97
|
-
cift
|
|
133
|
+
cift flow --function DecideState --source foo.c --track state
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
1: int DecideState(int x)
|
|
138
|
+
2: {
|
|
139
|
+
3: int state = 0;
|
|
140
|
+
6: if (x > 0) {
|
|
141
|
+
8: state = 1;
|
|
142
|
+
9: } else {
|
|
143
|
+
11: state = 2;
|
|
144
|
+
12: }
|
|
145
|
+
15: return state;
|
|
146
|
+
16: }
|
|
98
147
|
```
|
|
99
148
|
|
|
100
|
-
|
|
149
|
+
失敗側や `else` 側の 1 本だけを追って、その後に何が起きるかまで見たいときは `path` を使います。
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
cift path --function DecideState --source foo.c --route 'else'
|
|
153
|
+
```
|
|
101
154
|
|
|
102
155
|
```text
|
|
103
|
-
1: int
|
|
156
|
+
1: int DecideState(int x)
|
|
104
157
|
2: {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
158
|
+
6: if (x > 0) {
|
|
159
|
+
9: } else {
|
|
160
|
+
10: PrepareFallback();
|
|
161
|
+
11: state = 2;
|
|
162
|
+
12: }
|
|
163
|
+
14: Finalize();
|
|
164
|
+
15: return state;
|
|
165
|
+
16: }
|
|
111
166
|
```
|
|
112
167
|
|
|
113
168
|
## Commands
|
|
114
169
|
|
|
170
|
+
バージョン表示:
|
|
171
|
+
|
|
172
|
+
```sh
|
|
173
|
+
cift --version
|
|
174
|
+
python -m cifter --version
|
|
175
|
+
```
|
|
176
|
+
|
|
115
177
|
`function`:
|
|
116
178
|
指定した関数の実装全体をそのまま抽出します。レビュー対象の最小切り出しに向きます。
|
|
117
179
|
|
|
@@ -178,6 +240,8 @@ cift function --name FooFunction --source examples/demo.c -D DEF_FOO -D ENABLE_B
|
|
|
178
240
|
|
|
179
241
|
```sh
|
|
180
242
|
cift function --name FooFunction --source examples/demo.c
|
|
243
|
+
cift function --name FooFunction --source examples/demo.c --color
|
|
244
|
+
cift function --name FooFunction --source examples/demo.c --no-color
|
|
181
245
|
cift flow --function FooFunction --source examples/demo.c --track 'ctx->state'
|
|
182
246
|
cift path --function FooFunction --source examples/demo.c --route 'case CMD_LOOP > if ret == OK'
|
|
183
247
|
```
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
- 公開サブコマンドは `function` / `flow` / `path` の 3 つです
|
|
10
10
|
- 出力は元ソースと対応付け可能な行番号付き text です
|
|
11
11
|
- `-D NAME[=VALUE]` により条件分岐前処理を評価できます
|
|
12
|
+
- 出力先が TTY の場合は既定でシンタックスハイライトします
|
|
12
13
|
|
|
13
14
|
## Why cifter
|
|
14
15
|
|
|
@@ -28,14 +29,16 @@ python -m pip install cifter-cli
|
|
|
28
29
|
最小確認:
|
|
29
30
|
|
|
30
31
|
```sh
|
|
32
|
+
cift --version
|
|
31
33
|
cift --help
|
|
34
|
+
python -m cifter --version
|
|
32
35
|
python -m cifter --help
|
|
33
36
|
```
|
|
34
37
|
|
|
35
38
|
GitHub Release の `wheel` / `sdist` から install することもできます。
|
|
36
39
|
|
|
37
40
|
```sh
|
|
38
|
-
python -m pip install ./cifter_cli-0.1
|
|
41
|
+
python -m pip install ./cifter_cli-0.2.1-py3-none-any.whl
|
|
39
42
|
```
|
|
40
43
|
|
|
41
44
|
開発用:
|
|
@@ -47,40 +50,99 @@ uv run cift --help
|
|
|
47
50
|
|
|
48
51
|
## Quick Start
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
サンプルソース `foo.c`:
|
|
51
54
|
|
|
52
55
|
```c
|
|
53
|
-
int
|
|
56
|
+
int DecideState(int x)
|
|
54
57
|
{
|
|
58
|
+
int state = 0;
|
|
59
|
+
LogStart();
|
|
60
|
+
|
|
55
61
|
if (x > 0) {
|
|
56
|
-
|
|
62
|
+
Prepare();
|
|
63
|
+
state = 1;
|
|
64
|
+
} else {
|
|
65
|
+
PrepareFallback();
|
|
66
|
+
state = 2;
|
|
57
67
|
}
|
|
58
68
|
|
|
59
|
-
|
|
69
|
+
Finalize();
|
|
70
|
+
return state;
|
|
60
71
|
}
|
|
61
72
|
```
|
|
62
73
|
|
|
63
|
-
|
|
74
|
+
まず全体を確認したいときは、関数をそのまま抜きます。
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
cift function --name DecideState --source foo.c
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
1: int DecideState(int x)
|
|
82
|
+
2: {
|
|
83
|
+
3: int state = 0;
|
|
84
|
+
4: LogStart();
|
|
85
|
+
5:
|
|
86
|
+
6: if (x > 0) {
|
|
87
|
+
7: Prepare();
|
|
88
|
+
8: state = 1;
|
|
89
|
+
9: } else {
|
|
90
|
+
10: PrepareFallback();
|
|
91
|
+
11: state = 2;
|
|
92
|
+
12: }
|
|
93
|
+
13:
|
|
94
|
+
14: Finalize();
|
|
95
|
+
15: return state;
|
|
96
|
+
16: }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
分岐の骨格と、見たい値更新だけを薄く追いたいときは `flow` を使います。
|
|
64
100
|
|
|
65
101
|
```sh
|
|
66
|
-
cift
|
|
102
|
+
cift flow --function DecideState --source foo.c --track state
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```text
|
|
106
|
+
1: int DecideState(int x)
|
|
107
|
+
2: {
|
|
108
|
+
3: int state = 0;
|
|
109
|
+
6: if (x > 0) {
|
|
110
|
+
8: state = 1;
|
|
111
|
+
9: } else {
|
|
112
|
+
11: state = 2;
|
|
113
|
+
12: }
|
|
114
|
+
15: return state;
|
|
115
|
+
16: }
|
|
67
116
|
```
|
|
68
117
|
|
|
69
|
-
|
|
118
|
+
失敗側や `else` 側の 1 本だけを追って、その後に何が起きるかまで見たいときは `path` を使います。
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
cift path --function DecideState --source foo.c --route 'else'
|
|
122
|
+
```
|
|
70
123
|
|
|
71
124
|
```text
|
|
72
|
-
1: int
|
|
125
|
+
1: int DecideState(int x)
|
|
73
126
|
2: {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
127
|
+
6: if (x > 0) {
|
|
128
|
+
9: } else {
|
|
129
|
+
10: PrepareFallback();
|
|
130
|
+
11: state = 2;
|
|
131
|
+
12: }
|
|
132
|
+
14: Finalize();
|
|
133
|
+
15: return state;
|
|
134
|
+
16: }
|
|
80
135
|
```
|
|
81
136
|
|
|
82
137
|
## Commands
|
|
83
138
|
|
|
139
|
+
バージョン表示:
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
cift --version
|
|
143
|
+
python -m cifter --version
|
|
144
|
+
```
|
|
145
|
+
|
|
84
146
|
`function`:
|
|
85
147
|
指定した関数の実装全体をそのまま抽出します。レビュー対象の最小切り出しに向きます。
|
|
86
148
|
|
|
@@ -147,6 +209,8 @@ cift function --name FooFunction --source examples/demo.c -D DEF_FOO -D ENABLE_B
|
|
|
147
209
|
|
|
148
210
|
```sh
|
|
149
211
|
cift function --name FooFunction --source examples/demo.c
|
|
212
|
+
cift function --name FooFunction --source examples/demo.c --color
|
|
213
|
+
cift function --name FooFunction --source examples/demo.c --no-color
|
|
150
214
|
cift flow --function FooFunction --source examples/demo.c --track 'ctx->state'
|
|
151
215
|
cift path --function FooFunction --source examples/demo.c --route 'case CMD_LOOP > if ret == OK'
|
|
152
216
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cifter-cli"
|
|
3
|
-
version = "0.1
|
|
3
|
+
version = "0.2.1"
|
|
4
4
|
description = "C/C++ の関数実装を抽出する軽量 CLI"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -22,7 +22,6 @@ classifiers = [
|
|
|
22
22
|
"Development Status :: 3 - Alpha",
|
|
23
23
|
"Environment :: Console",
|
|
24
24
|
"Intended Audience :: Developers",
|
|
25
|
-
"License :: OSI Approved :: MIT License",
|
|
26
25
|
"Operating System :: OS Independent",
|
|
27
26
|
"Programming Language :: Python :: 3",
|
|
28
27
|
"Programming Language :: Python :: 3.12",
|
|
@@ -32,6 +31,7 @@ classifiers = [
|
|
|
32
31
|
]
|
|
33
32
|
dependencies = [
|
|
34
33
|
"pcpp>=1.30",
|
|
34
|
+
"rich>=14.3.3",
|
|
35
35
|
"tree-sitter>=0.25.2",
|
|
36
36
|
"tree-sitter-c>=0.24.1",
|
|
37
37
|
"tree-sitter-cpp>=0.23.4",
|
|
@@ -10,9 +10,10 @@ from cifter.errors import CiftError
|
|
|
10
10
|
from cifter.extract_flow import extract_flow
|
|
11
11
|
from cifter.extract_function import extract_function
|
|
12
12
|
from cifter.extract_path import extract_path
|
|
13
|
-
from cifter.model import TrackPath
|
|
13
|
+
from cifter.model import ExtractionResult, TrackPath
|
|
14
14
|
from cifter.parser import parse_source
|
|
15
|
-
from cifter.render import
|
|
15
|
+
from cifter.render import print_result
|
|
16
|
+
from cifter.version import format_version_output
|
|
16
17
|
|
|
17
18
|
app = typer.Typer(no_args_is_help=True, help="C/C++ の関数実装を抽出する CLI")
|
|
18
19
|
|
|
@@ -21,6 +22,28 @@ def main() -> None:
|
|
|
21
22
|
app()
|
|
22
23
|
|
|
23
24
|
|
|
25
|
+
def _version_callback(value: bool) -> None:
|
|
26
|
+
if not value:
|
|
27
|
+
return
|
|
28
|
+
typer.echo(format_version_output())
|
|
29
|
+
raise typer.Exit()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@app.callback()
|
|
33
|
+
def common_options(
|
|
34
|
+
version: Annotated[
|
|
35
|
+
bool,
|
|
36
|
+
typer.Option(
|
|
37
|
+
"--version",
|
|
38
|
+
callback=_version_callback,
|
|
39
|
+
is_eager=True,
|
|
40
|
+
help="バージョンを表示して終了する",
|
|
41
|
+
),
|
|
42
|
+
] = False,
|
|
43
|
+
) -> None:
|
|
44
|
+
_ = version
|
|
45
|
+
|
|
46
|
+
|
|
24
47
|
SourceOption = Annotated[
|
|
25
48
|
Path,
|
|
26
49
|
typer.Option(
|
|
@@ -34,17 +57,30 @@ SourceOption = Annotated[
|
|
|
34
57
|
),
|
|
35
58
|
]
|
|
36
59
|
|
|
60
|
+
ColorOption = Annotated[
|
|
61
|
+
bool | None,
|
|
62
|
+
typer.Option(
|
|
63
|
+
"--color/--no-color",
|
|
64
|
+
help="抽出結果のシンタックスハイライトを制御する",
|
|
65
|
+
),
|
|
66
|
+
]
|
|
67
|
+
|
|
37
68
|
|
|
38
69
|
@app.command("function")
|
|
39
70
|
def function_command(
|
|
40
71
|
name: Annotated[str, typer.Option("--name", help="抽出する関数名")],
|
|
41
72
|
source: SourceOption,
|
|
73
|
+
color: ColorOption = None,
|
|
42
74
|
defines: Annotated[
|
|
43
75
|
list[str] | None,
|
|
44
76
|
typer.Option("--define", "-D", help="条件分岐評価に使うマクロ定義"),
|
|
45
77
|
] = None,
|
|
46
78
|
) -> None:
|
|
47
|
-
|
|
79
|
+
def task() -> tuple[ExtractionResult, str]:
|
|
80
|
+
parsed = parse_source(source, defines or [])
|
|
81
|
+
return extract_function(parsed, name), parsed.language_name
|
|
82
|
+
|
|
83
|
+
_run(task, color=color)
|
|
48
84
|
|
|
49
85
|
|
|
50
86
|
@app.command("flow")
|
|
@@ -52,17 +88,18 @@ def flow_command(
|
|
|
52
88
|
function_name: Annotated[str, typer.Option("--function", help="対象関数名")],
|
|
53
89
|
source: SourceOption,
|
|
54
90
|
track: Annotated[list[str], typer.Option("--track", help="保持するアクセスパス")] | None = None,
|
|
91
|
+
color: ColorOption = None,
|
|
55
92
|
defines: Annotated[
|
|
56
93
|
list[str] | None,
|
|
57
94
|
typer.Option("--define", "-D", help="条件分岐評価に使うマクロ定義"),
|
|
58
95
|
] = None,
|
|
59
96
|
) -> None:
|
|
60
|
-
def task() -> str:
|
|
97
|
+
def task() -> tuple[ExtractionResult, str]:
|
|
61
98
|
parsed = parse_source(source, defines or [])
|
|
62
99
|
tracks = tuple(TrackPath.parse(value) for value in (track or []))
|
|
63
|
-
return
|
|
100
|
+
return extract_flow(parsed, function_name, tracks), parsed.language_name
|
|
64
101
|
|
|
65
|
-
_run(task)
|
|
102
|
+
_run(task, color=color)
|
|
66
103
|
|
|
67
104
|
|
|
68
105
|
@app.command("path")
|
|
@@ -70,21 +107,23 @@ def path_command(
|
|
|
70
107
|
function_name: Annotated[str, typer.Option("--function", help="対象関数名")],
|
|
71
108
|
source: SourceOption,
|
|
72
109
|
route: Annotated[str, typer.Option("--route", help="抽出する経路 DSL")],
|
|
110
|
+
color: ColorOption = None,
|
|
73
111
|
defines: Annotated[
|
|
74
112
|
list[str] | None,
|
|
75
113
|
typer.Option("--define", "-D", help="条件分岐評価に使うマクロ定義"),
|
|
76
114
|
] = None,
|
|
77
115
|
) -> None:
|
|
78
|
-
def task() -> str:
|
|
116
|
+
def task() -> tuple[ExtractionResult, str]:
|
|
79
117
|
parsed = parse_source(source, defines or [])
|
|
80
|
-
return
|
|
118
|
+
return extract_path(parsed, function_name, route), parsed.language_name
|
|
81
119
|
|
|
82
|
-
_run(task)
|
|
120
|
+
_run(task, color=color)
|
|
83
121
|
|
|
84
122
|
|
|
85
|
-
def _run(task: Callable[[], str]) -> None:
|
|
123
|
+
def _run(task: Callable[[], tuple[ExtractionResult, str]], *, color: bool | None) -> None:
|
|
86
124
|
try:
|
|
87
|
-
|
|
125
|
+
result, language_name = task()
|
|
126
|
+
print_result(result, language_name, color=color)
|
|
88
127
|
except CiftError as error:
|
|
89
128
|
typer.echo(error.message, err=True)
|
|
90
129
|
raise typer.Exit(code=1) from error
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from io import StringIO
|
|
5
|
+
from typing import TextIO
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.syntax import Syntax
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
|
|
11
|
+
from cifter.model import ExtractionResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def render_result(result: ExtractionResult) -> str:
|
|
15
|
+
width = len(str(result.lines[-1].line_no))
|
|
16
|
+
return "\n".join(f"{line.line_no:>{width}}: {line.text}" for line in result.lines)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def print_result(
|
|
20
|
+
result: ExtractionResult,
|
|
21
|
+
language_name: str,
|
|
22
|
+
*,
|
|
23
|
+
color: bool | None,
|
|
24
|
+
file: TextIO | None = None,
|
|
25
|
+
) -> None:
|
|
26
|
+
output = file or sys.stdout
|
|
27
|
+
if not _should_use_color(color, output):
|
|
28
|
+
output.write(f"{render_result(result)}\n")
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
console = Console(
|
|
32
|
+
file=StringIO(),
|
|
33
|
+
record=True,
|
|
34
|
+
force_terminal=True,
|
|
35
|
+
color_system="truecolor",
|
|
36
|
+
)
|
|
37
|
+
syntax = Syntax("", language_name)
|
|
38
|
+
width = len(str(result.lines[-1].line_no))
|
|
39
|
+
for line in result.lines:
|
|
40
|
+
rendered_line = Text.assemble(
|
|
41
|
+
(f"{line.line_no:>{width}}: ", "dim"),
|
|
42
|
+
syntax.highlight(line.text)[:-1],
|
|
43
|
+
)
|
|
44
|
+
console.print(rendered_line)
|
|
45
|
+
output.write(console.export_text(styles=True))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _should_use_color(color: bool | None, file: TextIO) -> bool:
|
|
49
|
+
if color is not None:
|
|
50
|
+
return color
|
|
51
|
+
isatty = getattr(file, "isatty", None)
|
|
52
|
+
if not callable(isatty):
|
|
53
|
+
return False
|
|
54
|
+
return bool(isatty())
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import tomllib
|
|
4
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
PACKAGE_NAME = "cifter-cli"
|
|
8
|
+
COMMAND_NAME = "cift"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_version() -> str:
|
|
12
|
+
try:
|
|
13
|
+
return version(PACKAGE_NAME)
|
|
14
|
+
except PackageNotFoundError:
|
|
15
|
+
return _read_version_from_pyproject()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def format_version_output() -> str:
|
|
19
|
+
return f"{COMMAND_NAME} {get_version()}"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _read_version_from_pyproject() -> str:
|
|
23
|
+
pyproject_path = Path(__file__).resolve().parents[2] / "pyproject.toml"
|
|
24
|
+
data = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))
|
|
25
|
+
project = data.get("project")
|
|
26
|
+
if not isinstance(project, dict):
|
|
27
|
+
raise RuntimeError("pyproject.toml に project セクションがありません")
|
|
28
|
+
version_value = project.get("version")
|
|
29
|
+
if not isinstance(version_value, str):
|
|
30
|
+
raise RuntimeError("pyproject.toml の project.version が文字列ではありません")
|
|
31
|
+
return version_value
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from cifter.model import ExtractionResult
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def render_result(result: ExtractionResult) -> str:
|
|
7
|
-
width = len(str(result.lines[-1].line_no))
|
|
8
|
-
return "\n".join(f"{line.line_no:>{width}}: {line.text}" for line in result.lines)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|