Skip to content

Commit

Permalink
gentodo: Finish Click support
Browse files Browse the repository at this point in the history
Rationale
=========

Click is more modular and easier to use for larger command-line
programs.

Breaking Changes
================

- Can no longer use an arbitrary number of arguments to options,
  i.e. `gentodo add -t title -d Arbitrary Number of Arguments`
  this is due to a technicial limitation and will not be fixed.

Major Changes
=============

- Convert from argparse to click
- Remove parser.py as it is no longer needed

Minor Changes
=============
- Automatically make config directory if it doesn't exist

Signed-off-by: Christopher Fore <[email protected]>
  • Loading branch information
csfore committed Apr 14, 2024
1 parent a7d983b commit 34ab0d1
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 180 deletions.
5 changes: 2 additions & 3 deletions gentodo-0.2.1.ebuild → gentodo-1.0.0.ebuild
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,10 @@ DEPEND="
RDEPEND="
${PYTHON_DEPS}
dev-python/python-bugzilla
dev-python/click
"

python_install_all() {
if use bash-completion; then
newbashcomp src/gentodo/gentodo-completions.bash gentodo
fi
newbashcomp src/gentodo/gentodo-completions.bash gentodo
distutils-r1_python_install_all
}
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ license = {text = "GPLv3"}
dependencies = [
"requests",
"python-bugzilla",
"click",
]

[project.scripts]
Expand Down
2 changes: 1 addition & 1 deletion src/gentodo/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
'''__init__.py'''
__version__ = "0.2.1"
__version__ = "1.0.0"
13 changes: 11 additions & 2 deletions src/gentodo/bugs.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
'''bugs.py:
Module that handles interactions between Gentodo and Bugzillas
'''

import bugzilla
from gentodo import config

# TODO:
# Better error handling
class Bugs:

'''Class that handles Bugzilla interactions'''
def __init__(self):
conf = config.Config()
token = conf.get_token()
self.urls = conf.get_urls()
self.emails = conf.get_emails()

self.bz = bugzilla.Bugzilla(self.urls[0], api_key=token)

def get_assigned(self):
'''Gets the assigned bugs to email addresses'''
query = self.bz.build_query(assigned_to=self.emails[0])
result = self.bz.query(query)
summaries = []
Expand All @@ -21,6 +28,7 @@ def get_assigned(self):
return summaries

def get_cced(self):
'''Gets bugs the user is CC'd on'''
query = self.bz.build_query(cc=self.emails[0])
result = self.bz.query(query)
summaries = []
Expand All @@ -29,6 +37,7 @@ def get_cced(self):
return summaries

def get_reported(self):
'''Gets bugs reported by the user'''
query = self.bz.build_query(reporter=self.emails[0])
result = self.bz.query(query)
summaries = []
Expand Down
118 changes: 71 additions & 47 deletions src/gentodo/cli.py → src/gentodo/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import json
import os
import click

from gentodo import bugs

OLD_PATH = os.path.expanduser("~/.local/share/todo/todo.json")
Expand All @@ -14,11 +16,8 @@ class Gentodo:
__slots__ = ["data", "longest"]

def __init__(self):
try:
if os.path.isfile(OLD_PATH):
os.rename(OLD_PATH, TODO_FILE)
except:
pass
if os.path.isfile(OLD_PATH):
os.rename(OLD_PATH, TODO_FILE)

if not os.path.isdir(STORAGE_DIR):
os.makedirs(STORAGE_DIR)
Expand All @@ -41,8 +40,7 @@ def calc_length(self):
# Edge case in case someone doesn't put anything in
if self.data[todo_id]['title'] is None:
continue
if len(self.data[todo_id]['title']) > longest:
longest = len(self.data[todo_id]['title'])
longest = max(longest, len((self.data[todo_id]['title'])))

return longest

Expand All @@ -68,26 +66,31 @@ def write(self):
# Fix long titles breaking the output
# TODO:
# Implement a form of wrapping
def show_todo(args, gentodo):
@click.command(help="Shows the todo list")
@click.pass_context
@click.option("--verbose", is_flag=True)
@click.option("--brief", type=click.STRING, default='left', required=False)
def show(ctx, verbose, brief):
'''Shows the items to do'''
spaces = gentodo.longest + 2
spaces = ctx.obj['GENTODO'].longest + 2

