gramcheck 0.1.0__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.
- gramcheck-0.1.0/LICENSE +21 -0
- gramcheck-0.1.0/PKG-INFO +36 -0
- gramcheck-0.1.0/README.md +19 -0
- gramcheck-0.1.0/gramcheck.egg-info/PKG-INFO +36 -0
- gramcheck-0.1.0/gramcheck.egg-info/SOURCES.txt +10 -0
- gramcheck-0.1.0/gramcheck.egg-info/dependency_links.txt +1 -0
- gramcheck-0.1.0/gramcheck.egg-info/entry_points.txt +2 -0
- gramcheck-0.1.0/gramcheck.egg-info/requires.txt +3 -0
- gramcheck-0.1.0/gramcheck.egg-info/top_level.txt +1 -0
- gramcheck-0.1.0/gramcheck.py +225 -0
- gramcheck-0.1.0/pyproject.toml +31 -0
- gramcheck-0.1.0/setup.cfg +4 -0
gramcheck-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 degD
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
gramcheck-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gramcheck
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Use LLMs to check grammar.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/degD/gramcheck
|
|
7
|
+
Project-URL: Issues, https://github.com/degD/gramcheck/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: colored>=2.3.2
|
|
14
|
+
Requires-Dist: python-dotenv>=1.0.1
|
|
15
|
+
Requires-Dist: google-genai>=2.8.0
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# GRAMCHECK
|
|
19
|
+
This is a Python tool to check text for grammar mistakes using online LLMs. It uses [Google AI Studio](https://aistudio.google.com/) under the hood. You must provide your own API key. Because of the probabilistic nature of LLMs, the results may or may not be correct. Take them with a grain of salt. However, results are generated using a predefined seed to keep them consistent.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
1. Install from PyPI: `pipx install gramcheck`.
|
|
23
|
+
2. Set your API key: `gramcheck --set-api-key <YOUR_API_KEY_HERE>` (writes to `.env` in the project directory).
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
1. Check a file: `gramcheck example.txt`.
|
|
27
|
+
2. Check a single text: `gramcheck -t "Your text here"`.
|
|
28
|
+
3. Check the Nth line in a file: `gramcheck example.txt -n 0`.
|
|
29
|
+
4. Check a file as a whole: `gramcheck example.txt -a`.
|
|
30
|
+
5. Show help: `gramcheck --help`.
|
|
31
|
+
|
|
32
|
+
## Development
|
|
33
|
+
1. Install the [`uv`](https://docs.astral.sh/uv/) project manager.
|
|
34
|
+
2. Clone the project and run `uv sync`.
|
|
35
|
+
3. Set your API key in `.env` as shown above.
|
|
36
|
+
4. Run locally with `python gramcheck.py` or `./gramcheck`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# GRAMCHECK
|
|
2
|
+
This is a Python tool to check text for grammar mistakes using online LLMs. It uses [Google AI Studio](https://aistudio.google.com/) under the hood. You must provide your own API key. Because of the probabilistic nature of LLMs, the results may or may not be correct. Take them with a grain of salt. However, results are generated using a predefined seed to keep them consistent.
|
|
3
|
+
|
|
4
|
+
## Install
|
|
5
|
+
1. Install from PyPI: `pipx install gramcheck`.
|
|
6
|
+
2. Set your API key: `gramcheck --set-api-key <YOUR_API_KEY_HERE>` (writes to `.env` in the project directory).
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
1. Check a file: `gramcheck example.txt`.
|
|
10
|
+
2. Check a single text: `gramcheck -t "Your text here"`.
|
|
11
|
+
3. Check the Nth line in a file: `gramcheck example.txt -n 0`.
|
|
12
|
+
4. Check a file as a whole: `gramcheck example.txt -a`.
|
|
13
|
+
5. Show help: `gramcheck --help`.
|
|
14
|
+
|
|
15
|
+
## Development
|
|
16
|
+
1. Install the [`uv`](https://docs.astral.sh/uv/) project manager.
|
|
17
|
+
2. Clone the project and run `uv sync`.
|
|
18
|
+
3. Set your API key in `.env` as shown above.
|
|
19
|
+
4. Run locally with `python gramcheck.py` or `./gramcheck`.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gramcheck
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Use LLMs to check grammar.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/degD/gramcheck
|
|
7
|
+
Project-URL: Issues, https://github.com/degD/gramcheck/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: colored>=2.3.2
|
|
14
|
+
Requires-Dist: python-dotenv>=1.0.1
|
|
15
|
+
Requires-Dist: google-genai>=2.8.0
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# GRAMCHECK
|
|
19
|
+
This is a Python tool to check text for grammar mistakes using online LLMs. It uses [Google AI Studio](https://aistudio.google.com/) under the hood. You must provide your own API key. Because of the probabilistic nature of LLMs, the results may or may not be correct. Take them with a grain of salt. However, results are generated using a predefined seed to keep them consistent.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
1. Install from PyPI: `pipx install gramcheck`.
|
|
23
|
+
2. Set your API key: `gramcheck --set-api-key <YOUR_API_KEY_HERE>` (writes to `.env` in the project directory).
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
1. Check a file: `gramcheck example.txt`.
|
|
27
|
+
2. Check a single text: `gramcheck -t "Your text here"`.
|
|
28
|
+
3. Check the Nth line in a file: `gramcheck example.txt -n 0`.
|
|
29
|
+
4. Check a file as a whole: `gramcheck example.txt -a`.
|
|
30
|
+
5. Show help: `gramcheck --help`.
|
|
31
|
+
|
|
32
|
+
## Development
|
|
33
|
+
1. Install the [`uv`](https://docs.astral.sh/uv/) project manager.
|
|
34
|
+
2. Clone the project and run `uv sync`.
|
|
35
|
+
3. Set your API key in `.env` as shown above.
|
|
36
|
+
4. Run locally with `python gramcheck.py` or `./gramcheck`.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
gramcheck.py
|
|
4
|
+
pyproject.toml
|
|
5
|
+
gramcheck.egg-info/PKG-INFO
|
|
6
|
+
gramcheck.egg-info/SOURCES.txt
|
|
7
|
+
gramcheck.egg-info/dependency_links.txt
|
|
8
|
+
gramcheck.egg-info/entry_points.txt
|
|
9
|
+
gramcheck.egg-info/requires.txt
|
|
10
|
+
gramcheck.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gramcheck
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import dotenv
|
|
6
|
+
from colored import Fore
|
|
7
|
+
from google import genai
|
|
8
|
+
from google.genai import types
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_config_env_path() -> str:
|
|
12
|
+
project_root = os.path.dirname(os.path.abspath(__file__))
|
|
13
|
+
return os.path.join(project_root, ".env")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
SEED = 4224442
|
|
17
|
+
API_KEY_PROVIDED = True
|
|
18
|
+
CONFIG_ENV_PATH = get_config_env_path()
|
|
19
|
+
dotenv.load_dotenv(CONFIG_ENV_PATH)
|
|
20
|
+
dotenv.load_dotenv()
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
client = genai.Client()
|
|
24
|
+
except ValueError:
|
|
25
|
+
API_KEY_PROVIDED = False
|
|
26
|
+
|
|
27
|
+
file_read_error = "Unable to read from file. Please try again."
|
|
28
|
+
|
|
29
|
+
number_error = 'Text number "-n" is not an integer.'
|
|
30
|
+
|
|
31
|
+
number_range_error = "The number specified is not in the range of number of texts in file (counting starts from 0)."
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def set_api_key(api_key: str) -> str:
|
|
35
|
+
env_path = CONFIG_ENV_PATH
|
|
36
|
+
os.makedirs(os.path.dirname(env_path), exist_ok=True)
|
|
37
|
+
|
|
38
|
+
lines = []
|
|
39
|
+
if os.path.exists(env_path):
|
|
40
|
+
with open(env_path) as fp:
|
|
41
|
+
lines = fp.read().splitlines()
|
|
42
|
+
|
|
43
|
+
updated = False
|
|
44
|
+
for index, line in enumerate(lines):
|
|
45
|
+
if line.startswith("GEMINI_API_KEY="):
|
|
46
|
+
lines[index] = f"GEMINI_API_KEY={api_key}"
|
|
47
|
+
updated = True
|
|
48
|
+
break
|
|
49
|
+
if not updated:
|
|
50
|
+
lines.append(f"GEMINI_API_KEY={api_key}")
|
|
51
|
+
|
|
52
|
+
with open(env_path, "w") as fp:
|
|
53
|
+
fp.write("\n".join(lines).rstrip("\n") + "\n")
|
|
54
|
+
|
|
55
|
+
return env_path
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def build_arg_parser() -> argparse.ArgumentParser:
|
|
59
|
+
return argparse.ArgumentParser(
|
|
60
|
+
prog="gramcheck",
|
|
61
|
+
description=(
|
|
62
|
+
"Read lines from FILE and check them for grammatical errors using LLMs.\n"
|
|
63
|
+
'Or grammar check TEXT with "-t" option.\n'
|
|
64
|
+
'To check Nth text in FILE, use the "-n" option.\n'
|
|
65
|
+
'To check FILE as a whole, use option "-a".'
|
|
66
|
+
),
|
|
67
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def parse_text_number(value: str) -> int:
|
|
72
|
+
try:
|
|
73
|
+
return int(value)
|
|
74
|
+
except ValueError as exc:
|
|
75
|
+
raise argparse.ArgumentTypeError(number_error) from exc
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def text_grammar_check(text: str):
|
|
79
|
+
response = client.models.generate_content(
|
|
80
|
+
model="gemini-3.1-flash-lite",
|
|
81
|
+
contents=text,
|
|
82
|
+
config=types.GenerateContentConfig(
|
|
83
|
+
system_instruction="You are a tool made for language teaching. Check the text given by the user sentence by sentence for syntactic and semantic errors. Strictly avoid any greetings or filler.",
|
|
84
|
+
seed=SEED,
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
return response.text
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def flatten_list(l: list):
|
|
91
|
+
l_flat = []
|
|
92
|
+
count = 0
|
|
93
|
+
for l_inner in l:
|
|
94
|
+
if isinstance(l_inner, list):
|
|
95
|
+
count += 1
|
|
96
|
+
l_flat.extend(l_inner)
|
|
97
|
+
if count == 0:
|
|
98
|
+
return l
|
|
99
|
+
else:
|
|
100
|
+
return flatten_list(l_flat)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def parse_long_text(text: str):
|
|
104
|
+
word_count = len(text.split())
|
|
105
|
+
if word_count < 40_000:
|
|
106
|
+
return text
|
|
107
|
+
else:
|
|
108
|
+
text_first_half = " ".join(text.split()[: word_count // 2])
|
|
109
|
+
text_second_half = " ".join(text.split()[word_count // 2 :])
|
|
110
|
+
return flatten_list(
|
|
111
|
+
[parse_long_text(text_first_half), parse_long_text(text_second_half)]
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def parse_file_text(file_text: str):
|
|
116
|
+
texts = []
|
|
117
|
+
for text in file_text.splitlines():
|
|
118
|
+
if text:
|
|
119
|
+
texts.append(parse_long_text(text))
|
|
120
|
+
return texts
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def parse_only_file_text(file_text: str):
|
|
124
|
+
texts = []
|
|
125
|
+
for text in file_text.splitlines():
|
|
126
|
+
if text:
|
|
127
|
+
texts.append(text)
|
|
128
|
+
return texts
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def read_from_file(path: str):
|
|
132
|
+
with open(path) as fp:
|
|
133
|
+
file_text = "".join(fp.readlines())
|
|
134
|
+
return file_text
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def main(texts: list[str]):
|
|
138
|
+
responses = []
|
|
139
|
+
for text in texts:
|
|
140
|
+
responses.append(text_grammar_check(text))
|
|
141
|
+
|
|
142
|
+
for i in range(len(texts)):
|
|
143
|
+
print("\n" + "#" * os.get_terminal_size().columns + "\n")
|
|
144
|
+
print(f"{Fore.red}{texts[i]}\n\n{Fore.green}{responses[i]}{Fore.white}")
|
|
145
|
+
print("\n" + "#" * os.get_terminal_size().columns + "\n")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def cli():
|
|
149
|
+
parser = build_arg_parser()
|
|
150
|
+
parser.add_argument("file", nargs="?", help="File to read")
|
|
151
|
+
parser.add_argument("-t", "--text", help="Text to check")
|
|
152
|
+
parser.add_argument(
|
|
153
|
+
"-n",
|
|
154
|
+
"--number",
|
|
155
|
+
type=parse_text_number,
|
|
156
|
+
help="Check Nth text in FILE (0-based)",
|
|
157
|
+
)
|
|
158
|
+
parser.add_argument(
|
|
159
|
+
"-a",
|
|
160
|
+
"--all",
|
|
161
|
+
action="store_true",
|
|
162
|
+
help="Check FILE as a whole",
|
|
163
|
+
)
|
|
164
|
+
parser.add_argument(
|
|
165
|
+
"--set-api-key",
|
|
166
|
+
metavar="KEY",
|
|
167
|
+
help="Store GEMINI_API_KEY in the user config",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
args = parser.parse_args()
|
|
171
|
+
|
|
172
|
+
if args.set_api_key:
|
|
173
|
+
if args.text or args.file or args.number is not None or args.all:
|
|
174
|
+
parser.error("--set-api-key cannot be combined with other options")
|
|
175
|
+
env_path = set_api_key(args.set_api_key)
|
|
176
|
+
print(f"Saved GEMINI_API_KEY to {env_path}")
|
|
177
|
+
sys.exit(0)
|
|
178
|
+
|
|
179
|
+
if API_KEY_PROVIDED:
|
|
180
|
+
if not args.text and not args.file:
|
|
181
|
+
parser.print_help()
|
|
182
|
+
sys.exit(0)
|
|
183
|
+
if args.text and args.file:
|
|
184
|
+
parser.error("FILE and -t/--text cannot be used together")
|
|
185
|
+
if args.text and args.number is not None:
|
|
186
|
+
parser.error("-n/--number cannot be used with -t/--text")
|
|
187
|
+
if args.number is not None and not args.file:
|
|
188
|
+
parser.error("-n/--number requires FILE")
|
|
189
|
+
if args.all and not args.file:
|
|
190
|
+
parser.error("-a/--all requires FILE")
|
|
191
|
+
if args.all and args.text:
|
|
192
|
+
parser.error("-a/--all cannot be used with -t/--text")
|
|
193
|
+
else:
|
|
194
|
+
parser.error(
|
|
195
|
+
"No API key was provided. Please pass a valid API key using --set-api-key"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if args.text:
|
|
199
|
+
texts = parse_file_text(args.text)
|
|
200
|
+
else:
|
|
201
|
+
try:
|
|
202
|
+
file_text = read_from_file(args.file)
|
|
203
|
+
except:
|
|
204
|
+
sys.stderr.write(file_read_error + "\n")
|
|
205
|
+
sys.exit(1)
|
|
206
|
+
|
|
207
|
+
if args.number is not None:
|
|
208
|
+
file_texts = parse_only_file_text(file_text)
|
|
209
|
+
if args.number < 0 or args.number >= len(file_texts):
|
|
210
|
+
sys.stderr.write(number_range_error + "\n")
|
|
211
|
+
sys.exit(3)
|
|
212
|
+
file_text = file_texts[args.number]
|
|
213
|
+
texts = parse_file_text(file_text)
|
|
214
|
+
elif args.all:
|
|
215
|
+
text_or_texts = parse_long_text(file_text)
|
|
216
|
+
texts = (
|
|
217
|
+
text_or_texts if isinstance(text_or_texts, list) else [text_or_texts]
|
|
218
|
+
)
|
|
219
|
+
else:
|
|
220
|
+
texts = parse_file_text(file_text)
|
|
221
|
+
main(texts)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
if __name__ == "__main__":
|
|
225
|
+
cli()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gramcheck"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Use LLMs to check grammar."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICEN[CS]E*"]
|
|
12
|
+
requires-python = ">=3.12"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
]
|
|
17
|
+
dependencies = [
|
|
18
|
+
"colored>=2.3.2",
|
|
19
|
+
"python-dotenv>=1.0.1",
|
|
20
|
+
"google-genai>=2.8.0",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.scripts]
|
|
24
|
+
gramcheck = "gramcheck:cli"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools]
|
|
27
|
+
py-modules = ["gramcheck"]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/degD/gramcheck"
|
|
31
|
+
Issues = "https://github.com/degD/gramcheck/issues"
|