rolfedh-doc-utils 0.1.6__py3-none-any.whl → 0.1.7__py3-none-any.whl
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.
- format_asciidoc_spacing.py +318 -0
- rolfedh_doc_utils-0.1.7.dist-info/METADATA +187 -0
- {rolfedh_doc_utils-0.1.6.dist-info → rolfedh_doc_utils-0.1.7.dist-info}/RECORD +7 -6
- rolfedh_doc_utils-0.1.6.dist-info/METADATA +0 -303
- {rolfedh_doc_utils-0.1.6.dist-info → rolfedh_doc_utils-0.1.7.dist-info}/WHEEL +0 -0
- {rolfedh_doc_utils-0.1.6.dist-info → rolfedh_doc_utils-0.1.7.dist-info}/entry_points.txt +0 -0
- {rolfedh_doc_utils-0.1.6.dist-info → rolfedh_doc_utils-0.1.7.dist-info}/licenses/LICENSE +0 -0
- {rolfedh_doc_utils-0.1.6.dist-info → rolfedh_doc_utils-0.1.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Format AsciiDoc spacing - ensures blank lines after headings and around include directives"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import List, Tuple
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Colors for output
|
|
14
|
+
class Colors:
|
|
15
|
+
RED = '\033[0;31m'
|
|
16
|
+
GREEN = '\033[0;32m'
|
|
17
|
+
YELLOW = '\033[1;33m'
|
|
18
|
+
NC = '\033[0m' # No Color
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def print_colored(message: str, color: str = Colors.NC) -> None:
|
|
22
|
+
"""Print message with color"""
|
|
23
|
+
print(f"{color}{message}{Colors.NC}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def process_file(file_path: Path, dry_run: bool = False, verbose: bool = False) -> bool:
|
|
27
|
+
"""
|
|
28
|
+
Process a single AsciiDoc file to fix spacing issues.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
file_path: Path to the file to process
|
|
32
|
+
dry_run: If True, show what would be changed without modifying
|
|
33
|
+
verbose: If True, show detailed output
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
True if changes were made (or would be made in dry-run), False otherwise
|
|
37
|
+
"""
|
|
38
|
+
if verbose:
|
|
39
|
+
print(f"Processing: {file_path}")
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
43
|
+
lines = f.readlines()
|
|
44
|
+
except (IOError, UnicodeDecodeError) as e:
|
|
45
|
+
print_colored(f"Error reading {file_path}: {e}", Colors.RED)
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
# Remove trailing newlines from lines for processing
|
|
49
|
+
lines = [line.rstrip('\n\r') for line in lines]
|
|
50
|
+
|
|
51
|
+
new_lines = []
|
|
52
|
+
changes_made = False
|
|
53
|
+
in_block = False # Track if we're inside a block (admonition, listing, etc.)
|
|
54
|
+
in_conditional = False # Track if we're inside a conditional block
|
|
55
|
+
|
|
56
|
+
for i, current_line in enumerate(lines):
|
|
57
|
+
prev_line = lines[i-1] if i > 0 else ""
|
|
58
|
+
next_line = lines[i+1] if i + 1 < len(lines) else ""
|
|
59
|
+
|
|
60
|
+
# Check for conditional start (ifdef:: or ifndef::)
|
|
61
|
+
if re.match(r'^(ifdef::|ifndef::)', current_line):
|
|
62
|
+
in_conditional = True
|
|
63
|
+
# Add blank line before conditional if needed
|
|
64
|
+
if (prev_line and
|
|
65
|
+
not re.match(r'^\s*$', prev_line) and
|
|
66
|
+
not re.match(r'^(ifdef::|ifndef::|endif::)', prev_line)):
|
|
67
|
+
new_lines.append("")
|
|
68
|
+
changes_made = True
|
|
69
|
+
if verbose:
|
|
70
|
+
print(f" Added blank line before conditional block")
|
|
71
|
+
new_lines.append(current_line)
|
|
72
|
+
|
|
73
|
+
# Check for conditional end (endif::)
|
|
74
|
+
elif re.match(r'^endif::', current_line):
|
|
75
|
+
new_lines.append(current_line)
|
|
76
|
+
in_conditional = False
|
|
77
|
+
# Add blank line after conditional if needed
|
|
78
|
+
if (next_line and
|
|
79
|
+
not re.match(r'^\s*$', next_line) and
|
|
80
|
+
not re.match(r'^(ifdef::|ifndef::|endif::)', next_line)):
|
|
81
|
+
new_lines.append("")
|
|
82
|
+
changes_made = True
|
|
83
|
+
if verbose:
|
|
84
|
+
print(f" Added blank line after conditional block")
|
|
85
|
+
|
|
86
|
+
# Check for block delimiters (====, ----, ...., ____)
|
|
87
|
+
# These are used for admonitions, listing blocks, literal blocks, etc.
|
|
88
|
+
elif re.match(r'^(====+|----+|\.\.\.\.+|____+)$', current_line):
|
|
89
|
+
in_block = not in_block # Toggle block state
|
|
90
|
+
new_lines.append(current_line)
|
|
91
|
+
# Check if current line is a heading (but not if we're in a block)
|
|
92
|
+
elif not in_block and re.match(r'^=+\s+', current_line):
|
|
93
|
+
new_lines.append(current_line)
|
|
94
|
+
|
|
95
|
+
# Check if next line is not empty and not another heading
|
|
96
|
+
if (next_line and
|
|
97
|
+
not re.match(r'^=+\s+', next_line) and
|
|
98
|
+
not re.match(r'^\s*$', next_line)):
|
|
99
|
+
new_lines.append("")
|
|
100
|
+
changes_made = True
|
|
101
|
+
if verbose:
|
|
102
|
+
truncated = current_line[:50] + "..." if len(current_line) > 50 else current_line
|
|
103
|
+
print(f" Added blank line after heading: {truncated}")
|
|
104
|
+
|
|
105
|
+
# Check if current line is a comment (AsciiDoc comments start with //)
|
|
106
|
+
elif re.match(r'^//', current_line):
|
|
107
|
+
# Skip special handling if we're inside a conditional block
|
|
108
|
+
if in_conditional:
|
|
109
|
+
new_lines.append(current_line)
|
|
110
|
+
else:
|
|
111
|
+
# Check if next line is an include directive
|
|
112
|
+
if next_line and re.match(r'^include::', next_line):
|
|
113
|
+
# This comment belongs to the include, add blank line before comment if needed
|
|
114
|
+
if (prev_line and
|
|
115
|
+
not re.match(r'^\s*$', prev_line) and
|
|
116
|
+
not re.match(r'^//', prev_line) and
|
|
117
|
+
not re.match(r'^:', prev_line)): # Don't add if previous is attribute
|
|
118
|
+
new_lines.append("")
|
|
119
|
+
changes_made = True
|
|
120
|
+
if verbose:
|
|
121
|
+
print(f" Added blank line before comment above include")
|
|
122
|
+
new_lines.append(current_line)
|
|
123
|
+
|
|
124
|
+
# Check if current line is an attribute (starts with :)
|
|
125
|
+
elif re.match(r'^:', current_line):
|
|
126
|
+
# Skip special handling if we're inside a conditional block
|
|
127
|
+
if in_conditional:
|
|
128
|
+
new_lines.append(current_line)
|
|
129
|
+
else:
|
|
130
|
+
# Check if next line is an include directive
|
|
131
|
+
if next_line and re.match(r'^include::', next_line):
|
|
132
|
+
# This attribute belongs to the include, add blank line before attribute if needed
|
|
133
|
+
if (prev_line and
|
|
134
|
+
not re.match(r'^\s*$', prev_line) and
|
|
135
|
+
not re.match(r'^//', prev_line) and
|
|
136
|
+
not re.match(r'^:', prev_line)): # Don't add if previous is comment or attribute
|
|
137
|
+
new_lines.append("")
|
|
138
|
+
changes_made = True
|
|
139
|
+
if verbose:
|
|
140
|
+
print(f" Added blank line before attribute above include")
|
|
141
|
+
new_lines.append(current_line)
|
|
142
|
+
|
|
143
|
+
# Check if current line is an include directive
|
|
144
|
+
elif re.match(r'^include::', current_line):
|
|
145
|
+
# Skip special handling if we're inside a conditional block
|
|
146
|
+
if in_conditional:
|
|
147
|
+
new_lines.append(current_line)
|
|
148
|
+
else:
|
|
149
|
+
# Check if this is an attribute include (contains "attribute" in the path)
|
|
150
|
+
is_attribute_include = 'attribute' in current_line.lower()
|
|
151
|
+
|
|
152
|
+
# Check if this appears near the top of the file (within first 10 lines after H1)
|
|
153
|
+
# Find the H1 heading position
|
|
154
|
+
h1_position = -1
|
|
155
|
+
for j in range(min(i, 10)): # Look back up to 10 lines or to current position
|
|
156
|
+
if re.match(r'^=\s+', lines[j]): # H1 heading starts with single =
|
|
157
|
+
h1_position = j
|
|
158
|
+
break
|
|
159
|
+
|
|
160
|
+
# If this is an attribute include near the H1 heading, don't add surrounding blank lines
|
|
161
|
+
is_near_h1 = h1_position >= 0 and (i - h1_position) <= 2
|
|
162
|
+
|
|
163
|
+
# Check if previous line is a comment or attribute (which belongs to this include)
|
|
164
|
+
has_comment_above = prev_line and re.match(r'^//', prev_line)
|
|
165
|
+
has_attribute_above = prev_line and re.match(r'^:', prev_line)
|
|
166
|
+
|
|
167
|
+
# If it's an attribute include near H1, only the heading's blank line is needed
|
|
168
|
+
if not (is_attribute_include and is_near_h1):
|
|
169
|
+
# Don't add blank line if there's a comment or attribute above (it was handled by the comment/attribute logic)
|
|
170
|
+
if not has_comment_above and not has_attribute_above:
|
|
171
|
+
# Add blank line before include if previous line is not empty and not an include
|
|
172
|
+
if (prev_line and
|
|
173
|
+
not re.match(r'^\s*$', prev_line) and
|
|
174
|
+
not re.match(r'^include::', prev_line)):
|
|
175
|
+
new_lines.append("")
|
|
176
|
+
changes_made = True
|
|
177
|
+
if verbose:
|
|
178
|
+
truncated = current_line[:50] + "..." if len(current_line) > 50 else current_line
|
|
179
|
+
print(f" Added blank line before include: {truncated}")
|
|
180
|
+
|
|
181
|
+
new_lines.append(current_line)
|
|
182
|
+
|
|
183
|
+
# If it's an attribute include near H1, don't add blank line after
|
|
184
|
+
if not (is_attribute_include and is_near_h1):
|
|
185
|
+
# Add blank line after include if next line exists and is not empty and not an include
|
|
186
|
+
if (next_line and
|
|
187
|
+
not re.match(r'^\s*$', next_line) and
|
|
188
|
+
not re.match(r'^include::', next_line)):
|
|
189
|
+
new_lines.append("")
|
|
190
|
+
changes_made = True
|
|
191
|
+
if verbose:
|
|
192
|
+
truncated = current_line[:50] + "..." if len(current_line) > 50 else current_line
|
|
193
|
+
print(f" Added blank line after include: {truncated}")
|
|
194
|
+
|
|
195
|
+
else:
|
|
196
|
+
new_lines.append(current_line)
|
|
197
|
+
|
|
198
|
+
# Apply changes if any were made
|
|
199
|
+
if changes_made:
|
|
200
|
+
# Clean up any consecutive blank lines we may have added
|
|
201
|
+
cleaned_lines = []
|
|
202
|
+
for i, line in enumerate(new_lines):
|
|
203
|
+
# Check if this is a blank line we're about to add
|
|
204
|
+
if line == "":
|
|
205
|
+
# Check if the previous line is also a blank line
|
|
206
|
+
if i > 0 and cleaned_lines and cleaned_lines[-1] == "":
|
|
207
|
+
# Skip this blank line as we already have one
|
|
208
|
+
continue
|
|
209
|
+
cleaned_lines.append(line)
|
|
210
|
+
|
|
211
|
+
if dry_run:
|
|
212
|
+
print_colored(f"Would modify: {file_path}", Colors.YELLOW)
|
|
213
|
+
else:
|
|
214
|
+
try:
|
|
215
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
216
|
+
for line in cleaned_lines:
|
|
217
|
+
f.write(line + '\n')
|
|
218
|
+
print_colored(f"Modified: {file_path}", Colors.GREEN)
|
|
219
|
+
except IOError as e:
|
|
220
|
+
print_colored(f"Error writing {file_path}: {e}", Colors.RED)
|
|
221
|
+
return False
|
|
222
|
+
else:
|
|
223
|
+
if verbose:
|
|
224
|
+
print(" No changes needed")
|
|
225
|
+
|
|
226
|
+
return changes_made
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def find_adoc_files(path: Path) -> List[Path]:
|
|
230
|
+
"""Find all .adoc files in the given path"""
|
|
231
|
+
adoc_files = []
|
|
232
|
+
|
|
233
|
+
if path.is_file():
|
|
234
|
+
if path.suffix == '.adoc':
|
|
235
|
+
adoc_files.append(path)
|
|
236
|
+
else:
|
|
237
|
+
print_colored(f"Warning: {path} is not an AsciiDoc file (.adoc)", Colors.YELLOW)
|
|
238
|
+
elif path.is_dir():
|
|
239
|
+
adoc_files = list(path.rglob('*.adoc'))
|
|
240
|
+
|
|
241
|
+
return adoc_files
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def main():
|
|
245
|
+
"""Main entry point"""
|
|
246
|
+
parser = argparse.ArgumentParser(
|
|
247
|
+
description="Format AsciiDoc files to ensure proper spacing",
|
|
248
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
249
|
+
epilog="""
|
|
250
|
+
Format AsciiDoc files to ensure proper spacing:
|
|
251
|
+
- Blank line after headings (=, ==, ===, etc.)
|
|
252
|
+
- Blank lines around include:: directives
|
|
253
|
+
|
|
254
|
+
Examples:
|
|
255
|
+
%(prog)s # Process all .adoc files in current directory
|
|
256
|
+
%(prog)s modules/ # Process all .adoc files in modules/
|
|
257
|
+
%(prog)s assemblies/my-guide.adoc # Process single file
|
|
258
|
+
%(prog)s --dry-run modules/ # Preview changes without modifying
|
|
259
|
+
"""
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
parser.add_argument(
|
|
263
|
+
'path',
|
|
264
|
+
nargs='?',
|
|
265
|
+
default='.',
|
|
266
|
+
help='File or directory to process (default: current directory)'
|
|
267
|
+
)
|
|
268
|
+
parser.add_argument(
|
|
269
|
+
'-n', '--dry-run',
|
|
270
|
+
action='store_true',
|
|
271
|
+
help='Show what would be changed without modifying files'
|
|
272
|
+
)
|
|
273
|
+
parser.add_argument(
|
|
274
|
+
'-v', '--verbose',
|
|
275
|
+
action='store_true',
|
|
276
|
+
help='Show detailed output'
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
args = parser.parse_args()
|
|
280
|
+
|
|
281
|
+
# Convert path to Path object
|
|
282
|
+
target_path = Path(args.path)
|
|
283
|
+
|
|
284
|
+
# Check if path exists
|
|
285
|
+
if not target_path.exists():
|
|
286
|
+
print_colored(f"Error: Path does not exist: {target_path}", Colors.RED)
|
|
287
|
+
sys.exit(1)
|
|
288
|
+
|
|
289
|
+
# Display dry-run mode message
|
|
290
|
+
if args.dry_run:
|
|
291
|
+
print_colored("DRY RUN MODE - No files will be modified", Colors.YELLOW)
|
|
292
|
+
|
|
293
|
+
# Find all AsciiDoc files
|
|
294
|
+
adoc_files = find_adoc_files(target_path)
|
|
295
|
+
|
|
296
|
+
if not adoc_files:
|
|
297
|
+
print(f"Processed 0 AsciiDoc file(s)")
|
|
298
|
+
print("AsciiDoc spacing formatting complete!")
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
# Process each file
|
|
302
|
+
files_processed = 0
|
|
303
|
+
for file_path in adoc_files:
|
|
304
|
+
try:
|
|
305
|
+
process_file(file_path, args.dry_run, args.verbose)
|
|
306
|
+
files_processed += 1
|
|
307
|
+
except KeyboardInterrupt:
|
|
308
|
+
print_colored("\nOperation cancelled by user", Colors.YELLOW)
|
|
309
|
+
sys.exit(1)
|
|
310
|
+
except Exception as e:
|
|
311
|
+
print_colored(f"Unexpected error processing {file_path}: {e}", Colors.RED)
|
|
312
|
+
|
|
313
|
+
print(f"Processed {files_processed} AsciiDoc file(s)")
|
|
314
|
+
print("AsciiDoc spacing formatting complete!")
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
if __name__ == "__main__":
|
|
318
|
+
main()
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rolfedh-doc-utils
|
|
3
|
+
Version: 0.1.7
|
|
4
|
+
Summary: CLI tools for AsciiDoc documentation projects
|
|
5
|
+
Author: Rolfe Dlugy-Hegwer
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 Rolfe Dlugy-Hegwer
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Requires-Python: >=3.8
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Requires-Dist: PyYAML>=6.0
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# doc-utils
|
|
35
|
+
|
|
36
|
+
[](https://pypi.org/project/rolfedh-doc-utils/)
|
|
37
|
+
[](https://www.python.org/downloads/)
|
|
38
|
+
[](https://github.com/rolfedh/doc-utils/blob/main/LICENSE)
|
|
39
|
+
[](https://rolfedh.github.io/doc-utils/)
|
|
40
|
+
|
|
41
|
+
Python CLI tools for maintaining clean, consistent AsciiDoc documentation repositories.
|
|
42
|
+
|
|
43
|
+
📚 **[View Full Documentation](https://rolfedh.github.io/doc-utils/)** | 📦 **[PyPI Package](https://pypi.org/project/rolfedh-doc-utils/)** | 🐙 **[GitHub](https://github.com/rolfedh/doc-utils)**
|
|
44
|
+
|
|
45
|
+
## ⚠️ Safety First
|
|
46
|
+
|
|
47
|
+
These tools can modify or delete files. **Always:**
|
|
48
|
+
- Work in a git branch (never main/master)
|
|
49
|
+
- Review changes with `git diff`
|
|
50
|
+
- Test documentation builds after changes
|
|
51
|
+
|
|
52
|
+
## 🚀 Quick Start
|
|
53
|
+
|
|
54
|
+
### Install with pipx (Recommended)
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pipx install rolfedh-doc-utils
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Alternative Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# With pip user flag
|
|
64
|
+
pip install --user rolfedh-doc-utils
|
|
65
|
+
|
|
66
|
+
# For development
|
|
67
|
+
git clone https://github.com/rolfedh/doc-utils.git
|
|
68
|
+
cd doc-utils
|
|
69
|
+
pip install -e .
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 🛠️ Available Tools
|
|
73
|
+
|
|
74
|
+
| Tool | Description | Usage |
|
|
75
|
+
|------|-------------|-------|
|
|
76
|
+
| **`format-asciidoc-spacing`** | Standardizes spacing after headings and around includes | `format-asciidoc-spacing --dry-run .` |
|
|
77
|
+
| **`check-scannability`** | Analyzes readability (sentence/paragraph length) | `check-scannability --max-words 25` |
|
|
78
|
+
| **`archive-unused-files`** | Finds and archives unreferenced .adoc files | `archive-unused-files --archive` |
|
|
79
|
+
| **`archive-unused-images`** | Finds and archives unreferenced images | `archive-unused-images --archive` |
|
|
80
|
+
| **`find-unused-attributes`** | Identifies unused attribute definitions | `find-unused-attributes attributes.adoc` |
|
|
81
|
+
|
|
82
|
+
## 📖 Documentation
|
|
83
|
+
|
|
84
|
+
Comprehensive documentation is available at **[rolfedh.github.io/doc-utils](https://rolfedh.github.io/doc-utils/)**
|
|
85
|
+
|
|
86
|
+
- [Getting Started Guide](https://rolfedh.github.io/doc-utils/getting-started)
|
|
87
|
+
- [Tools Reference](https://rolfedh.github.io/doc-utils/tools/)
|
|
88
|
+
- [Best Practices](https://rolfedh.github.io/doc-utils/best-practices)
|
|
89
|
+
- [Contributing](https://rolfedh.github.io/doc-utils/contributing)
|
|
90
|
+
|
|
91
|
+
## 💡 Common Workflows
|
|
92
|
+
|
|
93
|
+
### Clean Up Documentation
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# 1. Create a branch
|
|
97
|
+
git checkout -b doc-cleanup
|
|
98
|
+
|
|
99
|
+
# 2. Preview what would change
|
|
100
|
+
format-asciidoc-spacing --dry-run .
|
|
101
|
+
archive-unused-files
|
|
102
|
+
archive-unused-images
|
|
103
|
+
|
|
104
|
+
# 3. Apply changes
|
|
105
|
+
format-asciidoc-spacing .
|
|
106
|
+
archive-unused-files --archive
|
|
107
|
+
archive-unused-images --archive
|
|
108
|
+
|
|
109
|
+
# 4. Review and commit
|
|
110
|
+
git diff
|
|
111
|
+
git add -A && git commit -m "Clean up documentation"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Check Documentation Quality
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Check readability
|
|
118
|
+
check-scannability --max-words 30 --max-sentences 5
|
|
119
|
+
|
|
120
|
+
# Find unused attributes
|
|
121
|
+
find-unused-attributes attributes.adoc
|
|
122
|
+
|
|
123
|
+
# Save results
|
|
124
|
+
find-unused-attributes attributes.adoc --output unused.txt
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 🔧 Exclusions
|
|
128
|
+
|
|
129
|
+
All tools support excluding files and directories:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Exclude specific directories
|
|
133
|
+
archive-unused-files --exclude-dir ./temp --exclude-dir ./drafts
|
|
134
|
+
|
|
135
|
+
# Exclude specific files
|
|
136
|
+
check-scannability --exclude-file ./README.adoc
|
|
137
|
+
|
|
138
|
+
# Use exclusion list file
|
|
139
|
+
echo "./deprecated/" > .docutils-ignore
|
|
140
|
+
archive-unused-images --exclude-list .docutils-ignore
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 🧪 Development
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Install dev dependencies
|
|
147
|
+
pip install -r requirements-dev.txt
|
|
148
|
+
|
|
149
|
+
# Run tests
|
|
150
|
+
python -m pytest tests/ -v
|
|
151
|
+
|
|
152
|
+
# Run specific test
|
|
153
|
+
python -m pytest tests/test_file_utils.py
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## 🤝 Contributing
|
|
157
|
+
|
|
158
|
+
We welcome contributions! See our [Contributing Guide](https://rolfedh.github.io/doc-utils/contributing) for details.
|
|
159
|
+
|
|
160
|
+
Before submitting PRs:
|
|
161
|
+
- ✅ All tests pass
|
|
162
|
+
- ✅ Code follows PEP 8
|
|
163
|
+
- ✅ Documentation updated
|
|
164
|
+
- ✅ Changelog entry added
|
|
165
|
+
|
|
166
|
+
## 📊 Project Status
|
|
167
|
+
|
|
168
|
+
- **Latest Version**: 0.1.6
|
|
169
|
+
- **Python Support**: 3.8+
|
|
170
|
+
- **Test Coverage**: 66+ tests (100% passing)
|
|
171
|
+
- **Dependencies**: Minimal (PyYAML for OpenShift-docs support)
|
|
172
|
+
|
|
173
|
+
## 🔗 Resources
|
|
174
|
+
|
|
175
|
+
- [Documentation](https://rolfedh.github.io/doc-utils/)
|
|
176
|
+
- [PyPI Package](https://pypi.org/project/rolfedh-doc-utils/)
|
|
177
|
+
- [Issue Tracker](https://github.com/rolfedh/doc-utils/issues)
|
|
178
|
+
- [Changelog](https://github.com/rolfedh/doc-utils/blob/main/CHANGELOG.md)
|
|
179
|
+
- [License](https://github.com/rolfedh/doc-utils/blob/main/LICENSE)
|
|
180
|
+
|
|
181
|
+
## 📝 License
|
|
182
|
+
|
|
183
|
+
This project is licensed under the terms specified in the [LICENSE](https://github.com/rolfedh/doc-utils/blob/main/LICENSE) file.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
Created by [Rolfe Dlugy-Hegwer](https://github.com/rolfedh) for technical writers everywhere.
|
|
@@ -2,6 +2,7 @@ archive_unused_files.py,sha256=KMC5a1WL3rZ5owoVnncvfpT1YeMKbVXq9giHvadDgbM,1936
|
|
|
2
2
|
archive_unused_images.py,sha256=PG2o3haovYckgfhoPhl6KRG_a9czyZuqlLkzkupKTCY,1526
|
|
3
3
|
check_scannability.py,sha256=gcM-vFXKHGP_yFBz7-V5xbXWhIMmtMzBYIGwP9CFbzI,5140
|
|
4
4
|
find_unused_attributes.py,sha256=fk-K32eoCVHxoj7RiBNgSmX1arBLuwYfdSAOMc-wIx0,1677
|
|
5
|
+
format_asciidoc_spacing.py,sha256=Jy_V8c03KGRPJxu4uPdDuMsFg-Q-4AGD9BdkGnApUu8,13241
|
|
5
6
|
doc_utils/__init__.py,sha256=qqZR3lohzkP63soymrEZPBGzzk6-nFzi4_tSffjmu_0,74
|
|
6
7
|
doc_utils/file_utils.py,sha256=fpTh3xx759sF8sNocdn_arsP3KAv8XA6cTQTAVIZiZg,4247
|
|
7
8
|
doc_utils/scannability.py,sha256=XwlmHqDs69p_V36X7DLjPTy0DUoLszSGqYjJ9wE-3hg,982
|
|
@@ -9,9 +10,9 @@ doc_utils/topic_map_parser.py,sha256=tKcIO1m9r2K6dvPRGue58zqMr0O2zKU1gnZMzEE3U6o
|
|
|
9
10
|
doc_utils/unused_adoc.py,sha256=2cbqcYr1os2EhETUU928BlPRlsZVSdI00qaMhqjSIqQ,5263
|
|
10
11
|
doc_utils/unused_attributes.py,sha256=HBgmHelqearfWl3TTC2bZGiJytjLADIgiGQUNKqXXPg,1847
|
|
11
12
|
doc_utils/unused_images.py,sha256=nqn36Bbrmon2KlGlcaruNjJJvTQ8_9H0WU9GvCW7rW8,1456
|
|
12
|
-
rolfedh_doc_utils-0.1.
|
|
13
|
-
rolfedh_doc_utils-0.1.
|
|
14
|
-
rolfedh_doc_utils-0.1.
|
|
15
|
-
rolfedh_doc_utils-0.1.
|
|
16
|
-
rolfedh_doc_utils-0.1.
|
|
17
|
-
rolfedh_doc_utils-0.1.
|
|
13
|
+
rolfedh_doc_utils-0.1.7.dist-info/licenses/LICENSE,sha256=vLxtwMVOJA_hEy8b77niTkdmQI9kNJskXHq0dBS36e0,1075
|
|
14
|
+
rolfedh_doc_utils-0.1.7.dist-info/METADATA,sha256=KZ9pa1ZFWS_rj4iVRW9TTbL6RPWvVFfL2twZseuk9Ko,6200
|
|
15
|
+
rolfedh_doc_utils-0.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
16
|
+
rolfedh_doc_utils-0.1.7.dist-info/entry_points.txt,sha256=dA-u4qlxKcewUAOFgaeW1NUIrkhsVknuwFuGTn1-84w,271
|
|
17
|
+
rolfedh_doc_utils-0.1.7.dist-info/top_level.txt,sha256=WrYqTa4wbjRPMUl2hgJBfP734XsXLkhbnzUublx8S0s,119
|
|
18
|
+
rolfedh_doc_utils-0.1.7.dist-info/RECORD,,
|
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: rolfedh-doc-utils
|
|
3
|
-
Version: 0.1.6
|
|
4
|
-
Summary: CLI tools for AsciiDoc documentation projects
|
|
5
|
-
Author: Rolfe Dlugy-Hegwer
|
|
6
|
-
License: MIT License
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2025 Rolfe Dlugy-Hegwer
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
|
18
|
-
copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
-
SOFTWARE.
|
|
27
|
-
|
|
28
|
-
Requires-Python: >=3.8
|
|
29
|
-
Description-Content-Type: text/markdown
|
|
30
|
-
License-File: LICENSE
|
|
31
|
-
Requires-Dist: PyYAML>=6.0
|
|
32
|
-
Dynamic: license-file
|
|
33
|
-
|
|
34
|
-
# doc-utils
|
|
35
|
-
|
|
36
|
-
A set of Python utilities and CLI tools to help technical writers maintain AsciiDoc documentation repositories.
|
|
37
|
-
|
|
38
|
-
> ⚠️ **IMPORTANT: Safety First**
|
|
39
|
-
>
|
|
40
|
-
> These tools can modify or delete files in your documentation repository. Always:
|
|
41
|
-
> - **Work in a git branch** - Never run these tools on the main/master branch
|
|
42
|
-
> - **Review all changes carefully** - Use `git diff` or a pull request to verify modifications
|
|
43
|
-
> - **Check your preview builds** - Ensure no documentation errors were introduced
|
|
44
|
-
|
|
45
|
-
## Resources
|
|
46
|
-
|
|
47
|
-
- [PyPI: rolfedh-doc-utils](https://pypi.org/project/rolfedh-doc-utils/)
|
|
48
|
-
- [GitHub repository](https://github.com/rolfedh/doc-utils)
|
|
49
|
-
|
|
50
|
-
## Installation
|
|
51
|
-
|
|
52
|
-
### From PyPI
|
|
53
|
-
|
|
54
|
-
On modern Linux distributions, you may encounter an "externally-managed-environment" error. Use one of these methods:
|
|
55
|
-
|
|
56
|
-
**Option 1: pipx (Recommended for CLI tools)**
|
|
57
|
-
```sh
|
|
58
|
-
pipx install rolfedh-doc-utils
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
**Option 2: pip with --user flag**
|
|
62
|
-
```sh
|
|
63
|
-
pip install --user rolfedh-doc-utils
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
**Option 3: Traditional pip (may require virtual environment)**
|
|
67
|
-
```sh
|
|
68
|
-
pip install rolfedh-doc-utils
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Upgrading
|
|
72
|
-
|
|
73
|
-
To upgrade to the latest version:
|
|
74
|
-
|
|
75
|
-
```sh
|
|
76
|
-
# If installed with pipx:
|
|
77
|
-
pipx upgrade rolfedh-doc-utils
|
|
78
|
-
|
|
79
|
-
# If installed with pip:
|
|
80
|
-
pip install --upgrade rolfedh-doc-utils # or --user flag if needed
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### For Development
|
|
84
|
-
|
|
85
|
-
If you're developing or testing locally, install the package in editable mode:
|
|
86
|
-
|
|
87
|
-
```sh
|
|
88
|
-
# Clone the repository
|
|
89
|
-
git clone https://github.com/rolfedh/doc-utils.git
|
|
90
|
-
cd doc-utils
|
|
91
|
-
|
|
92
|
-
# Install in editable mode
|
|
93
|
-
pip install -e .
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
The following CLI tools are installed:
|
|
97
|
-
|
|
98
|
-
* `check-scannability`
|
|
99
|
-
* `archive-unused-files`
|
|
100
|
-
* `archive-unused-images`
|
|
101
|
-
* `find-unused-attributes`
|
|
102
|
-
* `format-asciidoc-spacing`
|
|
103
|
-
|
|
104
|
-
These tools can be run from any directory.
|
|
105
|
-
|
|
106
|
-
### Add to PATH (if needed)
|
|
107
|
-
|
|
108
|
-
By default, CLI tools install to:
|
|
109
|
-
|
|
110
|
-
```sh
|
|
111
|
-
$HOME/.local/bin
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
If this directory isn’t in your `PATH`, commands may not run. Append it to your shell configuration:
|
|
115
|
-
|
|
116
|
-
```sh
|
|
117
|
-
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc # or ~/.zshrc
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
Then reload your shell:
|
|
121
|
-
|
|
122
|
-
```sh
|
|
123
|
-
source ~/.bashrc # or ~/.zshrc
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
## CLI Tools Overview
|
|
127
|
-
|
|
128
|
-
### `check-scannability`
|
|
129
|
-
|
|
130
|
-
Scans `.adoc` files in the current directory to report:
|
|
131
|
-
|
|
132
|
-
* Sentences that exceed a length limit (default: 22 words)
|
|
133
|
-
* Paragraphs with too many sentences (default: 3 sentences)
|
|
134
|
-
* Supports exclusion of files and directories
|
|
135
|
-
|
|
136
|
-
➡️ See [Scannability Checker for AsciiDoc Files](https://github.com/rolfedh/doc-utils/blob/main/check_scannability.md) for details.
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
### `archive-unused-files`
|
|
141
|
-
|
|
142
|
-
Scans the `./modules` and `./assemblies` directories for `.adoc` files that are not referenced. Optionally archives and deletes them.
|
|
143
|
-
|
|
144
|
-
Works with both:
|
|
145
|
-
- **OpenShift-docs style** repositories (uses `_topic_maps/*.yml` files)
|
|
146
|
-
- **Traditional AsciiDoc** repositories (uses `master.adoc` files)
|
|
147
|
-
|
|
148
|
-
➡️ See [Archive Unused AsciiDoc Files](https://github.com/rolfedh/doc-utils/blob/main/archive_unused_files.md).
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
### `archive-unused-images`
|
|
153
|
-
|
|
154
|
-
Finds unused image files (e.g., `.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`) in the current directory and optionally archives and deletes them.
|
|
155
|
-
|
|
156
|
-
➡️ See [Archive Unused Images](https://github.com/rolfedh/doc-utils/blob/main/archive_unused_images.md).
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
### `find-unused-attributes`
|
|
161
|
-
|
|
162
|
-
Scans an attributes file (e.g., `attributes.adoc`) for unused attribute definitions across all `.adoc` files in the current directory.
|
|
163
|
-
|
|
164
|
-
➡️ See [Find Unused AsciiDoc Attributes](https://github.com/rolfedh/doc-utils/blob/main/find_unused_attributes.md).
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
### `format-asciidoc-spacing`
|
|
169
|
-
|
|
170
|
-
Ensures proper spacing in AsciiDoc files by adding blank lines after headings and around `include::` directives.
|
|
171
|
-
|
|
172
|
-
Formatting rules:
|
|
173
|
-
- Adds blank line after headings (`=`, `==`, `===`, etc.)
|
|
174
|
-
- Adds blank lines before and after `include::` directives
|
|
175
|
-
- Preserves existing spacing where appropriate
|
|
176
|
-
|
|
177
|
-
Implemented as a Python script (`format-asciidoc-spacing.py`).
|
|
178
|
-
|
|
179
|
-
➡️ See [AsciiDoc Spacing Formatter](https://github.com/rolfedh/doc-utils/blob/main/format_asciidoc_spacing.md).
|
|
180
|
-
|
|
181
|
-
## Best Practices for Safe Usage
|
|
182
|
-
|
|
183
|
-
### Before Running Any Tool:
|
|
184
|
-
|
|
185
|
-
- **Create a feature branch:**
|
|
186
|
-
```sh
|
|
187
|
-
git checkout -b doc-cleanup-$(date +%Y%m%d)
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
- **Commit any pending changes:**
|
|
191
|
-
```sh
|
|
192
|
-
git add -A && git commit -m "Save work before cleanup"
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
- **Run tools in preview mode first:**
|
|
196
|
-
- For archive tools: Run without `--archive` to see what would be affected
|
|
197
|
-
|
|
198
|
-
- **Review all changes and preview builds**
|
|
199
|
-
|
|
200
|
-
- **Only merge after verification:**
|
|
201
|
-
|
|
202
|
-
## Usage
|
|
203
|
-
|
|
204
|
-
To run the tools after installation:
|
|
205
|
-
|
|
206
|
-
```sh
|
|
207
|
-
check-scannability --help
|
|
208
|
-
archive-unused-files --help
|
|
209
|
-
find-unused-attributes attributes.adoc
|
|
210
|
-
format-asciidoc-spacing --help
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
Or run them directly from source:
|
|
214
|
-
|
|
215
|
-
```sh
|
|
216
|
-
python3 check_scannability.py
|
|
217
|
-
python3 archive_unused_files.py
|
|
218
|
-
python3 find_unused_attributes.py attributes.adoc
|
|
219
|
-
python3 format-asciidoc-spacing.py
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### Directory/File Exclusion
|
|
223
|
-
|
|
224
|
-
All tools support excluding specific directories and files from scanning. You can use these options:
|
|
225
|
-
|
|
226
|
-
1. **`--exclude-dir`** - Exclude specific directories (can be used multiple times):
|
|
227
|
-
```sh
|
|
228
|
-
archive-unused-files --exclude-dir ./modules/temp --exclude-dir ./modules/old
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
2. **`--exclude-file`** - Exclude specific files (can be used multiple times):
|
|
232
|
-
```sh
|
|
233
|
-
check-scannability --exclude-file ./README.adoc --exclude-file ./test.adoc
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
3. **`--exclude-list`** - Point to a text file containing exclusions:
|
|
237
|
-
```sh
|
|
238
|
-
archive-unused-images --exclude-list .docutils-ignore
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
The exclusion file format:
|
|
242
|
-
```
|
|
243
|
-
# Comments are supported
|
|
244
|
-
./modules/deprecated/
|
|
245
|
-
./assemblies/archive/
|
|
246
|
-
./images/temp/
|
|
247
|
-
specific-file.adoc
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
**Note:** When you exclude a parent directory, all its subdirectories are automatically excluded. Symbolic links are never followed during scanning.
|
|
251
|
-
|
|
252
|
-
## Troubleshooting
|
|
253
|
-
|
|
254
|
-
### ModuleNotFoundError
|
|
255
|
-
|
|
256
|
-
If you see an error like `ModuleNotFoundError: No module named 'find_unused_attributes'`, this typically means:
|
|
257
|
-
|
|
258
|
-
1. The package isn't installed. Run:
|
|
259
|
-
```sh
|
|
260
|
-
pipx install rolfedh-doc-utils # or pip install --user rolfedh-doc-utils
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
2. You're trying to run the script directly without installation. Either:
|
|
264
|
-
- Install the package first (see Installation section)
|
|
265
|
-
- Run the script using Python directly:
|
|
266
|
-
```sh
|
|
267
|
-
python3 find_unused_attributes.py attributes.adoc
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
### Command not found
|
|
271
|
-
|
|
272
|
-
If the command isn't found after installation, ensure `$HOME/.local/bin` is in your PATH (see "Add to PATH" section above).
|
|
273
|
-
|
|
274
|
-
## Development
|
|
275
|
-
|
|
276
|
-
### Running Tests
|
|
277
|
-
|
|
278
|
-
The project includes a comprehensive test suite. To run tests:
|
|
279
|
-
|
|
280
|
-
```sh
|
|
281
|
-
# Install development dependencies
|
|
282
|
-
pip install -r requirements-dev.txt
|
|
283
|
-
|
|
284
|
-
# Run all tests
|
|
285
|
-
python -m pytest tests/
|
|
286
|
-
|
|
287
|
-
# Run with verbose output
|
|
288
|
-
python -m pytest tests/ -v
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### Contributing
|
|
292
|
-
|
|
293
|
-
Contributions are welcome! Please ensure:
|
|
294
|
-
- All tests pass before submitting a PR
|
|
295
|
-
- New features include appropriate tests
|
|
296
|
-
- Code follows PEP 8 style guidelines
|
|
297
|
-
- Documentation is updated as needed
|
|
298
|
-
|
|
299
|
-
See [Contributing Guidelines](https://github.com/rolfedh/doc-utils/blob/main/CONTRIBUTING.md) for more details.
|
|
300
|
-
|
|
301
|
-
## License
|
|
302
|
-
|
|
303
|
-
This project is licensed under the terms of the [LICENSE](https://github.com/rolfedh/doc-utils/blob/main/LICENSE).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|