fishertools 0.2.1__py3-none-any.whl → 0.4.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.
- fishertools/__init__.py +16 -5
- fishertools/errors/__init__.py +11 -3
- fishertools/errors/exception_types.py +282 -0
- fishertools/errors/explainer.py +87 -1
- fishertools/errors/models.py +73 -1
- fishertools/errors/patterns.py +40 -0
- fishertools/examples/cli_example.py +156 -0
- fishertools/examples/learn_example.py +65 -0
- fishertools/examples/logger_example.py +176 -0
- fishertools/examples/menu_example.py +101 -0
- fishertools/examples/storage_example.py +175 -0
- fishertools/input_utils.py +185 -0
- fishertools/learn/__init__.py +19 -2
- fishertools/learn/examples.py +88 -1
- fishertools/learn/knowledge_engine.py +321 -0
- fishertools/learn/repl/__init__.py +19 -0
- fishertools/learn/repl/cli.py +31 -0
- fishertools/learn/repl/code_sandbox.py +229 -0
- fishertools/learn/repl/command_handler.py +544 -0
- fishertools/learn/repl/command_parser.py +165 -0
- fishertools/learn/repl/engine.py +479 -0
- fishertools/learn/repl/models.py +121 -0
- fishertools/learn/repl/session_manager.py +284 -0
- fishertools/learn/repl/test_code_sandbox.py +261 -0
- fishertools/learn/repl/test_code_sandbox_pbt.py +148 -0
- fishertools/learn/repl/test_command_handler.py +224 -0
- fishertools/learn/repl/test_command_handler_pbt.py +189 -0
- fishertools/learn/repl/test_command_parser.py +160 -0
- fishertools/learn/repl/test_command_parser_pbt.py +100 -0
- fishertools/learn/repl/test_engine.py +190 -0
- fishertools/learn/repl/test_session_manager.py +310 -0
- fishertools/learn/repl/test_session_manager_pbt.py +182 -0
- fishertools/learn/test_knowledge_engine.py +241 -0
- fishertools/learn/test_knowledge_engine_pbt.py +180 -0
- fishertools/patterns/__init__.py +46 -0
- fishertools/patterns/cli.py +175 -0
- fishertools/patterns/logger.py +140 -0
- fishertools/patterns/menu.py +99 -0
- fishertools/patterns/storage.py +127 -0
- fishertools/readme_transformer.py +631 -0
- fishertools/safe/__init__.py +6 -1
- fishertools/safe/files.py +329 -1
- fishertools/transform_readme.py +105 -0
- fishertools-0.4.0.dist-info/METADATA +104 -0
- fishertools-0.4.0.dist-info/RECORD +131 -0
- {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/WHEEL +1 -1
- tests/test_documentation_properties.py +329 -0
- tests/test_documentation_structure.py +349 -0
- tests/test_errors/test_exception_types.py +446 -0
- tests/test_errors/test_exception_types_pbt.py +333 -0
- tests/test_errors/test_patterns.py +52 -0
- tests/test_input_utils/__init__.py +1 -0
- tests/test_input_utils/test_input_utils.py +65 -0
- tests/test_learn/test_examples.py +179 -1
- tests/test_learn/test_explain_properties.py +307 -0
- tests/test_patterns_cli.py +611 -0
- tests/test_patterns_docstrings.py +473 -0
- tests/test_patterns_logger.py +465 -0
- tests/test_patterns_menu.py +440 -0
- tests/test_patterns_storage.py +447 -0
- tests/test_readme_enhancements_v0_3_1.py +2036 -0
- tests/test_readme_transformer/__init__.py +1 -0
- tests/test_readme_transformer/test_readme_infrastructure.py +1023 -0
- tests/test_readme_transformer/test_transform_readme_integration.py +431 -0
- tests/test_safe/test_files.py +726 -1
- fishertools-0.2.1.dist-info/METADATA +0 -256
- fishertools-0.2.1.dist-info/RECORD +0 -81
- {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Safe input collection module for fishertools.
|
|
3
|
+
|
|
4
|
+
This module provides validated input collection functions with automatic type checking
|
|
5
|
+
and range validation. It helps beginners collect user input safely without writing
|
|
6
|
+
repetitive validation code.
|
|
7
|
+
|
|
8
|
+
Functions:
|
|
9
|
+
ask_int() - Prompt user for an integer with optional range validation
|
|
10
|
+
ask_float() - Prompt user for a float with optional range validation
|
|
11
|
+
ask_str() - Prompt user for a string with optional length validation
|
|
12
|
+
ask_choice() - Prompt user to choose from a list of options
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import List, Optional, Any
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def ask_int(prompt: str, min: Optional[int] = None, max: Optional[int] = None) -> int:
|
|
19
|
+
"""
|
|
20
|
+
Prompt user for an integer with optional range validation.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
prompt: The prompt to display to the user
|
|
24
|
+
min: Minimum allowed value (inclusive), optional
|
|
25
|
+
max: Maximum allowed value (inclusive), optional
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Validated integer from user input
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
EOFError: If user provides EOF (Ctrl+D)
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> age = ask_int("How old are you? ", min=0, max=150)
|
|
35
|
+
>>> score = ask_int("Enter your score: ")
|
|
36
|
+
"""
|
|
37
|
+
while True:
|
|
38
|
+
try:
|
|
39
|
+
user_input = input(prompt)
|
|
40
|
+
value = int(user_input)
|
|
41
|
+
|
|
42
|
+
# Check min constraint
|
|
43
|
+
if min is not None and value < min:
|
|
44
|
+
print(f"Error: Value must be at least {min}")
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
# Check max constraint
|
|
48
|
+
if max is not None and value > max:
|
|
49
|
+
print(f"Error: Value must be at most {max}")
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
return value
|
|
53
|
+
except ValueError:
|
|
54
|
+
print(f"Error: Please enter a valid integer")
|
|
55
|
+
except EOFError:
|
|
56
|
+
raise
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def ask_float(prompt: str, min: Optional[float] = None, max: Optional[float] = None) -> float:
|
|
60
|
+
"""
|
|
61
|
+
Prompt user for a float with optional range validation.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
prompt: The prompt to display to the user
|
|
65
|
+
min: Minimum allowed value (inclusive), optional
|
|
66
|
+
max: Maximum allowed value (inclusive), optional
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Validated float from user input
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
EOFError: If user provides EOF (Ctrl+D)
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
>>> temperature = ask_float("Enter temperature (C): ", min=-273.15)
|
|
76
|
+
>>> price = ask_float("Enter price: ", min=0)
|
|
77
|
+
"""
|
|
78
|
+
while True:
|
|
79
|
+
try:
|
|
80
|
+
user_input = input(prompt)
|
|
81
|
+
value = float(user_input)
|
|
82
|
+
|
|
83
|
+
# Check min constraint
|
|
84
|
+
if min is not None and value < min:
|
|
85
|
+
print(f"Error: Value must be at least {min}")
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
# Check max constraint
|
|
89
|
+
if max is not None and value > max:
|
|
90
|
+
print(f"Error: Value must be at most {max}")
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
return value
|
|
94
|
+
except ValueError:
|
|
95
|
+
print(f"Error: Please enter a valid number")
|
|
96
|
+
except EOFError:
|
|
97
|
+
raise
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def ask_str(prompt: str, min_length: Optional[int] = None, max_length: Optional[int] = None) -> str:
|
|
101
|
+
"""
|
|
102
|
+
Prompt user for a string with optional length validation.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
prompt: The prompt to display to the user
|
|
106
|
+
min_length: Minimum string length, optional
|
|
107
|
+
max_length: Maximum string length, optional
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Validated string from user input (whitespace stripped)
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
EOFError: If user provides EOF (Ctrl+D)
|
|
114
|
+
|
|
115
|
+
Example:
|
|
116
|
+
>>> name = ask_str("Enter your name: ", min_length=1, max_length=50)
|
|
117
|
+
>>> password = ask_str("Enter password: ", min_length=8)
|
|
118
|
+
"""
|
|
119
|
+
while True:
|
|
120
|
+
try:
|
|
121
|
+
user_input = input(prompt)
|
|
122
|
+
value = user_input.strip()
|
|
123
|
+
|
|
124
|
+
# Check min_length constraint
|
|
125
|
+
if min_length is not None and len(value) < min_length:
|
|
126
|
+
print(f"Error: String must be at least {min_length} characters long")
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
# Check max_length constraint
|
|
130
|
+
if max_length is not None and len(value) > max_length:
|
|
131
|
+
print(f"Error: String must be at most {max_length} characters long")
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
return value
|
|
135
|
+
except EOFError:
|
|
136
|
+
raise
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def ask_choice(prompt: str, options: List[str]) -> str:
|
|
140
|
+
"""
|
|
141
|
+
Prompt user to choose from a list of options.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
prompt: The prompt to display to the user
|
|
145
|
+
options: List of available choices
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
The selected option (exact string from options list)
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
EOFError: If user provides EOF (Ctrl+D)
|
|
152
|
+
ValueError: If options list is empty
|
|
153
|
+
|
|
154
|
+
Example:
|
|
155
|
+
>>> color = ask_choice("Choose a color: ", ["red", "green", "blue"])
|
|
156
|
+
>>> choice = ask_choice("Select: ", ["Yes", "No", "Maybe"])
|
|
157
|
+
"""
|
|
158
|
+
if not options:
|
|
159
|
+
raise ValueError("Options list cannot be empty")
|
|
160
|
+
|
|
161
|
+
while True:
|
|
162
|
+
try:
|
|
163
|
+
# Display options
|
|
164
|
+
for i, option in enumerate(options, 1):
|
|
165
|
+
print(f" {i}. {option}")
|
|
166
|
+
|
|
167
|
+
user_input = input(prompt).strip()
|
|
168
|
+
|
|
169
|
+
# Try numeric selection first
|
|
170
|
+
try:
|
|
171
|
+
choice_index = int(user_input) - 1
|
|
172
|
+
if 0 <= choice_index < len(options):
|
|
173
|
+
return options[choice_index]
|
|
174
|
+
else:
|
|
175
|
+
print(f"Error: Please enter a number between 1 and {len(options)}")
|
|
176
|
+
continue
|
|
177
|
+
except ValueError:
|
|
178
|
+
# Try direct text matching
|
|
179
|
+
if user_input in options:
|
|
180
|
+
return user_input
|
|
181
|
+
else:
|
|
182
|
+
print(f"Error: '{user_input}' is not a valid option")
|
|
183
|
+
continue
|
|
184
|
+
except EOFError:
|
|
185
|
+
raise
|
fishertools/learn/__init__.py
CHANGED
|
@@ -5,14 +5,31 @@ This module provides educational utilities that help beginners
|
|
|
5
5
|
learn Python best practices and concepts.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from .examples import generate_example, list_available_concepts, get_concept_info
|
|
8
|
+
from .examples import generate_example, list_available_concepts, get_concept_info, explain
|
|
9
9
|
from .tips import show_best_practice, list_available_topics, get_topic_summary
|
|
10
|
+
from .knowledge_engine import (
|
|
11
|
+
KnowledgeEngine,
|
|
12
|
+
get_topic,
|
|
13
|
+
list_topics,
|
|
14
|
+
search_topics,
|
|
15
|
+
get_random_topic,
|
|
16
|
+
get_learning_path,
|
|
17
|
+
get_engine
|
|
18
|
+
)
|
|
10
19
|
|
|
11
20
|
__all__ = [
|
|
12
21
|
"generate_example",
|
|
13
22
|
"list_available_concepts",
|
|
14
23
|
"get_concept_info",
|
|
24
|
+
"explain",
|
|
15
25
|
"show_best_practice",
|
|
16
26
|
"list_available_topics",
|
|
17
|
-
"get_topic_summary"
|
|
27
|
+
"get_topic_summary",
|
|
28
|
+
"KnowledgeEngine",
|
|
29
|
+
"get_topic",
|
|
30
|
+
"list_topics",
|
|
31
|
+
"search_topics",
|
|
32
|
+
"get_random_topic",
|
|
33
|
+
"get_learning_path",
|
|
34
|
+
"get_engine"
|
|
18
35
|
]
|
fishertools/learn/examples.py
CHANGED
|
@@ -5,6 +5,8 @@ This module contains functions to generate educational code examples
|
|
|
5
5
|
for common Python concepts that beginners need to learn.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
8
10
|
from typing import Dict, Optional
|
|
9
11
|
|
|
10
12
|
|
|
@@ -547,4 +549,89 @@ def get_concept_info(concept: str) -> Optional[Dict[str, str]]:
|
|
|
547
549
|
return {
|
|
548
550
|
"title": CODE_EXAMPLES[concept_lower]["title"],
|
|
549
551
|
"description": CODE_EXAMPLES[concept_lower]["description"]
|
|
550
|
-
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def explain(topic: str) -> Dict[str, str]:
|
|
557
|
+
"""
|
|
558
|
+
Get a structured explanation for a Python topic.
|
|
559
|
+
|
|
560
|
+
This function loads explanations from the explanations.json file and returns
|
|
561
|
+
a dictionary containing a description, usage guidance, and code example for
|
|
562
|
+
the requested topic.
|
|
563
|
+
|
|
564
|
+
Parameters
|
|
565
|
+
----------
|
|
566
|
+
topic : str
|
|
567
|
+
The name of the Python topic to explain (e.g., 'list', 'for', 'lambda').
|
|
568
|
+
Topic names are case-insensitive.
|
|
569
|
+
|
|
570
|
+
Returns
|
|
571
|
+
-------
|
|
572
|
+
dict
|
|
573
|
+
A dictionary with the following keys:
|
|
574
|
+
- 'description' (str): A clear, concise explanation of what the topic is
|
|
575
|
+
- 'when_to_use' (str): Practical guidance on when to use this topic
|
|
576
|
+
- 'example' (str): Valid, runnable Python code demonstrating the topic
|
|
577
|
+
|
|
578
|
+
Raises
|
|
579
|
+
------
|
|
580
|
+
ValueError
|
|
581
|
+
If the topic is not found in the explanations database. The error message
|
|
582
|
+
includes a helpful list of all available topics.
|
|
583
|
+
FileNotFoundError
|
|
584
|
+
If the explanations.json file cannot be found.
|
|
585
|
+
json.JSONDecodeError
|
|
586
|
+
If the explanations.json file is corrupted or invalid.
|
|
587
|
+
|
|
588
|
+
Examples
|
|
589
|
+
--------
|
|
590
|
+
>>> explanation = explain('list')
|
|
591
|
+
>>> print(explanation['description'])
|
|
592
|
+
Ordered collection of items that can be of different types...
|
|
593
|
+
|
|
594
|
+
>>> explanation = explain('lambda')
|
|
595
|
+
>>> print(explanation['example'])
|
|
596
|
+
square = lambda x: x ** 2
|
|
597
|
+
print(square(5))
|
|
598
|
+
|
|
599
|
+
>>> try:
|
|
600
|
+
... explain('invalid_topic')
|
|
601
|
+
... except ValueError as e:
|
|
602
|
+
... print(str(e))
|
|
603
|
+
Topic 'invalid_topic' not found. Available topics: int, float, str, ...
|
|
604
|
+
"""
|
|
605
|
+
# Normalize the topic name
|
|
606
|
+
topic_normalized = topic.strip().lower()
|
|
607
|
+
|
|
608
|
+
# Get the path to the explanations.json file
|
|
609
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
610
|
+
explanations_path = os.path.join(current_dir, 'explanations.json')
|
|
611
|
+
|
|
612
|
+
# Load the explanations from JSON file
|
|
613
|
+
try:
|
|
614
|
+
with open(explanations_path, 'r', encoding='utf-8') as f:
|
|
615
|
+
explanations = json.load(f)
|
|
616
|
+
except FileNotFoundError:
|
|
617
|
+
raise FileNotFoundError(
|
|
618
|
+
f"Explanations file not found at {explanations_path}. "
|
|
619
|
+
"Please ensure explanations.json is in the fishertools/learn/ directory."
|
|
620
|
+
)
|
|
621
|
+
except json.JSONDecodeError as e:
|
|
622
|
+
raise json.JSONDecodeError(
|
|
623
|
+
f"Failed to parse explanations.json: {e.msg}",
|
|
624
|
+
e.doc,
|
|
625
|
+
e.pos
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
# Check if the topic exists
|
|
629
|
+
if topic_normalized not in explanations:
|
|
630
|
+
available_topics = sorted(explanations.keys())
|
|
631
|
+
topics_str = ", ".join(available_topics)
|
|
632
|
+
raise ValueError(
|
|
633
|
+
f"Topic '{topic}' not found. Available topics: {topics_str}"
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
# Return the explanation dictionary
|
|
637
|
+
return explanations[topic_normalized]
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Knowledge Engine for fishertools - Educational system for Python concepts.
|
|
3
|
+
|
|
4
|
+
This module provides a structured knowledge base of Python concepts for beginners,
|
|
5
|
+
with explanations, examples, common mistakes, and related topics.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import random
|
|
11
|
+
from typing import Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class KnowledgeEngine:
|
|
15
|
+
"""
|
|
16
|
+
Knowledge Engine for managing and accessing educational content about Python concepts.
|
|
17
|
+
|
|
18
|
+
The engine loads topics from a JSON file and provides methods to search, filter,
|
|
19
|
+
and retrieve information about Python concepts for beginners.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
topics (Dict[str, Dict]): Dictionary of all loaded topics indexed by name
|
|
23
|
+
categories (Dict[str, List[str]]): Dictionary mapping categories to topic names
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
>>> engine = KnowledgeEngine()
|
|
27
|
+
>>> topic = engine.get_topic("Lists")
|
|
28
|
+
>>> print(topic["description"])
|
|
29
|
+
>>> all_topics = engine.list_topics()
|
|
30
|
+
>>> related = engine.get_related_topics("Lists")
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, topics_file: Optional[str] = None):
|
|
34
|
+
"""
|
|
35
|
+
Initialize the Knowledge Engine by loading topics from a JSON file.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
topics_file: Path to the JSON file containing topics. If None, uses default location.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
FileNotFoundError: If the topics file is not found
|
|
42
|
+
ValueError: If the JSON file is invalid or corrupted
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
>>> engine = KnowledgeEngine()
|
|
46
|
+
>>> engine = KnowledgeEngine("custom_topics.json")
|
|
47
|
+
"""
|
|
48
|
+
if topics_file is None:
|
|
49
|
+
# Use default location relative to this file
|
|
50
|
+
topics_file = os.path.join(os.path.dirname(__file__), "topics.json")
|
|
51
|
+
|
|
52
|
+
if not os.path.exists(topics_file):
|
|
53
|
+
raise FileNotFoundError(f"Topics file not found: {topics_file}")
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
with open(topics_file, 'r', encoding='utf-8') as f:
|
|
57
|
+
topics_list = json.load(f)
|
|
58
|
+
except json.JSONDecodeError as e:
|
|
59
|
+
raise ValueError(f"Invalid JSON in topics file: {e}")
|
|
60
|
+
|
|
61
|
+
# Index topics by name for fast lookup
|
|
62
|
+
self.topics: Dict[str, Dict] = {}
|
|
63
|
+
self.categories: Dict[str, List[str]] = {}
|
|
64
|
+
|
|
65
|
+
for topic in topics_list:
|
|
66
|
+
topic_name = topic.get("topic")
|
|
67
|
+
if not topic_name:
|
|
68
|
+
raise ValueError("Topic missing 'topic' field")
|
|
69
|
+
|
|
70
|
+
self.topics[topic_name] = topic
|
|
71
|
+
|
|
72
|
+
# Index by category
|
|
73
|
+
category = topic.get("category", "Uncategorized")
|
|
74
|
+
if category not in self.categories:
|
|
75
|
+
self.categories[category] = []
|
|
76
|
+
self.categories[category].append(topic_name)
|
|
77
|
+
|
|
78
|
+
def get_topic(self, name: str) -> Optional[Dict]:
|
|
79
|
+
"""
|
|
80
|
+
Get a topic by name.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
name: The name of the topic to retrieve
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Dictionary containing the topic information, or None if not found
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
>>> engine = KnowledgeEngine()
|
|
90
|
+
>>> topic = engine.get_topic("Lists")
|
|
91
|
+
>>> if topic:
|
|
92
|
+
... print(topic["description"])
|
|
93
|
+
"""
|
|
94
|
+
return self.topics.get(name)
|
|
95
|
+
|
|
96
|
+
def list_topics(self) -> List[str]:
|
|
97
|
+
"""
|
|
98
|
+
Get a list of all available topics.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Sorted list of all topic names
|
|
102
|
+
|
|
103
|
+
Example:
|
|
104
|
+
>>> engine = KnowledgeEngine()
|
|
105
|
+
>>> topics = engine.list_topics()
|
|
106
|
+
>>> print(f"Available topics: {len(topics)}")
|
|
107
|
+
"""
|
|
108
|
+
return sorted(self.topics.keys())
|
|
109
|
+
|
|
110
|
+
def search_topics(self, keyword: str) -> List[str]:
|
|
111
|
+
"""
|
|
112
|
+
Search for topics containing a keyword in name or description.
|
|
113
|
+
|
|
114
|
+
The search is case-insensitive and matches partial words.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
keyword: The keyword to search for
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
List of topic names matching the keyword
|
|
121
|
+
|
|
122
|
+
Example:
|
|
123
|
+
>>> engine = KnowledgeEngine()
|
|
124
|
+
>>> results = engine.search_topics("list")
|
|
125
|
+
>>> print(f"Found {len(results)} topics about lists")
|
|
126
|
+
"""
|
|
127
|
+
keyword_lower = keyword.lower()
|
|
128
|
+
results = []
|
|
129
|
+
|
|
130
|
+
for name, topic in self.topics.items():
|
|
131
|
+
# Search in topic name
|
|
132
|
+
if keyword_lower in name.lower():
|
|
133
|
+
results.append(name)
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
# Search in description
|
|
137
|
+
description = topic.get("description", "").lower()
|
|
138
|
+
if keyword_lower in description:
|
|
139
|
+
results.append(name)
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
# Search in when_to_use
|
|
143
|
+
when_to_use = topic.get("when_to_use", "").lower()
|
|
144
|
+
if keyword_lower in when_to_use:
|
|
145
|
+
results.append(name)
|
|
146
|
+
|
|
147
|
+
return sorted(results)
|
|
148
|
+
|
|
149
|
+
def get_random_topic(self) -> Dict:
|
|
150
|
+
"""
|
|
151
|
+
Get a random topic for learning.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
A randomly selected topic dictionary
|
|
155
|
+
|
|
156
|
+
Example:
|
|
157
|
+
>>> engine = KnowledgeEngine()
|
|
158
|
+
>>> topic = engine.get_random_topic()
|
|
159
|
+
>>> print(f"Today's topic: {topic['topic']}")
|
|
160
|
+
"""
|
|
161
|
+
topic_name = random.choice(list(self.topics.keys()))
|
|
162
|
+
return self.topics[topic_name]
|
|
163
|
+
|
|
164
|
+
def get_related_topics(self, topic_name: str) -> List[str]:
|
|
165
|
+
"""
|
|
166
|
+
Get topics related to a given topic.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
topic_name: The name of the topic
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
List of related topic names that exist in the knowledge base
|
|
173
|
+
|
|
174
|
+
Example:
|
|
175
|
+
>>> engine = KnowledgeEngine()
|
|
176
|
+
>>> related = engine.get_related_topics("Lists")
|
|
177
|
+
>>> print(f"Related topics: {related}")
|
|
178
|
+
"""
|
|
179
|
+
topic = self.get_topic(topic_name)
|
|
180
|
+
if not topic:
|
|
181
|
+
return []
|
|
182
|
+
|
|
183
|
+
related = topic.get("related_topics", [])
|
|
184
|
+
# Filter to only include topics that exist in the knowledge base
|
|
185
|
+
return [t for t in related if t in self.topics]
|
|
186
|
+
|
|
187
|
+
def get_topics_by_category(self, category: str) -> List[str]:
|
|
188
|
+
"""
|
|
189
|
+
Get all topics in a specific category.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
category: The category name
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Sorted list of topic names in the category
|
|
196
|
+
|
|
197
|
+
Example:
|
|
198
|
+
>>> engine = KnowledgeEngine()
|
|
199
|
+
>>> basic_types = engine.get_topics_by_category("Basic Types")
|
|
200
|
+
>>> print(f"Topics in Basic Types: {basic_types}")
|
|
201
|
+
"""
|
|
202
|
+
return sorted(self.categories.get(category, []))
|
|
203
|
+
|
|
204
|
+
def get_learning_path(self) -> List[str]:
|
|
205
|
+
"""
|
|
206
|
+
Get the recommended learning path from simple to complex topics.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
List of topic names ordered by difficulty
|
|
210
|
+
|
|
211
|
+
Example:
|
|
212
|
+
>>> engine = KnowledgeEngine()
|
|
213
|
+
>>> path = engine.get_learning_path()
|
|
214
|
+
>>> for topic_name in path:
|
|
215
|
+
... print(topic_name)
|
|
216
|
+
"""
|
|
217
|
+
# Sort topics by order field, then by difficulty
|
|
218
|
+
sorted_topics = sorted(
|
|
219
|
+
self.topics.items(),
|
|
220
|
+
key=lambda x: (x[1].get("order", 999), x[1].get("difficulty", ""))
|
|
221
|
+
)
|
|
222
|
+
return [name for name, _ in sorted_topics]
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# Global engine instance
|
|
226
|
+
_engine: Optional[KnowledgeEngine] = None
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def get_engine() -> KnowledgeEngine:
|
|
230
|
+
"""
|
|
231
|
+
Get the global Knowledge Engine instance.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
The global KnowledgeEngine instance
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
>>> engine = get_engine()
|
|
238
|
+
>>> topics = engine.list_topics()
|
|
239
|
+
"""
|
|
240
|
+
global _engine
|
|
241
|
+
if _engine is None:
|
|
242
|
+
_engine = KnowledgeEngine()
|
|
243
|
+
return _engine
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def get_topic(name: str) -> Optional[Dict]:
|
|
247
|
+
"""
|
|
248
|
+
Get a topic by name using the global engine.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
name: The name of the topic
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
Topic dictionary or None if not found
|
|
255
|
+
|
|
256
|
+
Example:
|
|
257
|
+
>>> topic = get_topic("Lists")
|
|
258
|
+
>>> if topic:
|
|
259
|
+
... print(topic["description"])
|
|
260
|
+
"""
|
|
261
|
+
return get_engine().get_topic(name)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def list_topics() -> List[str]:
|
|
265
|
+
"""
|
|
266
|
+
Get a list of all available topics using the global engine.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Sorted list of all topic names
|
|
270
|
+
|
|
271
|
+
Example:
|
|
272
|
+
>>> topics = list_topics()
|
|
273
|
+
>>> print(f"Total topics: {len(topics)}")
|
|
274
|
+
"""
|
|
275
|
+
return get_engine().list_topics()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def search_topics(keyword: str) -> List[str]:
|
|
279
|
+
"""
|
|
280
|
+
Search for topics by keyword using the global engine.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
keyword: The keyword to search for
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
List of matching topic names
|
|
287
|
+
|
|
288
|
+
Example:
|
|
289
|
+
>>> results = search_topics("loop")
|
|
290
|
+
>>> print(f"Found {len(results)} topics about loops")
|
|
291
|
+
"""
|
|
292
|
+
return get_engine().search_topics(keyword)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def get_random_topic() -> Dict:
|
|
296
|
+
"""
|
|
297
|
+
Get a random topic using the global engine.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
A randomly selected topic dictionary
|
|
301
|
+
|
|
302
|
+
Example:
|
|
303
|
+
>>> topic = get_random_topic()
|
|
304
|
+
>>> print(f"Random topic: {topic['topic']}")
|
|
305
|
+
"""
|
|
306
|
+
return get_engine().get_random_topic()
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def get_learning_path() -> List[str]:
|
|
310
|
+
"""
|
|
311
|
+
Get the recommended learning path using the global engine.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
List of topic names ordered by difficulty
|
|
315
|
+
|
|
316
|
+
Example:
|
|
317
|
+
>>> path = get_learning_path()
|
|
318
|
+
>>> for topic_name in path[:5]:
|
|
319
|
+
... print(topic_name)
|
|
320
|
+
"""
|
|
321
|
+
return get_engine().get_learning_path()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Knowledge Engine Interactive REPL - An interactive command-line interface for exploring Python topics.
|
|
3
|
+
|
|
4
|
+
This package provides a beginner-friendly REPL for the Knowledge Engine that enables:
|
|
5
|
+
- Topic browsing and discovery
|
|
6
|
+
- Code execution in a safe sandbox
|
|
7
|
+
- Learning progress tracking
|
|
8
|
+
- Contextual hints and guidance
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def get_repl_engine():
|
|
12
|
+
"""Get a REPL engine instance."""
|
|
13
|
+
from fishertools.learn.repl.engine import REPLEngine
|
|
14
|
+
return REPLEngine()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"get_repl_engine",
|
|
19
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface entry point for the Knowledge Engine REPL.
|
|
3
|
+
|
|
4
|
+
This module provides the main entry point for running the REPL from the command line.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from fishertools.learn.repl.engine import REPLEngine
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main() -> int:
|
|
12
|
+
"""
|
|
13
|
+
Main entry point for the REPL.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
Exit code (0 for success, 1 for error)
|
|
17
|
+
"""
|
|
18
|
+
try:
|
|
19
|
+
engine = REPLEngine()
|
|
20
|
+
engine.start()
|
|
21
|
+
return 0
|
|
22
|
+
except KeyboardInterrupt:
|
|
23
|
+
print("\n\n👋 Goodbye!")
|
|
24
|
+
return 0
|
|
25
|
+
except Exception as e:
|
|
26
|
+
print(f"❌ Error: {e}", file=sys.stderr)
|
|
27
|
+
return 1
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
sys.exit(main())
|