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,136 @@
1
+ """Line Reader.
2
+
3
+ The Default Input Reader.
4
+ Processes a single line at a time, and determines its key properties.
5
+ The Depth is the Integer number of directories between the current line and the root.
6
+ The Directory Boolean indicates whether the line represents a Directory.
7
+ The Name String is the name of the line.
8
+ Author: DK96-OS 2024 - 2025
9
+ """
10
+ from itertools import groupby
11
+ from sys import exit
12
+ from typing import Generator
13
+
14
+ from treescript_builder.data.tree_data import TreeData
15
+ from treescript_builder.input.string_validation import validate_dir_name, validate_name
16
+
17
+ SPACE_CHARS = (' ', ' ', ' ', ' ')
18
+
19
+
20
+ def read_input_tree(input_tree_data: str) -> Generator[TreeData, None, None]:
21
+ """ Generate structured Tree Data from the Input Data String.
22
+
23
+ **Parameters:**
24
+ - input_data (InputData): The Input.
25
+
26
+ **Returns:**
27
+ Generator[TreeData] - Produces TreeData from the Input Data.
28
+
29
+ **Raises:**
30
+ SystemExit - When any Line cannot be read successfully.
31
+ """
32
+ line_number = 1
33
+ for is_newline, group in groupby(input_tree_data, lambda x: x in ["\n", "\r"]):
34
+ if is_newline:
35
+ line_number += sum(1 for _ in group) # Line number increase by size of group
36
+ else:
37
+ line = ''.join(group)
38
+ if len(lstr := line.lstrip()) == 0 or lstr.startswith('#'):
39
+ continue
40
+ yield _process_line(line_number, line)
41
+
42
+
43
+ def _process_line(
44
+ line_number: int,
45
+ line: str,
46
+ ) -> TreeData:
47
+ """ Processes a single line of the input tree structure.
48
+ - Returns a tuple indicating the depth, type (file or directory), name of file or dir, and file data if available.
49
+
50
+ **Parameters:**
51
+ - line_number (int): The line-number in the input tree structure, starting from 1.
52
+ - line (str): A line from the input tree structure.
53
+
54
+ **Returns:**
55
+ tuple: (int, bool, str, str) where int is the depth, bool is true when is Directory, and str is name, followed by str data.
56
+
57
+ **Raises:**
58
+ SystemExit - When Line cannot be read successfully.
59
+ """
60
+ # Calculate the Depth
61
+ depth = _calculate_depth(line)
62
+ if depth < 0:
63
+ exit(f"Invalid Space Count in Line: {line_number}")
64
+ # Remove Space
65
+ args = line.strip()
66
+ # Try to split line into multiple arguments
67
+ for space_char in SPACE_CHARS:
68
+ if space_char in args:
69
+ args = args.split(space_char)
70
+ break
71
+ # Check whether line was split or not
72
+ if isinstance(args, str):
73
+ name = args
74
+ data_label = ""
75
+ elif isinstance(args, list) and len(args) >= 2:
76
+ name = args[0]
77
+ data_label = args[1]
78
+ else:
79
+ exit(f"Invalid Line: {line_number}")
80
+ # Validate the Node Name and Type.
81
+ node_info = _validate_node_name(name)
82
+ if node_info is None:
83
+ exit(f'Invalid Node on Line: {line_number}')
84
+ (is_dir, name) = node_info
85
+ return TreeData(
86
+ line_number,
87
+ depth,
88
+ is_dir,
89
+ name,
90
+ data_label
91
+ )
92
+
93
+
94
+ def _validate_node_name(node_name: str) -> tuple[bool, str] | None:
95
+ """ Determine whether this Tree Node is a Directory, and validate the name.
96
+
97
+ **Parameters:**
98
+ - node_name (str): The argument received for the node name.
99
+
100
+ **Returns:**
101
+ tuple[bool, str] - Node information, first whether it is a directory, then the valid name of the node.
102
+
103
+ **Raises:**
104
+ SystemExit - When the directory name is invalid.
105
+ """
106
+ try:
107
+ # Check if the line contains any slash characters
108
+ if (dir_name := validate_dir_name(node_name)) is not None:
109
+ return (True, dir_name)
110
+ # Fall-Through to File Node
111
+ except ValueError as e:
112
+ # An error in the dir name, such that it cannot be a file either
113
+ return None
114
+ # Is a File
115
+ if validate_name(node_name):
116
+ return (False, node_name)
117
+ return None
118
+
119
+
120
+ def _calculate_depth(line: str) -> int:
121
+ """ Calculates the depth of a line in the tree structure.
122
+
123
+ **Parameters:**
124
+ - line (str): A line from the tree command output.
125
+
126
+ **Returns:**
127
+ int: The depth of the line in the tree structure, or -1 if space count is invalid.
128
+ """
129
+ from itertools import takewhile
130
+ space_count = len(list(
131
+ takewhile(lambda c: c in SPACE_CHARS, line)
132
+ ))
133
+ # Bit Shift Shuffle Equivalence Validation (space_count is divisible by 2)
134
+ if (depth := space_count >> 1) << 1 == space_count:
135
+ return depth
136
+ return -1 # Invalid Space Count! Someone made an off-by-one whitespace mistake!
@@ -0,0 +1,130 @@
1
+ """String Validation Methods.
2
+ Author: DK96-OS 2024 - 2025
3
+ """
4
+ from typing import Literal
5
+
6
+
7
+ def validate_name(argument) -> bool:
8
+ """ Determine whether an argument is a non-empty string.
9
+ - Does not count whitespace.
10
+ - Uses the strip method to remove empty space.
11
+
12
+ **Parameters:**
13
+ - argument (str) : The given argument.
14
+
15
+ **Returns:**
16
+ bool - True if the argument qualifies as valid.
17
+ """
18
+ if argument is None or not isinstance(argument, str):
19
+ return False
20
+ elif len(argument.strip()) < 1:
21
+ return False
22
+ return True
23
+
24
+
25
+ def validate_data_label(data_label: str) -> bool:
26
+ """ Determine whether a Data Label is Valid.
27
+
28
+ **Parameters:**
29
+ - data_label (str): The String to check for validity.
30
+
31
+ **Returns:**
32
+ bool - Whether the String is a valid Data Label.
33
+ """
34
+ if not 0 < len(data_label) < 100:
35
+ return False
36
+ if '/' in data_label or '\\' in data_label:
37
+ return False
38
+ # Remove Dash Characters
39
+ if '-' in data_label:
40
+ data_label = data_label.replace('-', '')
41
+ # Remove Underscore Characters
42
+ if '_' in data_label:
43
+ data_label = data_label.replace('_', '')
44
+ # Remove Dot Characters
45
+ if '.' in data_label:
46
+ data_label = data_label.replace('.', '')
47
+ if '!' == data_label:
48
+ return True
49
+ # All Remaining Characters must be alphanumeric
50
+ return data_label.isalnum()
51
+
52
+
53
+ def validate_dir_name(dir_name: str) -> str | None:
54
+ """ Determine that a directory is correctly formatted.
55
+ - This method should be called once for each slash type.
56
+
57
+ **Parameters:**
58
+ - dir_name (str): The given input to be validated.
59
+
60
+ **Returns:**
61
+ str | None - The valid directory name, or none if it may be a file.
62
+
63
+ **Raises:**
64
+ ValueError - When the name is not suitable for directories or files.
65
+ """
66
+ # Keep Name Length Reasonable
67
+ if (name_length := len(dir_name)) >= 100:
68
+ raise ValueError(f'Name too Long!: {name_length}')
69
+ # Check for slash characters
70
+ if (name := _filter_slash_chars(dir_name)) is not None:
71
+ # Is a Dir
72
+ if len(name) == 0:
73
+ raise ValueError('The name is empty')
74
+ # Check for invalid characters (parent dir, current dir)
75
+ if name in ['.', '..']:
76
+ raise ValueError('Invalid Directory')
77
+ return name
78
+ # Is a File
79
+ return None
80
+
81
+
82
+ def _validate_slash_char(dir_name: str) -> Literal['\\', '/'] | None:
83
+ """ Determine which slash char is used by the directory, if it is a directory.
84
+ - Discourages use of both slash chars, by raising ValueError.
85
+
86
+ **Parameters:**
87
+ - dir_name (str): The given input to be validated.
88
+
89
+ **Returns:**
90
+ str | None - The slash character used, or none if no chars were found.
91
+
92
+ **Raises:**
93
+ ValueError - When the name contains both slash characters.
94
+ """
95
+ slash = None
96
+ if '/' in dir_name:
97
+ slash = '/'
98
+ if '\\' in dir_name:
99
+ if slash is not None:
100
+ raise ValueError('Invalid Directory slash character combination.')
101
+ slash = '\\'
102
+ return slash
103
+
104
+
105
+ def _filter_slash_chars(dir_name: str) -> str | None:
106
+ """ Remove all of the slash characters and return the directory name.
107
+ - Returns None when there are no slash characters found.
108
+ - Raises ValueError when slash characters are used improperly.
109
+
110
+ **Parameters:**
111
+ - dir_name (str): The given input to be validated.
112
+
113
+ **Returns:**
114
+ str | None - The valid directory name, or none if it may be a file.
115
+
116
+ **Raises:**
117
+ ValueError - When the name is not suitable for directories or files.
118
+ """
119
+ slash = _validate_slash_char(dir_name)
120
+ if slash is None:
121
+ return None
122
+ if dir_name.endswith(slash) or dir_name.startswith(slash):
123
+ name = dir_name.strip(slash)
124
+ # Check for internal slash characters
125
+ if slash in name:
126
+ raise ValueError('Multi-dir line detected')
127
+ else:
128
+ # Found slash chars only within the node name (multi-dir line)
129
+ raise ValueError('Multi-dir line detected')
130
+ return name
@@ -0,0 +1,56 @@
1
+ """The Tree Module.
2
+ """
3
+ from treescript_builder.input.input_data import InputData
4
+ from treescript_builder.input.line_reader import read_input_tree
5
+
6
+
7
+ def build_tree(input_data: InputData) -> tuple[bool, ...]:
8
+ """ Build The Tree as defined by the InputData.
9
+
10
+ **Parameters:**
11
+ - input_data (str): The InputData produced by the Input Module.
12
+
13
+ **Returns:**
14
+ tuple[bool, ...] - The results of each individual Builder operation.
15
+
16
+ **Raises:**
17
+ SystemExit - If a Tree Validation error occurs.
18
+ """
19
+ if input_data.is_reversed:
20
+ from treescript_builder.tree.trim_validation import validate_trim
21
+ instructions = validate_trim(
22
+ read_input_tree(input_data.tree_input),
23
+ input_data.data_dir
24
+ )
25
+ from treescript_builder.tree.tree_trimmer import trim
26
+ results = trim(instructions)
27
+ else:
28
+ from treescript_builder.tree.build_validation import validate_build
29
+ instructions = validate_build(
30
+ read_input_tree(input_data.tree_input),
31
+ input_data.data_dir
32
+ )
33
+ from treescript_builder.tree.tree_builder import build
34
+ results = build(instructions)
35
+ #
36
+ return results
37
+
38
+
39
+ def process_results(results: tuple[bool, ...]) -> str:
40
+ """ Process and Summarize the Results.
41
+
42
+ **Parameters:**
43
+ - results (tuple[bool]): A tuple containing the results of the operations.
44
+
45
+ **Returns:**
46
+ str - A summary of the number of operations that succeeded.
47
+ """
48
+ if (length := len(results)) == 0:
49
+ return 'No operations ran.'
50
+ if (success := sum(iter(results))) == 0:
51
+ return f"All {length} operations failed."
52
+ elif success == length:
53
+ return f"All {length} operations succeeded."
54
+ # Compute the Fraction of success operations
55
+ success_percent = round(100 * success / length, 1)
56
+ return f"{success} out of {length} operations succeeded: {success_percent}%"
@@ -0,0 +1,111 @@
1
+ """Tree Validation Methods for the Build Operation.
2
+ Author: DK96-OS 2024 - 2025
3
+ """
4
+ from pathlib import Path
5
+ from typing import Generator
6
+
7
+ from treescript_builder.data.data_directory import DataDirectory
8
+ from treescript_builder.data.instruction_data import InstructionData
9
+ from treescript_builder.data.tree_data import TreeData
10
+ from treescript_builder.data.tree_state import TreeState
11
+
12
+
13
+ def validate_build(
14
+ tree_data: Generator[TreeData, None, None],
15
+ data_dir_path: Path | None = None,
16
+ verbose: bool = False,
17
+ ) -> tuple[InstructionData, ...]:
18
+ """ Validate the Build Instructions.
19
+
20
+ **Parameters:**
21
+ - tree_data (Generator[TreeData]): The Generator that provides TreeData.
22
+ - data_dir_path (Path?): The optional Data Directory Path. Default: None.
23
+ - verbose (bool): Whether to print DataDirectory information during validation.
24
+
25
+ **Returns:**
26
+ tuple[InstructionData] - A generator that yields Instructions.
27
+ """
28
+ if data_dir_path is None:
29
+ return tuple(iter(_validate_build_generator(tree_data)))
30
+ else:
31
+ data = DataDirectory(data_dir_path)
32
+ if verbose:
33
+ print(f"Validating Build With DataDir: {data_dir_path}")
34
+ return tuple(iter(_validate_build_generator_data(tree_data, data)))
35
+
36
+
37
+ def _validate_build_generator(
38
+ tree_data: Generator[TreeData, None, None]
39
+ ) -> Generator[InstructionData, None, None]:
40
+ tree_state = TreeState()
41
+ for node in tree_data:
42
+ # Error if any Nodes have Data Labels
43
+ if node.data_label != '':
44
+ exit(f"Found Data Label on Line {node.line_number} with no Data Directory: {node.data_label}")
45
+ # Calculate Tree Depth Change
46
+ if tree_state.validate_tree_data(node) == 0:
47
+ if node.is_dir:
48
+ tree_state.add_to_queue(node.name)
49
+ else:
50
+ # Merge Queue into Stack
51
+ if (new_dir := tree_state.process_queue()) is not None:
52
+ yield InstructionData(True, new_dir)
53
+ yield InstructionData(
54
+ False, tree_state.get_current_path() / node.name
55
+ )
56
+ else:
57
+ # Merge Queue into Stack
58
+ if (new_dir := tree_state.process_queue()) is not None:
59
+ yield InstructionData(True, new_dir, None)
60
+ # Pop Stack to required Depth
61
+ if not tree_state.reduce_depth(node.depth):
62
+ exit(f"Invalid Depth at Line: {node.line_number}")
63
+ if node.is_dir:
64
+ tree_state.add_to_queue(node.name)
65
+ else:
66
+ yield InstructionData(
67
+ False, tree_state.get_current_path() / node.name
68
+ )
69
+ # Always Finish Build Sequence with ProcessQueue
70
+ if (dir := tree_state.process_queue()) is not None:
71
+ yield InstructionData(True, dir)
72
+
73
+
74
+ def _validate_build_generator_data(
75
+ tree_data: Generator[TreeData, None, None],
76
+ data_dir: DataDirectory
77
+ ) -> Generator[InstructionData, None, None]:
78
+ tree_state = TreeState()
79
+ for node in tree_data:
80
+ # Calculate Tree Depth Change
81
+ if tree_state.validate_tree_data(node) == 0:
82
+ if node.is_dir:
83
+ tree_state.add_to_queue(node.name)
84
+ else:
85
+ # Build Queued Directories
86
+ if (new_dir := tree_state.process_queue()) is not None:
87
+ yield InstructionData(True, new_dir)
88
+ # Build File
89
+ yield InstructionData(
90
+ False,
91
+ tree_state.get_current_path() / node.name,
92
+ data_dir.validate_build(node)
93
+ )
94
+ else:
95
+ # Merge Queue into Stack
96
+ if (new_dir := tree_state.process_queue()) is not None:
97
+ yield InstructionData(True, new_dir)
98
+ # Pop Stack to required Depth
99
+ if not tree_state.reduce_depth(node.depth):
100
+ exit(f"Invalid Tree Depth on Line {node.line_number} : {node.name}")
101
+ if node.is_dir:
102
+ tree_state.add_to_queue(node.name)
103
+ else:
104
+ yield InstructionData(
105
+ False,
106
+ tree_state.get_current_path() / node.name,
107
+ data_dir.validate_build(node)
108
+ )
109
+ # Always Finish Build Sequence with ProcessQueue
110
+ if (dir := tree_state.process_queue()) is not None:
111
+ yield InstructionData(True, dir)
@@ -0,0 +1,77 @@
1
+ """Tree Building Operations.
2
+ Author: DK96-OS 2024 - 2025
3
+ """
4
+ from pathlib import Path
5
+ from shutil import copy2
6
+
7
+ from treescript_builder.data.instruction_data import InstructionData
8
+
9
+
10
+ def build(instructions: tuple[InstructionData, ...]) -> tuple[bool, ...]:
11
+ """ Execute the Instructions in build mode.
12
+
13
+ **Parameters:**
14
+ - instructions(tuple[InstructionData]): The Instructions to execute.
15
+
16
+ **Returns:**
17
+ tuple[bool] - The success or failure of each instruction.
18
+ """
19
+ return tuple(_build(i) for i in instructions)
20
+
21
+
22
+ def _build(i: InstructionData) -> bool:
23
+ """ Execute a single instruction.
24
+
25
+ **Parameters:**
26
+ - instruction(InstructionData): The data required to execute the operation.
27
+
28
+ **Returns:**
29
+ bool - Whether the given operation succeeded.
30
+ """
31
+ if i.is_dir:
32
+ return _make_dir_exist(i.path)
33
+ elif i.data_path is None:
34
+ i.path.touch(exist_ok=True)
35
+ return True
36
+ else:
37
+ return _create_file(i.path, i.data_path)
38
+
39
+
40
+ def _create_file(
41
+ path: Path,
42
+ data: Path
43
+ ) -> bool:
44
+ """ Create a File at the given path, with data from the Data Directory.
45
+
46
+ **Parameters:**
47
+ - path (Path): The Path to the File to be created, and written to.
48
+ - data (Path): A Data Directory Path to be copied to the new File.
49
+
50
+ **Returns:**
51
+ bool - Whether the File operation succeeded.
52
+ """
53
+ try:
54
+ copy2(data, path)
55
+ except BaseException as e:
56
+ return False
57
+ return True
58
+
59
+
60
+ def _make_dir_exist(
61
+ path: Path
62
+ ) -> bool:
63
+ """ Ensure that the Directory at the given Path exists.
64
+
65
+ **Parameters:**
66
+ - path (Path): The Path to the File to be created, and written to.
67
+
68
+ **Returns:**
69
+ bool - True if the Operation Succeeded, or if the Path already exists.
70
+ """
71
+ if path.exists():
72
+ return True
73
+ try:
74
+ path.mkdir(parents=True, exist_ok=True)
75
+ except IOError as e:
76
+ return False
77
+ return True
@@ -0,0 +1,70 @@
1
+ """Tree Trimming Methods.
2
+ Author: DK96-OS 2024 - 2025
3
+ """
4
+ from pathlib import Path
5
+ from shutil import move
6
+
7
+ from treescript_builder.data.instruction_data import InstructionData
8
+
9
+
10
+ def trim(instructions: tuple[InstructionData, ...]) -> tuple[bool, ...]:
11
+ """ Execute the Instructions in trim mode.
12
+
13
+ **Parameters:**
14
+ - instructions(tuple[InstructionData]): The Instructions to execute.
15
+
16
+ **Returns:**
17
+ tuple[bool] - The success or failure of each instruction.
18
+ """
19
+ return tuple(_trim(i) for i in instructions)
20
+
21
+
22
+ def _trim(instruct: InstructionData) -> bool:
23
+ if instruct.is_dir:
24
+ return _remove_dir(instruct.path)
25
+ if instruct.data_path is None:
26
+ try:
27
+ instruct.path.unlink(missing_ok=True)
28
+ return True
29
+ except IOError as e:
30
+ print("IO File Error")
31
+ return False
32
+ return _extract_file(instruct.path, instruct.data_path)
33
+
34
+
35
+ def _extract_file(
36
+ path: Path,
37
+ data: Path
38
+ ) -> bool:
39
+ """ Moves the File to the Data Directory.
40
+
41
+ **Parameters:**
42
+ - path (Path): The path to the File in the Tree.
43
+ - data (Path): A Path to a File in the Data Directory.
44
+
45
+ **Returns:**
46
+ bool - Whether the entire operation succeeded.
47
+ """
48
+ try:
49
+ move(path, data)
50
+ return True
51
+ except:
52
+ return False
53
+
54
+
55
+ def _remove_dir(
56
+ path: Path,
57
+ ) -> bool:
58
+ """ Tries to Remove a Directory, if it is Empty.
59
+
60
+ **Parameters:**
61
+ - path (Path): The path to the Directory.
62
+
63
+ **Returns:**
64
+ bool : Whether the Directory was Empty, and has been removed.
65
+ """
66
+ try:
67
+ path.rmdir()
68
+ except BaseException as e:
69
+ return False
70
+ return True
@@ -0,0 +1,97 @@
1
+ """Tree Validation Methods for the Trim Operation.
2
+ Author: DK96-OS 2024 - 2025
3
+ """
4
+ from pathlib import Path
5
+ from typing import Generator
6
+
7
+ from treescript_builder.data.data_directory import DataDirectory
8
+ from treescript_builder.data.instruction_data import InstructionData
9
+ from treescript_builder.data.tree_data import TreeData
10
+ from treescript_builder.data.tree_state import TreeState
11
+
12
+
13
+ def validate_trim(
14
+ tree_data: Generator[TreeData, None, None],
15
+ data_dir_path: Path | None = None,
16
+ verbose: bool = False,
17
+ ) -> tuple[InstructionData, ...]:
18
+ """ Validate the Trim Instructions.
19
+
20
+ **Parameters:**
21
+ - tree_data (Generator[TreeData]): The Generator that provides TreeData.
22
+ - data_dir_path (Path?): The optional Path to a Data Directory. Default: None.
23
+ - verbose (bool): Whether to print DataDirectory information during validation.
24
+
25
+ **Returns:**
26
+ tuple[InstructionData] - A generator that yields Instructions.
27
+ """
28
+ if data_dir_path is None:
29
+ return tuple(iter(_validate_trim_generator(tree_data)))
30
+ else:
31
+ data = DataDirectory(data_dir_path)
32
+ if verbose:
33
+ print(f"Validating Trim With DataDir: {data_dir_path}")
34
+ return tuple(iter(_validate_trim_generator_data(tree_data, data)))
35
+
36
+
37
+ def _validate_trim_generator(
38
+ tree_data: Generator[TreeData, None, None]
39
+ ) -> Generator[InstructionData, None, None]:
40
+ tree_state = TreeState()
41
+ for node in tree_data:
42
+ # Calculate Tree Depth Change
43
+ if tree_state.validate_tree_data(node) == 1:
44
+ if node.is_dir:
45
+ tree_state.add_to_stack(node.name)
46
+ else:
47
+ yield InstructionData(
48
+ False, tree_state.get_current_path() / node.name
49
+ )
50
+ else:
51
+ # Pop Stack to required Depth
52
+ for i in tree_state.process_stack(node.depth):
53
+ yield InstructionData(True, i)
54
+ # Dir or File
55
+ if node.is_dir:
56
+ tree_state.add_to_stack(node.name)
57
+ else:
58
+ yield InstructionData(
59
+ False, tree_state.get_current_path() / node.name
60
+ )
61
+ # Finish Trim Sequence with Pop Stack
62
+ for i in tree_state.process_stack(0):
63
+ yield InstructionData(True, i)
64
+
65
+
66
+ def _validate_trim_generator_data(
67
+ tree_data: Generator[TreeData, None, None],
68
+ data_dir: DataDirectory
69
+ ) -> Generator[InstructionData, None, None]:
70
+ tree_state = TreeState()
71
+ for node in tree_data:
72
+ # Calculate Tree Depth Change
73
+ if tree_state.validate_tree_data(node) == 1:
74
+ if node.is_dir:
75
+ tree_state.add_to_stack(node.name)
76
+ else:
77
+ yield InstructionData(
78
+ False,
79
+ tree_state.get_current_path() / node.name,
80
+ data_dir.validate_trim(node)
81
+ )
82
+ else:
83
+ # Pop Stack to required Depth
84
+ for i in tree_state.process_stack(node.depth):
85
+ yield InstructionData(True, i)
86
+ # Dir or File
87
+ if node.is_dir:
88
+ tree_state.add_to_stack(node.name)
89
+ else:
90
+ yield InstructionData(
91
+ False,
92
+ tree_state.get_current_path() / node.name,
93
+ data_dir.validate_trim(node)
94
+ )
95
+ # Finish Trim Sequence with Pop Stack
96
+ for i in tree_state.process_stack(0):
97
+ yield InstructionData(True, i)