pdd-cli 0.0.2__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.

Potentially problematic release.


This version of pdd-cli might be problematic. Click here for more details.

Files changed (95) hide show
  1. pdd/__init__.py +0 -0
  2. pdd/auto_deps_main.py +98 -0
  3. pdd/auto_include.py +175 -0
  4. pdd/auto_update.py +73 -0
  5. pdd/bug_main.py +99 -0
  6. pdd/bug_to_unit_test.py +159 -0
  7. pdd/change.py +141 -0
  8. pdd/change_main.py +240 -0
  9. pdd/cli.py +607 -0
  10. pdd/cmd_test_main.py +155 -0
  11. pdd/code_generator.py +117 -0
  12. pdd/code_generator_main.py +66 -0
  13. pdd/comment_line.py +35 -0
  14. pdd/conflicts_in_prompts.py +143 -0
  15. pdd/conflicts_main.py +90 -0
  16. pdd/construct_paths.py +251 -0
  17. pdd/context_generator.py +133 -0
  18. pdd/context_generator_main.py +73 -0
  19. pdd/continue_generation.py +140 -0
  20. pdd/crash_main.py +127 -0
  21. pdd/data/language_format.csv +61 -0
  22. pdd/data/llm_model.csv +15 -0
  23. pdd/detect_change.py +142 -0
  24. pdd/detect_change_main.py +100 -0
  25. pdd/find_section.py +28 -0
  26. pdd/fix_code_loop.py +212 -0
  27. pdd/fix_code_module_errors.py +143 -0
  28. pdd/fix_error_loop.py +216 -0
  29. pdd/fix_errors_from_unit_tests.py +240 -0
  30. pdd/fix_main.py +138 -0
  31. pdd/generate_output_paths.py +194 -0
  32. pdd/generate_test.py +140 -0
  33. pdd/get_comment.py +55 -0
  34. pdd/get_extension.py +52 -0
  35. pdd/get_language.py +41 -0
  36. pdd/git_update.py +84 -0
  37. pdd/increase_tests.py +93 -0
  38. pdd/insert_includes.py +150 -0
  39. pdd/llm_invoke.py +304 -0
  40. pdd/load_prompt_template.py +59 -0
  41. pdd/pdd_completion.fish +72 -0
  42. pdd/pdd_completion.sh +141 -0
  43. pdd/pdd_completion.zsh +418 -0
  44. pdd/postprocess.py +121 -0
  45. pdd/postprocess_0.py +52 -0
  46. pdd/preprocess.py +199 -0
  47. pdd/preprocess_main.py +72 -0
  48. pdd/process_csv_change.py +182 -0
  49. pdd/prompts/auto_include_LLM.prompt +230 -0
  50. pdd/prompts/bug_to_unit_test_LLM.prompt +17 -0
  51. pdd/prompts/change_LLM.prompt +34 -0
  52. pdd/prompts/conflict_LLM.prompt +23 -0
  53. pdd/prompts/continue_generation_LLM.prompt +3 -0
  54. pdd/prompts/detect_change_LLM.prompt +65 -0
  55. pdd/prompts/example_generator_LLM.prompt +10 -0
  56. pdd/prompts/extract_auto_include_LLM.prompt +6 -0
  57. pdd/prompts/extract_code_LLM.prompt +22 -0
  58. pdd/prompts/extract_conflict_LLM.prompt +19 -0
  59. pdd/prompts/extract_detect_change_LLM.prompt +19 -0
  60. pdd/prompts/extract_program_code_fix_LLM.prompt +16 -0
  61. pdd/prompts/extract_prompt_change_LLM.prompt +7 -0
  62. pdd/prompts/extract_prompt_split_LLM.prompt +9 -0
  63. pdd/prompts/extract_prompt_update_LLM.prompt +8 -0
  64. pdd/prompts/extract_promptline_LLM.prompt +11 -0
  65. pdd/prompts/extract_unit_code_fix_LLM.prompt +332 -0
  66. pdd/prompts/extract_xml_LLM.prompt +7 -0
  67. pdd/prompts/fix_code_module_errors_LLM.prompt +17 -0
  68. pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +62 -0
  69. pdd/prompts/generate_test_LLM.prompt +12 -0
  70. pdd/prompts/increase_tests_LLM.prompt +16 -0
  71. pdd/prompts/insert_includes_LLM.prompt +30 -0
  72. pdd/prompts/split_LLM.prompt +94 -0
  73. pdd/prompts/summarize_file_LLM.prompt +11 -0
  74. pdd/prompts/trace_LLM.prompt +30 -0
  75. pdd/prompts/trim_results_LLM.prompt +83 -0
  76. pdd/prompts/trim_results_start_LLM.prompt +45 -0
  77. pdd/prompts/unfinished_prompt_LLM.prompt +18 -0
  78. pdd/prompts/update_prompt_LLM.prompt +19 -0
  79. pdd/prompts/xml_convertor_LLM.prompt +54 -0
  80. pdd/split.py +119 -0
  81. pdd/split_main.py +103 -0
  82. pdd/summarize_directory.py +212 -0
  83. pdd/trace.py +135 -0
  84. pdd/trace_main.py +108 -0
  85. pdd/track_cost.py +102 -0
  86. pdd/unfinished_prompt.py +114 -0
  87. pdd/update_main.py +96 -0
  88. pdd/update_prompt.py +115 -0
  89. pdd/xml_tagger.py +122 -0
  90. pdd_cli-0.0.2.dist-info/LICENSE +7 -0
  91. pdd_cli-0.0.2.dist-info/METADATA +225 -0
  92. pdd_cli-0.0.2.dist-info/RECORD +95 -0
  93. pdd_cli-0.0.2.dist-info/WHEEL +5 -0
  94. pdd_cli-0.0.2.dist-info/entry_points.txt +2 -0
  95. pdd_cli-0.0.2.dist-info/top_level.txt +1 -0
