diff --git a/README.md b/README.md
index f7d2015..61bef7b 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,44 @@
-# Templateorator
-Small python script that creates directory structure for Jinja2 template with sidebar and tree view.
+## Templateorator
+
+Small Python CLI script to create desired directory structure with empty files from a JSON file and use the structure in Jinja2 static html template.
+
+Purpose:
+Script can be used to create small static web page with "toc tree" and links. Good for all kinds of reporting representation and data gathering.
+
+##### Preconditions:
+ - python must be installed (Version 3.6.5)
+ - read and write privileges must be available in execution folder
+ - correct html template must be available (example: defaultTemplate.html)
+
+##### Example
+ Example contains result of the script run with default settings
+
+ preconditions for example run:
+ ReportStructureCreator.py in the same directory as default.json, defaultTemplate.json and img directory
+
+ run from CLI:
+ python ReportStructureCreator.py
+
+ output:
+ index.html and ProjectRoot directory
+ output files together with img directory make a full product of this script
+
+ what to check in example:
+ check TOC tree in index.html, check directory structure under ProjectRoot and check the structure of default.json
+
+##### Usage:
+ 1. Create JSON file with correct structure: (-h for CLA description or check default.json)
+ { directory/file : ["dir_name"/"file_name" , "name of entry in index-page"] }
+ -> in case of directory: add "children:[{}]" for subdirectories and subfiles
+ -> "name of entry in index-page" can be left out, then folder/file is created but not shown in TOC tree of html output file
+
+ 2. Run python script (-h for CLA description)
+ python ReportStructureCreator.py [--source_file ] [--template_file ] [--html_file ] [--dest_file ] [--help]
+
+ 3. Check output file (index.html)
+
+ 4. Replace created empty files with a valid version of files
+
+ 5. Enjoy in your new static web page and data structure
+
+ Note: You can adjust the width of the sidebar manually by left-clicking in the bottom right corner of the sidebar, holding the mouse button down and dragging the mouse left or right
diff --git a/example/ProjectRoot/Example/file_1 b/example/ProjectRoot/Example/file_1
new file mode 100644
index 0000000..e69de29
diff --git a/example/ProjectRoot/Example/folder_1/subfolder_1/subsubfolder_1/file_2 b/example/ProjectRoot/Example/folder_1/subfolder_1/subsubfolder_1/file_2
new file mode 100644
index 0000000..e69de29
diff --git a/example/ProjectRoot/Example/folder_1/subfolder_1/subsubfolder_2/file_3 b/example/ProjectRoot/Example/folder_1/subfolder_1/subsubfolder_2/file_3
new file mode 100644
index 0000000..e69de29
diff --git a/example/ProjectRoot/Example/folder_1/subfolder_1/subsubfolder_3/file_4 b/example/ProjectRoot/Example/folder_1/subfolder_1/subsubfolder_3/file_4
new file mode 100644
index 0000000..e69de29
diff --git a/example/ProjectRoot/Example/folder_1/subfolder_2/subsubfolder_1/file_5 b/example/ProjectRoot/Example/folder_1/subfolder_2/subsubfolder_1/file_5
new file mode 100644
index 0000000..e69de29
diff --git a/example/ProjectRoot/Example/folder_1/subfolder_2/subsubfolder_2/file_6 b/example/ProjectRoot/Example/folder_1/subfolder_2/subsubfolder_2/file_6
new file mode 100644
index 0000000..e69de29
diff --git a/example/ProjectRoot/Example/folder_1/subfolder_2/subsubfolder_2/file_7 b/example/ProjectRoot/Example/folder_1/subfolder_2/subsubfolder_2/file_7
new file mode 100644
index 0000000..e69de29
diff --git a/example/ProjectRoot/Example/folder_1/subfolder_2/subsubfolder_3/file_8 b/example/ProjectRoot/Example/folder_1/subfolder_2/subsubfolder_3/file_8
new file mode 100644
index 0000000..e69de29
diff --git a/example/ProjectRoot/Example/folder_1/subfolder_2/subsubfolder_4/file_10 b/example/ProjectRoot/Example/folder_1/subfolder_2/subsubfolder_4/file_10
new file mode 100644
index 0000000..e69de29
diff --git a/example/ProjectRoot/Example/folder_1/subfolder_2/subsubfolder_4/file_11 b/example/ProjectRoot/Example/folder_1/subfolder_2/subsubfolder_4/file_11
new file mode 100644
index 0000000..e69de29
diff --git a/example/ProjectRoot/Example/folder_1/subfolder_2/subsubfolder_4/file_12 b/example/ProjectRoot/Example/folder_1/subfolder_2/subsubfolder_4/file_12
new file mode 100644
index 0000000..e69de29
diff --git a/example/ProjectRoot/Example/folder_1/subfolder_2/subsubfolder_4/file_9 b/example/ProjectRoot/Example/folder_1/subfolder_2/subsubfolder_4/file_9
new file mode 100644
index 0000000..e69de29
diff --git a/example/img/Default.htm b/example/img/Default.htm
new file mode 100644
index 0000000..4760b96
--- /dev/null
+++ b/example/img/Default.htm
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+ ______________________________________________________
+
+
+
+ Some text can be put here (edit Default.htm)
+
+
+
\ No newline at end of file
diff --git a/example/img/logo.png b/example/img/logo.png
new file mode 100644
index 0000000..b9cec53
Binary files /dev/null and b/example/img/logo.png differ
diff --git a/example/index.html b/example/index.html
new file mode 100644
index 0000000..4a7c137
--- /dev/null
+++ b/example/index.html
@@ -0,0 +1,221 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ReportStructureCreator.py b/src/ReportStructureCreator.py
new file mode 100644
index 0000000..56ba219
--- /dev/null
+++ b/src/ReportStructureCreator.py
@@ -0,0 +1,329 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import argparse
+import re
+import json
+import shutil
+from io import StringIO
+from collections import OrderedDict
+from jinja2 import Template
+from argparse import RawDescriptionHelpFormatter as help_formatter
+from time import sleep
+
+# to get directory where the script is
+dir_path = os.path.dirname(os.path.realpath(__file__))
+
+# structure for JSON valid keys
+class JSON_KEYS:
+ DIRECTORY = 'directory'
+ FILE = 'file'
+ CHILDREN = 'children'
+
+# structure for node elements
+class NODE_MEMBERS:
+ NAME = 'name'
+ TOCNAME = 'toc_name'
+ TYPE = 'type'
+ CHILDREN = 'children'
+
+# structure for different file names
+class SORT_NAMES:
+ FILENAME = 0
+ TOCNAME = 1
+
+# helper functions
+def get_type(path):
+ ''' Returns TYPE for path.
+ get_type(folder): 'directory'
+ get_type(file): 'file'
+ '''
+ if os.path.isdir(path):
+ return JSON_KEYS.DIRECTORY
+ return JSON_KEYS.FILE
+
+def is_json(filename):
+ return filename.endswith('.json')
+
+def read_json(path):
+ ''' Opens + loads .json file, returns dictionary.
+ read_json('valid_path.json')
+
+ Exits if fails to open. Does ensure valid structure.
+ '''
+ try:
+ with open(path, 'r') as f:
+ path_dict = json.load(f)
+ return path_dict
+ except IOError as errmsg:
+ if errmsg.errno == 21: # IsADirectoryError
+ raise TypeError("Specified JSON, gave folder: {}".format(path))
+ sys.exit('Cannot open. Error: {}'.format(errmsg))
+
+def delete_if_dir_exists(path):
+ if os.path.isdir(path):
+ if input('WARNING: dest_file already exists. Overwrite? (y/n)') != 'y':
+ print('Exiting.')
+ sys.exit()
+ try:
+ shutil.rmtree(path)
+ sleep(0.1) # give time for OS to clean
+ print('\nWARNING: Existing directory deleted.\n')
+ except:
+ print('\nERROR: Could not overwrite:\n')
+ sys.exit(sys.exc_info()[0].__name__)
+
+# node is one element inside loaded JSON tree structure
+class Node(object):
+ ''' Node Instance Class.
+ node = Node(path_data)
+ path_data > node position
+
+ : param : self.name
+ : param : self.toc_name > name for template table of content
+ : param : self.type > directory, file
+ : param : self.children
+
+ method : node.get_dict() > return sorted dictionary
+ '''
+ def __init__(self, path_data):
+ self.parent = None # root does not have a parent
+
+ def sort_name_and_type(path_data):
+ configuration_error = False
+
+ if JSON_KEYS.DIRECTORY in path_data: # sort types
+ self.type = JSON_KEYS.DIRECTORY
+ names = path_data.get(JSON_KEYS.DIRECTORY, None)
+ elif JSON_KEYS.FILE in path_data:
+ self.type = JSON_KEYS.FILE
+ names = path_data.get(JSON_KEYS.FILE, None)
+ else: # no valid key name
+ configuration_error = True
+
+ if len(names) == 2: # file name and TOC Name in the list
+ self.name = names[SORT_NAMES.FILENAME]
+ self.toc_name = names[SORT_NAMES.TOCNAME]
+ elif len(names) == 1: # only file name in the list
+ self.name = names[SORT_NAMES.FILENAME]
+ self.toc_name = None
+ else: # to many or to few elements in the list
+ configuration_error = True
+
+ if configuration_error: # check is JSON valid for the script
+ print('ERROR: wrong configuration file (JSON) format')
+ sys.exit()
+
+ sort_name_and_type(path_data)
+ self.children = [
+ Node(child) # use recursion to generate all nodes inside a tree
+ for child in path_data.get(JSON_KEYS.CHILDREN, []) # files have children[] added to the node element
+ ]
+ for child in self.children:
+ child.parent = self
+
+ def get_dict(self): # use it to get a complete dictionary of the structure
+ d = OrderedDict()
+ d[NODE_MEMBERS.NAME] = self.name
+ d[NODE_MEMBERS.TOCNAME] = self.toc_name
+ d[NODE_MEMBERS.TYPE] = self.type
+ d[NODE_MEMBERS.CHILDREN] = [child.get_dict() for child in self.children]
+ return d
+
+ def __iter__(self):
+ for child in self.children:
+ yield child
+
+ def __repr__(self):
+ return ''.format(self.name, self.type)
+
+# object which links all nodes
+class Tree(object):
+ ''' Create a Tree Object from a valid json.
+ tree = Tree(pathToJson)
+ path_to_json: a valid path to a JSON file with a directory structure
+
+ : param : self.root > returns tree root node
+ : param : self.path > returns the absolute path were tree will be created
+
+ Methods:
+ tree.write_tree(destination) > creates direcotry structure on the disk
+ tree.print_tree > console print of a tree and all nodes
+ tree.print_django > stream tree html output to a correct template
+
+ Properties:
+ tree.as_dict > self.root.get_dict()
+ '''
+
+ def __init__(self, path_to_json):
+ path_dict = read_json(path_to_json)
+ self.root = Node(path_dict)
+ self.path = None
+
+ def write_tree(self, dest_path): # assume that the directoryes are not existing
+ ''' Creates directory tree from root node.
+ write_tree(dest_path)
+ Will delete tree if exists.
+ '''
+
+ def make(dest_path, node):
+ dest_path = os.path.join(dest_path, node.name)
+ if node.type == JSON_KEYS.DIRECTORY:
+ try:
+ os.makedirs(dest_path)
+ except:
+ raise
+ elif node.type == JSON_KEYS.FILE:
+ with open(dest_path, 'a') as f: # 'a' is for creating a file
+ pass
+
+ for child in node.children:
+ make(dest_path, child)
+ make(dest_path, self.root)
+ self.path = dest_path
+ print('''Directoryes and files located at:''')
+ print(dest_path + '\n')
+
+ @property
+ def as_dict(self):
+ return self.root.get_dict()
+
+ def print_tree(self):
+ print('=' * 40)
+ print(self)
+
+ def print_node(node, level=0):
+ print('{level} {name}'.format(level='|' * level or '|', name=node.name))
+ level += 1
+ for child in node.children:
+ print_node(child, level)
+
+ print_node(self.root)
+
+ def print_django(self): # this can be done better "a quick hack"
+ string_stream = StringIO()
+ default_space_offset = ' '
+ number_of_spaces = ' '
+ def print_node(node, level=1, dest_path=self.path):
+ dest_path = os.path.join(dest_path, node.name)
+ if node.type == JSON_KEYS.DIRECTORY and node.toc_name != None:
+ print('{spaces}'.format(
+ spaces= (number_of_spaces * level) + default_space_offset),
+ file = string_stream)
+ print('{spaces}{name}'.format(
+ spaces= (number_of_spaces * level) + default_space_offset,
+ name=node.toc_name),
+ file = string_stream)
+ print('{spaces}'.format(
+ spaces= (number_of_spaces * level) + default_space_offset),
+ file = string_stream)
+ level += 1
+ elif node.type == JSON_KEYS.FILE and node.toc_name != None:
+ path_to_node_file = os.path.relpath(dest_path, dir_path).replace('\\', '/')
+ print('{spaces}- '.format(
+ spaces= (number_of_spaces * level) + default_space_offset),
+ file = string_stream)
+ print('{spaces}{name}'.format(
+ spaces= (number_of_spaces * level) + default_space_offset,
+ link=path_to_node_file, name = node.toc_name),
+ file = string_stream)
+ level += 1
+
+ for child in node.children:
+ print_node(child, level, dest_path)
+
+ level -= 1
+ if node.type == JSON_KEYS.DIRECTORY and node.toc_name != None:
+ print('{spaces}
'.format(
+ spaces= (number_of_spaces * level) + default_space_offset),
+ file = string_stream)
+ print('{spaces}'.format(
+ spaces= (number_of_spaces * level) + default_space_offset),
+ file = string_stream)
+ elif node.type == JSON_KEYS.FILE and node.toc_name != None:
+ print('{spaces}'.format(
+ spaces= (number_of_spaces * level) + default_space_offset),
+ file = string_stream)
+
+ print_node(self.root)
+
+ data = string_stream.getvalue()
+ string_stream.close()
+ return data
+
+ def __repr__(self):
+ return ''.format(self.root.name)
+
+
+def main():
+ usage = 'python ReportStructureCreator.py sourceFile.json [--source_file ] [--template_file ] [--html_file ] [--dest_file ] [--help]'
+ description = '''
+ ====================================================================================
+ ReportStructureCreator - Tool for generating Report folder structure and html
+ Version: {version}
+ Author: {author}
+ Project: {projectName}
+ ProjectVersion: {projectVersion}
+
+ small json input Example:
+ "directory": ["Root Folder", "TOC RootName"],
+ "children": ["file": ["File A-A-1", "checkExample.txt"]]
+ ====================================================================================
+ '''.format(version='V01.00.00', author='mate.vukic@gmail.com', projectName='Terminator', projectVersion='N/A')
+
+ # command line arguments handeling
+ parser = argparse.ArgumentParser(prog='ReportStructureCreator', description=description,
+ usage=usage,
+ formatter_class=help_formatter,
+ )
+
+ parser.add_argument("-i", "--source_file", type=str, default=os.path.join(dir_path, "default.json"), nargs='?',
+ help='file path to .json which contains directory structure, default: "default.json')
+
+ parser.add_argument("-t", "--template_file", type=str, default=os.path.join(dir_path, "defaultTemplate.html"), nargs='?',
+ help='Jinja2-Template for creation of html index page, default: "defaultTemplate.html')
+
+ parser.add_argument("-o", "--html_file", type=str, default=os.path.join(dir_path, "index.html"), nargs='?',
+ help='html to be created, default: index.html')
+
+ parser.add_argument("-p", "--dest_file", type=str, nargs='?', default=os.path.join(dir_path, "ProjectRoot"),
+ help='file path to a directory where structure will be created, default: Path were script is executed + "ProjectRoot"')
+
+ args_dict = vars(parser.parse_args())
+ globals().update(args_dict)
+
+ # ensure source_file exists, if not exit.
+ exists = os.path.exists
+ if not exists(source_file):
+ print('=' * 40)
+ parser.print_help()
+ print('=' * 40)
+ print('\nERROR: configuration file does not exist.\n')
+ sys.exit()
+
+ # if dest_file exists, prompt for overwrite before continuing.
+ if exists(dest_file):
+ delete_if_dir_exists(dest_file)
+
+ # make a tree from JSON file with node elements
+ tree = Tree(source_file)
+
+ # create directory structure inside destitnation directory
+ tree.write_tree(dest_file)
+
+ with open(template_file) as f:
+ template = Template(f.read())
+
+ render = template.render(tree = tree)
+
+ tree.print_tree()
+
+ Html_file= open(html_file,"w")
+ Html_file.write(render)
+ Html_file.close
+
+ print('Done.')
+
+if __name__ == '__main__':
+ main()
diff --git a/src/default.json b/src/default.json
new file mode 100644
index 0000000..0ede2b8
--- /dev/null
+++ b/src/default.json
@@ -0,0 +1,67 @@
+{
+ "directory": ["Example", "TOC Root"],
+ "children": [
+ { "file": ["file_1", "file name_1"] },
+ {
+ "directory": ["folder_1", "folder name 1"],
+ "children": [
+ {
+ "directory": ["subfolder_1", "subfolder name 1"],
+ "children": [
+ {
+ "directory": ["subsubfolder_1"],
+ "children": [
+ { "file": ["file_2", "file name 2"] }
+ ]
+ },
+ {
+ "directory": ["subsubfolder_2", "subsubfolder name 2"],
+ "children": [
+ { "file": ["file_3", "file name 3"] }
+ ]
+ },
+ {
+ "directory": ["subsubfolder_3"],
+ "children": [
+ { "file": ["file_4", "file name 4"] }
+ ]
+ }
+ ]
+ },
+ {
+ "directory": ["subfolder_2", "subfolder name 2"],
+ "children":[
+ {
+ "directory": ["subsubfolder_1", "subsubfolder name 1"],
+ "children": [
+ { "file": ["file_5", "file name 5"] }
+ ]
+ },
+ {
+ "directory": ["subsubfolder_2", "subsubfolder name 2"],
+ "children": [
+ { "file": ["file_6", "file name 6"] },
+ { "file": ["file_7", "file name 7"] }
+ ]
+ },
+ {
+ "directory": ["subsubfolder_3", "subsubfolder name 3"],
+ "children": [
+ { "file": ["file_8", "file name 8"] }
+ ]
+ },
+ {
+ "directory": ["subsubfolder_4", "subsubfolder name 4"],
+ "children":[
+ { "file": ["file_9", "file name 9"] },
+ { "file": ["file_10", "file name 10"] },
+ { "file": ["file_11", "file name 11"] },
+ { "file": ["file_12", "file name 12"] }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/defaultTemplate.html b/src/defaultTemplate.html
new file mode 100644
index 0000000..1ff9a9a
--- /dev/null
+++ b/src/defaultTemplate.html
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/img/Default.htm b/src/img/Default.htm
new file mode 100644
index 0000000..2e5b02e
--- /dev/null
+++ b/src/img/Default.htm
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ ______________________________________________________
+
+
+
+ Some text can be put here (edit Default.htm)
+
+
+
\ No newline at end of file
diff --git a/src/img/logo.png b/src/img/logo.png
new file mode 100644
index 0000000..b9cec53
Binary files /dev/null and b/src/img/logo.png differ