Skip to content

Commit 5c6cb14

Browse files
authored
Release 0.2 (#17)
Main: * Update __main__.py - check if os is windows and use tsft_windows method Publish: * Update setup.py - update version Treescriptify Package: * Create windows_tree.py * Update __init__.py - add method tsfy_windows * Update windows_tree.py - add docs, optimize imports, simplify subdirectory content check, add method _dirs_only, add method _dirs_only_prune, modify method win_tree to accept a path parameter Treescriptify Test Package: * Create test_windows_tree.py * Update test_windows_tree.py - add file system dependent expected assertion values, alter mock_hidden_tree dir and file order, differentiate input data further, add test cases, replace Path mocks with tmp_path, refactor test cases Workflows: * Update ci_run.yml - disable fail-fast
1 parent 1bca10f commit 5c6cb14

File tree

6 files changed

+331
-3
lines changed

6 files changed

+331
-3
lines changed

.github/workflows/ci_run.yml

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ jobs:
1313
build:
1414
strategy:
1515
max-parallel: 3
16+
fail-fast: false
1617
matrix:
1718
os: [ubuntu-latest, windows-latest, macos-latest]
1819
python-version: [ '3.11', '3.12' ]

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setup(
77
name="treescriptify",
8-
version="0.1.2",
8+
version="0.2",
99
description="Create TreeScript from an existing filesystem tree.",
1010
long_description=open('README.md').read(),
1111
long_description_content_type='text/markdown',

test/test_windows_tree.py

+229
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
"""Testing Windows tree Methods
2+
"""
3+
import os
4+
from pathlib import Path
5+
import pytest
6+
7+
from treescriptify.input_data import InputData
8+
from treescriptify.tree_node_data import TreeNodeData
9+
from treescriptify.windows_tree import win_tree
10+
11+
12+
# File Systems on different Operating Systems behave differently.
13+
# In test cases with files and dirs at the same level in a tree,
14+
# the order of the files and dirs is switched.
15+
IS_WINDOWS = os.name == 'nt'
16+
17+
18+
DEFAULT_INPUT = InputData()
19+
EXCLUDE_HIDDEN_INPUT = InputData(include_hidden=False)
20+
DIR_ONLY_INPUT = InputData(directories_only=True)
21+
PRUNE_DIR_INPUT = InputData(prune_dirs=True)
22+
DIR_ONLY_PRUNE_INPUT = InputData(directories_only=True, prune_dirs=True)
23+
EXCLUDE_HIDDEN_WITH_DIR_ONLY_PRUNE = InputData(
24+
include_hidden=False, directories_only=True, prune_dirs=True
25+
)
26+
27+
28+
# BASIC_TREE
29+
# src/
30+
# data.txt
31+
32+
# NESTED_TREE
33+
# src/
34+
# main/
35+
# SourceClass.java
36+
37+
# HIDDEN_TREE
38+
# on Windows:
39+
# .github/
40+
# dependabot.yml
41+
# workflows/
42+
# ci.yml
43+
# .hidden.txt
44+
#
45+
# on unix:
46+
# .github/
47+
# workflows/
48+
# ci.yml
49+
# dependabot.yml
50+
# .hidden.txt
51+
52+
53+
@pytest.fixture
54+
def mock_basic_tree(tmp_path):
55+
src_dir = tmp_path / 'src'
56+
src_dir.mkdir()
57+
(src_dir / 'data.txt').touch()
58+
return Path(str(tmp_path))
59+
60+
61+
@pytest.fixture
62+
def mock_nested_tree(tmp_path):
63+
src_dir = tmp_path / 'src'
64+
src_dir.mkdir()
65+
main_dir = src_dir / 'main'
66+
main_dir.mkdir()
67+
(main_dir / 'SourceClass.java').touch()
68+
return Path(str(tmp_path))
69+
70+
71+
@pytest.fixture
72+
def mock_hidden_tree(tmp_path):
73+
github_dir = tmp_path / '.github'
74+
github_dir.mkdir()
75+
(github_dir / 'dependabot.yml').touch()
76+
workflows_dir = github_dir / 'workflows'
77+
workflows_dir.mkdir()
78+
(workflows_dir / 'ci.yml').touch()
79+
(tmp_path / '.hidden.txt').touch()
80+
return Path(str(tmp_path))
81+
82+
83+
def test_win_tree_default_input_basic_tree_returns_data(mock_basic_tree):
84+
result = [x for x in win_tree(DEFAULT_INPUT, mock_basic_tree)]
85+
assert result == [
86+
TreeNodeData(0, True, 'src'),
87+
TreeNodeData(1, False, 'data.txt'),
88+
]
89+
90+
91+
def test_win_tree_default_input_nested_tree_returns_data(mock_nested_tree):
92+
result = [x for x in win_tree(DEFAULT_INPUT, mock_nested_tree)]
93+
assert result == [
94+
TreeNodeData(0, True, 'src'),
95+
TreeNodeData(1, True, 'main'),
96+
TreeNodeData(2, False, 'SourceClass.java'),
97+
]
98+
99+
100+
def test_win_tree_default_input_hidden_tree_returns_data(mock_hidden_tree):
101+
result = [x for x in win_tree(DEFAULT_INPUT, mock_hidden_tree)]
102+
if IS_WINDOWS:
103+
expected = [
104+
TreeNodeData(0, True, '.github'),
105+
TreeNodeData(1, False, 'dependabot.yml'),
106+
TreeNodeData(1, True, 'workflows'),
107+
TreeNodeData(2, False, 'ci.yml'),
108+
TreeNodeData(0, False, '.hidden.txt'),
109+
]
110+
else:
111+
expected = [
112+
TreeNodeData(0, True, '.github'),
113+
TreeNodeData(1, True, 'workflows'),
114+
TreeNodeData(2, False, 'ci.yml'),
115+
TreeNodeData(1, False, 'dependabot.yml'),
116+
TreeNodeData(0, False, '.hidden.txt'),
117+
]
118+
assert result == expected
119+
120+
121+
def test_win_tree_exclude_hidden_input_basic_tree_returns_data(mock_basic_tree):
122+
result = [x for x in win_tree(EXCLUDE_HIDDEN_INPUT, mock_basic_tree)]
123+
assert result == [
124+
TreeNodeData(0, True, 'src'),
125+
TreeNodeData(1, False, 'data.txt'),
126+
]
127+
128+
129+
def test_win_tree_exclude_hidden_input_nested_tree_returns_data(mock_nested_tree):
130+
result = [x for x in win_tree(EXCLUDE_HIDDEN_INPUT, mock_nested_tree)]
131+
assert result == [
132+
TreeNodeData(0, True, 'src'),
133+
TreeNodeData(1, True, 'main'),
134+
TreeNodeData(2, False, 'SourceClass.java'),
135+
]
136+
137+
138+
def test_win_tree_exclude_hidden_input_hidden_tree_returns_data(mock_hidden_tree):
139+
result = [x for x in win_tree(EXCLUDE_HIDDEN_INPUT, mock_hidden_tree)]
140+
assert result == []
141+
142+
143+
def test_win_tree_dir_only_input_basic_tree_returns_data(mock_basic_tree):
144+
result = [x for x in win_tree(DIR_ONLY_INPUT, mock_basic_tree)]
145+
assert result == [
146+
TreeNodeData(0, True, 'src'),
147+
]
148+
149+
150+
def test_win_tree_dir_only_input_nested_tree_returns_data(mock_nested_tree):
151+
result = [x for x in win_tree(DIR_ONLY_INPUT, mock_nested_tree)]
152+
assert result == [
153+
TreeNodeData(0, True, 'src'),
154+
TreeNodeData(1, True, 'main'),
155+
]
156+
157+
158+
def test_win_tree_dir_only_input_hidden_tree_returns_data(mock_hidden_tree):
159+
result = [x for x in iter(win_tree(DIR_ONLY_INPUT, mock_hidden_tree))]
160+
assert result == [
161+
TreeNodeData(0, True, '.github'),
162+
TreeNodeData(1, True, 'workflows'),
163+
]
164+
165+
166+
def test_win_tree_prune_dir_input_basic_tree_returns_data(mock_basic_tree):
167+
result = [x for x in win_tree(PRUNE_DIR_INPUT, mock_basic_tree)]
168+
assert result == [
169+
TreeNodeData(0, True, 'src'),
170+
TreeNodeData(1, False, 'data.txt'),
171+
]
172+
173+
174+
def test_win_tree_prune_dir_input_nested_tree_returns_data(mock_nested_tree):
175+
result = [x for x in win_tree(PRUNE_DIR_INPUT, mock_nested_tree)]
176+
assert result == [
177+
TreeNodeData(0, True, 'src'),
178+
TreeNodeData(1, True, 'main'),
179+
TreeNodeData(2, False, 'SourceClass.java'),
180+
]
181+
182+
183+
def test_win_tree_prune_dir_input_hidden_tree_returns_data(mock_hidden_tree):
184+
result = [x for x in win_tree(PRUNE_DIR_INPUT, mock_hidden_tree)]
185+
if IS_WINDOWS:
186+
expected = [
187+
TreeNodeData(0, True, '.github'),
188+
TreeNodeData(1, False, 'dependabot.yml'),
189+
TreeNodeData(1, True, 'workflows'),
190+
TreeNodeData(2, False, 'ci.yml'),
191+
TreeNodeData(0, False, '.hidden.txt'),
192+
]
193+
else:
194+
expected = [
195+
TreeNodeData(0, True, '.github'),
196+
TreeNodeData(1, True, 'workflows'),
197+
TreeNodeData(2, False, 'ci.yml'),
198+
TreeNodeData(1, False, 'dependabot.yml'),
199+
TreeNodeData(0, False, '.hidden.txt'),
200+
]
201+
assert result == expected
202+
203+
204+
def test_win_tree_dir_only_prune_input_basic_tree_returns_data(mock_basic_tree):
205+
result = [x for x in win_tree(DIR_ONLY_PRUNE_INPUT, mock_basic_tree)]
206+
assert result == [
207+
TreeNodeData(0, True, 'src'),
208+
]
209+
210+
211+
def test_win_tree_dir_only_prune_input_nested_tree_returns_data(mock_nested_tree):
212+
result = [x for x in win_tree(DIR_ONLY_PRUNE_INPUT, mock_nested_tree)]
213+
assert result == [
214+
TreeNodeData(0, True, 'src'),
215+
TreeNodeData(1, True, 'main'),
216+
]
217+
218+
219+
def test_win_tree_dir_only_prune_input_hidden_tree_returns_data(mock_hidden_tree):
220+
result = [x for x in win_tree(DIR_ONLY_PRUNE_INPUT, mock_hidden_tree)]
221+
assert result == [
222+
TreeNodeData(0, True, '.github'),
223+
TreeNodeData(1, True, 'workflows'),
224+
]
225+
226+
227+
def test_win_tree_dir_only_prune_input_hidden_tree_returns_data(mock_hidden_tree):
228+
result = [x for x in win_tree(EXCLUDE_HIDDEN_WITH_DIR_ONLY_PRUNE, mock_hidden_tree)]
229+
assert result == []

treescriptify/__init__.py

+11
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .tree_runner import get_tree_json
55
from .tree_reader import generate_from_json
66
from .script_writer import generate_script
7+
from .windows_tree import win_tree
78

89

910
def tsfy(data: InputData) -> str:
@@ -16,3 +17,13 @@ def tsfy(data: InputData) -> str:
1617
)
1718
)
1819
)
20+
21+
22+
def tsfy_windows(data: InputData) -> str:
23+
"""Run Treescriptify on the given Inputs, on a Windows OS.
24+
"""
25+
return '\n'.join(
26+
generate_script(
27+
win_tree(data)
28+
)
29+
)

