Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion _version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.2.12"
__version__ = "0.2.14"
8 changes: 4 additions & 4 deletions codeplain_REST_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,11 @@ def render_functional_requirement(
run_state: RunState,
) -> dict[str, str]:
"""
Renders the content of a functional requirement based on the provided ID,
Renders the content of a functionality based on the provided ID,
corresponding sections from a Plain document, and existing files' content.

Args:
frid (str): The unique identifier for the functional requirement to be rendered.
frid (str): The unique identifier for the functionality to be rendered.
plain_source_tree (dict): A dictionary containing the plain source tree.
linked_resources (dict): A dictionary where the keys represent filenames of linked
resources and the values are dictionaries containing
Expand All @@ -170,7 +170,7 @@ def render_functional_requirement(
and the values are the content of those files.
memory_files_content (dict): A dictionary where the keys represent memory filenames
and the values are the content of those files.
module_name (str): The name of the module to render the functional requirement for.
module_name (str): The name of the module to render the functionality for.
required_modules (dict): A dictionary where the keys represent module names
and the values are lists of functionalities implemented in those modules.
run_state (RunState): The current state of the rendering process.
Expand Down Expand Up @@ -390,7 +390,7 @@ def render_acceptance_tests(
Renders acceptance tests based on the provided parameters.

Args:
frid (str): The unique identifier for the functional requirement.
frid (str): The unique identifier for the functionality.
plain_source_tree (dict): A dictionary containing the plain source tree.
linked_resources (dict): A dictionary where the keys represent resource names
and the values are the content of those resources.
Expand Down
4 changes: 2 additions & 2 deletions docs/plain2code_cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ options:
Name of the log file. Defaults to 'codeplain.log'.Always resolved relative to the plain file directory.If file on this path already exists, the already existing log file will be overwritten by the
current logs.
--render-range RENDER_RANGE
Specify a range of functional requirements to render (e.g. `1` , `2`, `3`). Use comma to separate start and end IDs. If only one ID is provided, only that requirement is rendered. Range is
Specify a range of functionalities to render (e.g. `1` , `2`, `3`). Use comma to separate start and end IDs. If only one ID is provided, only that requirement is rendered. Range is
inclusive of both start and end IDs.
--render-from RENDER_FROM
Continue generation starting from this specific functional requirement (e.g. `2`). The requirement with this ID will be included in the output. The ID must match one of the functional requirements
Continue generation starting from this specific functionality (e.g. `2`). The requirement with this ID will be included in the output. The ID must match one of the functionalities
in your plain file.
--force-render Force re-render of all the required modules.
--unittests-script UNITTESTS_SCRIPT
Expand Down
14 changes: 6 additions & 8 deletions git_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,20 @@
import file_utils
from plain2code_exceptions import InvalidGitRepositoryError

FUNCTIONAL_REQUIREMENT_IMPLEMENTED_COMMIT_MESSAGE = (
"[Codeplain] Implemented code and unit tests for functional requirement {}"
)
REFACTORED_CODE_COMMIT_MESSAGE = "[Codeplain] Refactored code after implementing functional requirement {}"
FUNCTIONAL_REQUIREMENT_IMPLEMENTED_COMMIT_MESSAGE = "[Codeplain] Implemented code and unit tests for functionality {}"
REFACTORED_CODE_COMMIT_MESSAGE = "[Codeplain] Refactored code after implementing functionality {}"
CONFORMANCE_TESTS_PASSED_COMMIT_MESSAGE = (
"[Codeplain] Fixed issues in the implementation code identified during conformance testing"
)

# Following messages are used as checkpoints in the git history
# Changing them will break backwards compatibility so change them with care
FUNCTIONAL_REQUIREMENT_FINISHED_COMMIT_MESSAGE = "[Codeplain] Functional requirement ID (FRID):{} fully implemented"
FUNCTIONAL_REQUIREMENT_FINISHED_COMMIT_MESSAGE = "[Codeplain] functionality ID (FRID):{} fully implemented"
INITIAL_COMMIT_MESSAGE = "[Codeplain] Initial module commit"
BASE_FOLDER_COMMIT_MESSAGE = "[Codeplain] Initialize build with Base Folder content"


RENDERED_FRID_MESSAGE = "Changes related to Functional requirement ID (FRID): {}"
RENDERED_FRID_MESSAGE = "Changes related to functionality ID (FRID): {}"
MODULE_NAME_MESSAGE = "Module name: {}"
RENDER_ID_MESSAGE = "Render ID: {}"

Expand Down Expand Up @@ -244,7 +242,7 @@ def diff(repo_path: Union[str, os.PathLike], previous_frid: str = None) -> dict:

Args:
repo_path (str | os.PathLike): Path to the git repository
previous_frid (str): Functional requirement ID (FRID) of the previous commit
previous_frid (str): functionality ID (FRID) of the previous commit

Returns:
dict: Dictionary with file names as keys and their clean diff strings as values
Expand Down Expand Up @@ -285,7 +283,7 @@ def _get_commit_with_frid(repo: Repo, frid: str, module_name: Optional[str] = No

Args:
repo (Repo): Git repository object
frid (str): Functional requirement ID
frid (str): functionality ID
module_name (Optional[str]): Module name to filter by. If provided, only returns
commits that have both the FRID and module name.

Expand Down
8 changes: 3 additions & 5 deletions plain2code.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,22 +86,20 @@ def _get_frids_range(plain_source, start, end=None):
start = str(start)

if start not in frids:
raise InvalidFridArgument(f"Invalid start functional requirement ID: {start}. Valid IDs are: {frids}.")
raise InvalidFridArgument(f"Invalid start functionality ID: {start}. Valid IDs are: {frids}.")

if end is not None:
end = str(end)
if end not in frids:
raise InvalidFridArgument(f"Invalid end functional requirement ID: {end}. Valid IDs are: {frids}.")
raise InvalidFridArgument(f"Invalid end functionality ID: {end}. Valid IDs are: {frids}.")

end_idx = frids.index(end) + 1
else:
end_idx = len(frids)

start_idx = frids.index(start)
if start_idx >= end_idx:
raise InvalidFridArgument(
f"Start functional requirement ID: {start} must be before end functional requirement ID: {end}."
)
raise InvalidFridArgument(f"Start functionality ID: {start} must be before end functionality ID: {end}.")

return frids[start_idx:end_idx]

Expand Down
23 changes: 11 additions & 12 deletions plain2code_arguments.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import argparse
import os
import re

from plain2code_console import console
from plain2code_exceptions import AmbiguousConfigFileError
Expand Down Expand Up @@ -59,14 +58,14 @@ def non_empty_string(s):


def frid_string(s):
"""Validate that the string contains only numbers separated by dots."""
"""Validate that the FRID is an integer."""
if not s:
raise argparse.ArgumentTypeError("The functional requirement ID cannot be empty.")
raise argparse.ArgumentTypeError("The functionality ID cannot be empty.")

if not re.match(r"^\d+(\.\d+)*$", s):
raise argparse.ArgumentTypeError(
"Functional requirement ID string must contain only numbers optionally separated by dots (e.g. '1', '1.2.3')"
)
try:
int(s)
except ValueError:
raise argparse.ArgumentTypeError("Functionality ID string must be a number.")
return s


Expand All @@ -77,7 +76,7 @@ def frid_range_string(s):

parts = s.split(",")
if len(parts) > 2:
raise argparse.ArgumentTypeError("Range must contain at most two functional requirement IDs separated by comma")
raise argparse.ArgumentTypeError("Range must contain at most two functionality IDs separated by comma")

for part in parts:
frid_string(part)
Expand Down Expand Up @@ -205,15 +204,15 @@ def create_parser():
render_range_group.add_argument(
"--render-range",
type=frid_range_string,
help="Specify a range of functional requirements to render (e.g. `1` , `2`, `3`). "
"Use comma to separate start and end IDs. If only one ID is provided, only that requirement is rendered. "
help="Specify a range of functionalities to render (e.g. `1` , `2`, `3`). "
"Use comma to separate start and end IDs. If only one ID is provided, only that functionality is rendered. "
"Range is inclusive of both start and end IDs.",
)
render_range_group.add_argument(
"--render-from",
type=frid_string,
help="Continue generation starting from this specific functional requirement (e.g. `2`). "
"The requirement with this ID will be included in the output. The ID must match one of the functional requirements in your plain file.",
help="Continue generation starting from this specific functionality (e.g. `2`). "
"The requirement with this ID will be included in the output. The ID must match one of the functionalities in your plain file.",
)

parser.add_argument(
Expand Down
6 changes: 3 additions & 3 deletions plain2code_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
AMBIGUITY_CAUSES = {
"reference_resource_ambiguity": "Ambiguity is in the reference resources",
"definition_ambiguity": "Ambiguity is in the definitions",
"non_functional_requirement_ambiguity": "Ambiguity is in the non-functional requirements",
"functional_requirement_ambiguity": "Ambiguity is in the functional requirements",
"non_functional_requirement_ambiguity": "Ambiguity is in the implementation reqs",
"functional_requirement_ambiguity": "Ambiguity is in the functionality",
"other": "Ambiguity in the other parts of the specification",
}

Expand All @@ -23,7 +23,7 @@ def print_dry_run_output(plain_source_tree: dict, render_range: Optional[list[st
functional_requirement_text = specifications[plain_spec.FUNCTIONAL_REQUIREMENTS][-1]
console.info(
"-------------------------------------\n"
f"Rendering functional requirement {frid}\n"
f"Rendering functionality {frid}\n"
f"{functional_requirement_text}\n"
"-------------------------------------\n"
)
Expand Down
22 changes: 11 additions & 11 deletions plain_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def check_if_functional_requirements_are_specified(plain_source, non_functional_

found_functional_requirements = plain_spec.FUNCTIONAL_REQUIREMENTS in plain_source
if found_functional_requirements and len(non_functional_requirements) == 0:
raise PlainSyntaxError("Syntax error: Functional requirement with no non-functional requirements specified.")
raise PlainSyntaxError("Syntax error: functionality with no implementation reqs specified.")

return found_functional_requirements

Expand Down Expand Up @@ -183,16 +183,16 @@ def _is_acceptance_test_heading(token) -> tuple[bool, str | None]:

def _process_single_acceptance_test_requirement(functional_requirement: mistletoe.block_token.ListItem):
"""
Process a single functional requirement to extract acceptance tests.
Process a single functionality to extract acceptance tests.

