Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
recursive-include src/githelp/data/overlays *.yml
27 changes: 27 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"]

2 changes: 2 additions & 0 deletions src/githelp/_init_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__all__ = ["__version__"]
__version__ = "0.1.0"
32 changes: 32 additions & 0 deletions src/githelp/cli.py
Original file line number Diff line number Diff line change
@@ -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())
Comment on lines +19 to +22
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@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( help="List of available githelp tip pages.")
def list():
'''List all available githelp tip.'''
click.echo(render_menu())

this is equivalent but slightly more compact


@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}'.")
135 changes: 135 additions & 0 deletions src/githelp/data/overlays/tips.yml
Original file line number Diff line number Diff line change
@@ -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 <name of the changed file>"
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 <github repository link>"
- 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 <branch name>"
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 <new-branch-name>"
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 <branch-name>"
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 <branch-name>"
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."



177 changes: 177 additions & 0 deletions src/githelp/overlays.py
Original file line number Diff line number Diff line change
@@ -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")
Comment on lines +7 to +8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's not use the term mater try mainor even just tips



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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#r opens the file for reading in text mode, and encoding="utf-8" tells Python to decode the file's bytes
# 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
Comment on lines +38 to +41
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the point of this function


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
Comment on lines +62 to +64
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, remove references to master and why not jsut use the read function directly?

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):
Comment on lines +65 to +69
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if no else, then no need for if

# 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)
Comment on lines +92 to +94
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do the values inside need to also be a dict?


names.sort()
return names
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't it be equivalent to just return sorted(data.keys())? since data will always be a dict (even if empty)

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
'''
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be a lot simpler, using fstrings.

#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 <subcommand> 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)