ic-code 1.0.0__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.
@@ -0,0 +1,587 @@
1
+ """
2
+ Documentation organizer module for IC.
3
+
4
+ This module provides functionality to organize and restructure documentation files.
5
+ """
6
+
7
+ import os
8
+ import re
9
+ import shutil
10
+ from pathlib import Path
11
+ from typing import Dict, Any, List, Optional, Tuple
12
+ import logging
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class DocsOrganizer:
18
+ """
19
+ Organizes documentation files into a structured docs directory.
20
+ """
21
+
22
+ def __init__(self):
23
+ """Initialize DocsOrganizer."""
24
+ self.docs_dir = Path("docs")
25
+ self.reorganization_history: List[Dict[str, Any]] = []
26
+ self.link_updates: List[Dict[str, Any]] = []
27
+
28
+ def reorganize_docs(self) -> bool:
29
+ """
30
+ Reorganize all documentation files into docs directory structure.
31
+
32
+ Returns:
33
+ True if reorganization was successful
34
+ """
35
+ try:
36
+ # Create docs directory structure
37
+ self._create_docs_structure()
38
+
39
+ # Find all markdown files
40
+ md_files = self._find_markdown_files()
41
+
42
+ # Categorize and move files
43
+ moved_files = []
44
+ for md_file in md_files:
45
+ new_location = self._categorize_and_move_file(md_file)
46
+ if new_location:
47
+ moved_files.append({
48
+ "original_path": str(md_file),
49
+ "new_path": str(new_location),
50
+ "category": self._get_file_category(md_file),
51
+ "size": md_file.stat().st_size if md_file.exists() else 0
52
+ })
53
+
54
+ # Update links in moved files
55
+ self._update_links_in_files(moved_files)
56
+
57
+ # Record reorganization
58
+ self.reorganization_history.extend(moved_files)
59
+
60
+ logger.info(f"Reorganized {len(moved_files)} documentation files")
61
+ return True
62
+
63
+ except Exception as e:
64
+ logger.error(f"Failed to reorganize documentation: {e}")
65
+ return False
66
+
67
+ def _create_docs_structure(self):
68
+ """Create the documentation directory structure."""
69
+ structure = {
70
+ "docs": {
71
+ "api": {},
72
+ "guides": {
73
+ "installation": {},
74
+ "configuration": {},
75
+ "usage": {}
76
+ },
77
+ "reference": {
78
+ "aws": {},
79
+ "azure": {},
80
+ "gcp": {},
81
+ "oci": {},
82
+ "ssh": {}
83
+ },
84
+ "development": {
85
+ "architecture": {},
86
+ "contributing": {},
87
+ "testing": {}
88
+ },
89
+ "migration": {},
90
+ "security": {},
91
+ "troubleshooting": {}
92
+ }
93
+ }
94
+
95
+ self._create_directory_structure(Path("."), structure)
96
+
97
+ def _create_directory_structure(self, base_path: Path, structure: Dict[str, Any]):
98
+ """Recursively create directory structure."""
99
+ for name, subdirs in structure.items():
100
+ dir_path = base_path / name
101
+ dir_path.mkdir(exist_ok=True)
102
+
103
+ if isinstance(subdirs, dict) and subdirs:
104
+ self._create_directory_structure(dir_path, subdirs)
105
+
106
+ def _find_markdown_files(self) -> List[Path]:
107
+ """Find all markdown files in the project."""
108
+ md_files = []
109
+
110
+ # Search patterns
111
+ search_paths = [
112
+ Path(".").glob("*.md"),
113
+ Path(".").glob("**/*.md")
114
+ ]
115
+
116
+ # Exclude certain directories
117
+ exclude_dirs = {".git", "node_modules", "__pycache__", ".pytest_cache", "venv", "env"}
118
+
119
+ for pattern in search_paths:
120
+ for md_file in pattern:
121
+ # Skip if in excluded directory
122
+ if any(excluded in md_file.parts for excluded in exclude_dirs):
123
+ continue
124
+
125
+ # Skip if already in docs directory
126
+ if "docs" in md_file.parts and md_file.parts.index("docs") == 0:
127
+ continue
128
+
129
+ md_files.append(md_file)
130
+
131
+ # Remove duplicates
132
+ return list(set(md_files))
133
+
134
+ def _categorize_and_move_file(self, md_file: Path) -> Optional[Path]:
135
+ """Categorize a markdown file and move it to appropriate location."""
136
+ if not md_file.exists():
137
+ return None
138
+
139
+ try:
140
+ # Determine category based on filename and content
141
+ category = self._get_file_category(md_file)
142
+
143
+ # Determine target directory
144
+ target_dir = self._get_target_directory(category, md_file)
145
+
146
+ # Generate new filename
147
+ new_filename = self._generate_new_filename(md_file, category)
148
+
149
+ # Move file
150
+ new_path = target_dir / new_filename
151
+
152
+ # Ensure target directory exists
153
+ target_dir.mkdir(parents=True, exist_ok=True)
154
+
155
+ # Handle filename conflicts
156
+ counter = 1
157
+ original_new_path = new_path
158
+ while new_path.exists():
159
+ stem = original_new_path.stem
160
+ suffix = original_new_path.suffix
161
+ new_path = target_dir / f"{stem}_{counter}{suffix}"
162
+ counter += 1
163
+
164
+ shutil.move(str(md_file), str(new_path))
165
+ logger.debug(f"Moved {md_file} to {new_path}")
166
+
167
+ return new_path
168
+
169
+ except Exception as e:
170
+ logger.warning(f"Failed to move {md_file}: {e}")
171
+ return None
172
+
173
+ def _get_file_category(self, md_file: Path) -> str:
174
+ """Determine the category of a markdown file."""
175
+ filename = md_file.name.lower()
176
+
177
+ # Read first few lines to understand content
178
+ try:
179
+ with open(md_file, 'r', encoding='utf-8') as f:
180
+ content = f.read(1000).lower() # First 1000 characters
181
+ except Exception:
182
+ content = ""
183
+
184
+ # Categorize based on filename patterns
185
+ if filename in ['readme.md', 'readme.rst']:
186
+ return "main"
187
+ elif 'install' in filename or 'setup' in filename:
188
+ return "installation"
189
+ elif 'config' in filename or 'configuration' in filename:
190
+ return "configuration"
191
+ elif 'api' in filename or 'reference' in filename:
192
+ return "api"
193
+ elif 'guide' in filename or 'tutorial' in filename or 'howto' in filename:
194
+ return "guides"
195
+ elif 'security' in filename or 'security' in content:
196
+ return "security"
197
+ elif 'migration' in filename or 'migrate' in filename:
198
+ return "migration"
199
+ elif 'troubleshoot' in filename or 'faq' in filename or 'problem' in filename:
200
+ return "troubleshooting"
201
+ elif 'develop' in filename or 'contribute' in filename or 'architecture' in filename:
202
+ return "development"
203
+ elif any(cloud in filename for cloud in ['aws', 'azure', 'gcp', 'oci', 'ssh']):
204
+ # Cloud-specific documentation
205
+ for cloud in ['aws', 'azure', 'gcp', 'oci', 'ssh']:
206
+ if cloud in filename:
207
+ return f"reference_{cloud}"
208
+ return "reference"
209
+ elif 'test' in filename:
210
+ return "development_testing"
211
+ else:
212
+ return "general"
213
+
214
+ def _get_target_directory(self, category: str, md_file: Path) -> Path:
215
+ """Get target directory based on category."""
216
+ base_docs = self.docs_dir
217
+
218
+ category_mapping = {
219
+ "main": base_docs,
220
+ "installation": base_docs / "guides" / "installation",
221
+ "configuration": base_docs / "guides" / "configuration",
222
+ "api": base_docs / "api",
223
+ "guides": base_docs / "guides" / "usage",
224
+ "security": base_docs / "security",
225
+ "migration": base_docs / "migration",
226
+ "troubleshooting": base_docs / "troubleshooting",
227
+ "development": base_docs / "development",
228
+ "development_testing": base_docs / "development" / "testing",
229
+ "reference_aws": base_docs / "reference" / "aws",
230
+ "reference_azure": base_docs / "reference" / "azure",
231
+ "reference_gcp": base_docs / "reference" / "gcp",
232
+ "reference_oci": base_docs / "reference" / "oci",
233
+ "reference_ssh": base_docs / "reference" / "ssh",
234
+ "reference": base_docs / "reference",
235
+ "general": base_docs
236
+ }
237
+
238
+ return category_mapping.get(category, base_docs)
239
+
240
+ def _generate_new_filename(self, md_file: Path, category: str) -> str:
241
+ """Generate appropriate filename for the new location."""
242
+ original_name = md_file.name
243
+
244
+ # Special handling for README files
245
+ if original_name.lower() in ['readme.md', 'readme.rst']:
246
+ if category == "main":
247
+ return "README.md"
248
+ else:
249
+ return "overview.md"
250
+
251
+ # Clean up filename
252
+ clean_name = original_name.lower()
253
+
254
+ # Remove redundant category prefixes
255
+ category_prefixes = ['aws-', 'azure-', 'gcp-', 'oci-', 'ssh-', 'config-', 'guide-', 'api-']
256
+ for prefix in category_prefixes:
257
+ if clean_name.startswith(prefix):
258
+ clean_name = clean_name[len(prefix):]
259
+ break
260
+
261
+ # Ensure .md extension
262
+ if not clean_name.endswith('.md'):
263
+ clean_name = clean_name.rsplit('.', 1)[0] + '.md'
264
+
265
+ return clean_name
266
+
267
+ def _update_links_in_files(self, moved_files: List[Dict[str, Any]]):
268
+ """Update links in moved files to reflect new structure."""
269
+ # Create mapping of old paths to new paths
270
+ path_mapping = {
271
+ Path(f["original_path"]): Path(f["new_path"])
272
+ for f in moved_files
273
+ }
274
+
275
+ # Update links in each moved file
276
+ for file_info in moved_files:
277
+ new_path = Path(file_info["new_path"])
278
+ if new_path.exists():
279
+ self._update_links_in_single_file(new_path, path_mapping)
280
+
281
+ def _update_links_in_single_file(self, file_path: Path, path_mapping: Dict[Path, Path]):
282
+ """Update links in a single file."""
283
+ try:
284
+ with open(file_path, 'r', encoding='utf-8') as f:
285
+ content = f.read()
286
+
287
+ original_content = content
288
+
289
+ # Find markdown links: [text](path)
290
+ link_pattern = r'\\[([^\\]]+)\\]\\(([^\\)]+)\\)'
291
+
292
+ def replace_link(match):
293
+ link_text = match.group(1)
294
+ link_path = match.group(2)
295
+
296
+ # Skip external links
297
+ if link_path.startswith(('http://', 'https://', 'mailto:', '#')):
298
+ return match.group(0)
299
+
300
+ # Convert to Path and resolve
301
+ try:
302
+ old_link_path = Path(link_path)
303
+
304
+ # Check if this path was moved
305
+ for old_path, new_path in path_mapping.items():
306
+ if old_link_path.name == old_path.name:
307
+ # Calculate relative path from current file to new location
308
+ relative_path = os.path.relpath(new_path, file_path.parent)
309
+ self.link_updates.append({
310
+ "file": str(file_path),
311
+ "old_link": link_path,
312
+ "new_link": relative_path
313
+ })
314
+ return f"[{link_text}]({relative_path})"
315
+
316
+ return match.group(0)
317
+
318
+ except Exception:
319
+ return match.group(0)
320
+
321
+ # Replace links
322
+ content = re.sub(link_pattern, replace_link, content)
323
+
324
+ # Write back if changed
325
+ if content != original_content:
326
+ with open(file_path, 'w', encoding='utf-8') as f:
327
+ f.write(content)
328
+ logger.debug(f"Updated links in {file_path}")
329
+
330
+ except Exception as e:
331
+ logger.warning(f"Failed to update links in {file_path}: {e}")
332
+
333
+ def create_docs_index(self) -> bool:
334
+ """
335
+ Create a main index file for the documentation.
336
+
337
+ Returns:
338
+ True if index was created successfully
339
+ """
340
+ try:
341
+ index_content = self._generate_docs_index_content()
342
+
343
+ index_path = self.docs_dir / "README.md"
344
+ with open(index_path, 'w', encoding='utf-8') as f:
345
+ f.write(index_content)
346
+
347
+ logger.info(f"Created documentation index at {index_path}")
348
+ return True
349
+
350
+ except Exception as e:
351
+ logger.error(f"Failed to create documentation index: {e}")
352
+ return False
353
+
354
+ def _generate_docs_index_content(self) -> str:
355
+ """Generate content for the documentation index."""
356
+ content = """# IC Documentation
357
+
358
+ Welcome to the IC (Infrastructure CLI) documentation. This documentation has been reorganized to provide better structure and easier navigation.
359
+
360
+ ## Quick Start
361
+
362
+ - [Installation Guide](guides/installation/)
363
+ - [Configuration Guide](guides/configuration/)
364
+ - [Usage Examples](guides/usage/)
365
+
366
+ ## Documentation Structure
367
+
368
+ ### 📚 Guides
369
+ Step-by-step instructions for common tasks:
370
+ - **[Installation](guides/installation/)** - How to install and set up IC
371
+ - **[Configuration](guides/configuration/)** - Configuration management and setup
372
+ - **[Usage](guides/usage/)** - Common usage patterns and examples
373
+
374
+ ### 📖 Reference
375
+ Detailed reference documentation for each cloud provider:
376
+ - **[AWS](reference/aws/)** - Amazon Web Services integration
377
+ - **[Azure](reference/azure/)** - Microsoft Azure integration
378
+ - **[GCP](reference/gcp/)** - Google Cloud Platform integration
379
+ - **[OCI](reference/oci/)** - Oracle Cloud Infrastructure integration
380
+ - **[SSH](reference/ssh/)** - SSH management and automation
381
+
382
+ ### 🔧 Development
383
+ Information for developers and contributors:
384
+ - **[Architecture](development/architecture/)** - System architecture and design
385
+ - **[Contributing](development/contributing/)** - How to contribute to the project
386
+ - **[Testing](development/testing/)** - Testing guidelines and procedures
387
+
388
+ ### 🔒 Security
389
+ Security-related documentation:
390
+ - **[Security Guide](security/)** - Security best practices and configuration
391
+
392
+ ### 🚀 Migration
393
+ Migration guides and documentation:
394
+ - **[Migration Guide](migration/)** - Migrating from old configurations
395
+
396
+ ### 🛠️ Troubleshooting
397
+ Common issues and solutions:
398
+ - **[Troubleshooting](troubleshooting/)** - Common problems and solutions
399
+
400
+ ### 🔌 API Reference
401
+ API documentation and references:
402
+ - **[API Reference](api/)** - Detailed API documentation
403
+
404
+ ## Recent Changes
405
+
406
+ This documentation has been reorganized as part of the IC configuration system migration. Key improvements include:
407
+
408
+ - **Better Structure**: Documentation is now organized by topic and purpose
409
+ - **Improved Navigation**: Clear hierarchy and cross-references
410
+ - **Updated Links**: All internal links have been updated to reflect the new structure
411
+ - **Consolidated Content**: Related documentation has been grouped together
412
+
413
+ ## Getting Help
414
+
415
+ - Check the [Troubleshooting](troubleshooting/) section for common issues
416
+ - Review the relevant [Reference](reference/) documentation for your cloud provider
417
+ - Look at [Usage Examples](guides/usage/) for practical examples
418
+
419
+ ## Contributing to Documentation
420
+
421
+ See the [Contributing Guide](development/contributing/) for information on how to improve this documentation.
422
+
423
+ ---
424
+
425
+ *This documentation was automatically reorganized on """ + f"{datetime.now().strftime('%Y-%m-%d')}*"
426
+
427
+ return content
428
+
429
+ def create_reorganization_history_document(self) -> bool:
430
+ """
431
+ Create a document recording the documentation reorganization.
432
+
433
+ Returns:
434
+ True if document was created successfully
435
+ """
436
+ try:
437
+ history_content = self._generate_reorganization_history_content()
438
+
439
+ history_path = self.docs_dir / "reorganization_history.md"
440
+ with open(history_path, 'w', encoding='utf-8') as f:
441
+ f.write(history_content)
442
+
443
+ logger.info(f"Created reorganization history at {history_path}")
444
+ return True
445
+
446
+ except Exception as e:
447
+ logger.error(f"Failed to create reorganization history: {e}")
448
+ return False
449
+
450
+ def _generate_reorganization_history_content(self) -> str:
451
+ """Generate reorganization history document content."""
452
+ content = """# Documentation Reorganization History
453
+
454
+ This document records the reorganization of IC documentation files into a structured format.
455
+
456
+ ## Overview
457
+
458
+ The documentation has been reorganized to improve discoverability and maintainability. Files have been categorized and moved to appropriate directories within the `docs/` folder.
459
+
460
+ ## New Structure
461
+
462
+ ```
463
+ docs/
464
+ ├── README.md # Main documentation index
465
+ ├── api/ # API reference documentation
466
+ ├── guides/ # User guides and tutorials
467
+ │ ├── installation/ # Installation guides
468
+ │ ├── configuration/ # Configuration guides
469
+ │ └── usage/ # Usage examples
470
+ ├── reference/ # Reference documentation
471
+ │ ├── aws/ # AWS-specific documentation
472
+ │ ├── azure/ # Azure-specific documentation
473
+ │ ├── gcp/ # GCP-specific documentation
474
+ │ ├── oci/ # OCI-specific documentation
475
+ │ └── ssh/ # SSH-specific documentation
476
+ ├── development/ # Development documentation
477
+ │ ├── architecture/ # Architecture documentation
478
+ │ ├── contributing/ # Contributing guidelines
479
+ │ └── testing/ # Testing documentation
480
+ ├── migration/ # Migration guides
481
+ ├── security/ # Security documentation
482
+ └── troubleshooting/ # Troubleshooting guides
483
+ ```
484
+
485
+ ## File Movements
486
+
487
+ """
488
+
489
+ if self.reorganization_history:
490
+ # Group by category
491
+ files_by_category = {}
492
+ for file_info in self.reorganization_history:
493
+ category = file_info['category']
494
+ if category not in files_by_category:
495
+ files_by_category[category] = []
496
+ files_by_category[category].append(file_info)
497
+
498
+ for category, files in files_by_category.items():
499
+ content += f"### {category.replace('_', ' ').title()}\n\n"
500
+
501
+ content += "| Original Location | New Location | Size |\n"
502
+ content += "|-------------------|--------------|------|\n"
503
+
504
+ for file_info in files:
505
+ size = self._format_size(file_info['size'])
506
+ content += f"| `{file_info['original_path']}` | `{file_info['new_path']}` | {size} |\n"
507
+
508
+ content += "\n"
509
+ else:
510
+ content += "No file movements recorded.\n\n"
511
+
512
+ # Link updates section
513
+ if self.link_updates:
514
+ content += "## Link Updates\n\n"
515
+ content += "The following internal links were updated to reflect the new structure:\n\n"
516
+
517
+ content += "| File | Old Link | New Link |\n"
518
+ content += "|------|----------|----------|\n"
519
+
520
+ for link_update in self.link_updates:
521
+ content += f"| `{link_update['file']}` | `{link_update['old_link']}` | `{link_update['new_link']}` |\n"
522
+
523
+ content += "\n"
524
+
525
+ content += """## Benefits of Reorganization
526
+
527
+ 1. **Improved Discoverability**: Related documentation is grouped together
528
+ 2. **Better Navigation**: Clear hierarchy makes it easier to find information
529
+ 3. **Consistent Structure**: Standardized organization across all documentation
530
+ 4. **Easier Maintenance**: Logical grouping makes updates and maintenance simpler
531
+ 5. **Better User Experience**: Users can quickly find the information they need
532
+
533
+ ## Accessing Old Locations
534
+
535
+ If you have bookmarks or references to old documentation locations, use this mapping to find the new locations. All content has been preserved - only the organization has changed.
536
+
537
+ ## Maintenance
538
+
539
+ - New documentation should be placed in the appropriate category directory
540
+ - Update the main README.md index when adding new major sections
541
+ - Keep the structure consistent with the established patterns
542
+ - Update cross-references when moving or renaming files
543
+
544
+ """
545
+
546
+ return content
547
+
548
+ def _format_size(self, size_bytes: int) -> str:
549
+ """Format file size in human-readable format."""
550
+ if size_bytes == 0:
551
+ return "0 B"
552
+
553
+ for unit in ['B', 'KB', 'MB']:
554
+ if size_bytes < 1024:
555
+ return f"{size_bytes:.1f} {unit}"
556
+ size_bytes /= 1024
557
+
558
+ return f"{size_bytes:.1f} GB"
559
+
560
+ def validate_reorganization(self) -> Dict[str, List[str]]:
561
+ """
562
+ Validate the documentation reorganization.
563
+
564
+ Returns:
565
+ Dictionary of validation results
566
+ """
567
+ issues = {
568
+ "missing_files": [],
569
+ "broken_links": [],
570
+ "empty_directories": []
571
+ }
572
+
573
+ # Check if moved files exist
574
+ for file_info in self.reorganization_history:
575
+ new_path = Path(file_info["new_path"])
576
+ if not new_path.exists():
577
+ issues["missing_files"].append(file_info["new_path"])
578
+
579
+ # Check for empty directories
580
+ for root, dirs, files in os.walk(self.docs_dir):
581
+ root_path = Path(root)
582
+ if not files and not dirs:
583
+ issues["empty_directories"].append(str(root_path))
584
+
585
+ # TODO: Add broken link detection
586
+
587
+ return issues