jott-cli 0.2.2__tar.gz → 0.3.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.
- {jott_cli-0.2.2/jott_cli.egg-info → jott_cli-0.3.0}/PKG-INFO +1 -1
- jott_cli-0.3.0/jot/__init__.py +47 -0
- jott_cli-0.3.0/jot/categories/__init__.py +0 -0
- jott_cli-0.3.0/jot/categories/config.py +133 -0
- jott_cli-0.3.0/jot/categories/manager.py +146 -0
- jott_cli-0.3.0/jot/categories/templates.py +121 -0
- jott_cli-0.3.0/jot/commands/__init__.py +5 -0
- jott_cli-0.3.0/jot/commands/handler.py +3398 -0
- jott_cli-0.3.0/jot/core/__init__.py +0 -0
- jott_cli-0.3.0/jot/core/constants.py +35 -0
- jott_cli-0.3.0/jot/core/id_manager.py +139 -0
- jott_cli-0.3.0/jot/core/task_manager.py +1155 -0
- jott_cli-0.3.0/jot/integrations/__init__.py +0 -0
- jott_cli-0.3.0/jot/integrations/gcal/__init__.py +0 -0
- jott_cli-0.3.0/jot/integrations/gcal/account_manager.py +66 -0
- jott_cli-0.3.0/jot/integrations/gcal/auth.py +151 -0
- jott_cli-0.3.0/jot/integrations/gcal/events.py +115 -0
- jott_cli-0.3.0/jot/integrations/keywords/__init__.py +0 -0
- jott_cli-0.3.0/jot/integrations/keywords/handler.py +443 -0
- jott_cli-0.3.0/jot/projects/__init__.py +0 -0
- jott_cli-0.3.0/jot/projects/backup.py +98 -0
- jott_cli-0.3.0/jot/projects/registry.py +79 -0
- jott_cli-0.3.0/jot/ui/__init__.py +22 -0
- jott_cli-0.3.0/jot/ui/display.py +819 -0
- jott_cli-0.3.0/jot/ui/formatting.py +72 -0
- jott_cli-0.3.0/jot/utils/__init__.py +0 -0
- jott_cli-0.3.0/jot/utils/date_utils.py +71 -0
- jott_cli-0.3.0/jot/utils/text_utils.py +97 -0
- jott_cli-0.3.0/jot/utils/validation.py +58 -0
- jott_cli-0.3.0/jot.py +1929 -0
- {jott_cli-0.2.2 → jott_cli-0.3.0/jott_cli.egg-info}/PKG-INFO +1 -1
- jott_cli-0.3.0/jott_cli.egg-info/SOURCES.txt +40 -0
- {jott_cli-0.2.2 → jott_cli-0.3.0}/pyproject.toml +5 -2
- jott_cli-0.2.2/jot.py +0 -8161
- jott_cli-0.2.2/jott_cli.egg-info/SOURCES.txt +0 -12
- {jott_cli-0.2.2 → jott_cli-0.3.0}/LICENSE +0 -0
- {jott_cli-0.2.2 → jott_cli-0.3.0}/README.md +0 -0
- {jott_cli-0.2.2 → jott_cli-0.3.0}/jott_cli.egg-info/dependency_links.txt +0 -0
- {jott_cli-0.2.2 → jott_cli-0.3.0}/jott_cli.egg-info/entry_points.txt +0 -0
- {jott_cli-0.2.2 → jott_cli-0.3.0}/jott_cli.egg-info/requires.txt +0 -0
- {jott_cli-0.2.2 → jott_cli-0.3.0}/jott_cli.egg-info/top_level.txt +0 -0
- {jott_cli-0.2.2 → jott_cli-0.3.0}/mcp_server.py +0 -0
- {jott_cli-0.2.2 → jott_cli-0.3.0}/setup.cfg +0 -0
- {jott_cli-0.2.2 → jott_cli-0.3.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jott-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Feature-rich interactive CLI task manager with AI integration, calendar sync, and keyword automation
|
|
5
5
|
Author-email: Scott Anderson <sonander@gmail.com>
|
|
6
6
|
Maintainer-email: Scott Anderson <sonander@gmail.com>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""jot package - Simple, extensible interactive CLI task list
|
|
2
|
+
|
|
3
|
+
This package provides modular components for the jot task management system.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import importlib.util
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
# Import extracted classes from their new modules
|
|
10
|
+
from jot.core.id_manager import IDManager
|
|
11
|
+
from jot.core.task_manager import TaskManager
|
|
12
|
+
from jot.projects.registry import ProjectRegistry
|
|
13
|
+
from jot.projects.backup import ProjectBackup
|
|
14
|
+
from jot.categories.manager import CategoryManager
|
|
15
|
+
from jot.categories.config import CategoryConfig
|
|
16
|
+
from jot.categories.templates import CategoryTemplates
|
|
17
|
+
from jot.integrations.gcal.account_manager import GoogleCalendarAccountManager
|
|
18
|
+
from jot.integrations.gcal.auth import GoogleCalendarAuth
|
|
19
|
+
from jot.integrations.gcal.events import create_gcal_event, fetch_gcal_events
|
|
20
|
+
from jot.integrations.keywords.handler import KeywordHandler
|
|
21
|
+
|
|
22
|
+
# Load jot.py for main() and other not-yet-extracted components
|
|
23
|
+
_jot_script_path = Path(__file__).parent.parent / 'jot.py'
|
|
24
|
+
_spec = importlib.util.spec_from_file_location("_jot_script", _jot_script_path)
|
|
25
|
+
_jot_script = importlib.util.module_from_spec(_spec)
|
|
26
|
+
_spec.loader.exec_module(_jot_script)
|
|
27
|
+
|
|
28
|
+
# Export main function from jot.py
|
|
29
|
+
main = _jot_script.main
|
|
30
|
+
|
|
31
|
+
__version__ = "0.3.0"
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
'main',
|
|
35
|
+
'TaskManager',
|
|
36
|
+
'ProjectRegistry',
|
|
37
|
+
'ProjectBackup',
|
|
38
|
+
'IDManager',
|
|
39
|
+
'CategoryManager',
|
|
40
|
+
'CategoryConfig',
|
|
41
|
+
'CategoryTemplates',
|
|
42
|
+
'KeywordHandler',
|
|
43
|
+
'GoogleCalendarAccountManager',
|
|
44
|
+
'GoogleCalendarAuth',
|
|
45
|
+
'create_gcal_event',
|
|
46
|
+
'fetch_gcal_events',
|
|
47
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Category configuration and color management"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CategoryConfig:
|
|
8
|
+
"""Manages category colors and configuration"""
|
|
9
|
+
|
|
10
|
+
# ANSI color codes
|
|
11
|
+
COLORS = {
|
|
12
|
+
'red': '\033[91m',
|
|
13
|
+
'green': '\033[92m',
|
|
14
|
+
'yellow': '\033[93m',
|
|
15
|
+
'blue': '\033[94m',
|
|
16
|
+
'magenta': '\033[95m',
|
|
17
|
+
'cyan': '\033[96m',
|
|
18
|
+
'white': '\033[97m',
|
|
19
|
+
'orange': '\033[38;5;208m',
|
|
20
|
+
'purple': '\033[38;5;141m',
|
|
21
|
+
'pink': '\033[38;5;213m',
|
|
22
|
+
'teal': '\033[38;5;44m',
|
|
23
|
+
'lime': '\033[38;5;154m',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Default color assignments for common categories
|
|
27
|
+
DEFAULT_COLORS = {
|
|
28
|
+
'default': 'white',
|
|
29
|
+
'features': 'green',
|
|
30
|
+
'bugs': 'red',
|
|
31
|
+
'testing': 'yellow',
|
|
32
|
+
'docs': 'blue',
|
|
33
|
+
'backlog': 'cyan',
|
|
34
|
+
'ideas': 'purple',
|
|
35
|
+
'work': 'teal',
|
|
36
|
+
'personal': 'pink',
|
|
37
|
+
'bills': 'orange',
|
|
38
|
+
'health': 'lime',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def __init__(self, project_dir=None):
|
|
42
|
+
"""Initialize category config"""
|
|
43
|
+
self.project_dir = Path(project_dir) if project_dir else Path.cwd()
|
|
44
|
+
self.config_file = self.project_dir / '.jot.config.json'
|
|
45
|
+
self.config = self._load_config()
|
|
46
|
+
|
|
47
|
+
def _load_config(self):
|
|
48
|
+
"""Load config from .jot.config.json"""
|
|
49
|
+
if self.config_file.exists():
|
|
50
|
+
try:
|
|
51
|
+
with open(self.config_file, 'r') as f:
|
|
52
|
+
return json.load(f)
|
|
53
|
+
except json.JSONDecodeError:
|
|
54
|
+
return {}
|
|
55
|
+
return {}
|
|
56
|
+
|
|
57
|
+
def _save_config(self):
|
|
58
|
+
"""Save config to .jot.config.json"""
|
|
59
|
+
self.config_file.parent.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
with open(self.config_file, 'w') as f:
|
|
61
|
+
json.dump(self.config, indent=2, fp=f)
|
|
62
|
+
|
|
63
|
+
def get_color(self, category_name, for_global=False):
|
|
64
|
+
"""Get ANSI color code for a category"""
|
|
65
|
+
# Check custom colors first
|
|
66
|
+
if 'colors' in self.config and category_name in self.config['colors']:
|
|
67
|
+
color_name = self.config['colors'][category_name]
|
|
68
|
+
return self.COLORS.get(color_name, '\033[96m') # Default to cyan
|
|
69
|
+
|
|
70
|
+
# Use default color for known categories
|
|
71
|
+
if category_name in self.DEFAULT_COLORS:
|
|
72
|
+
return self.COLORS[self.DEFAULT_COLORS[category_name]]
|
|
73
|
+
|
|
74
|
+
# Global categories get magenta by default
|
|
75
|
+
if for_global:
|
|
76
|
+
return self.COLORS['magenta']
|
|
77
|
+
|
|
78
|
+
# Unknown local categories get cyan
|
|
79
|
+
return self.COLORS['cyan']
|
|
80
|
+
|
|
81
|
+
def set_color(self, category_name, color_name):
|
|
82
|
+
"""Set color for a category"""
|
|
83
|
+
if color_name not in self.COLORS:
|
|
84
|
+
return False, f"Invalid color. Available: {', '.join(sorted(self.COLORS.keys()))}"
|
|
85
|
+
|
|
86
|
+
if 'colors' not in self.config:
|
|
87
|
+
self.config['colors'] = {}
|
|
88
|
+
|
|
89
|
+
self.config['colors'][category_name] = color_name
|
|
90
|
+
self._save_config()
|
|
91
|
+
return True, f"Set {category_name} color to {color_name}"
|
|
92
|
+
|
|
93
|
+
def get_all_colors(self):
|
|
94
|
+
"""Get all configured colors"""
|
|
95
|
+
return self.config.get('colors', {})
|
|
96
|
+
|
|
97
|
+
def reset_colors(self):
|
|
98
|
+
"""Reset all colors to defaults"""
|
|
99
|
+
if 'colors' in self.config:
|
|
100
|
+
del self.config['colors']
|
|
101
|
+
self._save_config()
|
|
102
|
+
return True
|
|
103
|
+
|
|
104
|
+
def list_available_colors(self):
|
|
105
|
+
"""List all available color names"""
|
|
106
|
+
return sorted(self.COLORS.keys())
|
|
107
|
+
|
|
108
|
+
def get_tts_config(self):
|
|
109
|
+
"""Get TTS configuration settings"""
|
|
110
|
+
default_tts = {
|
|
111
|
+
'enabled': True,
|
|
112
|
+
'rate': 150, # Words per minute
|
|
113
|
+
'volume': 0.9, # 0.0 to 1.0
|
|
114
|
+
'voice': None, # None = system default
|
|
115
|
+
}
|
|
116
|
+
return self.config.get('tts', default_tts)
|
|
117
|
+
|
|
118
|
+
def set_tts_config(self, **kwargs):
|
|
119
|
+
"""Set TTS configuration settings"""
|
|
120
|
+
if 'tts' not in self.config:
|
|
121
|
+
self.config['tts'] = {
|
|
122
|
+
'enabled': True,
|
|
123
|
+
'rate': 150,
|
|
124
|
+
'volume': 0.9,
|
|
125
|
+
'voice': None,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for key, value in kwargs.items():
|
|
129
|
+
if key in ['enabled', 'rate', 'volume', 'voice']:
|
|
130
|
+
self.config['tts'][key] = value
|
|
131
|
+
|
|
132
|
+
self._save_config()
|
|
133
|
+
return True
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Category management for jot projects"""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CategoryManager:
|
|
7
|
+
"""Manages categories within a project directory and globally"""
|
|
8
|
+
|
|
9
|
+
MAX_CATEGORIES = 12
|
|
10
|
+
GLOBAL_CATEGORIES_DIR = Path.home() / '.jot-categories'
|
|
11
|
+
|
|
12
|
+
def __init__(self, project_dir=None):
|
|
13
|
+
"""Initialize category manager for a project directory"""
|
|
14
|
+
self.project_dir = Path(project_dir) if project_dir else Path.cwd()
|
|
15
|
+
|
|
16
|
+
def discover_categories(self):
|
|
17
|
+
"""
|
|
18
|
+
Find all category files in .jot-categories/ directory.
|
|
19
|
+
Returns list of category names (without .json suffix).
|
|
20
|
+
"""
|
|
21
|
+
categories = []
|
|
22
|
+
|
|
23
|
+
# Check if .jot-categories directory exists
|
|
24
|
+
categories_dir = self.project_dir / '.jot-categories'
|
|
25
|
+
if not categories_dir.exists():
|
|
26
|
+
return categories
|
|
27
|
+
|
|
28
|
+
# Find all .json files in .jot-categories/
|
|
29
|
+
for file in categories_dir.glob('*.json'):
|
|
30
|
+
# Extract category name from filename
|
|
31
|
+
# features.json -> features
|
|
32
|
+
category = file.stem # Get filename without extension
|
|
33
|
+
categories.append(category)
|
|
34
|
+
|
|
35
|
+
return sorted(categories)
|
|
36
|
+
|
|
37
|
+
def count_categories(self):
|
|
38
|
+
"""Count existing categories in the project"""
|
|
39
|
+
return len(self.discover_categories())
|
|
40
|
+
|
|
41
|
+
def can_create_category(self, category_name):
|
|
42
|
+
"""
|
|
43
|
+
Check if a new category can be created.
|
|
44
|
+
Returns (can_create: bool, message: str)
|
|
45
|
+
"""
|
|
46
|
+
existing = self.discover_categories()
|
|
47
|
+
|
|
48
|
+
# Category already exists
|
|
49
|
+
if category_name in existing:
|
|
50
|
+
return True, None
|
|
51
|
+
|
|
52
|
+
# Check limit
|
|
53
|
+
if len(existing) >= self.MAX_CATEGORIES:
|
|
54
|
+
return (
|
|
55
|
+
False,
|
|
56
|
+
f"Category limit reached ({self.MAX_CATEGORIES} max). Existing: {', '.join(existing)}",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return True, None
|
|
60
|
+
|
|
61
|
+
def get_warning_message(self, category_name):
|
|
62
|
+
"""
|
|
63
|
+
Get warning message if approaching category limit.
|
|
64
|
+
Returns None if no warning needed.
|
|
65
|
+
"""
|
|
66
|
+
existing = self.discover_categories()
|
|
67
|
+
|
|
68
|
+
# No warning if category already exists
|
|
69
|
+
if category_name in existing:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
count = len(existing)
|
|
73
|
+
|
|
74
|
+
if count == 10:
|
|
75
|
+
return f"Warning: Approaching category limit ({count + 1}/12 categories)"
|
|
76
|
+
elif count == 11:
|
|
77
|
+
return f"Warning: Near category limit ({count + 1}/12 categories)"
|
|
78
|
+
elif count >= 12:
|
|
79
|
+
return f"Error: Category limit reached ({self.MAX_CATEGORIES} max)"
|
|
80
|
+
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
def discover_global_categories(self):
|
|
84
|
+
"""
|
|
85
|
+
Find all global category files in ~/.jot-categories/
|
|
86
|
+
Returns list of category names (without .json suffix).
|
|
87
|
+
Excludes special files like ids, archive, etc.
|
|
88
|
+
"""
|
|
89
|
+
# Return empty list if global directory doesn't exist
|
|
90
|
+
if not self.GLOBAL_CATEGORIES_DIR.exists():
|
|
91
|
+
return []
|
|
92
|
+
|
|
93
|
+
categories = []
|
|
94
|
+
reserved_names = {'ids', 'archive', 'index', 'config'}
|
|
95
|
+
|
|
96
|
+
# Find all *.json files in global categories directory
|
|
97
|
+
for file in self.GLOBAL_CATEGORIES_DIR.glob('*.json'):
|
|
98
|
+
category = file.stem # filename without .json
|
|
99
|
+
|
|
100
|
+
# Skip reserved names and special files
|
|
101
|
+
if category not in reserved_names and not category.endswith('.ids'):
|
|
102
|
+
categories.append(category)
|
|
103
|
+
|
|
104
|
+
return sorted(categories)
|
|
105
|
+
|
|
106
|
+
def category_exists_locally(self, category_name):
|
|
107
|
+
"""Check if category exists locally in project"""
|
|
108
|
+
categories_dir = self.project_dir / '.jot-categories'
|
|
109
|
+
local_file = categories_dir / f'{category_name}.json'
|
|
110
|
+
return local_file.exists()
|
|
111
|
+
|
|
112
|
+
def category_exists_globally(self, category_name):
|
|
113
|
+
"""Check if category exists globally"""
|
|
114
|
+
global_file = self.GLOBAL_CATEGORIES_DIR / f'{category_name}.json'
|
|
115
|
+
return global_file.exists()
|
|
116
|
+
|
|
117
|
+
def resolve_category_location(self, category_name):
|
|
118
|
+
"""
|
|
119
|
+
Determine if category is local, global, or new.
|
|
120
|
+
Returns: 'local', 'global', or 'new'
|
|
121
|
+
"""
|
|
122
|
+
if self.category_exists_locally(category_name):
|
|
123
|
+
return 'local'
|
|
124
|
+
elif self.category_exists_globally(category_name):
|
|
125
|
+
return 'global'
|
|
126
|
+
else:
|
|
127
|
+
return 'new'
|
|
128
|
+
|
|
129
|
+
def get_category_file_path(self, category_name, is_global=False):
|
|
130
|
+
"""
|
|
131
|
+
Get the full path for a category file.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
category_name: Name of the category
|
|
135
|
+
is_global: If True, return global path; if False, return local path
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Path object for the category file
|
|
139
|
+
"""
|
|
140
|
+
if is_global:
|
|
141
|
+
return self.GLOBAL_CATEGORIES_DIR / f'{category_name}.json'
|
|
142
|
+
else:
|
|
143
|
+
# Local categories are stored in .jot-categories/ directory
|
|
144
|
+
categories_dir = self.project_dir / '.jot-categories'
|
|
145
|
+
categories_dir.mkdir(exist_ok=True)
|
|
146
|
+
return categories_dir / f'{category_name}.json'
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Category templates for quick project setup"""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from jot.categories.config import CategoryConfig
|
|
6
|
+
from jot.categories.manager import CategoryManager
|
|
7
|
+
from jot.core.task_manager import TaskManager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CategoryTemplates:
|
|
11
|
+
"""Manages category templates for quick project setup"""
|
|
12
|
+
|
|
13
|
+
TEMPLATES = {
|
|
14
|
+
'software-dev': {
|
|
15
|
+
'name': 'Software Development',
|
|
16
|
+
'description': 'Standard software project categories',
|
|
17
|
+
'categories': ['features', 'bugs', 'testing', 'docs'],
|
|
18
|
+
'sample_tasks': {
|
|
19
|
+
'features': ['Plan next sprint', 'Review backlog'],
|
|
20
|
+
'bugs': ['Triage bug reports'],
|
|
21
|
+
'testing': ['Run test suite', 'Update test coverage'],
|
|
22
|
+
'docs': ['Update README', 'Write API docs'],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
'personal': {
|
|
26
|
+
'name': 'Personal Tasks',
|
|
27
|
+
'description': 'Personal life management categories',
|
|
28
|
+
'categories': ['work', 'home', 'bills', 'health'],
|
|
29
|
+
'sample_tasks': {
|
|
30
|
+
'work': ['Check email', 'Weekly planning'],
|
|
31
|
+
'home': ['Grocery shopping', 'Clean house'],
|
|
32
|
+
'bills': ['Pay rent', 'Review budget'],
|
|
33
|
+
'health': ['Exercise', 'Doctor appointment'],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
'creative': {
|
|
37
|
+
'name': 'Creative Project',
|
|
38
|
+
'description': 'Creative and content creation categories',
|
|
39
|
+
'categories': ['ideas', 'drafts', 'editing', 'publishing'],
|
|
40
|
+
'sample_tasks': {
|
|
41
|
+
'ideas': ['Brainstorm topics', 'Research inspiration'],
|
|
42
|
+
'drafts': ['Write first draft'],
|
|
43
|
+
'editing': ['Review and revise'],
|
|
44
|
+
'publishing': ['Format and publish'],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
'business': {
|
|
48
|
+
'name': 'Business Management',
|
|
49
|
+
'description': 'Business and client work categories',
|
|
50
|
+
'categories': ['leads', 'clients', 'invoicing', 'marketing'],
|
|
51
|
+
'sample_tasks': {
|
|
52
|
+
'leads': ['Follow up prospects', 'Update CRM'],
|
|
53
|
+
'clients': ['Client meetings', 'Project deliverables'],
|
|
54
|
+
'invoicing': ['Send invoices', 'Track payments'],
|
|
55
|
+
'marketing': ['Social media posts', 'Email campaign'],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
'academic': {
|
|
59
|
+
'name': 'Academic/Research',
|
|
60
|
+
'description': 'Academic and research project categories',
|
|
61
|
+
'categories': ['reading', 'research', 'writing', 'teaching'],
|
|
62
|
+
'sample_tasks': {
|
|
63
|
+
'reading': ['Literature review', 'Paper analysis'],
|
|
64
|
+
'research': ['Experiment design', 'Data collection'],
|
|
65
|
+
'writing': ['Draft paper', 'Revise manuscript'],
|
|
66
|
+
'teaching': ['Prepare lecture', 'Grade assignments'],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
def __init__(self, project_dir=None):
|
|
72
|
+
"""Initialize templates manager"""
|
|
73
|
+
self.project_dir = Path(project_dir) if project_dir else Path.cwd()
|
|
74
|
+
self.cat_manager = CategoryManager(project_dir=self.project_dir)
|
|
75
|
+
self.cat_config = CategoryConfig(project_dir=self.project_dir)
|
|
76
|
+
|
|
77
|
+
def list_templates(self):
|
|
78
|
+
"""List all available templates"""
|
|
79
|
+
return self.TEMPLATES
|
|
80
|
+
|
|
81
|
+
def apply_template(self, template_name, with_samples=False):
|
|
82
|
+
"""Apply a template to create categories"""
|
|
83
|
+
if template_name not in self.TEMPLATES:
|
|
84
|
+
return False, f"Template '{template_name}' not found"
|
|
85
|
+
|
|
86
|
+
template = self.TEMPLATES[template_name]
|
|
87
|
+
categories_to_create = template['categories']
|
|
88
|
+
|
|
89
|
+
existing_categories = self.cat_manager.discover_categories()
|
|
90
|
+
total_after = len(existing_categories) + len(categories_to_create)
|
|
91
|
+
|
|
92
|
+
if total_after > CategoryManager.MAX_CATEGORIES:
|
|
93
|
+
return (
|
|
94
|
+
False,
|
|
95
|
+
f"Cannot create {len(categories_to_create)} categories. "
|
|
96
|
+
f"Would exceed limit ({CategoryManager.MAX_CATEGORIES} max)",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
conflicts = [c for c in categories_to_create if c in existing_categories]
|
|
100
|
+
if conflicts:
|
|
101
|
+
return False, f"Categories already exist: {', '.join(conflicts)}"
|
|
102
|
+
|
|
103
|
+
created = []
|
|
104
|
+
for category in categories_to_create:
|
|
105
|
+
tm = TaskManager(
|
|
106
|
+
directory=self.project_dir,
|
|
107
|
+
category=category,
|
|
108
|
+
is_global=False,
|
|
109
|
+
)
|
|
110
|
+
if with_samples and category in template.get('sample_tasks', {}):
|
|
111
|
+
for task_text in template['sample_tasks'][category]:
|
|
112
|
+
tm.add_task(task_text)
|
|
113
|
+
created.append(category)
|
|
114
|
+
|
|
115
|
+
return True, f"Created {len(created)} categories: {', '.join(created)}"
|
|
116
|
+
|
|
117
|
+
def get_template_info(self, template_name):
|
|
118
|
+
"""Get detailed information about a template"""
|
|
119
|
+
if template_name not in self.TEMPLATES:
|
|
120
|
+
return None
|
|
121
|
+
return self.TEMPLATES[template_name]
|