Skip to content

Instantly share code, notes, and snippets.

@tomschr
Last active December 1, 2021 14:08
Show Gist options
  • Save tomschr/4b40408259498ae57c5c787c9b0c2341 to your computer and use it in GitHub Desktop.
Save tomschr/4b40408259498ae57c5c787c9b0c2341 to your computer and use it in GitHub Desktop.
Write a task.py script

Write a task.py script

The following document gives an overview about the exercise. It shows some examples how such script can work. You don't have to slavishly adhere to it. ;-) For example, if you prefer a certain file format or you don't like to output, then be creative and do how you like it!

Exercise

Write a Python script task.py which is able to manage your tasks. It has the following properties:

  1. Like "git", the task.py script contains several subcommands: create, list, edit, remove, show, help.

  2. Return error codes !=0 if there is an error.

  3. Use the directory ~/.config/task/ to store your task files.

  4. Use simple text files to store your tasks. Every file ends with .task.

  5. Use the current date and time as a basename for your task file.

  6. To make it easier, the content of a task file follows this structure:

    title
    tag
    message
    

    If the tag is empty use an empty line.

Help Output

Here is the help output from task.py:

$ python3 task.py -h
usage: task.py [-h] [--version] [--verbose] action ...

task is a small command line utility to create, list, remove, and edit tasks.

optional arguments:
  -h, --help      show this help message and exit
  --version       show program's version number and exit
  --verbose, -v   increase verbosity level

Subcommands:
  action          Available actions
    create (c)    Create a task
    list (l, li)  List tasks
    edit (e, ed)  Edit a task
    remove (r, rm)
                  Removes a task
    show (s)      Shows a task
    help (h, ?)   Help of subcommands

Help output for create subcommand

$ python3 task.py create -h
usage: task.py create [-h] title [tag] message

Create a task with a title, an optional tag and a description

positional arguments:
  title       A short summary of this task
  tag         the optional tag for this task
  message     a short description of this task

Help output for list subcommand

$ python3 task.py list -h
usage: task.py list [-h]

optional arguments:
  -h, --help  show this help message and exit

Help output for edit subcommand

$ python3 task.py edit -h
usage: task.py edit [-h] [--title TITLE] [--tag TAG] [--message MESSAGE] date

positional arguments:
  date                  the tasks' date

optional arguments:
  -h, --help            show this help message and exit
  --title TITLE, -t TITLE
                        The title to change of this task
  --tag TAG             The tag to change for this task
  --message MESSAGE, -m MESSAGE
                        The message to change for this task

Help output for remove subcommand

$ python3 task.py remove -h
usage: task.py remove [-h] date

positional arguments:
  date        Removes the task with the specified date

optional arguments:
  -h, --help  show this help message and exit

Help output for the show command

$ python3 task.py show -h
usage: task.py show [-h] date

positional arguments:
  date        Shows the task of the specified date

optional arguments:
  -h, --help  show this help message and exit

Creating a new task

To create a new task, pass a title, an optional tag, and a message:

$ python3 task.py create \
     "Participate in a Python exercise" python "Write a task.py script"
Creating task:
  title: Participate in a Python exercise
    tag: python
    msg: Write a task.py script
Wrote 2020-12-11T11:33:40.595956.task

Listing tasks

The list subcommand just enumerates and lists your tasks:

$ python3 task.py list
Available Tasks:
   1: 2020-12-11T11:33:40.595956
      Participate in a Python exercise
      ['python']

More ambitious users can pass an optional tag to list only tasks which contains this tag.

Editing tasks

To edit a task, you need the date (you get it from the list subommand. Depending on what you want to change, you can pass a title, a tag, or a message. For example, to change the title:

$ python3 task.py edit --message "Hiho" 2020-12-11T11:33:40.595956
Changed message

If you pass an date which does not contain a matching filename, the script will show you an error message:

$ python3 task.py edit --message "Hiho" unknown
[Errno 2] No such file or directory: '/home/toms/.config/task/tasks/unknown.task'

The exit code is != 0.

Removing a task

This should be straightforward. You pass a date and it removes the task from your directory.

$ python3 task.py remove 2020-12-11T11:33:40.595956

Tips

  1. Start with the CLI parser and a parsecli function.
  2. Read the Python documentation about the argparse module.
  3. Use docstrings! :-) You probably can't write doctests for all functions you write.
  4. Distinguish between output and return code. Any error messages must be written to stderror, normal messages to stdout.
  5. Name your command functions cmd_<TASKCOMMAND> to make it consistent and to distinguish it from other functions.
  6. If you don't know a solution, research on StackOverflow, your favorite search engine, or in the python channel in RocketChat.
  7. Research if there is a standard module available before you write your own implementation.