pdd/fix_main.py ADDED
@@ -0,0 +1,138 @@
1
+ import sys
2
+ from typing import Tuple, Optional
3
+ import click
4
+ from rich import print as rprint
5
+
6
+ from .construct_paths import construct_paths
7
+ from .fix_errors_from_unit_tests import fix_errors_from_unit_tests
8
+ from .fix_error_loop import fix_error_loop
9
+
10
+ def fix_main(
11
+ ctx: click.Context,
12
+ prompt_file: str,
13
+ code_file: str,
14
+ unit_test_file: str,
15
+ error_file: str,
16
+ output_test: Optional[str],
17
+ output_code: Optional[str],
18
+ output_results: Optional[str],
19
+ loop: bool,
20
+ verification_program: Optional[str],
21
+ max_attempts: int,
22
+ budget: float,
23
+ auto_submit: bool
24
+ ) -> Tuple[bool, str, str, int, float, str]:
25
+ """
26
+ Main function to fix errors in code and unit tests.
27
+
28
+ Args:
29
+ ctx: Click context containing command-line parameters
30
+ prompt_file: Path to the prompt file that generated the code
31
+ code_file: Path to the code file to be fixed
32
+ unit_test_file: Path to the unit test file
33
+ error_file: Path to the error log file
34
+ output_test: Path to save the fixed unit test file
35
+ output_code: Path to save the fixed code file
36
+ output_results: Path to save the fix results
37
+ loop: Whether to use iterative fixing process
38
+ verification_program: Path to program that verifies code correctness
39
+ max_attempts: Maximum number of fix attempts
40
+ budget: Maximum cost allowed for fixing
41
+ auto_submit: Whether to auto-submit example if tests pass
42
+
43
+ Returns:
44
+ Tuple containing:
45
+ - Success status (bool)
46
+ - Fixed unit test code (str)
47
+ - Fixed source code (str)
48
+ - Total number of fix attempts (int)
49
+ - Total cost of operation (float)
50
+ - Name of model used (str)
51
+ """
52
+ # Check verification program requirement before any file operations
53
+ if loop and not verification_program:
54
+ raise click.UsageError("--verification-program is required when using --loop")
55
+
56
+ try:
57
+ # Construct file paths
58
+ input_file_paths = {
59
+ "prompt_file": prompt_file,
60
+ "code_file": code_file,
61
+ "unit_test_file": unit_test_file
62
+ }
63
+ if not loop:
64
+ input_file_paths["error_file"] = error_file
65
+
66
+ command_options = {
67
+ "output_test": output_test,
68
+ "output_code": output_code,
69
+ "output_results": output_results
70
+ }
71
+
72
+ input_strings, output_file_paths, _ = construct_paths(
73
+ input_file_paths=input_file_paths,
74
+ force=ctx.obj.get('force', False),
75
+ quiet=ctx.obj.get('quiet', False),
76
+ command="fix",
77
+ command_options=command_options
78
+ )
79
+
80
+ # Get parameters from context
81
+ strength = ctx.obj.get('strength', 0.9)
82
+ temperature = ctx.obj.get('temperature', 0)
83
+
84
+ if loop:
85
+ # Use fix_error_loop for iterative fixing
86
+ success, fixed_unit_test, fixed_code, attempts, total_cost, model_name = fix_error_loop(
87
+ unit_test_file=unit_test_file,
88
+ code_file=code_file,
89
+ prompt=input_strings["prompt_file"],
90
+ verification_program=verification_program,
91
+ strength=strength,
92
+ temperature=temperature,
93
+ max_attempts=max_attempts,
94
+ budget=budget,
95
+ error_log_file=output_file_paths.get("output_results")
96
+ )
97
+ else:
98
+ # Use fix_errors_from_unit_tests for single-pass fixing
99
+ update_unit_test, update_code, fixed_unit_test, fixed_code, total_cost, model_name = fix_errors_from_unit_tests(
100
+ unit_test=input_strings["unit_test_file"],
101
+ code=input_strings["code_file"],
102
+ prompt=input_strings["prompt_file"],
103
+ error=input_strings["error_file"],
104
+ error_file=output_file_paths.get("output_results"),
105
+ strength=strength,
106
+ temperature=temperature
107
+ )
108
+ success = update_unit_test or update_code
109
+ attempts = 1
110
+
111
+ # Save fixed files
112
+ if fixed_unit_test:
113
+ with open(output_file_paths["output_test"], 'w') as f:
114
+ f.write(fixed_unit_test)
115
+
116
+ if fixed_code:
117
+ with open(output_file_paths["output_code"], 'w') as f:
118
+ f.write(fixed_code)
119
+
120
+ # Provide user feedback
121
+ if not ctx.obj.get('quiet', False):
122
+ rprint(f"[bold]{'Success' if success else 'Failed'} to fix errors[/bold]")
123
+ rprint(f"[bold]Total attempts:[/bold] {attempts}")
124
+ rprint(f"[bold]Total cost:[/bold] ${total_cost:.6f}")
125
+ rprint(f"[bold]Model used:[/bold] {model_name}")
126
+ if success:
127
+ rprint("[bold green]Fixed files saved:[/bold green]")
128
+ rprint(f" Test file: {output_file_paths['output_test']}")
129
+ rprint(f" Code file: {output_file_paths['output_code']}")
130
+ if output_file_paths.get("output_results"):
131
+ rprint(f" Results file: {output_file_paths['output_results']}")
132
+
133
+ return success, fixed_unit_test, fixed_code, attempts, total_cost, model_name
134
+
135
+ except Exception as e:
136
+ if not ctx.obj.get('quiet', False):
137
+ rprint(f"[bold red]Error:[/bold red] {str(e)}")
138
+ sys.exit(1)
@@ -0,0 +1,194 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ def generate_output_paths(command, output_locations, basename, language, file_extension):
5
+ """
6
+ Generates output filenames based on command, output_locations, basename, language, and file_extension.
7
+
8
+ Args:
9
+ command (str): The command being executed.
10
+ output_locations (dict): Dictionary of output locations specified by the user.
11
+ basename (str): The base name of the file.
12
+ language (str): The programming language.
13
+ file_extension (str): The file extension, including the leading dot (e.g., ".py").
14
+
15
+ Returns:
16
+ dict: A dictionary containing the generated output filenames with full paths.
17
+ """
18
+ output_paths = {}
19
+ default_keys = {
20
+ 'generate': ['output'],
21
+ 'example': ['output'],
22
+ 'test': ['output'],
23
+ 'preprocess': ['output'],
24
+ 'fix': ['output_test', 'output_code', 'output_results'],
25
+ 'split': ['output_sub', 'output_modified'],
26
+ 'change': ['output'],
27
+ 'update': ['output'],
28
+ 'detect': ['output'],
29
+ 'conflicts': ['output'],
30
+ 'crash': ['output', 'output_program'],
31
+ 'trace': ['output'],
32
+ 'bug': ['output'],
33
+ 'auto-deps': ['output']
34
+ }
35
+
36
+ # Ensure output_locations has all necessary keys for the given command
37
+ for key in default_keys.get(command, []):
38
+ if key not in output_locations:
39
+ output_locations[key] = None
40
+
41
+ if command == 'generate':
42
+ output_paths['output'] = get_output_path(
43
+ output_locations.get('output'),
44
+ 'PDD_GENERATE_OUTPUT_PATH',
45
+ f"{basename}{file_extension}"
46
+ )
47
+ elif command == 'example':
48
+ output_paths['output'] = get_output_path(
49
+ output_locations.get('output'),
50
+ 'PDD_EXAMPLE_OUTPUT_PATH',
51
+ f"{basename}_example{file_extension}"
52
+ )
53
+ elif command == 'test':
54
+ output_paths['output'] = get_output_path(
55
+ output_locations.get('output'),
56
+ 'PDD_TEST_OUTPUT_PATH',
57
+ f"test_{basename}{file_extension}"
58
+ )
59
+ elif command == 'preprocess':
60
+ output_paths['output'] = get_output_path(
61
+ output_locations.get('output'),
62
+ 'PDD_PREPROCESS_OUTPUT_PATH',
63
+ f"{basename}_{language}_preprocessed.prompt"
64
+ )
65
+ elif command == 'fix':
66
+ output_paths['output_test'] = get_output_path(
67
+ output_locations.get('output_test'),
68
+ 'PDD_FIX_TEST_OUTPUT_PATH',
69
+ f"test_{basename}_fixed{file_extension}"
70
+ )
71
+ output_paths['output_code'] = get_output_path(
72
+ output_locations.get('output_code'),
73
+ 'PDD_FIX_CODE_OUTPUT_PATH',
74
+ f"{basename}_fixed{file_extension}"
75
+ )
76
+ output_paths['output_results'] = get_output_path(
77
+ output_locations.get('output_results'),
78
+ 'PDD_FIX_RESULTS_OUTPUT_PATH',
79
+ f"{basename}_fix_results.log"
80
+ )
81
+ elif command == 'split':
82
+ output_paths['output_sub'] = get_output_path(
83
+ output_locations.get('output_sub'),
84
+ 'PDD_SPLIT_SUB_PROMPT_OUTPUT_PATH',
85
+ f"sub_{basename}.prompt"
86
+ )
87
+ output_paths['output_modified'] = get_output_path(
88
+ output_locations.get('output_modified'),
89
+ 'PDD_SPLIT_MODIFIED_PROMPT_OUTPUT_PATH',
90
+ f"modified_{basename}.prompt"
91
+ )
92
+ elif command == 'change':
93
+ output_paths['output'] = get_output_path(
94
+ output_locations.get('output'),
95
+ 'PDD_CHANGE_OUTPUT_PATH',
96
+ f"modified_{basename}.prompt"
97
+ )
98
+ elif command == 'update':
99
+ output_paths['output'] = get_output_path(
100
+ output_locations.get('output'),
101
+ 'PDD_UPDATE_OUTPUT_PATH',
102
+ f"modified_{basename}.prompt"
103
+ )
104
+ elif command == 'detect':
105
+ output_paths['output'] = get_output_path(
106
+ output_locations.get('output'),
107
+ 'PDD_DETECT_OUTPUT_PATH',
108
+ f"{basename}_detect.csv"
109
+ )
110
+ elif command == 'conflicts':
111
+ output_paths['output'] = get_output_path(
112
+ output_locations.get('output'),
113
+ 'PDD_CONFLICTS_OUTPUT_PATH',
114
+ f"{basename}_conflict.csv"
115
+ )
116
+ elif command == 'crash':
117
+ output_paths['output'] = get_output_path(
118
+ output_locations.get('output'),
119
+ 'PDD_CRASH_OUTPUT_PATH',
120
+ f"{basename}_fixed{file_extension}"
121
+ )
122
+ output_paths['output_program'] = get_output_path(
123
+ output_locations.get('output_program'),
124
+ 'PDD_CRASH_PROGRAM_OUTPUT_PATH',
125
+ f"{basename}_fixed{file_extension}"
126
+ )
127
+ elif command == 'trace':
128
+ output_paths['output'] = get_output_path(
129
+ output_locations.get('output'),
130
+ 'PDD_TRACE_OUTPUT_PATH',
131
+ f"{basename}_trace_results.log"
132
+ )
133
+ elif command == 'bug':
134
+ output_paths['output'] = get_output_path(
135
+ output_locations.get('output'),
136
+ 'PDD_BUG_OUTPUT_PATH',
137
+ f"test_{basename}_bug{file_extension}"
138
+ )
139
+ elif command == 'auto-deps':
140
+ output_paths['output'] = get_output_path(
141
+ output_locations.get('output'),
142
+ 'PDD_AUTO_DEPS_OUTPUT_PATH',
143
+ f"{basename}_with_deps.prompt"
144
+ )
145
+ else:
146
+ raise ValueError(f"Invalid command: {command}")
147
+
148
+ return output_paths
149
+
150
+ def get_output_path(user_path, env_var, default_filename):
151
+ """
152
+ Determines the output path based on user input, environment variables, and default behavior.
153
+ """
154
+ if user_path:
155
+ # Check if user_path is a directory
156
+ try:
157
+ # A path is considered a directory if:
158
+ # 1. It ends with a separator
159
+ # 2. It exists and is a directory
160
+ # 3. It doesn't contain a file extension
161
+ is_dir = (user_path.endswith(os.sep) or
162
+ (os.path.exists(user_path) and os.path.isdir(user_path)) or
163
+ not os.path.splitext(user_path)[1])
164
+ except (TypeError, ValueError):
165
+ is_dir = user_path.endswith(os.sep)
166
+
167
+ # If it's a directory, join with default filename
168
+ if is_dir:
169
+ path = os.path.join(user_path.rstrip(os.sep), default_filename)
170
+ else:
171
+ path = user_path
172
+
173
+ # Create parent directory if needed
174
+ try:
175
+ parent_dir = os.path.dirname(path)
176
+ if parent_dir:
177
+ Path(parent_dir).mkdir(parents=True, exist_ok=True)
178
+ except (OSError, PermissionError):
179
+ # If we can't create the directory, just return the path
180
+ pass
181
+ return path
182
+ else:
183
+ env_path = os.environ.get(env_var)
184
+ if env_path:
185
+ path = os.path.join(env_path, default_filename)
186
+ try:
187
+ # Create parent directory if needed
188
+ Path(env_path).mkdir(parents=True, exist_ok=True)
189
+ except (OSError, PermissionError):
190
+ # If we can't create the directory, just return the path
191
+ pass
192
+ return path
193
+ else:
194
+ return default_filename
pdd/generate_test.py ADDED
@@ -0,0 +1,140 @@
1
+ from typing import Tuple, Optional
2
+ from rich import print
3
+ from rich.markdown import Markdown
4
+ from rich.console import Console
5
+ from .load_prompt_template import load_prompt_template
6
+ from .preprocess import preprocess
7
+ from .llm_invoke import llm_invoke
8
+ from .unfinished_prompt import unfinished_prompt
9
+ from .continue_generation import continue_generation
10
+ from .postprocess import postprocess
11
+
12
+ console = Console()
13
+
14
+ def generate_test(
15
+ prompt: str,
16
+ code: str,
17
+ strength: float,
18
+ temperature: float,
19
+ language: str,
20
+ verbose: bool = False
21
+ ) -> Tuple[str, float, str]:
22
+ """
23
+ Generate a unit test from a code file using LLM.
24
+
25
+ Args:
26
+ prompt (str): The prompt that generated the code file.
27
+ code (str): The code to generate a unit test from.
28
+ strength (float): The strength of the LLM model (0-1).
29
+ temperature (float): The temperature of the LLM model.
30
+ language (str): The programming language for the unit test.
31
+ verbose (bool): Whether to print detailed information.
32
+
33
+ Returns:
34
+ Tuple[str, float, str]: (unit_test, total_cost, model_name)
35
+ """
36
+ total_cost = 0.0
37
+ model_name = ""
38
+
39
+ try:
40
+ # Step 1: Load prompt template
41
+ template = load_prompt_template("generate_test_LLM")
42
+ if not template:
43
+ raise ValueError("Failed to load generate_test_LLM prompt template")
44
+
45
+ # Step 2: Preprocess template
46
+ processed_template = preprocess(template, recursive=False, double_curly_brackets=False)
47
+ processed_prompt = preprocess(prompt, recursive=False, double_curly_brackets=False)
48
+
49
+ # Step 3: Run through LLM
50
+ input_json = {
51
+ "prompt_that_generated_code": processed_prompt,
52
+ "code": code,
53
+ "language": language
54
+ }
55
+
56
+ if verbose:
57
+ console.print("[bold blue]Generating unit test...[/bold blue]")
58
+
59
+ response = llm_invoke(
60
+ prompt=processed_template,
61
+ input_json=input_json,
62
+ strength=strength,
63
+ temperature=temperature,
64
+ verbose=verbose
65
+ )
66
+
67
+ total_cost += response['cost']
68
+ model_name = response['model_name']
69
+ result = response['result']
70
+
71
+ if verbose:
72
+ console.print(Markdown(result))
73
+ console.print(f"[bold green]Initial generation cost: ${total_cost:.6f}[/bold green]")
74
+
75
+ # Step 4: Check if generation is complete
76
+ last_600_chars = result[-600:] if len(result) > 600 else result
77
+ reasoning, is_finished, check_cost, check_model = unfinished_prompt(
78
+ prompt_text=last_600_chars,
79
+ strength=0.895,
80
+ temperature=temperature,
81
+ verbose=verbose
82
+ )
83
+ total_cost += check_cost
84
+
85
+ if not is_finished:
86
+ if verbose:
87
+ console.print("[bold yellow]Generation incomplete. Continuing...[/bold yellow]")
88
+
89
+ continued_result, continue_cost, continue_model = continue_generation(
90
+ formatted_input_prompt=processed_template,
91
+ llm_output=result,
92
+ strength=strength,
93
+ temperature=temperature,
94
+ verbose=verbose
95
+ )
96
+ total_cost += continue_cost
97
+ result = continued_result
98
+ model_name = continue_model
99
+
100
+ # Process the final result
101
+ processed_result, post_cost, post_model = postprocess(
102
+ result,
103
+ language=language,
104
+ strength=0.895,
105
+ temperature=temperature,
106
+ verbose=verbose
107
+ )
108
+ total_cost += post_cost
109
+
110
+ # Step 5: Print total cost if verbose
111
+ if verbose:
112
+ console.print(f"[bold green]Total cost: ${total_cost:.6f}[/bold green]")
113
+ console.print(f"[bold blue]Final model used: {model_name}[/bold blue]")
114
+
115
+ # Step 6: Return results
116
+ return processed_result, total_cost, model_name
117
+
118
+ except Exception as e:
119
+ console.print(f"[bold red]Error: {str(e)}[/bold red]")
120
+ raise
121
+
122
+
123
+ def _validate_inputs(
124
+ prompt: str,
125
+ code: str,
126
+ strength: float,
127
+ temperature: float,
128
+ language: str
129
+ ) -> None:
130
+ """Validate input parameters."""
131
+ if not prompt or not isinstance(prompt, str):
132
+ raise ValueError("Prompt must be a non-empty string")
133
+ if not code or not isinstance(code, str):
134
+ raise ValueError("Code must be a non-empty string")
135
+ if not isinstance(strength, float) or not 0 <= strength <= 1:
136
+ raise ValueError("Strength must be a float between 0 and 1")
137
+ if not isinstance(temperature, float):
138
+ raise ValueError("Temperature must be a float")
139
+ if not language or not isinstance(language, str):
140
+ raise ValueError("Language must be a non-empty string")
pdd/get_comment.py ADDED
@@ -0,0 +1,55 @@
1
+ # To achieve the task of writing a Python function `get_comment` that returns the comment character(s) associated with a given programming language, we need to follow the steps outlined in your description. Here's how you can implement this:
2
+
3
+ # ```python
4
+ import os
5
+ import csv
6
+
7
+ def get_comment(language):
8
+ # Step 1: Load environment variables to get the path to the CSV file
9
+ pdd_path = os.getenv('PDD_PATH')
10
+ if not pdd_path:
11
+ return 'del' # Return 'del' if the environment variable is not set
12
+
13
+ csv_file_path = os.path.join(pdd_path, 'data', 'language_format.csv')
14
+
15
+ # Step 2: Lower case the language string
16
+ language = language.lower()
17
+
18
+ try:
19
+ # Step 3: Open the CSV file and look up the comment character(s)
20
+ with open(csv_file_path, mode='r', newline='') as csvfile:
21
+ reader = csv.DictReader(csvfile)
22
+ for row in reader:
23
+ if row['language'].lower() == language:
24
+ comment = row['comment']
25
+ # Step 4: Check if the comment character(s) is valid
26
+ if comment:
27
+ return comment
28
+ else:
29
+ return 'del'
30
+ except FileNotFoundError:
31
+ return 'del' # Return 'del' if the file is not found
32
+ except Exception as e:
33
+ return 'del' # Return 'del' for any other exceptions
34
+
35
+ return 'del' # Return 'del' if the language is not found
36
+
37
+ # Example usage:
38
+ # Assuming the environment variable PDD_PATH is set correctly
39
+ # print(get_comment('Python')) # Output: #
40
+ # print(get_comment('Java')) # Output: //
41
+ # ```
42
+
43
+ # ### Explanation:
44
+
45
+ # 1. **Environment Variable**: The function first retrieves the `PDD_PATH` environment variable to locate the CSV file. If the environment variable is not set, it returns `'del'`.
46
+
47
+ # 2. **Case Insensitivity**: The input language string is converted to lowercase to ensure case-insensitive comparison.
48
+
49
+ # 3. **CSV Reading**: The function reads the CSV file using Python's `csv.DictReader`, which allows accessing each row as a dictionary. It checks if the lowercase version of the language matches any entry in the CSV.
50
+
51
+ # 4. **Validation**: If a match is found, it checks if the comment character(s) is valid (i.e., not an empty string). If valid, it returns the comment character(s); otherwise, it returns `'del'`.
52
+
53
+ # 5. **Error Handling**: The function handles potential errors such as file not found or other exceptions by returning `'del'`.
54
+
55
+ # This implementation assumes that the CSV file is correctly formatted and that the environment variable `PDD_PATH` is set to the correct path.
pdd/get_extension.py ADDED
@@ -0,0 +1,52 @@
1
+ # To implement the `get_extension` function as described, we will follow the steps outlined in your request. We'll use the `pandas` library to read the CSV file, and we'll handle the environment variable for the file path. Here's how you can implement this function:
2
+
3
+ # ```python
4
+ import os
5
+ import pandas as pd
6
+
7
+ def get_extension(language):
8
+ # Step 1: Load the environment variable PDD_PATH
9
+ pdd_path = os.getenv('PDD_PATH')
10
+ if not pdd_path:
11
+ raise ValueError("Environment variable PDD_PATH is not set.")
12
+
13
+ # Construct the full path to the CSV file
14
+ csv_file_path = os.path.join(pdd_path, 'data', 'language_format.csv')
15
+
16
+ # Step 2: Lower case the language string
17
+ language_lower = language.lower()
18
+
19
+ # Step 3: Load the CSV file and look up the file extension
20
+ try:
21
+ df = pd.read_csv(csv_file_path)
22
+ except FileNotFoundError:
23
+ raise FileNotFoundError(f"The file {csv_file_path} does not exist.")
24
+
25
+ # Check if the language exists in the DataFrame
26
+ row = df[df['language'].str.lower() == language_lower]
27
+
28
+ # Step 4: Return the file extension or an empty string if not found
29
+ if not row.empty:
30
+ extension = row['extension'].values[0]
31
+ return extension if isinstance(extension, str) and extension else ''
32
+
33
+ return ''
34
+
35
+ # Example usage:
36
+ # Assuming the environment variable PDD_PATH is set correctly
37
+ # print(get_extension('Python')) # Output: .py
38
+ # ```
39
+
40
+ # ### Explanation of the Code:
41
+ # 1. **Environment Variable**: We use `os.getenv` to retrieve the `PDD_PATH` environment variable. If it's not set, we raise a `ValueError`.
42
+ # 2. **Lowercase Language**: The input language string is converted to lowercase to ensure case-insensitive comparison.
43
+ # 3. **Load CSV**: We use `pandas` to read the CSV file. If the file is not found, we raise a `FileNotFoundError`.
44
+ # 4. **Lookup**: We filter the DataFrame to find the row corresponding to the given language. If found, we check if the extension is a valid string and return it; otherwise, we return an empty string.
45
+ # 5. **Return Value**: If the language is not found, we return an empty string.
46
+
47
+ # ### Note:
48
+ # - Make sure to have the `pandas` library installed in your Python environment. You can install it using pip:
49
+ # ```bash
50
+ # pip install pandas
51
+ # ```
52
+ # - Ensure that the CSV file is structured correctly and located at the specified path.
pdd/get_language.py ADDED
@@ -0,0 +1,41 @@
1
+ import os
2
+ import csv
3
+
4
+ def get_language(extension: str) -> str:
5
+ """
6
+ Determines the programming language associated with a given file extension.
7
+
8
+ Args:
9
+ extension (str): The file extension to look up.
10
+
11
+ Returns:
12
+ str: The name of the programming language or an empty string if not found.
13
+
14
+ Raises:
15
+ ValueError: If PDD_PATH environment variable is not set.
16
+ """
17
+ # Step 1: Load environment variable PDD_PATH
18
+ pdd_path = os.environ.get('PDD_PATH')
19
+ if not pdd_path:
20
+ raise ValueError("PDD_PATH environment variable is not set")
21
+
22
+ # Step 2: Ensure the extension starts with a dot and convert to lowercase
23
+ if not extension.startswith('.'):
24
+ extension = '.' + extension
25
+ extension = extension.lower()
26
+
27
+ # Step 3 & 4: Look up the language name and handle exceptions
28
+ csv_path = os.path.join(pdd_path, 'data', 'language_format.csv')
29
+ try:
30
+ with open(csv_path, 'r') as csvfile:
31
+ reader = csv.DictReader(csvfile)
32
+ for row in reader:
33
+ if row['extension'].lower() == extension:
34
+ language = row['language'].strip()
35
+ return language if language else ''
36
+ except FileNotFoundError:
37
+ print(f"CSV file not found at {csv_path}")
38
+ except csv.Error as e:
39
+ print(f"Error reading CSV file: {e}")
40
+
41
+ return '' # Return empty string if extension not found or any error occurs