From 59c2e948797d088e795cc2290fa8c3004ddb71c1 Mon Sep 17 00:00:00 2001 From: Frederick Yin Date: Sun, 14 Jun 2020 18:03:01 +0800 Subject: Massive improvements - Python CGI server script renamed to gateway.py to avoid confusion - Repo not found error handling - Page header & navs - Fix faulty 20 response header - View (non-)raw blob --- git-gmi/cgi | 2 +- git-gmi/cgi.py | 76 ------------------------------------------------------ git-gmi/const.py | 6 +++-- git-gmi/gateway.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ git-gmi/git.py | 55 +++++++++++++++++++++++++++++---------- 5 files changed, 122 insertions(+), 93 deletions(-) delete mode 100644 git-gmi/cgi.py create mode 100644 git-gmi/gateway.py (limited to 'git-gmi') diff --git a/git-gmi/cgi b/git-gmi/cgi index c0e0d25..7ef8259 100755 --- a/git-gmi/cgi +++ b/git-gmi/cgi @@ -1,3 +1,3 @@ #!/home/fakefred/p/git.gmi/venv/bin/python3.8 # gotta change the executable path before running -import cgi.py \ No newline at end of file +import gateway \ No newline at end of file diff --git a/git-gmi/cgi.py b/git-gmi/cgi.py deleted file mode 100644 index 76f1522..0000000 --- a/git-gmi/cgi.py +++ /dev/null @@ -1,76 +0,0 @@ -from git import * -from const import * -from os import environ, listdir - -# be careful when using print(); stdout is passed to the client. -# this cgi uses \n as newline. - - -def generate_navigation(repo_name: str): - pass # TODO - - -def handle_cgi_request(path: str): - # intended to work with Jetforce. - # url: gemini://git.gemini.site/cgi-bin/cgi.py/repo/src/static/css/[index.css] - # path: /repo/src/static/css/[index.css] - # path_trace = ['repo', 'src', 'static', 'css', 'index.css'] - path_trace = path[1:].split("/") - if path_trace == [""]: # empty path - print(f"{STATUS_SUCCESS} {META_GEMINI}") # welcome page - print("Welcome to the git.gmi demo") - print("Available repositories:") - print("\n".join([f"=> {dir}/" for dir in listdir(GIT_CATALOG)])) - return - - try: - repo = GitGmiRepo(path_trace[0], f"{GIT_CATALOG}/{path_trace[0]}") - except FileNotFoundError: - print(STATUS_NOT_FOUND) - return - - if len(path_trace) > 1: - view = path_trace[1] # e.g. summary, tree, log - else: - print("31 summary") - return - - if view == "summary": - try: - print(repo.view_summary()) - return - except: - print(STATUS_TEMPORARY_FAILURE) - return - - elif view == "tree": - if len(path_trace) == 2: - print("31 master/") - return - - if len(path_trace) > 2: - branch = path_trace[2] - - if len(path_trace) == 3: - location = [] - else: - location = path_trace[3:] - - try: # is dir - print(repo.view_tree(branch, location)) - except FileNotFoundError: # is file - try: - print(repo.view_blob(branch, location)) - except FileNotFoundError: - print("50 Error locating content") - - elif view == "log": - try: - print(repo.view_log()) - return - except: - print(STATUS_TEMPORARY_FAILURE) - return - - -handle_cgi_request(environ.get("PATH_INFO")) diff --git a/git-gmi/const.py b/git-gmi/const.py index 9a1bddd..dcf97b1 100644 --- a/git-gmi/const.py +++ b/git-gmi/const.py @@ -1,5 +1,7 @@ -GIT_CATALOG = "/home/fakefred/p/gemini/git/" -STATUS_SUCCESS = "20 SUCCESS" +GIT_CATALOG = "/home/fakefred/p/gemini/repos/" +CGI_PATH = "/git/cgi/" +GIT_GMI_SITE_TITLE = "git.gmi demo instance" +STATUS_SUCCESS = "20" STATUS_NOT_FOUND = "51 NOT FOUND" STATUS_TEMPORARY_FAILURE = "40 TEMPORARY FAILURE" META_GEMINI = "text/gemini" diff --git a/git-gmi/gateway.py b/git-gmi/gateway.py new file mode 100644 index 0000000..0ae963f --- /dev/null +++ b/git-gmi/gateway.py @@ -0,0 +1,76 @@ +from git import * +from const import * +from os import environ, listdir + +# be careful when using print(); stdout is passed to the client. +# this cgi uses \n as newline. + + +def generate_navigation(repo_name: str): + pass # TODO + + +def handle_cgi_request(path: str, query: str): + # intended to work with Jetforce. + # url: gemini://git.gemini.site/cgi-bin/cgi.py/repo/src/static/css/[index.css] + # path: /repo/src/static/css/[index.css] + # path_trace = ['repo', 'src', 'static', 'css', 'index.css'] + path_trace = path[1:].split("/") + if path_trace == [""]: # empty path + print(f"{STATUS_SUCCESS} {META_GEMINI}") # welcome page + print(f"# Welcome to {GIT_GMI_SITE_TITLE}") + print("## Available repositories:") + print("\n".join([f"=> {dir}/" for dir in listdir(GIT_CATALOG)])) + return + + try: + repo = GitGmiRepo(path_trace[0], f"{GIT_CATALOG}/{path_trace[0]}") + except FileNotFoundError: + print(STATUS_NOT_FOUND) + return + + if len(path_trace) > 1: + view = path_trace[1] # e.g. summary, tree, log + else: + print("31 summary") + return + + if view == "summary": + try: + print(repo.view_summary()) + return + except: + print(STATUS_TEMPORARY_FAILURE) + return + + elif view == "tree": + if len(path_trace) == 2: + print("31 master/") + return + + if len(path_trace) > 2: + branch = path_trace[2] + + location = path_trace[3:] + + try: # is dir + print(repo.view_tree(branch, location)) + except FileNotFoundError: # is file + try: + if query == "raw": + print(repo.view_raw_blob(branch, location)) + else: + print(repo.view_blob(branch, location)) + except FileNotFoundError: + print(STATUS_NOT_FOUND) + + elif view == "log": + try: + print(repo.view_log()) + return + except: + print(STATUS_TEMPORARY_FAILURE) + return + + +handle_cgi_request(environ.get("PATH_INFO"), environ.get("QUERY_STRING")) diff --git a/git-gmi/git.py b/git-gmi/git.py index 9a0ab0d..990ca0c 100644 --- a/git-gmi/git.py +++ b/git-gmi/git.py @@ -2,6 +2,9 @@ from pygit2 import * import mimetypes from const import * +mimetypes.add_type("text/gemini", ".gmi") +mimetypes.add_type("text/gemini", ".gemini") + class GitGmiRepo: def __init__(self, name: str, path: str): @@ -9,25 +12,37 @@ class GitGmiRepo: self.path = path try: self.repo = Repository(path) - except FileNotFoundError: - print(f"Error: repository {path} not found") + except GitError: + raise FileNotFoundError(f"Error: no such repo: {name}") + + def generate_header(self): + header = ( + f"# {self.name}\n" + f"=> {CGI_PATH} {GIT_GMI_SITE_TITLE}\n" + f"=> {CGI_PATH}{self.name}/summary summary\n" + f"=> {CGI_PATH}{self.name}/tree/master/ tree\n" + f"=> {CGI_PATH}{self.name}/log log\n\n" + ) + return header def view_summary(self) -> str: + response = f"{STATUS_SUCCESS} {META_GEMINI}\n" + self.generate_header() tree = self.get_tree("master") trls = self.list_tree(tree) + found_readme = False for item in trls: - if item["type"] == "file" and item["name"].lower().split(".")[0] == ( - "readme" + if ( + item["type"] == "file" + and item["name"].lower().split(".")[0] == ("readme") + and not found_readme ): - # mimetypes.guess_type() returns tuple (type, encoding) - # only the first one of which we care about - response = ( - f"{STATUS_SUCCESS} {mimetypes.guess_type(item['name'])[0]}\n" - "=> tree/master/ tree\n" - "=> log/ log\n\n" + found_readme = True + response += ( + f"## {item['name']} | {item['size']} bytes\n" + f"{item['blob'].data.decode('utf-8')}" ) - response += item["blob"].data.decode("utf-8") - break + if not found_readme: + response += "## No readme found." return response def get_commit_log(self) -> list: @@ -48,7 +63,7 @@ class GitGmiRepo: return log # reverse chronical order def view_log(self) -> str: - response = f"{STATUS_SUCCESS} {META_GEMINI}\n" + response = f"{STATUS_SUCCESS} {META_GEMINI}\n" + self.generate_header() log = self.get_commit_log() for cmt in log: response += f"## {cmt['short_id']} - {cmt['author']}\n{cmt['msg']}\n\n" @@ -123,7 +138,7 @@ class GitGmiRepo: def view_tree(self, branch: str, location=[]) -> str: # actual Gemini response # consists of a header and a body - response = f"{STATUS_SUCCESS} {META_GEMINI}\n" + response = f"{STATUS_SUCCESS} {META_GEMINI}\n" + self.generate_header() tree = self.get_tree(branch) contents = self.list_tree(tree, location) for item in contents: @@ -150,6 +165,18 @@ class GitGmiRepo: raise FileNotFoundError(f"Error: No such tree: {'/'.join(location[:-1])}") def view_blob(self, branch: str, location=[]) -> str: + blob = self.get_blob(branch, location) + response = ( + f"{STATUS_SUCCESS} {META_GEMINI}\n" + + self.generate_header() + + f"## {self.name}/{'/'.join(location)} | {blob.size} bytes\n\n" + f"=> {blob.name}?raw view raw\n\n" + f"```\n" + ) + response += blob.data.decode("utf-8") + "\n```" + return response + + def view_raw_blob(self, branch: str, location=[]) -> str: blob = self.get_blob(branch, location) guessed_mimetype = mimetypes.guess_type(blob.name)[0] or "text/plain" response = f"{STATUS_SUCCESS} {guessed_mimetype}\n" -- cgit v1.2.3