!/usr/bin/env python3
"""
task is a small command line utility to create, list, remove, and edit tasks.
"""
# TODOS/TASKS:
# - add the code
import argparse
import datetime
import functools
import glob
import os
import sys
import tempfile
__author__ = "..."
__version__ = "0.1.0"
PROG = os.path.basename(sys.argv[0])
TASK_HOMEDIR = os.path.expandvars("$HOME/.config/task/")
TASKS_DIR = os.path.join(TASK_HOMEDIR, "tasks")
TASK_SUFFIX = '.task'
TASK_FILE_PATTERN = "*%s" % TASK_SUFFIX
# ------------------------------------------------------------
def cmd_create(args):
"""
"""
print("** create")
return 0
def cmd_list(args):
"""
"""
print("* list")
return 0
def cmd_edit(args):
"""
"""
print("* edit")
return 0
def cmd_remove(args):
"""
"""
print("* remove")
args.parser.error("Oh now, it's Monday!")
return 0
def cmd_help(args):
"""Shows the help of an optional subcommand
"""
if args.cmd is None:
args.parser.print_help()
return 0
args.parser.parse_args([args.cmd, "-h"])
return 0
def cmd_show(args):
"""Shows the help of an optional subcommand
"""
print("** show")
return 0
def parse_cli(args=None):
"""Parse command line arguments
:param list args: a list of arguments (basically sys.args[:1])
:return: :class:'argparse.Namespace'
"""
parser = argparse.ArgumentParser(prog=PROG,
description=__doc__)
parser.add_argument('--version',
action='version',
version=__version__)
parser.add_argument('--verbose', '-v', action='count')
subparsers = parser.add_subparsers(title='Subcommands',
metavar='Subcommands',
dest='subcmd',
help="Available actions")
# subparser for the "create" subcommand:
# Syntax: create TITLE [TAG] MESSAGE
pcreate = subparsers.add_parser('create',
aliases=['c'],
description=('Create a task with a title, '
'an optional tag and a description'),
help='Create a task')
pcreate.set_defaults(subcmd="create",
func=cmd_create,
)
pcreate.add_argument('title',
help="A short summary of this task")
pcreate.add_argument('tag',
nargs='?',
default=None,
help='the optional tag for this task')
pcreate.add_argument('msg',
metavar='MESSAGE',
help='a short description of this task')
# subparser of the "list" subcommand:
# Syntax: list [TAG]
plist = subparsers.add_parser('list',
aliases=['l', 'li'],
help='List tasks')
plist.set_defaults(subcmd="list",
func=cmd_list,
)
plist.add_argument('tag',
nargs='?',
help='the optional tag for this task')
# subparser for the "edit" subcommand:
# Syntax: edit
pedit = subparsers.add_parser('edit',
aliases=['e', 'ed'],
help='Edit a task')
pedit.set_defaults(subcmd="edit",
func=cmd_edit,
)
pedit.add_argument('date',
help='the tasks\' date')
pedit.add_argument('--title', '-t',
help='The title to change of this task')
pedit.add_argument('--tag',
help='The tag to change for this task')
pedit.add_argument('--message', '-m',
help='The message to change for this task')
# subparser for the "remove" subcommand:
# Syntax: remove DATE
prm = subparsers.add_parser('remove',
aliases=['r', 'rm'],
help='Removes a task')
prm.set_defaults(subcmd="remove",
func=cmd_remove,
)
prm.add_argument('date',
help='Removes the task with the specified date')
# subparser for the "show" subcommand:
# Syntax: show DATE
pshow = subparsers.add_parser('show',
aliases=['s'],
help='Shows a task')
pshow.set_defaults(subcmd="show",
func=cmd_show,
)
pshow.add_argument('date',
help='Shows the task of the specified date')
# subparser for the "help" subcommand
# Syntax: help [SUBCOMMAND]
phelp = subparsers.add_parser('help',
aliases=['h', '?'],
help="Help of subcommands"
)
phelp.set_defaults(subcmd="help",
func=cmd_help,
)
phelp.add_argument('cmd',
nargs='?',
help="Subcommand to get help")
# Parse the command line:
args = parser.parse_args(args)
# Save our parser object:
args.parser = parser
# If no argument is given, we print the help:
if not sys.argv[1:]:
parser.print_help()
sys.exit(0)
return args
def main(args=None):
"""main function of the script
:param list args: a list of arguments (basically sys.args[:1])
"""
# Parse command line
args = parse_cli(args)
print("Calling subcommand", args.subcmd)
print(args)
result = args.func(args)
return result
# ------------------------------------------------------------
# Library or script check
# ------------------------------------------------------------
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment