diff options
Diffstat (limited to 'g2h')
| -rw-r--r-- | g2h/__init__.py | 0 | ||||
| -rw-r--r-- | g2h/cli.py | 39 | ||||
| -rw-r--r-- | g2h/commit_page.py | 28 | ||||
| -rw-r--r-- | g2h/git.py | 20 | ||||
| -rw-r--r-- | g2h/log_page.py | 45 | ||||
| -rw-r--r-- | g2h/overview_page.py | 16 | ||||
| -rw-r--r-- | g2h/page.py | 19 | ||||
| -rw-r--r-- | g2h/page_factory.py | 22 | ||||
| -rw-r--r-- | g2h/tree_page.py | 51 |
9 files changed, 240 insertions, 0 deletions
diff --git a/g2h/__init__.py b/g2h/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/g2h/__init__.py diff --git a/g2h/cli.py b/g2h/cli.py new file mode 100644 index 0000000..52a2be7 --- /dev/null +++ b/g2h/cli.py @@ -0,0 +1,39 @@ +import argparse +import os + +from .git import Git +from .page_factory import PageFactory + +def parse_args(): + parser = argparse.ArgumentParser( + prog='g2h', + description='Convert a git repository into markdown files Hugo can understand.') + # TODO: Defaults for these two values should be $PWD. + parser.add_argument('repo_path', help='Path to the root of git repository') + parser.add_argument('out_dir', help='Path to where the generated files should be stored') + parser.add_argument('--name', help='Name of the project. Default: directory name of repo') + parser.add_argument('--content-subdir', help='Subdir(s) where the page will be served from. Default: None', default='') + + return parser.parse_args() + +def main(): + args = parse_args() + git = Git(args.repo_path) + + branches = sorted(git.get_branches(), key=lambda b: b.commit.committed_date, reverse=True) + commits = git.get_commits() + # TODO: Fix this for repo_path=(.|~) + project_name = args.name if args.name is not None else os.path.basename(args.repo_path) + tags = sorted(git.get_tags(), key=lambda t: t.commit.committed_date, reverse=True) + + factory = PageFactory(args.out_dir, project_name, args.content_subdir) + factory.new_overview(branches, tags).render() + factory.new_log(commits, branches, tags).render() + for commit in commits: + factory.new_commit(commit).render() + tree = factory.new_tree(commit, '', git.get_repo()) + for tree_page in tree.build_pages(factory): + tree_page.render() + +if __name__ == '__main__': + main() diff --git a/g2h/commit_page.py b/g2h/commit_page.py new file mode 100644 index 0000000..7123c82 --- /dev/null +++ b/g2h/commit_page.py @@ -0,0 +1,28 @@ +import os + +from .page import Page + +def redact_email(email): + if email is None or len(email) == 0: + return '' + + if '@' not in email: + return email[0] + '...' + email[-1] + + local, domain = email.split('@') + redacted_local = local[0] + '...' + local[-1] if len(local) > 0 else '' + redacted_domain = domain.replace('.', '...') + + return redacted_local + '@' + redacted_domain + +class CommitPage(Page): + def __init__(self, out_dir, name, commit, content_subdir): + super(CommitPage, self).__init__('commit.md', out_dir, os.path.join(commit.hexsha, 'index.md'), name, content_subdir) + self.commit = commit + + def context(self): + return { + 'commit': self.commit, + 'title': self.name + ': ' + self.commit.hexsha, + 'redacted_email': redact_email(self.commit.author.email) + } diff --git a/g2h/git.py b/g2h/git.py new file mode 100644 index 0000000..1e835e7 --- /dev/null +++ b/g2h/git.py @@ -0,0 +1,20 @@ +import git + +class Git: + def __init__(self, repo_path): + try: + self.repo = git.Repo(repo_path) + except git.InvalidGitRepositoryError: + raise ValueError(f"Invalid git repository at {repo_path}") + + def get_branches(self): + return self.repo.branches + + def get_commits(self): + return list(self.repo.iter_commits()) + + def get_repo(self): + return self.repo + + def get_tags(self): + return self.repo.tags diff --git a/g2h/log_page.py b/g2h/log_page.py new file mode 100644 index 0000000..4b8ee10 --- /dev/null +++ b/g2h/log_page.py @@ -0,0 +1,45 @@ +import os + +from .page import Page + +def ref_names_for_commit(refs, commit_sha): + ref_names = [] + if refs is None: + return ref_names + for ref in refs: + if ref.commit.hexsha == commit_sha: + ref_names.append(ref.name) + + return ref_names + +class LogPage(Page): + def __init__(self, out_dir, name, commits, branches, tags, content_subdir): + super(LogPage, self).__init__('log.md', out_dir, 'l.md', name, content_subdir) + self.commits = commits + self.branches = branches + self.tags = tags + + def _commits_with_refs(self): + commits_context = [] + for commit in self.commits: + refs = ref_names_for_commit(self.branches, commit.hexsha) + ref_names_for_commit(self.tags, commit.hexsha) + commit_context = { + 'committed_datetime': commit.committed_datetime, + 'hexsha': commit.hexsha, + 'refs': refs, + 'summary': commit.summary + } + commits_context.append(commit_context) + return commits_context + + def context(self): + commits_context = [] + if self.commits is not None: + commits_context = self._commits_with_refs() + + return { + 'commits': commits_context, + 'content_subdir': self.content_subdir, + 'title': self.name + ': Log' + } + diff --git a/g2h/overview_page.py b/g2h/overview_page.py new file mode 100644 index 0000000..76de8cc --- /dev/null +++ b/g2h/overview_page.py @@ -0,0 +1,16 @@ +from .page import Page + +class OverviewPage(Page): + def __init__(self, out_dir, name, branches, tags, content_subdir): + super(OverviewPage, self).__init__('overview.md', out_dir, '_index.md', name, content_subdir) + self.branches = branches + self.tags = tags + + def context(self): + return { + 'branches': self.branches, + 'content_subdir': self.content_subdir, + 'tags': self.tags, + 'title': self.name + } + diff --git a/g2h/page.py b/g2h/page.py new file mode 100644 index 0000000..a708f2b --- /dev/null +++ b/g2h/page.py @@ -0,0 +1,19 @@ +from jinja2 import Environment, FileSystemLoader +import os + +class Page: + def __init__(self, template_file, out_dir, out_file, name, content_subdir): + self.out_file_path = os.path.join(out_dir, out_file) + self.name = name + self.content_subdir = content_subdir + template_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'templates')) + self.template = Environment(loader=FileSystemLoader(template_dir)).get_template(template_file) + + def context(self): + raise NotImplementedError("Must override context") + + def render(self): + content = self.template.render(self.context()) + os.makedirs(os.path.dirname(self.out_file_path), exist_ok=True) + with open(self.out_file_path, 'w', encoding='utf-8') as f: + f.write(content) diff --git a/g2h/page_factory.py b/g2h/page_factory.py new file mode 100644 index 0000000..c4b89ed --- /dev/null +++ b/g2h/page_factory.py @@ -0,0 +1,22 @@ +from .commit_page import CommitPage +from .overview_page import OverviewPage +from .log_page import LogPage +from .tree_page import TreePage + +class PageFactory: + def __init__(self, out_dir, name, content_subdir): + self.out_dir = out_dir + self.name = name + self.content_subdir = content_subdir + + def new_commit(self, commit): + return CommitPage(self.out_dir, self.name, commit, self.content_subdir) + + def new_overview(self, branches, tags): + return OverviewPage(self.out_dir, self.name, branches, tags, self.content_subdir) + + def new_log(self, commits, branches, tags): + return LogPage(self.out_dir, self.name, commits, branches, tags, self.content_subdir) + + def new_tree(self, commit, tree_segment, repo): + return TreePage(self.out_dir, self.name, commit, tree_segment, repo, self.content_subdir) diff --git a/g2h/tree_page.py b/g2h/tree_page.py new file mode 100644 index 0000000..22e563a --- /dev/null +++ b/g2h/tree_page.py @@ -0,0 +1,51 @@ +import os + +from .page import Page + +class TreePage(Page): + def __init__(self, out_dir, name, commit, sub_tree, repo, content_subdir): + super(TreePage, self).__init__('tree.md', out_dir, os.path.join(commit.hexsha, 't', sub_tree, 'index.md'), name, content_subdir) + self.tree = commit.tree.join(sub_tree) if sub_tree else commit.tree + self.repo = repo + self.commit = commit + self.sub_tree = sub_tree + + def _generate_entries(self, nodes): + entries = [] + for node in nodes: + latest_commit = next(self.repo.iter_commits(self.commit, max_count=1, paths=node.path)) + entry = { + 'name': node.name + str('/' if node.type == 'tree' else ''), + 'modified': latest_commit.committed_datetime + } + entries.append(entry) + return entries + + def _get_entries(self): + files = self._generate_entries(self.tree.blobs) + dirs = self._generate_entries(self.tree.trees) + files.sort(key=lambda x: x['name']) + dirs.sort(key=lambda x: x['name']) + return dirs + files + + def context(self): + return { + 'entries': self._get_entries(), + 'title': self.name + ': Tree' + } + + def build_pages(self, factory): + visited = set() + remaining = [self.sub_tree] + tree_pages = [] + while remaining: + current = remaining.pop() + if not current in visited: + visited.add(current) + tree = self.commit.tree.join(current) if current else self.commit.tree + tree_pages.append(factory.new_tree(self.commit, tree.path, self.repo)) + for sub_tree in tree.trees: + remaining.append(sub_tree.path) + + return tree_pages + |