if gentodo.data is None:
if ctx.obj['GENTODO'].data is None:
print("Nothing to do!")
return

# Verbose output
if args.verbose:
if verbose:
print(f"ID | {'Title'.ljust(spaces)}| Detail")
print(f"{'─'*(spaces+14)}")
for key in gentodo.data:
print(f"{key:<2}{gentodo.data[key]['title'].ljust(spaces)}{gentodo.data[key]['details']}")
elif args.brief:
for key in ctx.obj['GENTODO'].data:
print(f"{key:<2}{ctx.obj['GENTODO'].data[key]['title'].ljust(spaces)}{ctx.obj['GENTODO'].data[key]['details']}")
elif brief:
print("Title".center(spaces))
print(f"{'─'*spaces}")
for key in gentodo.data:
title = gentodo.data[key]['title']
match args.brief:
for key in ctx.obj['GENTODO'].data:
title = ctx.obj['GENTODO'].data[key]['title']
print(title)
match brief:
case 'left':
print(title)
case 'center':
Expand All @@ -97,88 +100,109 @@ def show_todo(args, gentodo):
else:
print(f"{'Title'.ljust(spaces)}| Details")
print(f"{'─'*(spaces+9)}")
for key in gentodo.data:
print(f"{gentodo.data[key]['title'].ljust(spaces)}| {gentodo.data[key]['details']}")
for key in ctx.obj['GENTODO'].data:
print(f"{ctx.obj['GENTODO'].data[key]['title'].ljust(spaces)}| {ctx.obj['GENTODO'].data[key]['details']}")


def add_item(args, gentodo):
@click.command(help="Add an item to your todo list")
@click.pass_context
@click.option("-t", "--title", required=True)
@click.option("-d", "--details", default="No details")
def add(ctx, title, details):
'''Adds an item to the todo list'''
gentodo = ctx.obj['GENTODO']

newest_id = 0 if len(gentodo.data.keys()) == 0 else int(list(gentodo.data.keys())[-1])

if args.details is None:
args.details = ["No", "details"]

if args.title is None:
print("Missing required title argument, did you forget `-t`?")
return

title = args.title
if type(title) is not str:
if isinstance(title, str):
title = " ".join(title)
if len(title) > 40:
title = title[:40]
title += "..."

gentodo.data[newest_id + 1] = {
"title": title,
"details": " ".join(args.details)
"details": details
}

gentodo.write()
print(f"Added: {title} | {' '.join(args.details)}")
print(f"Added: {title} | {details}")


def rm_item(args, gentodo):
@click.command(name="del", help="Remove an item from the todo list")
@click.pass_context
@click.argument("id")
def rm(ctx, id):
'''Removes an item from the todo list by ID'''
gentodo = ctx.obj['GENTODO']

if os.path.exists(TODO_FILE) and os.path.getsize(TODO_FILE) > 0:
gentodo.data.pop(f"{args.id}")
gentodo.data.pop(f"{id}")
gentodo.write()


def item_count(args, gentodo):
@click.command(help="Shows the number of items remaining")
@click.pass_context
def count(ctx):
'''Tallies up the amount of items in the list'''
gentodo = ctx.obj['GENTODO']

remaining = len(gentodo.data.keys())
print(f"Items remaining: {remaining}")


def edit_item(args, gentodo):
@click.command(help="Edit an item by ID")
@click.pass_context
@click.argument("id")
@click.option("-t", "--title", required=True)
@click.option("-d", "--details", default="No details")
def edit(ctx, id, title, details):
'''Edits an item entry'''
gentodo.data[args.id]['title'] = " ".join(args.title)
gentodo.data[args.id]['details'] = " ".join(args.details)
gentodo = ctx.obj['GENTODO']

gentodo.data[id]['title'] = title
gentodo.data[id]['details'] = details

gentodo.write()


def search_items(args, gentodo):
@click.command(help="Search for an item")
@click.pass_context
@click.argument("term")
def search(ctx, term):
'''Searches for an item in the todo list'''
print(f"Searching for: {args.term}\n\n")
gentodo = ctx.obj['GENTODO']
print(f"Searching for: {term}\n")
print("ID | Title")
print(f"{'─'*int(gentodo.longest/2)}")
for key in gentodo.data:
for val in gentodo.data[key]:
if args.term in gentodo.data[key][val]:
if term in gentodo.data[key][val]:
print(f"{key} | {gentodo.data[key][val]}")

