diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..23c5580 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include src/githelp/data/overlays *.yml \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..cca40b6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +#how the project is built +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +#project's metadata +[project] +name = "githelp" +version = "0.1.0" +description = "example CLI using Click." +authors = [{ name = "Jimmy Zhang", email = "jimmy_zhang@uri.edu" }] +readme = "README.md" +requires-python = ">=3.9" +dependencies = ["click>=8.1", "pyyaml>=6.0"] + +#this creates the 'githelp' command that point to the click group +[project.scripts] +githelp = "githelp.cli:githelp_cli" + +#tell the setuptool to include non py file +[tool.setuptools] +include-package-data = true + +#include the yml file inside the install so that it can be found in the runtime +[tool.setuptools.package-data] +githelp = ["data/overlays/*.yml"] + diff --git a/src/githelp/_init_.py b/src/githelp/_init_.py new file mode 100644 index 0000000..469e977 --- /dev/null +++ b/src/githelp/_init_.py @@ -0,0 +1,2 @@ +__all__ = ["__version__"] +__version__ = "0.1.0" \ No newline at end of file diff --git a/src/githelp/cli.py b/src/githelp/cli.py new file mode 100644 index 0000000..d3d3f54 --- /dev/null +++ b/src/githelp/cli.py @@ -0,0 +1,32 @@ +import click +from .overlays import load_overlay, render_overlay, render_menu + +@click.group( + help="githelp terminal first Git helper.", + invoke_without_command=True, + add_help_option=False, +) +@click.option("-h", "--help", "show_help", is_flag=True, help="Provides option menu") +@click.pass_context +def githelp_cli(context, show_help): + if show_help or context.invoked_subcommand is None: + click.echo(context.get_help()) + click.echo() + click.echo(render_menu()) + if context.invoked_subcommand is None: + context.exit(0) + +@githelp_cli.command(name="list", help="List of available githelp tip pages.") +def list_cmd(): + '''List all available githelp tip.''' + click.echo(render_menu()) + +@githelp_cli.command(name="explain", help="Show githelp overlay tips for a git subcommand.") +@click.argument("cmd") +def explain_cmd(cmd): + '''Show tips for a specific git subcommand''' + tips = load_overlay(cmd) + if tips: + click.echo(render_overlay(tips)) + else: + click.echo(f"githelp tips\n\nNo tips found for '{cmd}'.") \ No newline at end of file diff --git a/src/githelp/data/overlays/tips.yml b/src/githelp/data/overlays/tips.yml new file mode 100644 index 0000000..cbc5266 --- /dev/null +++ b/src/githelp/data/overlays/tips.yml @@ -0,0 +1,135 @@ +pull: + command: pull + summary: "A git command that fetches from a remote repository and merges into the current branch (fetch + merge)." + when_to_use: + - "You want to get the latest updates into your local branch." + examples: + - cmd: "git pull" + say: "Fetches from your branch's tracked remote (usually 'origin') and merges into your current branch." + +commit: + command: commit + summary: "Create a snapshot of staged changes; keep messages short and imperative." + when_to_use: + - "You have staged changes and want to record them." + examples: + - cmd: "git commit -m \"messages\"" + say: "Short, informative summary." + +add: + command: add + summary: "Stages changes like new, modified, or deleted files to be included in the next commit. This also moves files from working directory to the staging area." + when_to_use: + - "When you want to stage changes this could include new, modified, or deleted files to the repo." + examples: + - cmd: "git add ." + say: "add all file" + - cmd: "git add " + say: "add a file" + +status: + command: status + summary: "command provides an overview of the current state of your Git repository. this is done by checking pointer and compare them to the current directory" + when_to_use: + - "when you want to check the differences/changes of current vs before" + examples: + - cmd: "git status" + say: "overview changes" + +clone: + command: clone + summary: "Creates a copy of a remote repository on your local machine." + when_to_use: + - "you want to use this to create a local copy of an existing Git repository" + examples: + - cmd: "git clone " + - say: "Downloads the remote repo into a new local folder." + +checkout: + command: checkout + summary: "switch between branch" + when_to_use: + - "you want to use this to navigate between difference branches" + examples: + - cmd: "git checkout " + say: "Switches your working directory to the specified branch." + +branch: + command: branch + summary: "Lists all branches in your repository" + when_to_use: + - "you use this to check what branch you're on" + examples: + - cmd: "git branch" + say: "Shows all local branches and highlights the one you're on." + - cmd: "git branch -m " + say: "Renames the current branch to the new name." + +logs: + command: logs + summary: "Displays the commit history of the repository. " + when_to_use: + - "you want to use this to see commit hashes, authors, dates, and messages." + examples: + - cmd: git logs + say: "information of commit history" + +tag: + command: tag + summary: "command in Git is used to mark specific points in a repository's history as important." + when_to_use: + - "use this to mark a particular point in the commit ancestry chain." + examples: + - cmd: git tag + say: "Lists all tags that mark important commits in the repo." + +rebase: + command: rebase + summary: "Moves or reapplies commits from one branch on top of another to keep history linear." + when_to_use: + - "You want to update your feature branch on top of the latest main or another branch." + - "You want a cleaner, straight line commit history instead of a lot of merge commits." + examples: + - cmd: "git rebase main" + say: "Replay your current branch’s commits on top of the latest 'main' branch." + - cmd: "git rebase origin/main" + say: "Rebase your work on top of the 'origin/main' branch from the remote." + +fetch: + command: fetch + summary: "Downloads new commits, branches, and tags from the remote without changing local branches or files." + when_to_use: + - "You want to see what changed on the remote before merging or rebasing." + - "You want to update remote-tracking branches like origin/main without touching your current branch." + examples: + - cmd: "git fetch" + say: "Gets all new data from the default remote (usually 'origin') without modifying your current branch." + - cmd: "git fetch origin main" + say: "Fetches only updates for the 'main' branch from 'origin'." + +push: + command: push + summary: "Sends your local commits to a remote repository branch." + when_to_use: + - "You want to upload your local commits so they appear on GitHub or another remote." + - "You want to share your changes with others or back them up remotely." + examples: + - cmd: "git push" + say: "Pushes your current branch to its tracked remote branch." + - cmd: "git push origin " + say: "Pushes the specified local branch to the 'origin' remote." + +merge: + command: merge + summary: "Combines another branch into the current branch by creating a merge commit." + when_to_use: + - "You want to bring changes from one branch into another and keep full history from both." + - "You want to integrate a completed feature branch into main (or another base branch)." + examples: + - cmd: "git merge " + say: "Merges the named branch into your current branch, creating a merge commit." + - cmd: "git merge main" + say: "Brings the latest changes from 'main' into your current branch." + + + diff --git a/src/githelp/overlays.py b/src/githelp/overlays.py new file mode 100644 index 0000000..897b20c --- /dev/null +++ b/src/githelp/overlays.py @@ -0,0 +1,177 @@ +import os +import yaml + +#build absolute path to githelp/data/overlays +THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +DATA_DIR = os.path.join(THIS_DIR, "data", "overlays") +#master dictionary file: to /src/githelp/data/overlays/tips.yml +MASTER_FILE = os.path.join(DATA_DIR, "tips.yml") + + +def read_yaml(path: str): + ''' + Read a YAML file into a Python object. + + Parameters + ---------- + path : str + Path to the YAML file to read. + + Returns + ------- + dict + Parsed YAML contents. Returns an empty dict if the file is empty + or if any error occurs while reading/parsing. + ''' + try: + #r opens the file for reading in text mode, and encoding="utf-8" tells Python to decode the file's bytes + #as UTF-8 text into normal Python strings + with open(path, "r", encoding="utf-8") as f: + #convert YAML content to Python object or empty dict if file is empty + data = yaml.safe_load(f) or {} + #return the data of whatever was loaded + return data + except Exception: + #if error occurs return empty dict + return {} + +def helper(): + + data = read_yaml(MASTER_FILE) + return data + +def load_overlay(command: str): + ''' + Get the tips for one git subcommand. + + This first looks in the master file tips.yml under the given + command name (ex "pull"). If found, it returns the corresponding + dictionary of tips. + + Parameters + ---------- + command : str + Name of the git subcommand whose overlay should be loaded. + + Returns + ------- + dict or None + The overlay dictionary for the given command, with a ``"command"`` key + ensured, or ``None`` if no overlay is defined. + ''' + #set data to the master dictionary file + data = helper() + #check to see if the master is a dict and not empty + if isinstance(data, dict): + #get the section for this specific command and store it in section + section = data.get(command) + #check if section is a dict + if isinstance(section, dict): + # make sure the section has a command key + # does nothing if "command" already exists + section.setdefault("command", command) + #return the section + return section + + +def list_overlay_names(): + ''' + List available subcommand names from tips.yml. + + Returns + ------- + list of str + Sorted list of subcommand names found in the master tips mapping. + ''' + names = [] + data = helper() + #check if data is a dict and not empty + if not isinstance(data, dict): + return names + + for key, value in data.items(): + if isinstance(value, dict): + names.append(key) + + names.sort() + return names +def render_overlay(d: dict) -> str: + ''' + Render a single overlay dictionary into a human-readable text block. + + Parameters + ---------- + d : dict + Overlay dictionary containing keys such as ``"summary"``, + ``"when_to_use"``, and ``"examples"`` (any of which may be absent). + + Returns + ------- + str + A formatted multi-line string + ''' + #line variable that stores a list of outputs + lines = [] + #append header + lines.append("\ngithelp tips") + + #get the summary, when to use, and example in the dictionary + summary = d.get("summary") + when_to_use = d.get("when_to_use") + examples = d.get("examples") + + #summary = true then append the summary text to line list + if summary: + lines.append("") + lines.append(summary) + #when to use = true then append the item in the when to use to line list + if when_to_use: + lines.append("\nWhen to use") + for item in d["when_to_use"]: + lines.append(f" - {item}") + #example = true then append new line and example into line + if examples: + lines.append("\nExamples") + #for each example in d["examples"] + for ex in d["examples"]: + #get the cmd and say + cmd = ex.get("cmd", "") + say = ex.get("say", "") + lines.append(f" $ {cmd}") + # If say exists, indent it by 3 spaces for formatting + if say: + lines.append(f" {say}") + #append a new line at the end + lines.append("") + #join all lines into a single string and return it + return "\n".join(lines) + +def render_menu(): + ''' + Render the main menu text for githelp. + + Returns + ------- + str + A formatted multi-line string describing available commands. + ''' + #declare an empty list called lines + lines = [] + #display commands + lines.append("Commands:") + lines.append(" - explain Show an explaination for a git subcommand") + lines.append(" - list List available tip pages\n") + + #name variable that stores the list of overlay names + names = list_overlay_names() + #if name is true then append the available tip pages to line list + if names: + lines.append("Available tip pages:") + #for each name in names + for n in names: + #append each name with a dash and space before it for formatting + lines.append(f" - {n}") + else: + #if no names found append no tip pages found to line list + lines.append("No tip pages found.") + return "\n".join(lines) \ No newline at end of file