2021-08-14 16:04:22 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
2022-08-20 12:27:45 +00:00
|
|
|
import time
|
2022-05-15 22:06:37 +00:00
|
|
|
import json
|
2021-08-26 22:22:48 +00:00
|
|
|
import html
|
2021-08-14 16:04:22 +00:00
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import uuid
|
|
|
|
from datetime import datetime
|
2022-08-20 12:27:45 +00:00
|
|
|
import traceback
|
|
|
|
|
|
|
|
import inotify.adapters
|
2021-08-14 16:04:22 +00:00
|
|
|
|
2022-05-07 21:03:26 +00:00
|
|
|
import org_rw
|
2022-05-06 13:58:28 +00:00
|
|
|
from org_rw import OrgTime, dom, Link
|
2021-08-14 16:04:22 +00:00
|
|
|
from org_rw import dump as dump_org
|
|
|
|
from org_rw import load as load_org
|
2021-08-26 22:22:48 +00:00
|
|
|
from org_rw import token_list_to_raw
|
2021-08-14 16:04:22 +00:00
|
|
|
|
|
|
|
EXTENSIONS = [
|
|
|
|
".org",
|
|
|
|
".org.txt",
|
|
|
|
]
|
|
|
|
|
2022-05-07 18:38:12 +00:00
|
|
|
MIN_HIDDEN_HEADLINE_LEVEL = 2
|
2022-08-20 11:59:15 +00:00
|
|
|
INDEX_ID = "ea48ec1d-f9d4-4fb7-b39a-faa7b6e2ba95"
|
2022-08-20 16:21:26 +00:00
|
|
|
SITE_NAME = "Código para llevar"
|
2021-08-14 16:04:22 +00:00
|
|
|
|
2022-08-20 12:27:45 +00:00
|
|
|
MONITORED_EVENT_TYPES = (
|
|
|
|
'IN_CREATE',
|
|
|
|
# 'IN_MODIFY',
|
|
|
|
'IN_CLOSE_WRITE',
|
|
|
|
'IN_DELETE',
|
|
|
|
'IN_MOVED_FROM',
|
|
|
|
'IN_MOVED_TO',
|
|
|
|
'IN_DELETE_SELF',
|
|
|
|
'IN_MOVE_SELF',
|
|
|
|
)
|
|
|
|
|
|
|
|
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
|
|
|
STATIC_PATH = os.path.join(ROOT_DIR, 'static')
|
|
|
|
|
|
|
|
def is_git_path(path):
|
2022-08-20 17:17:08 +00:00
|
|
|
return any([chunk == ".git" for chunk in path.split(os.sep)])
|
2022-08-20 12:27:45 +00:00
|
|
|
|
2021-08-14 16:04:22 +00:00
|
|
|
def load_all(top_dir_relative):
|
|
|
|
top = os.path.abspath(top_dir_relative)
|
|
|
|
|
|
|
|
docs = []
|
|
|
|
|
|
|
|
for root, dirs, files in os.walk(top):
|
|
|
|
for name in files:
|
|
|
|
if ".org" not in name:
|
|
|
|
continue
|
|
|
|
|
|
|
|
path = os.path.join(root, name)
|
|
|
|
|
|
|
|
try:
|
|
|
|
doc = load_org(open(path), extra_cautious=True)
|
|
|
|
docs.append(doc)
|
|
|
|
except Exception as err:
|
|
|
|
import traceback
|
|
|
|
|
|
|
|
traceback.print_exc()
|
|
|
|
print(f"== On {path}")
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
logging.info("Collected {} files".format(len(docs)))
|
|
|
|
return docs
|
|
|
|
|
2022-08-20 12:27:45 +00:00
|
|
|
def regen_all(src_top, dest_top, docs=None):
|
2021-08-26 22:22:48 +00:00
|
|
|
files_generated = 0
|
2021-08-14 16:04:22 +00:00
|
|
|
|
2022-08-20 12:27:45 +00:00
|
|
|
docs = load_all(src_top)
|
2022-05-16 21:55:58 +00:00
|
|
|
doc_to_headline_remapping = {}
|
|
|
|
|
2021-08-14 16:04:22 +00:00
|
|
|
os.makedirs(dest_top, exist_ok=True)
|
2022-05-15 22:06:37 +00:00
|
|
|
graph = {}
|
2021-08-14 16:04:22 +00:00
|
|
|
for doc in docs:
|
|
|
|
relpath = os.path.relpath(doc.path, src_top)
|
|
|
|
changed = False
|
2021-09-03 18:19:45 +00:00
|
|
|
headlines = list(doc.getAllHeadlines())
|
2021-08-14 16:04:22 +00:00
|
|
|
related = None
|
|
|
|
|
|
|
|
i = len(headlines)
|
|
|
|
while i > 0:
|
|
|
|
i -= 1
|
|
|
|
headline = headlines[i]
|
2021-09-03 18:19:45 +00:00
|
|
|
if headline.title.strip().lower() == "related" and headline.depth == 1:
|
|
|
|
if related is not None:
|
|
|
|
print(
|
|
|
|
"Found duplicated related: {} vs {}".format(
|
|
|
|
related.id, headline.id
|
|
|
|
)
|
|
|
|
)
|
|
|
|
assert related is None
|
2021-08-14 16:04:22 +00:00
|
|
|
related = headline
|
|
|
|
headlines.pop(i)
|
|
|
|
|
|
|
|
for headline in headlines:
|
|
|
|
if headline.id is None:
|
|
|
|
headline.id = str(uuid.uuid4())
|
|
|
|
changed = True
|
|
|
|
|
|
|
|
if changed:
|
|
|
|
print("Updated", relpath)
|
|
|
|
save_changes(doc)
|
|
|
|
|
|
|
|
if not relpath.startswith("public/"):
|
|
|
|
# print("Skip:", relpath)
|
|
|
|
continue
|
2021-08-26 22:22:48 +00:00
|
|
|
|
2022-05-16 21:55:58 +00:00
|
|
|
main_headline = None
|
|
|
|
topHeadlines = doc.getTopHeadlines()
|
2022-05-07 11:15:30 +00:00
|
|
|
|
2022-05-16 21:55:58 +00:00
|
|
|
if ((len(topHeadlines) == 1 and related is None)
|
|
|
|
or (len(topHeadlines) == 2 and related is not None)):
|
2022-05-07 11:15:30 +00:00
|
|
|
|
2022-05-16 21:55:58 +00:00
|
|
|
main_headline = [h for h in topHeadlines if h != related][0]
|
|
|
|
if doc.id is not None:
|
|
|
|
endpath = os.path.join(dest_top, doc.id + ".node.html")
|
2022-05-07 11:15:30 +00:00
|
|
|
with open(endpath, "wt") as f:
|
2022-05-16 21:55:58 +00:00
|
|
|
doc_to_headline_remapping['id:' + doc.id] = 'id:' + main_headline.id
|
|
|
|
|
2022-08-22 22:05:03 +00:00
|
|
|
f.write(as_document(render(main_headline, doc, headlineLevel=0), main_headline.title))
|
2022-05-07 11:15:30 +00:00
|
|
|
files_generated += 1
|
2022-05-16 21:55:58 +00:00
|
|
|
elif doc.id is not None:
|
|
|
|
logging.error("Cannot render document from id: {}. {} headlines {} related".format(
|
|
|
|
relpath,
|
|
|
|
len(topHeadlines),
|
|
|
|
'with' if related is not None else 'without'
|
|
|
|
))
|
2022-05-07 11:15:30 +00:00
|
|
|
|
2021-08-26 22:22:48 +00:00
|
|
|
for headline in headlines:
|
|
|
|
endpath = os.path.join(dest_top, headline.id + ".node.html")
|
|
|
|
|
2022-05-15 22:06:37 +00:00
|
|
|
links = []
|
2022-05-16 21:55:58 +00:00
|
|
|
headline_links = list(headline.get_links())
|
|
|
|
if headline == main_headline and related is not None:
|
|
|
|
headline_links.extend(list(related.get_links()))
|
|
|
|
|
|
|
|
for l in headline_links:
|
2022-05-15 22:06:37 +00:00
|
|
|
if l.value.startswith('http://') or l.value.startswith('https://'):
|
|
|
|
pass # Ignore for now, external URL
|
|
|
|
elif l.value.startswith('id:'):
|
|
|
|
links.append({'target': l.value})
|
|
|
|
elif l.value.startswith('attachment:'):
|
|
|
|
pass # Ignore, attachment
|
2022-05-16 21:27:26 +00:00
|
|
|
elif l.value.startswith('file:'):
|
|
|
|
pass # Ignore, attachment
|
|
|
|
elif l.value.startswith('notmuch:'):
|
|
|
|
pass # Ignore, mail
|
|
|
|
elif l.value.startswith('orgit-rev:'):
|
|
|
|
pass # Ignore, mail
|
|
|
|
elif l.value.startswith('*'):
|
|
|
|
pass # Ignore, internal
|
|
|
|
elif not ':' in l.value.split()[0]:
|
2022-05-15 22:06:37 +00:00
|
|
|
pass # Ignore, internal
|
|
|
|
elif l.value.startswith('./'):
|
|
|
|
pass # TODO: Properly handle
|
|
|
|
else:
|
2022-08-19 17:30:55 +00:00
|
|
|
logging.warning('On document {}, unknown link to {}'.format(doc.path, l.value))
|
2022-05-15 22:06:37 +00:00
|
|
|
|
|
|
|
if headline.parent:
|
|
|
|
if isinstance(headline.parent, org_rw.Headline):
|
|
|
|
links.append({
|
|
|
|
"target": headline.parent.id,
|
2022-05-16 21:28:43 +00:00
|
|
|
"relation": "in"
|
2022-05-15 22:06:37 +00:00
|
|
|
})
|
|
|
|
graph[headline.id] = {
|
|
|
|
"title": headline.title.strip(),
|
|
|
|
"links": links,
|
|
|
|
"depth": headline.depth,
|
|
|
|
}
|
|
|
|
|
2021-08-26 22:22:48 +00:00
|
|
|
with open(endpath, "wt") as f:
|
2022-08-20 16:21:26 +00:00
|
|
|
f.write(as_document(render(headline, doc, headlineLevel=0), headline.title))
|
2021-08-26 22:22:48 +00:00
|
|
|
files_generated += 1
|
|
|
|
|
2022-08-20 11:59:15 +00:00
|
|
|
if headline.id == INDEX_ID:
|
|
|
|
index_endpath = os.path.join(dest_top, "index.html")
|
|
|
|
with open(index_endpath, "wt") as f:
|
2022-08-20 16:21:26 +00:00
|
|
|
f.write(as_document(render(headline, doc, headlineLevel=0), headline.title))
|
2022-08-20 11:59:15 +00:00
|
|
|
files_generated += 1
|
|
|
|
|
2022-05-16 21:55:58 +00:00
|
|
|
# Update graph, replace document ids with headline ids
|
|
|
|
for headline_data in graph.values():
|
|
|
|
for link in headline_data['links']:
|
|
|
|
if link['target'] in doc_to_headline_remapping:
|
|
|
|
link['target'] = doc_to_headline_remapping[link['target']]
|
|
|
|
|
|
|
|
# Output graph files
|
2022-05-15 22:06:37 +00:00
|
|
|
graphpath = os.path.join(dest_top, "graph.json")
|
|
|
|
graph_explorer_path = os.path.join(dest_top, "graph.html")
|
|
|
|
with open(graphpath, "wt") as f:
|
|
|
|
json.dump(obj=graph, fp=f, indent=2)
|
|
|
|
graph_explorer_path = os.path.join(dest_top, "graph.html")
|
|
|
|
with open(graph_explorer_path, 'wt') as f:
|
2022-08-19 17:30:24 +00:00
|
|
|
with open(os.path.join(os.path.dirname(os.path.abspath(dest_top)), '..', 'static', 'graph_explorer.html'), 'rt') as template:
|
2022-05-15 22:06:37 +00:00
|
|
|
source = template.read()
|
|
|
|
f.write(source.replace('<!-- REPLACE_THIS_WITH_GRAPH -->',
|
|
|
|
json.dumps(graph)))
|
2021-08-26 22:22:48 +00:00
|
|
|
logging.info("Generated {} files".format(files_generated))
|
|
|
|
|
|
|
|
|
2022-08-20 12:27:45 +00:00
|
|
|
def main(src_top, dest_top):
|
|
|
|
notifier = inotify.adapters.InotifyTrees([src_top, STATIC_PATH])
|
|
|
|
|
|
|
|
## Initial load
|
|
|
|
t0 = time.time()
|
|
|
|
|
|
|
|
docs = regen_all(src_top, dest_top)
|
|
|
|
logging.info("Initial load completed in {:.2f}s".format(time.time() - t0))
|
|
|
|
|
|
|
|
## Updating
|
|
|
|
for event in notifier.event_gen(yield_nones=False):
|
|
|
|
(ev, types, directory, file) = event
|
|
|
|
if not any([type in MONITORED_EVENT_TYPES for type in types]):
|
|
|
|
continue
|
|
|
|
if is_git_path(directory):
|
|
|
|
continue
|
|
|
|
filepath = os.path.join(directory, file)
|
|
|
|
print("CHANGED: {}".format(filepath))
|
|
|
|
t0 = time.time()
|
|
|
|
try:
|
|
|
|
docs = regen_all(src_top, dest_top, docs)
|
|
|
|
except:
|
|
|
|
logging.error(traceback.format_exc())
|
|
|
|
logging.error("Loading new templates failed 😿")
|
|
|
|
continue
|
|
|
|
logging.info("Updated all in {:.2f}s".format(time.time() - t0))
|
|
|
|
|
|
|
|
|
2022-08-20 15:50:40 +00:00
|
|
|
def print_tree(tree, indentation=0, headline=None):
|
|
|
|
# if headline and headline.id != INDEX_ID:
|
|
|
|
# return
|
2022-05-16 21:28:43 +00:00
|
|
|
return
|
2021-08-26 22:22:48 +00:00
|
|
|
for element in tree:
|
|
|
|
if "children" in dir(element):
|
|
|
|
if len(element.children) > 0:
|
2022-08-20 15:50:40 +00:00
|
|
|
print_element(element.children, indentation + 1, headline)
|
2021-08-26 22:22:48 +00:00
|
|
|
print()
|
|
|
|
|
2022-08-20 15:50:40 +00:00
|
|
|
elif "content" in dir(element):
|
|
|
|
for content in element.content:
|
|
|
|
print_element(content, indentation + 1, headline)
|
|
|
|
|
|
|
|
def print_element(element, indentation, headline):
|
|
|
|
if isinstance(element, org_rw.Link):
|
|
|
|
print(" " * indentation, "Link:", element.get_raw())
|
|
|
|
elif isinstance(element, str):
|
|
|
|
print(" " * indentation, "{" + element + "}", type(element))
|
|
|
|
else:
|
|
|
|
print_tree(element, indentation, headline)
|
|
|
|
|
2021-08-26 22:22:48 +00:00
|
|
|
|
|
|
|
def render_property_drawer(element, acc):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def render_logbook_drawer(element, acc):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def render_property_node(element, acc):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def render_list_group(element, acc):
|
|
|
|
acc.append("<ul>")
|
|
|
|
render_tree(element.children, acc)
|
|
|
|
acc.append("</ul>")
|
|
|
|
|
|
|
|
|
|
|
|
def render_list_item(element, acc):
|
|
|
|
acc.append("<li>")
|
2022-05-06 18:19:11 +00:00
|
|
|
if element.tag is not None:
|
2021-08-26 22:22:48 +00:00
|
|
|
acc.append("<span class='tag'>")
|
2022-08-27 11:32:40 +00:00
|
|
|
render_text_tokens(element.tag, acc)
|
2021-08-26 22:22:48 +00:00
|
|
|
acc.append("</span>")
|
|
|
|
|
|
|
|
acc.append("<span class='item'>")
|
2022-05-06 18:19:11 +00:00
|
|
|
render_text_tokens(element.content, acc)
|
2021-08-26 22:22:48 +00:00
|
|
|
acc.append("</span></li>")
|
|
|
|
|
|
|
|
|
|
|
|
def render_code_block(element, acc):
|
2022-05-07 21:03:26 +00:00
|
|
|
acc.append('<pre><code>')
|
2022-05-07 21:44:37 +00:00
|
|
|
acc.append(html.escape(element.lines))
|
2022-05-07 21:03:26 +00:00
|
|
|
acc.append('</code></pre>')
|
2021-08-26 22:22:48 +00:00
|
|
|
|
2022-05-16 21:28:59 +00:00
|
|
|
def render_results_block(element, acc):
|
|
|
|
# TODO:
|
|
|
|
# acc.append('<pre class="results"><code>')
|
|
|
|
# render_tree(element.children, acc)
|
|
|
|
# acc.append('</code></pre>')
|
|
|
|
pass
|
|
|
|
|
2021-08-26 22:22:48 +00:00
|
|
|
|
|
|
|
def render_text(element, acc):
|
2022-08-20 15:51:08 +00:00
|
|
|
acc.append('<div class="text">')
|
2022-05-06 18:19:11 +00:00
|
|
|
render_text_tokens(element.content, acc)
|
2022-08-20 15:51:08 +00:00
|
|
|
acc.append('</div>')
|
2022-05-06 18:19:11 +00:00
|
|
|
|
|
|
|
def render_text_tokens(tokens, acc):
|
2022-08-20 15:51:08 +00:00
|
|
|
acc.append('<p>')
|
2022-05-06 18:19:11 +00:00
|
|
|
for chunk in tokens:
|
2022-05-06 13:58:28 +00:00
|
|
|
if isinstance(chunk, str):
|
2022-08-20 15:51:08 +00:00
|
|
|
lines = chunk.replace('\n\n', '</p><p>')
|
|
|
|
acc.append('<span class="line">{}</span>'.format(lines))
|
2022-05-06 13:58:28 +00:00
|
|
|
elif isinstance(chunk, Link):
|
2022-05-06 19:18:16 +00:00
|
|
|
link_target = chunk.value
|
|
|
|
if link_target.startswith('id:'):
|
|
|
|
link_target = './' + link_target[3:] + '.node.html'
|
2022-05-07 21:44:37 +00:00
|
|
|
description = chunk.description
|
|
|
|
if description is None:
|
|
|
|
description = chunk.value
|
|
|
|
|
|
|
|
acc.append('<a href="{}">{}</a>'.format(
|
|
|
|
html.escape(link_target),
|
|
|
|
html.escape(description),
|
|
|
|
))
|
2022-05-16 21:28:43 +00:00
|
|
|
# else:
|
|
|
|
# raise NotImplementedError('TextToken: {}'.format(chunk))
|
2022-08-20 15:51:08 +00:00
|
|
|
acc.append('</p>')
|
2021-08-26 22:22:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
def render_tag(element, acc):
|
|
|
|
return {
|
|
|
|
dom.PropertyDrawerNode: render_property_drawer,
|
|
|
|
dom.LogbookDrawerNode: render_logbook_drawer,
|
|
|
|
dom.PropertyNode: render_property_node,
|
|
|
|
dom.ListGroupNode: render_list_group,
|
|
|
|
dom.ListItem: render_list_item,
|
|
|
|
dom.CodeBlock: render_code_block,
|
|
|
|
dom.Text: render_text,
|
2022-05-16 21:28:59 +00:00
|
|
|
dom.ResultsDrawerNode: render_results_block,
|
2021-08-26 22:22:48 +00:00
|
|
|
}[type(element)](element, acc)
|
|
|
|
|
|
|
|
|
|
|
|
def render_tree(tree, acc):
|
|
|
|
for element in tree:
|
|
|
|
render_tag(element, acc)
|
|
|
|
|
|
|
|
|
2022-05-16 21:28:43 +00:00
|
|
|
def render(headline, doc, headlineLevel):
|
|
|
|
try:
|
|
|
|
dom = headline.as_dom()
|
|
|
|
except:
|
|
|
|
logging.error("Error generating DOM for {}".format(doc.path))
|
|
|
|
raise
|
2022-08-20 15:50:40 +00:00
|
|
|
print_tree(dom, indentation=2, headline=headline)
|
2021-08-26 22:22:48 +00:00
|
|
|
|
|
|
|
content = []
|
|
|
|
render_tree(dom, content)
|
2021-09-03 22:26:28 +00:00
|
|
|
for child in headline.children:
|
2022-05-07 18:38:12 +00:00
|
|
|
content.append(render(child, doc, headlineLevel=headlineLevel+1))
|
2021-08-26 22:22:48 +00:00
|
|
|
|
2021-09-03 18:19:45 +00:00
|
|
|
if headline.state is None:
|
|
|
|
state = ""
|
|
|
|
else:
|
|
|
|
state = f'<span class="state todo-{headline.is_todo} state-{headline.state}">{headline.state}</span>'
|
|
|
|
|
|
|
|
if headline.is_todo:
|
|
|
|
todo_state = "todo"
|
|
|
|
else:
|
|
|
|
todo_state = "done"
|
2022-05-07 18:38:12 +00:00
|
|
|
|
|
|
|
display_state = 'collapsed'
|
|
|
|
if headlineLevel < MIN_HIDDEN_HEADLINE_LEVEL:
|
|
|
|
display_state = 'expanded'
|
|
|
|
|
2021-08-26 22:22:48 +00:00
|
|
|
return f"""
|
2022-05-07 18:38:12 +00:00
|
|
|
<div id="{html.escape(headline.id)}" class="node {todo_state} {display_state}">
|
2021-08-26 22:22:48 +00:00
|
|
|
<h1 class="title">
|
2021-09-03 18:19:45 +00:00
|
|
|
{state}
|
2022-05-07 18:38:12 +00:00
|
|
|
<a href=\"javascript:toggle_expand('{html.escape(headline.id)}')\">
|
2021-08-26 22:22:48 +00:00
|
|
|
{html.escape(headline.title)}
|
|
|
|
</a>
|
|
|
|
</h1>
|
2022-05-07 18:38:12 +00:00
|
|
|
<div class='contents'>
|
|
|
|
{''.join(content)}
|
|
|
|
</div>
|
2021-09-03 22:26:10 +00:00
|
|
|
</div>
|
2021-08-26 22:22:48 +00:00
|
|
|
"""
|
2021-08-14 16:04:22 +00:00
|
|
|
|
|
|
|
|
2022-08-20 16:21:26 +00:00
|
|
|
def as_document(html, title):
|
2022-05-07 16:35:18 +00:00
|
|
|
return f"""<!DOCTYPE html>
|
|
|
|
<html>
|
|
|
|
<head>
|
2022-08-20 11:59:15 +00:00
|
|
|
<meta charset="utf-8">
|
2022-08-20 16:21:26 +00:00
|
|
|
<title>{title} @ {SITE_NAME}</title>
|
2021-09-03 22:26:10 +00:00
|
|
|
<link href="../static/style.css" rel="stylesheet"/>
|
2022-05-07 18:38:12 +00:00
|
|
|
<script type="text/javascript">
|
|
|
|
function toggle_expand(header_id) {{
|
|
|
|
var e = document.getElementById(header_id);
|
|
|
|
if (e.classList.contains('expanded')) {{
|
|
|
|
e.classList.add('collapsed');
|
|
|
|
e.classList.remove('expanded');
|
|
|
|
}}
|
|
|
|
else {{
|
|
|
|
e.classList.add('expanded');
|
|
|
|
e.classList.remove('collapsed');
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
</script>
|
2022-05-07 16:35:18 +00:00
|
|
|
</head>
|
|
|
|
<body>
|
2021-09-03 22:26:10 +00:00
|
|
|
{html}
|
2022-05-07 16:35:18 +00:00
|
|
|
</body>
|
|
|
|
</html>
|
2021-09-03 22:26:10 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
2021-08-14 16:04:22 +00:00
|
|
|
def save_changes(doc):
|
|
|
|
assert doc.path is not None
|
|
|
|
with open(doc.path, "wt") as f:
|
2021-09-03 18:19:45 +00:00
|
|
|
dump_org(doc, f)
|
2021-08-14 16:04:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
if len(sys.argv) != 3:
|
|
|
|
print("Usage: {} SOURCE_TOP DEST_TOP".format(sys.argv[0]))
|
|
|
|
exit(0)
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s")
|
|
|
|
main(sys.argv[1], sys.argv[2])
|