Expected functional_requirement properties:
- Is a list item
- If acceptance tests are specified, it has 3 children:
- List item element with functional requirement instructions/text
- List item element with functionality instructions/text
- Paragraph with `***Acceptance test:***` heading
- List of acceptance tests
- If acceptance tests are not specified, it has 1 child:
- List item element with functional requirement instructions/text
- List item element with functionality instructions/text
"""
new_children = []
functional_requirement_children = iter(functional_requirement.children)
Expand All @@ -205,15 +205,15 @@ def _process_single_acceptance_test_requirement(functional_requirement: mistleto
if acceptance_test_heading_problem:
# Handle the case when the heading is not valid. This case includes cases such as:
# - Writing `acceptance test` instead of `acceptance tests` (or any other syntax diffs).
# - Instead of specifying `acceptance tests` below the functional requirement, creator of the plain file
# - Instead of specifying `acceptance tests` below the functionality, creator of the plain file
# might have specified some other building block (e.g. `implementation reqs`)
raise PlainSyntaxError(acceptance_test_heading_problem)

if is_acceptance_test_heading:
if acceptance_tests_found_already:
# Handle edge case of duplicated ***acceptance tests*** heading
raise PlainSyntaxError(
f"Syntax error at line {functional_requirement_child.line_number}: Duplicate 'acceptance tests' heading found within the same functional requirement. Only one block of acceptance tests is allowed per functional requirement."
f"Syntax error at line {functional_requirement_child.line_number}: Duplicate 'acceptance tests' heading found within the same functionality. Only one block of acceptance tests is allowed per functionality."
)

try:
Expand All @@ -237,20 +237,20 @@ def _process_single_acceptance_test_requirement(functional_requirement: mistleto
# Regular token, keep it
new_children.append(functional_requirement_child)

# Assign the children property to all the children of the functional requirement from previous, with exception
# Assign the children property to all the children of the functionality from previous, with exception
# of those we parsed as acceptance tests
functional_requirement.children = type(functional_requirement.children)(new_children)


def process_acceptance_tests(plain_source):
# Early returns for cases without functional requirements
# Early returns for cases without functionalities
if plain_spec.FUNCTIONAL_REQUIREMENTS not in plain_source:
return
frs = plain_source[plain_spec.FUNCTIONAL_REQUIREMENTS]
if not hasattr(frs, "children"):
return

# Process each functional requirement
# Process each functionality
for functional_requirement in frs.children:
if not hasattr(functional_requirement, "children"):
continue
Expand Down Expand Up @@ -343,7 +343,7 @@ def process_imports(
)

if check_if_functional_requirements_are_specified(plain_file_parse_result.plain_source, []):
raise PlainSyntaxError("Imported module must not contain functional requirements.")
raise PlainSyntaxError("Imported module must not contain functionalities.")

for specification_heading in plain_file_parse_result.plain_source:
if specification_heading not in plain_spec.ALLOWED_IMPORT_SPECIFICATION_HEADINGS:
Expand Down Expand Up @@ -649,7 +649,7 @@ def plain_file_parser( # noqa: C901
)

if not check_if_functional_requirements_are_specified(plain_file_parse_result.plain_source, []):
raise PlainSyntaxError("Syntax error: No functional requirements specified.")
raise PlainSyntaxError("Syntax error: No functionality specified.")

exported_definitions = process_required_modules(
plain_file_parse_result.required_modules,
Expand Down
6 changes: 3 additions & 3 deletions plain_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def get_next_frid(plain_source_tree, frid):
if temp_frid == frid:
return next(functional_requirements, None)

raise Exception(f"Functional requirement {frid} does not exist.")
raise Exception(f"Functionality {frid} does not exist.")


def get_previous_frid(plain_source_tree, frid):
Expand All @@ -175,7 +175,7 @@ def get_previous_frid(plain_source_tree, frid):

previous_frid = temp_frid

raise Exception(f"Functional requirement {frid} does not exist.")
raise Exception(f"Functionality {frid} does not exist.")


def get_frids_before(plain_source_tree, target_frid: str) -> list[str]:
Expand Down Expand Up @@ -314,7 +314,7 @@ def get_specifications_for_frid(plain_source_tree, frid, replace_code_variables=
replace_code_variables,
)
if result is None:
raise Exception(f"Functional requirement {frid} does not exist.")
raise Exception(f"Functionality {frid} does not exist.")

specifications = {
DEFINITIONS: definitions,
Expand Down
4 changes: 2 additions & 2 deletions render_machine/actions/analyze_specification_ambiguity.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ def execute(self, render_context: RenderContext, _previous_action_payload: Any |
if rendering_analysis:
# TODO: Before this output is exposed to the user, we should check the 'guidance' field using LLM in the same way as we do conflicting requirements.
console.info(
f"Specification ambiguity detected! {AMBIGUITY_CAUSES[rendering_analysis['cause']]} of the functional requirement {render_context.frid_context.frid}."
f"Specification ambiguity detected! {AMBIGUITY_CAUSES[rendering_analysis['cause']]} of the functionality {render_context.frid_context.frid}."
)
console.info(rendering_analysis["guidance"])
else:
console.warning(
f"No specification ambiguity detected for functional requirement {render_context.frid_context.frid}."
f"No specification ambiguity detected for functionality {render_context.frid_context.frid}."
)
return self.SUCCESSFUL_OUTCOME, None
2 changes: 1 addition & 1 deletion render_machine/actions/exit_with_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def execute(self, render_context: RenderContext, previous_action_payload: Any |

if render_context.frid_context is not None:
console.info(
f"To continue rendering from the last successfully rendered functional requirement, provide the [red]--render-from {render_context.frid_context.frid}[/red] flag."
f"To continue rendering from the last successfully rendered functionality, provide the [red]--render-from {render_context.frid_context.frid}[/red] flag."
)

if render_context.run_state.render_id is not None:
Expand Down
Loading
Loading