def pull_bugs(args, gentodo):
@click.command(name="pull", help="Pulls bugs relating to you from the Bugzilla")
@click.pass_context
@click.option("-c", "--cc", is_flag=True)
@click.option("-a", "--assigned", is_flag=True)
def pull_bugs(ctx, cc, assigned):
'''Pulls bugs from the Bugzillas'''
bz = bugs.Bugs()
allbugs = []
if args.cc:
if cc:
cced = bz.get_cced()
for bug in cced:
bug = "[BUGZILLA] " + f"{bug}"
allbugs.append(bug)

if args.assigned:
if assigned:
assigned = bz.get_assigned()
for bug in assigned:
bug = "[BUGZILLA]" + f"{bug}"
allbugs.append(bug)

# Make sure bugs are unique
set(allbugs)

for bug in allbugs:
args.title = bug
args.details = None
add_item(args, gentodo)
# Passing it over to add to actually add the bug
ctx.invoke(add, title=bug)
21 changes: 16 additions & 5 deletions src/gentodo/config.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,48 @@
'''config.py
Module that handles configuration interactions
'''

import tomllib
import sys
import os

STORAGE_DIR = os.path.expanduser("~/.config/gentodo")
CONFIG_FILE = os.path.join(STORAGE_DIR, "config.toml")

class Config:
'''Class to handle the configuration file settings'''
def __init__(self):
if not os.path.isfile(CONFIG_FILE):
os.makedirs(STORAGE_DIR)
with open(CONFIG_FILE, "w", encoding="utf_8") as config:
config.write("[gentodo]\n")
self.data = self.load_config()

def load_config(self):
'''Loads the config from the TOML file'''
with open(CONFIG_FILE, "rb") as config:
return tomllib.load(config)

def get_token(self):
'''Gets the Bugzilla token'''
try:
return self.data['gentodo']['token']
except KeyError:
print("API Key not found, please add it to config.toml.")
exit(1)
sys.exit(1)

def get_urls(self):
'''Gets Bugzilla URLs'''
try:
return self.data['gentodo']['urls']
except KeyError:
print("Bugzilla URLs not found, please add them to config.toml.")
exit(1)
sys.exit(1)

def get_emails(self):
'''Gets emails to search for'''
try:
return self.data['gentodo']['emails']
except KeyError:
print("Emails not found, please add them to config.toml.")
exit(1)
sys.exit(1)
50 changes: 35 additions & 15 deletions src/gentodo/main.py
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
'''Gentodo (c) 2023 Christopher Fore
This code is licensed under the GPLv3 license (see LICENSE for details)
'''
import click

#import bugzilla // commented out until used
from gentodo import cli, parser, config, bugs
from gentodo import commands, __version__

def main():
'''Main function'''
todo = cli.Gentodo()
unparsed = parser.setup_parser()

args = unparsed.parse_args()
args.func(args, todo)
#conf = config.Config()
#print(conf.get_token())
#bz = bugs.Bugs()
#print(bz.get_cced())
@click.group(invoke_without_command=True)
@click.version_option(__version__)
@click.option('--verbose', '-v', default=False, is_flag=True)
@click.option('--brief', '-b', default=None, type=str)
@click.pass_context
def cmd(ctx, verbose, brief):
'''General purpose command group'''
ctx.ensure_object(dict)
ctx.obj['GENTODO'] = commands.Gentodo()
if ctx.invoked_subcommand is None:
ctx.forward(commands.show)


@cmd.group()
@click.pass_context
def bugs(ctx):
'''Bugs command group'''
ctx.ensure_object(dict)
ctx.obj['GENTODO'] = commands.Gentodo()

cmd.add_command(commands.show)
cmd.add_command(commands.add)
cmd.add_command(commands.rm)
cmd.add_command(commands.count)
cmd.add_command(commands.edit)
cmd.add_command(commands.search)
bugs.add_command(commands.pull_bugs)


def main():
'''Main entrypoint'''
cmd()

if __name__ == "__main__":
# Alternative Entrypoint
main()
Loading

0 comments on commit 34ab0d1

Please sign in to comment.