treescriptify/__main__.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
#!/usr/bin/python
22
"""Main Entry Point
33
"""
4+
import os
45
from sys import argv
56

67
from .argument_parser import parse_args
7-
from . import tsfy
8+
from . import tsfy, tsfy_windows
89

910

1011
def main():
1112
input_data = parse_args(argv[1:])
12-
print(tsfy(input_data))
13+
output_data = tsfy_windows(input_data) if os.name == 'nt' else tsfy(input_data)
14+
print(output_data)
1315

1416

1517
if __name__ == '__main__':

treescriptify/windows_tree.py

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""Python implementation of the Tree Command, for windows OS.
2+
"""
3+
from pathlib import Path
4+
from typing import Generator
5+
6+
from .input_data import InputData
7+
from .tree_node_data import TreeNodeData
8+
9+
10+
def win_tree(
11+
data: InputData,
12+
path: Path = Path('./'),
13+
) -> Generator[TreeNodeData, None, None]:
14+
"""Generate Tree Node Data for all files and directories in the given path.
15+
16+
Parameters:
17+
- data (InputData): The
18+
- path (str): The root path to run tree in.
19+
20+
Return:
21+
Generator[TreeNodeData]
22+
"""
23+
if data.directories_only:
24+
if data.prune_dirs:
25+
yield from _dirs_only_prune(data, path)
26+
else:
27+
yield from _dirs_only(data, path)
28+
else:
29+
yield from _gen_tree(data, path)
30+
31+
32+
def _gen_tree(
33+
data: InputData,
34+
path: Path,
35+
depth: int = 0
36+
) -> Generator[TreeNodeData, None, None]:
37+
"""Mimic the Tree command.
38+
"""
39+
for entry in path.iterdir():
40+
# Check Hidden Files option
41+
if not data.include_hidden and entry.name.startswith('.'):
42+
continue
43+
is_directory = entry.is_dir()
44+
yield TreeNodeData(depth, is_directory, entry.name)
45+
if is_directory:
46+
yield from _gen_tree(data, entry, depth + 1)
47+
48+
49+
def _dirs_only(
50+
data: InputData,
51+
path: Path,
52+
depth: int = 0
53+
) -> Generator[TreeNodeData, None, None]:
54+
"""Only Yields Directories.
55+
"""
56+
for entry in path.iterdir():
57+
if not data.include_hidden and entry.name.startswith('.'):
58+
continue
59+
if (is_directory := entry.is_dir()):
60+
yield TreeNodeData(depth, is_directory, entry.name)
61+
yield from _dirs_only(data, entry, depth + 1)
62+
else:
63+
#print(f"Ignoring File {entry.name}")
64+
pass
65+
66+
67+
def _dirs_only_prune(
68+
data: InputData,
69+
path: Path,
70+
depth: int = 0
71+
) -> Generator[TreeNodeData, None, None]:
72+
"""Only Yields Directories, after pruning.
73+
"""
74+
for entry in path.iterdir():
75+
if not data.include_hidden and entry.name.startswith('.'):
76+
continue
77+
if (is_directory := entry.is_dir()):
78+
# Check if directory is empty
79+
if any(entry.iterdir()):
80+
yield TreeNodeData(depth, is_directory, entry.name)
81+
yield from _dirs_only_prune(data, entry, depth + 1)
82+
else:
83+
#print(f"Ignoring File {entry.name}")
84+
pass
85+

0 commit comments

Comments
 (0)