Skip to content

Commit 3707703

Browse files
authored
Initialize Treescriptify Project (#1)
Documentation: * Create README.md Input: * Create __init__.py * Create argument_parser.py * Create input_data.py Test Input: * Create __init__.py * Create test_argument_parser.py Test Treescriptify: * Create __init__.py * Create test_script_writer.py Treescriptify: * Create __init__.py * Create script_writer.py * Create tree_node_data.py * Create tree_reader.py * Create tree_runner.py Workflows: * Create __main__.py * Create ci_run.yml * Create dependabot.yml
1 parent 86fa2fa commit 3707703

16 files changed

+339
-0
lines changed

.github/dependabot.yml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# To get started with Dependabot version updates, you'll need to specify which
2+
# package ecosystems to update and where the package manifests are located.
3+
# Please see the documentation for all configuration options:
4+
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5+
6+
version: 2
7+
updates:
8+
- package-ecosystem: "github-actions"
9+
# Workflow files stored in the default location of `.github/workflows`. (You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`.)
10+
directory: "/"
11+
schedule:
12+
# Check for updates to GitHub Actions every weekday
13+
interval: "daily"
14+
open-pull-requests-limit: 2
15+

.github/workflows/ci_run.yml

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# This workflow installs Python dependencies, run lint checks and unit tests
2+
# Info: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3+
4+
name: Python App lint check and unit test
5+
6+
on:
7+
push:
8+
branches: [ main ]
9+
pull_request:
10+
branches: [ main ]
11+
12+
jobs:
13+
build:
14+
runs-on: ubuntu-latest
15+
strategy:
16+
matrix:
17+
python-version: [ '3.12' ]
18+
steps:
19+
- uses: actions/checkout@v4
20+
- name: Set up Python ${{ matrix.python-version }}
21+
uses: actions/setup-python@v5
22+
with:
23+
python-version: ${{ matrix.python-version }}
24+
25+
- name: Install Dependencies
26+
run: python -m pip install --user pytest pytest-cov
27+
28+
- name: Run unit tests
29+
run: pytest test/ --cov --cov-report=html --cov-fail-under=75
30+
31+
- name: Upload Test Coverage Reports
32+
uses: actions/upload-artifact@v3
33+
with:
34+
name: treescriptify-cov
35+
path: htmlcov/

README.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# TreeScriptify
2+
Get Started fast with a TreeScript generated from an existing directory.
3+
4+
## How To Use
5+
Run `python <path-to-tsfy-dir>` command in a directory to treescript-ify.
6+
7+
1. You can pipe program output to a file, or
8+
2. Display it on-screen for copy-paste workflow integration.
9+
10+
## Program Architecture
11+
![tsfy](https://github.com/DK96-OS/tsfy/assets/69859316/84f127f0-cf19-418b-be78-572fbb96868c)
12+
13+

__main__.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/python
2+
"""Main Entry Point
3+
"""
4+
from sys import argv
5+
6+
from input.argument_parser import parse_args
7+
from treescriptify import tsfy
8+
9+
10+
def main():
11+
input_data = parse_args(argv[1:])
12+
print(tsfy(input_data))
13+
14+
15+
if __name__ == '__main__':
16+
main()

input/__init__.py

Whitespace-only changes.

input/argument_parser.py

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""Argument Parsing Methods
2+
"""
3+
from argparse import ArgumentParser
4+
5+
from input.input_data import InputData
6+
7+
8+
def parse_args(arguments: list[str]) -> InputData:
9+
"""Parse the arguments, and determine the program's Input Data.
10+
"""
11+
try:
12+
args = _define_arguments().parse_args(arguments)
13+
except SystemExit as e:
14+
exit("Unable to Parse Arguments.")
15+
return InputData(
16+
include_hidden=args.hide,
17+
directories_only=args.directories,
18+
git_ignore=args.gitignore,
19+
prune_dirs=args.prune
20+
)
21+
22+
23+
def _define_arguments() -> ArgumentParser:
24+
"""Create and initialize the ArgumentParser.
25+
"""
26+
parser = ArgumentParser(
27+
description='Treescriptify'
28+
)
29+
parser.add_argument(
30+
'-a',
31+
'--hide',
32+
action='store_false',
33+
default=True,
34+
help='Hidden files are shown by default. This flag hides them.'
35+
)
36+
parser.add_argument(
37+
'-d',
38+
'--directories',
39+
action='store_true',
40+
default=False,
41+
help='Flag to show only the directories.'
42+
)
43+
parser.add_argument(
44+
'--gitignore',
45+
action='store_false',
46+
default=True,
47+
help='Gitignore is enabled by default. This flag disables .gitignore.'
48+
)
49+
parser.add_argument(
50+
'--prune',
51+
action='store_true',
52+
default=False,
53+
help='Flag to prune empty directories from the output.'
54+
)
55+
return parser

input/input_data.py

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""Input Data to the Program
2+
"""
3+
from dataclasses import dataclass
4+
5+
6+
@dataclass(frozen=True)
7+
class InputData:
8+
"""The optional flags to modify the output of the program.
9+
"""
10+
11+
include_hidden: bool = True
12+
#
13+
directories_only: bool = False
14+
#
15+
git_ignore: bool = True
16+
#
17+
prune_dirs: bool = False

test/__init__.py

Whitespace-only changes.

test/input/__init__.py

Whitespace-only changes.

test/input/test_argument_parser.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Testing Module Methods
2+
"""
3+
import pytest
4+
5+
from input.input_data import InputData
6+
from input.argument_parser import parse_args
7+
8+
9+
@pytest.mark.parametrize(
10+
'test_input,expected',
11+
[
12+
([], InputData()),
13+
(['-a'], InputData(include_hidden=False)),
14+
(['-d'], InputData(directories_only=True)),
15+
(['--gitignore'], InputData(git_ignore=False)),
16+
(['--prune'], InputData(prune_dirs=True)),
17+
]
18+
)
19+
def test_parse_args_returns_input(test_input, expected):
20+
assert parse_args(test_input) == expected
21+
22+
23+
@pytest.mark.parametrize(
24+
'test_input',
25+
[
26+
(['']),
27+
([' ']),
28+
(['r']),
29+
(['eeee']),
30+
]
31+
)
32+
def test_parse_args_raises_exit(test_input):
33+
try:
34+
parse_args(test_input)
35+
assert False
36+
except SystemExit:
37+
assert True
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""Testing Module Methods
2+
"""
3+
import pytest
4+
5+
from treescriptify.script_writer import generate_script
6+
from treescriptify.tree_node_data import TreeNodeData
7+
8+
9+
def generator_single_dir_node(name: str, depth: int=0):
10+
yield TreeNodeData(depth, True, name)
11+
12+
13+
def generator_single_file_node(name: str, depth: int=0):
14+
yield TreeNodeData(depth, False, name)
15+
16+
17+
def generator_depth_1():
18+
yield TreeNodeData(0, True, 'module')
19+
yield TreeNodeData(1, False, 'file.txt')
20+
yield TreeNodeData(1, True, 'src')
21+
22+
23+
@pytest.mark.parametrize(
24+
'test_input,expected',
25+
[
26+
(generator_single_dir_node('.directory'), '.directory/'),
27+
(generator_single_dir_node('.directory', 1), ' .directory/'),
28+
(generator_single_file_node('file.txt'), 'file.txt'),
29+
(generator_single_file_node('file.txt', 1), ' file.txt'),
30+
(generator_depth_1(), 'module/\n file.txt\n src/')
31+
]
32+
)
33+
def test_generate_script_returns_str(test_input, expected):
34+
script_gen = generate_script(test_input)
35+
assert "\n".join(script_gen) == expected

treescriptify/__init__.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""TreeScriptify Package
2+
"""
3+
from input.input_data import InputData
4+
from treescriptify.tree_runner import get_tree_json
5+
from treescriptify.tree_reader import generate_from_json
6+
from treescriptify.script_writer import generate_script
7+
8+
9+
def tsfy(data: InputData) -> str:
10+
"""Run Treescriptify on the given Inputs.
11+
"""
12+
return '\n'.join(
13+
generate_script(
14+
generate_from_json(
15+
get_tree_json(data)
16+
)
17+
)
18+
)

treescriptify/script_writer.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Generate TreeScript from Tree Node Data.
2+
"""
3+
from typing import Generator
4+
5+
from treescriptify.tree_node_data import TreeNodeData
6+
7+
8+
def generate_script(tree_nodes: Generator[TreeNodeData, None, None]) -> Generator[str, None, None]:
9+
"""Generate the TreeScript Line by Line for each Tree Node.
10+
"""
11+
for node in tree_nodes:
12+
yield _write_line(node)
13+
14+
15+
def _write_line(data: TreeNodeData) -> str:
16+
"""Convert a Tree Node into a line of TreeScript.
17+
"""
18+
line = data.depth * ' ' + data.name
19+
if data.is_dir:
20+
line += '/'
21+
return line

treescriptify/tree_node_data.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""Tree Node Data
2+
"""
3+
from dataclasses import dataclass
4+
5+
6+
@dataclass(frozen=True)
7+
class TreeNodeData:
8+
"""
9+
"""
10+
11+
depth: int
12+
#
13+
is_dir: bool
14+
#
15+
name: str

treescriptify/tree_reader.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""
2+
"""
3+
import json
4+
from typing import Generator
5+
6+
from treescriptify.tree_node_data import TreeNodeData
7+
8+
9+
def generate_from_json(json_string: str) -> Generator[TreeNodeData, None, None]:
10+
"""Read the JSON string and generate TreeNodeData for all elements.
11+
"""
12+
full_json = json.loads(json_string)[0]
13+
for i in full_json['contents']:
14+
for node in _process_node(i, 0):
15+
yield node
16+
17+
18+
def _process_node(node: dict, depth: int) -> Generator[TreeNodeData, None, None]:
19+
"""
20+
"""
21+
if node['type'] == 'file':
22+
yield TreeNodeData(depth, False, node['name'])
23+
else:
24+
yield TreeNodeData(depth, True, node['name'])
25+
for nested_nodes in node.get('contents', []):
26+
for node in _process_node(nested_nodes, depth + 1):
27+
yield node

treescriptify/tree_runner.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""Tree Runner
2+
"""
3+
import subprocess
4+
5+
from input.input_data import InputData
6+
7+
8+
def get_tree_json(data: InputData) -> str:
9+
"""Obtain the Tree Information as a JSON string.
10+
"""
11+
result = subprocess.run(
12+
args='tree -Jix --noreport ' + _check_arguments(data),
13+
capture_output=True,
14+
text=True,
15+
shell=True,
16+
timeout=3
17+
)
18+
output = result.stdout
19+
#error = result.stderr
20+
return output
21+
22+
23+
def _check_arguments(data: InputData) -> str:
24+
"""Check the Input Data and map flags to tree command.
25+
"""
26+
extras = []
27+
if data.directories_only:
28+
extras.append('-d ')
29+
if data.prune_dirs:
30+
extras += ['--prune ']
31+
if data.include_hidden:
32+
extras += ['-a ']
33+
if data.git_ignore:
34+
extras.append('--gitignore ')
35+
return ' '.join(extras)

0 commit comments

Comments
 (0)