treescript-builder 0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ """ TreeScript Builder.
2
+ - Previously, and sometimes referred to as FileTreeBuilder.
3
+ Author: DK96-OS 2024 - 2025
4
+ """
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/python
2
+
3
+
4
+ def main():
5
+ # Author: DK96-OS 2024 - 2025
6
+ from treescript_builder.input import validate_input_arguments
7
+ from treescript_builder.tree import build_tree
8
+ from sys import argv
9
+ build_tree(validate_input_arguments(argv[1:]))
10
+
11
+
12
+ if __name__ == "__main__":
13
+ from sys import path
14
+ from pathlib import Path
15
+ # Get the directory of the current file (__file__ is the path to the script being executed)
16
+ current_directory = Path(__file__).resolve().parent.parent
17
+ path.append(str(current_directory)) # Add the directory to sys.path
18
+ main()
File without changes
@@ -0,0 +1,85 @@
1
+ """Data Directory Management.
2
+ Author: DK96-OS 2024 - 2025
3
+ """
4
+ from pathlib import Path
5
+ from sys import exit
6
+
7
+ from treescript_builder.data.tree_data import TreeData
8
+ from treescript_builder.input.string_validation import validate_data_label
9
+
10
+
11
+ class DataDirectory:
12
+ """ Manages Access to the Data Directory.
13
+ - Search for a Data Label, and obtain the Path to the Data File.
14
+ """
15
+
16
+ def __init__(self, data_dir: Path):
17
+ if not isinstance(data_dir, Path) or not data_dir.exists():
18
+ exit('The Data Directory must be a Path that Exists!')
19
+ self._data_dir = data_dir
20
+ # todo: Create a map of used Data Labels
21
+
22
+ def validate_build(self, node: TreeData) -> Path | None:
23
+ """ Determine if the Data File supporting this Tree node is available.
24
+
25
+ Parameters:
26
+ - node (TreeData): The TreeData to validate.
27
+
28
+ Returns:
29
+ Path - The Path to the Data File in the Data Directory.
30
+
31
+ Raises:
32
+ SystemExit - When the Data label is invalid, or the Data File does not exist.
33
+ """
34
+ if node.data_label == '' or node.is_dir:
35
+ return None
36
+ if node.data_label == '!':
37
+ data_path = self._search_label(node.name)
38
+ else:
39
+ data_path = self._search_label(node.data_label)
40
+ if data_path is None:
41
+ exit(f'Data Label ({node.data_label}) not found in Data Directory on line: {node.line_number}')
42
+ return data_path
43
+
44
+ def validate_trim(self, node: TreeData) -> Path | None:
45
+ """ Determine if the File already exists in the Data Directory.
46
+
47
+ Parameters:
48
+ - node (TreeData): The TreeData to validate.
49
+
50
+ Returns:
51
+ Path - The Path to a new File in the Data Directory.
52
+
53
+ Raises:
54
+ SystemExit - When the Data label is invalid, or the Data File already exists.
55
+ """
56
+ # Ensure that the name of the Label is valid
57
+ if node.data_label == '' or node.is_dir:
58
+ return None
59
+ data_label = node.name if node.data_label == '!' else node.data_label
60
+ if not validate_data_label(data_label):
61
+ exit(f'Invalid Data Label on line: {node.line_number}')
62
+ # Check if the Data File already exists
63
+ data_path = self._search_label(data_label)
64
+ if data_path is not None:
65
+ exit(f'Data File already exists!\n({data_label}) on line: {node.line_number}')
66
+ # Create the Data Label Path in the Directory
67
+ return self._data_dir / data_label
68
+
69
+ def _search_label(self, data_label: str) -> Path | None:
70
+ """ Search for a Data Label in this Data Directory.
71
+
72
+ Parameters:
73
+ - data_label (str): The Data Label to search for.
74
+
75
+ Returns:
76
+ Path (optional) - The Path to the Data File, or None.
77
+ """
78
+ if not validate_data_label(data_label):
79
+ return None
80
+ # Find the Data Label File
81
+ data_files = self._data_dir.glob(data_label)
82
+ try:
83
+ return next(data_files)
84
+ except StopIteration as s:
85
+ return None
@@ -0,0 +1,19 @@
1
+ """The Instruction Data in a Tree Operation.
2
+ Author: DK96-OS 2024 - 2025
3
+ """
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class InstructionData:
10
+ """ The Data required to execute the Instruction.
11
+
12
+ **Fields:**
13
+ - is_dir (bool): Whether the Instruction relates to a Directory.
14
+ - path (Path): The Path of the Instruction.
15
+ - data_path (Path?): The Data Directory Path of the Instruction, if applicable. Default: None.
16
+ """
17
+ is_dir: bool
18
+ path: Path
19
+ data_path: Path | None = None
@@ -0,0 +1,86 @@
1
+ """ Path Stack Management.
2
+ Author: DK96-OS 2024 - 2025
3
+ """
4
+ from pathlib import Path
5
+
6
+
7
+ class PathStack:
8
+ """ A Stack of Directory names in a Path.
9
+ """
10
+
11
+ def __init__(self):
12
+ # The Stack of Directories in the Path.
13
+ self._stack = []
14
+
15
+ def push(self, directory_name: str):
16
+ """ Push a directory to the Path Stack.
17
+
18
+ Parameters:
19
+ - directory_name (str): The name of the next directory in the Path Stack.
20
+ """
21
+ self._stack.append(directory_name)
22
+
23
+ def pop(self) -> str | None:
24
+ """
25
+ Pop the top of the Stack, and return the directory name.
26
+
27
+ Returns:
28
+ str : The String removed from the top of the Stack.
29
+ """
30
+ if len(self._stack) <= 0:
31
+ return None
32
+ return self._stack.pop()
33
+
34
+ def join_stack(self) -> Path:
35
+ """
36
+ Combines all elements in the path Stack to form the parent directory.
37
+
38
+ Returns:
39
+ Path representing the current directory.
40
+ """
41
+ if len(self._stack) == 0:
42
+ return Path("./")
43
+ return Path(f"./{'/'.join(self._stack)}/")
44
+
45
+ def create_path(self, filename: str) -> Path:
46
+ """
47
+ Combines all Elements in the Stack and appends a File name.
48
+
49
+ Parameters:
50
+ - filename (str): The name of the file to append to the end of the path.
51
+
52
+ Returns:
53
+ Path : The Path to the file.
54
+ """
55
+ if type(filename) is not str or len(filename) < 1:
56
+ return self.join_stack()
57
+ return self.join_stack() / filename
58
+
59
+ def reduce_depth(self, depth: int) -> bool:
60
+ """
61
+ Reduce the Depth of the Path Stack.
62
+
63
+ Parameters:
64
+ - depth (int): The depth to reduce the stack to.
65
+
66
+ Returns:
67
+ boolean : Whether the Reduction was successful, ie 0 or more Stack pops.
68
+ """
69
+ current_depth = self.get_depth()
70
+ if current_depth < depth or depth < 0:
71
+ return False
72
+ if current_depth == depth:
73
+ return True
74
+ for _ in range(current_depth, depth, -1):
75
+ self._stack.pop()
76
+ return True
77
+
78
+ def get_depth(self) -> int:
79
+ """
80
+ Obtain the current Depth of the Stack.
81
+ The state where the current directory is the path, ie: './' has a depth of 0.
82
+
83
+ Returns:
84
+ int : The number of elements in the Path Stack.
85
+ """
86
+ return len(self._stack)
@@ -0,0 +1,23 @@
1
+ """Tree Node DataClass.
2
+ Author: DK96-OS 2024 - 2025
3
+ """
4
+ from dataclasses import dataclass
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class TreeData:
9
+ """ A DataClass representing a Tree Node.
10
+
11
+ Fields:
12
+ - line_number (int): The line number of this tree node in the TreeScript file.
13
+ - depth (int): The depth in the tree, from the root. Starts at zero.
14
+ - is_dir (bool): Whether this Node is a directory.
15
+ - name (str): The Name of the Tree Node.
16
+ - data_label (str): The Data Label, may be empty string.
17
+ """
18
+
19
+ line_number: int
20
+ depth: int
21
+ is_dir: bool
22
+ name: str
23
+ data_label: str = ''
@@ -0,0 +1,132 @@
1
+ """Tree State.
2
+ A Key component in Tree Validation for Build operations.
3
+ Author: DK96-OS 2024 - 2025
4
+ """
5
+ from pathlib import Path
6
+ from sys import exit
7
+ from typing import Generator
8
+
9
+ from treescript_builder.data.path_stack import PathStack
10
+ from treescript_builder.data.tree_data import TreeData
11
+
12
+
13
+ class TreeState:
14
+ """ Manages the State of the Tree during Validation.
15
+ """
16
+
17
+ def __init__(self):
18
+ self._stack = PathStack()
19
+ self._queue = []
20
+ self._prev_line_number = 0
21
+
22
+ def validate_tree_data(self, node: TreeData) -> int:
23
+ """ Ensure that the next TreeData is valid, relative to current state.
24
+ - Calculate the change in depth, occurring with this TreeData.
25
+
26
+ Parameters:
27
+ - node (TreeData): The next TreeData in the sequence to Validate.
28
+
29
+ Returns:
30
+ int - The difference between the TreeData depth and the TreeState depth.
31
+
32
+ Raises:
33
+ SystemExit - When the TreeData is invalid, relative to the current TreeState.
34
+ """
35
+ self._update_line_number(node.line_number)
36
+ # Calculate the Change in Depth
37
+ if node.depth < 0:
38
+ exit("Invalid Depth Value")
39
+ # Ensure that the depth change is zero or less
40
+ if (delta := node.depth - self.get_current_depth()) > 0:
41
+ exit(f"You have jumped {delta} steps in the tree on line: {node.line_number}")
42
+ return delta
43
+
44
+ def get_current_depth(self) -> int:
45
+ """ Determine the Current Depth of the Tree.
46
+ - Includes Elements in both the Stack and the Queue.
47
+
48
+ Returns:
49
+ int - The total number of elements, combining stack and queue
50
+ """
51
+ return self._stack.get_depth() + len(self._queue)
52
+
53
+ def get_current_path(self) -> Path:
54
+ """ Obtain the Current Path of the Tree.
55
+
56
+ Returns:
57
+ str - A Path equivalent to the current Tree State.
58
+ """
59
+ if len(self._queue) > 0:
60
+ self.process_queue()
61
+ return self._stack.join_stack()
62
+
63
+ def add_to_queue(self, dir_name: str):
64
+ """ Add a directory to the Queue.
65
+
66
+ Parameters:
67
+ - dir_name (str): The name of the Directory to enqueue.
68
+ """
69
+ self._queue.append(dir_name)
70
+
71
+ def add_to_stack(self, dir_name: str):
72
+ """ Add a directory to the Stack.
73
+
74
+ Parameters:
75
+ - dir_name (str): The name of the Directory.
76
+ """
77
+ self._stack.push(dir_name)
78
+
79
+ def process_queue(self) -> Path | None:
80
+ """Process the Directories in the Queue.
81
+ - Adds all directories from the Queue to the Stack.
82
+
83
+ Returns:
84
+ Path? - The whole Path from the Stack.
85
+ """
86
+ if len(self._queue) < 1:
87
+ return None
88
+ for element in self._queue:
89
+ self._stack.push(element)
90
+ self._queue.clear()
91
+ # Return the new Path as a str
92
+ return self._stack.join_stack()
93
+
94
+ def process_stack(self, depth: int) -> Generator[Path, None, None]:
95
+ """ Pop the Stack to the Desired Depth.
96
+
97
+ Parameters:
98
+ - depth (int): The depth to process the stack to.
99
+
100
+ Returns:
101
+ Generator[Path] - Provides a Path for each entry in the Stack.
102
+ """
103
+ if depth < 0:
104
+ exit('Invalid Depth')
105
+ while depth < self._stack.get_depth():
106
+ if (entry := self._stack.pop()) is not None:
107
+ yield self._stack.create_path(entry)
108
+
109
+ def reduce_depth(self, depth: int) -> bool:
110
+ """ Pop an element from the stack.
111
+
112
+ Parameters:
113
+ - depth (int): The Depth to pop the Stack to.
114
+
115
+ Returns:
116
+ bool - Whether the depth reduction succeeded, ie. 0 or more elements popped.
117
+ """
118
+ return self._stack.reduce_depth(depth)
119
+
120
+ def _update_line_number(self, line_number: int):
121
+ """ Validate the Line Number is always increasing.
122
+
123
+ Parameters:
124
+ - line_number (int): The line number from the next TreeData.
125
+
126
+ Raises:
127
+ SystemExit - When the Line Number does not increase.
128
+ """
129
+ if self._prev_line_number < line_number:
130
+ self._prev_line_number = line_number
131
+ else:
132
+ exit("Invalid Tree Data Sequence")
@@ -0,0 +1,28 @@
1
+ """The Input Module.
2
+ - Validate And Format Input Arguments.
3
+ - Read Input Tree String from File.
4
+ Author: DK96-OS 2024 - 2025
5
+ """
6
+ from treescript_builder.input.argument_parser import parse_arguments
7
+ from treescript_builder.input.file_validation import validate_input_file, validate_directory
8
+ from treescript_builder.input.input_data import InputData
9
+
10
+
11
+ def validate_input_arguments(arguments: list[str]) -> InputData:
12
+ """ Parse and Validate the Arguments, then return as InputData.
13
+
14
+ **Parameters:**
15
+ - arguments (list[str]): The list of Arguments to validate.
16
+
17
+ **Returns:**
18
+ InputData - An InputData instance.
19
+
20
+ **Raises:**
21
+ SystemExit - If Arguments, Input File or Directory names invalid.
22
+ """
23
+ arg_data = parse_arguments(arguments)
24
+ return InputData(
25
+ validate_input_file(arg_data.input_file_path_str),
26
+ validate_directory(arg_data.data_dir_path_str),
27
+ arg_data.is_reversed
28
+ )
@@ -0,0 +1,22 @@
1
+ """The Arguments Received from the Command Line Input.
2
+ - This DataClass is created after the argument syntax is validated.
3
+ - Syntax Validation:
4
+ - The Input File is Present and non-blank.
5
+ - When Data Directory is present, it is non-blank.
6
+ Author: DK96-OS 2024 - 2025
7
+ """
8
+ from dataclasses import dataclass
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class ArgumentData:
13
+ """ The syntactically valid arguments received by the Program.
14
+
15
+ **Fields:**
16
+ - input_file_path_str (str): The Name of the File containing the Tree Structure.
17
+ - data_dir_path_str (str?): The Directory Name containing Files Used in File Tree Operation.
18
+ - is_reversed (bool): Flag to determine if the File Tree Operation Is To be Oppositely Trimmed.
19
+ """
20
+ input_file_path_str: str
21
+ data_dir_path_str: str | None
22
+ is_reversed: bool
@@ -0,0 +1,102 @@
1
+ """Defines and Validates Argument Syntax.
2
+ - Encapsulates Argument Parser.
3
+ - Returns Argument Data, the args provided by the User.
4
+ Author: DK96-OS 2024 - 2025
5
+ """
6
+ from argparse import ArgumentParser
7
+ from sys import exit
8
+ from typing import Optional
9
+
10
+ from treescript_builder.input.argument_data import ArgumentData
11
+ from treescript_builder.input.string_validation import validate_name
12
+
13
+
14
+ def parse_arguments(args: Optional[list[str]] = None) -> ArgumentData:
15
+ """ Parse command line arguments.
16
+
17
+ Parameters:
18
+ - args: A list of argument strings.
19
+
20
+ Returns:
21
+ ArgumentData : Container for Valid Argument Data.
22
+ """
23
+ if args is None or len(args) == 0:
24
+ exit("No Arguments given. ")
25
+ # Initialize the Parser and Parse Immediately
26
+ try:
27
+ parsed_args = _define_arguments().parse_args(args)
28
+ except SystemExit as e:
29
+ exit("Unable to Parse Arguments.")
30
+ #
31
+ return _validate_arguments(
32
+ parsed_args.tree_file_name,
33
+ parsed_args.data_dir,
34
+ parsed_args.reverse
35
+ )
36
+
37
+
38
+ def _validate_arguments(
39
+ tree_file_name: str,
40
+ data_dir_name: str,
41
+ is_reverse: bool
42
+ ) -> ArgumentData:
43
+ """ Checks the values received from the ArgParser.
44
+ - Uses Validate Name method from StringValidation.
45
+ - Ensures that Reverse Operations have a Data Directory.
46
+
47
+ Parameters:
48
+ - tree_file_name (str): The file name of the tree input.
49
+ - data_dir_name (str): The Data Directory name.
50
+ - is_reverse (bool): Whether the builder operation is reversed.
51
+
52
+ Returns:
53
+ ArgumentData - A DataClass of syntactically correct arguments.
54
+ """
55
+ # Validate Tree Name Syntax
56
+ if not validate_name(tree_file_name):
57
+ exit("The Tree File argument was invalid.")
58
+ # Validate Data Directory Name Syntax if Present
59
+ if data_dir_name is None:
60
+ pass
61
+ elif not validate_name(data_dir_name):
62
+ exit("The Data Directory argument was invalid.")
63
+ #
64
+ return ArgumentData(
65
+ tree_file_name,
66
+ data_dir_name,
67
+ is_reverse
68
+ )
69
+
70
+
71
+ def _define_arguments() -> ArgumentParser:
72
+ """
73
+ Initializes and Defines Argument Parser.
74
+ - Sets Required/Optional Arguments and Flags.
75
+
76
+ Returns:
77
+ argparse.ArgumentParser - An instance with all supported FTB Arguments.
78
+ """
79
+ parser = ArgumentParser(
80
+ description="File Tree Builder"
81
+ )
82
+ # Required argument
83
+ parser.add_argument(
84
+ 'tree_file_name',
85
+ type=str,
86
+ help='The File containing the Tree Node Structure'
87
+ )
88
+ # Optional arguments
89
+ parser.add_argument(
90
+ '--data_dir',
91
+ default=None,
92
+ help='The Data Directory'
93
+ )
94
+ parser.add_argument(
95
+ '-r',
96
+ '--reverse',
97
+ '--trim',
98
+ action='store_true',
99
+ default=False,
100
+ help='Flag to reverse the File Tree Operation'
101
+ )
102
+ return parser
@@ -0,0 +1,52 @@
1
+ """ File Validation Methods.
2
+ These Methods all raise SystemExit exceptions.
3
+ Author: DK96-OS 2024 - 2025
4
+ """
5
+ from pathlib import Path
6
+ from sys import exit
7
+
8
+ from treescript_builder.input.string_validation import validate_name
9
+
10
+
11
+ def validate_input_file(file_name: str) -> str | None:
12
+ """ Read the Input File, Validate (non-blank) data, and return Input str.
13
+
14
+ Parameters:
15
+ - file_name (str): The Name of the Input File.
16
+
17
+ Returns:
18
+ str - The String Contents of the Input File.
19
+
20
+ Raises:
21
+ SystemExit - If the File does not exist, or is empty or blank, or read failed.
22
+ """
23
+ file_path = Path(file_name)
24
+ if not file_path.exists():
25
+ exit("The Input File does not Exist.")
26
+ try:
27
+ if (data := file_path.read_text()) is not None and validate_name(data):
28
+ return data
29
+ except IOError as e:
30
+ exit("Failed to Read from File.")
31
+ return None
32
+
33
+
34
+ def validate_directory(dir_path_str: str | None) -> Path | None:
35
+ """ Ensure that if the Directory is present, it Exists.
36
+
37
+ Parameters:
38
+ - dir_path_str (str, optional): The String representation of the Path to the Directory.
39
+
40
+ Returns:
41
+ Path? - The , or None if given input is None.
42
+
43
+ Raises:
44
+ SystemExit - If a given path does not exist.
45
+ """
46
+ if dir_path_str is None:
47
+ return None
48
+ if not validate_name(dir_path_str):
49
+ exit("Data Directory is invalid")
50
+ if (path := Path(dir_path_str)).exists():
51
+ return path
52
+ exit("The given Directory does not exist!")
@@ -0,0 +1,19 @@
1
+ """Valid Input Data Class.
2
+ Author: DK96-OS 2024 - 2025
3
+ """
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class InputData:
10
+ """A Data Class Containing Program Input.
11
+
12
+ **Fields:**
13
+ - tree_input (str): The Tree Input to the FTB operation.
14
+ - data_dir (Path, optional): An Optional Path to the Data Directory.
15
+ - is_reversed (bool): Whether this FTB operation is reversed.
16
+ """
17
+ tree_input: str
18
+ data_dir: Path | None
19
+ is_reversed: bool