Skip to content

Commit

Permalink
Implement the init command for user config creation (fix #33)
Browse files Browse the repository at this point in the history
check if a config already exists 
ask for data  and create dynamically a config file
Add a overview of the configuration file
Add information about the init command
Update the docs with the init command
Add an -c/--change option for the init command
Add a second option to pass arguments with direct console input
Add  tests for the init cmd
Update setup.cfg
  • Loading branch information
EMaksy authored Jun 1, 2021
1 parent 2ec1fcb commit 6fdbaff
Show file tree
Hide file tree
Showing 8 changed files with 715 additions and 17 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ jobs:
- name: Run Tests and create coverage
run: |
pytest -v
pytest -rP -v
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ Just clone the repository and cd into the directory

### Subcommands

- `init` : initialize and create config on first time
- `new` : new day for entries
- `add` : creates a new entry
- `change` :change an existing entry
- `change` : change an existing entry
- `delete` : delete an entry
- `list`: listing all existing entries
- `export` : export all entries in a file format
Expand All @@ -43,6 +44,8 @@ Just clone the repository and cd into the directory
```bash
git clone [this project]
cd reportdaily
python3 bin/reportdaily.py init
# create user config
python3 bin/reportdaily.py new
# a new day has been added.
python3 bin/reportdaily.py add "message"
Expand Down
256 changes: 247 additions & 9 deletions bin/reportdaily.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@
import logging
from logging.config import dictConfig
import sys
# configparser
from datetime import date
from configparser import ConfigParser
import os


class MissingSubCommand(ValueError):
pass


CONFIGPATH = os.path.expanduser("~/.config/reportdaily/reportdailyrc")
__version__ = "0.3.0"
__author__ = "Eugen Maksymenko <[email protected]>"

Expand Down Expand Up @@ -56,6 +61,226 @@ class MissingSubCommand(ValueError):
log.addHandler(logging.NullHandler())


def cmd_init(args, CONFIGPATH):
"""
Initializes global user data (required for the first time).
Either user data can be entered directly via options or user will be asked.
:param argparse.Namespace args: Arguments given by the command line
:param str CONFIGPATH: Path where the configuration file is stored
:return: exit code of this function
:rtype: int
"""
log.debug("INIT selected %s", args)

# check if a config file already exist
if os.path.exists(CONFIGPATH):
show_config(CONFIGPATH)
# check if the user wants to change in the existing file
if args.change is True:
how_to_change_config(args, CONFIGPATH)
return 1
# create a config if there is none
else:
create_config(args, CONFIGPATH)
show_config(CONFIGPATH)
return 0


def show_config(CONFIGPATH):
"""
Show the configs to the user
:param str CONFIGPATH: String with an absolute path for looking up the values of the configfile
"""

# read the config
parser = ConfigParser()
parser.read(CONFIGPATH)

print(
f"""
"Your current configuration at the moment"
Name: {parser.get("settings","name")}
Team: {parser.get("settings", "team")}
Date: {parser.get("settings", "current_day")}
Year: {parser.get("settings", "start_year")}
If you desire to make changes to the configuration try the -c or --change option for the init command
"""
)


def create_config(args, CONFIGPATH):
"""
Create a config file, from users input, where the user data is stored as a dict
:param argparser.Namespace args: Arguments given by the command line
:param str CONFIGPATH: Path where the configuration file is stored
"""
# name
print("Please enter your full name --> example: 'Max Musterman'")
name = ask_for_input(args.name, "Enter your Name: ")
# team
team = ask_for_input(args.team, "Enter your Team: ")
year = int(ask_for_input(
args.year, "In which year did your start your apprenticeship ?: "))
# time
today_date = date.today()
# create a config file
config = ConfigParser()
config["settings"] = {'name': name,
'team': team, 'current_day': today_date, 'start_year': year}
# create a config file
os.makedirs(os.path.dirname(CONFIGPATH), exist_ok=True)
with open(CONFIGPATH, 'w') as user_config:
config.write(user_config)
print(f"The file was created at this path {CONFIGPATH}")


def ask_for_input(var, message):
"""
Asks for input if passed variable is None, otherwise return variable value.
:param str|None var: The variable to check
:param str message: The message to use as a prompt
:return: Either the input from the user or the value of a variable
:rtype: str
"""
if var is None:
var = input(message)
return var


def how_to_change_config(args, CONFIGPATH):
"""
Change or overwrite the configs via direct input or cli Attributes
:param argparse.Namespace args: Attributes given by the command line
:param str CONFIGPATH: Path where the configuration file is stored
:return int: int return value for testing
"""
if args.name is None and args.year is None and args.team is None and args.change:
user_input_change(args, CONFIGPATH)
result_value = 0
else:
namespace_config_change(args, CONFIGPATH)

result_value = 1
show_config(CONFIGPATH)
return result_value


def namespace_config_change(args, CONFIGPATH):
"""
Input arguments direct via the console
:param argparse.Namespace args: Attributes given by the command line
:param str CONFIGPATH: Path where the configuration file is stored
"""
# store all the args from namespace
name = args.name
team = args.team
year = args.year
change = args.change

# add config parser to file
config = ConfigParser()
config.read(CONFIGPATH)
if change is True:
# overwrite if namespace is filled
if name is not None:
config.set("settings", "name", name)
if team is not None:
config.set("settings", "team", team)
if year is not None:
config.set("settings", "start_year", year)

with open(CONFIGPATH, "w") as configfile:
config.write(configfile)
# show config to the user , so changes are visible to the user
log.debug("namespace_config was selected")


def check_is_int(input_str, input_is_int):
"""
Prove if the given argument is an int and return True or decline and return False
:param str input_str: String given that needs to be checked
:param bool input_is_int: bool value default False
:return: True if str is an int, False if str is not an int
:rtype: bool
"""

if input_str.strip().isdigit():
print("Year is a int value")
input_is_int = True
return input_is_int
else:
print("Input is not a int sorry, try again")
input_is_int = False
return input_is_int


def user_input_change(args, CONFIGPATH):
"""
Input the data that is asked by function tp change configs
:param argparse.Namespace args: Attributes given by the command line
:param str CONFIGPATH: Path where the configuration file is stored
:return: the ConfigParser object
:rtype: configparse.ConfigParser
"""
# all user options
choice_table = {"Name": "t1", "Team": "t2", "Year": "t3"}
tmp_input = ''
overwrite_input = ' '

# show and ask user what he wants to overwrite
print("""
"What do you want to change?"
Your options are
Name
Team
Year
""")

# check for right user input
while(True):
tmp_input = input("Name, Team, Year? ").capitalize()
if tmp_input in choice_table:
# need to map the keys right to the settings --> from Name to name
if tmp_input == "Name":
tmp_input = "name"
elif tmp_input == "Team":
tmp_input = "team"
elif tmp_input == "Year":
tmp_input = "start_year"
break
else:
print("No key in config found --> Try again")
continue

input_is_int = False
while(True):
overwrite_input = input("Enter the change ")
if tmp_input == "start_year" and input_is_int == False:
input_is_int = check_is_int(
overwrite_input, input_is_int)
if input_is_int == True:
break
elif tmp_input != "start_year":
break

# add config parser to file
config = ConfigParser()
config.read(CONFIGPATH)
config.set("settings", f"{tmp_input}", f"{overwrite_input}")
with open(CONFIGPATH, "w") as configfile:
config.write(configfile)
# show config to the user , so changes are visible to the user
return config


def cmd_new(args):
"""Creates a new day for the incoming entries"""
log.debug("New selected %s", args)
Expand Down Expand Up @@ -99,10 +324,10 @@ def cmd_export(args):


def parsecli(cliargs=None) -> argparse.Namespace:
"""Parse CLI with :class:`argparse.ArgumentParser` and return parsed result
"""Parse CLI with: class:`argparse.ArgumentParser` and return parsed result
:param cliargs: Arguments to parse or None (=use sys.argv)
:return: parsed CLI result
: param cliargs: Arguments to parse or None (=use sys.argv)
: return: parsed CLI result
"""
parser = argparse.ArgumentParser(description=__doc__,
epilog="Version %s written by %s " % (
Expand All @@ -120,6 +345,19 @@ def parsecli(cliargs=None) -> argparse.Namespace:

# subparser
subparsers = parser.add_subparsers(help='available sub commands')
# init cmd
parser_init = subparsers.add_parser(
'init', help="Create an initial Configuration file")
parser_init.set_defaults(func=cmd_init)
parser_init.add_argument(
'--name', "-n", help='User Name')
parser_init.add_argument(
'--year', "-y", help='Start year of the trainee')
parser_init.add_argument(
'--team', "-t", help='Current team name')
parser_init.add_argument(
'--change', '-c', action='store_true', help='Change an existing configuration')

# new cmd
parser_new = subparsers.add_parser('new', help="creates a new day entry")
parser_new.set_defaults(func=cmd_new)
Expand Down Expand Up @@ -177,12 +415,12 @@ def main(cliargs=None) -> int:
args = parsecli(cliargs)
# do some useful things here...
# If everything was good, return without error:
# log.info("I'm an info message")
# log.debug("I'm a debug message.")
# log.warning("I'm a warning message.")
# log.error("I'm an error message.")
# log.fatal("I'm a really fatal massage!")
exit_code = args.func(args)
# log.info("I'm an info message")
# log.debug("I'm a debug message.")
# log.warning("I'm a warning message.")
# log.error("I'm an error message.")
# log.fatal("I'm a really fatal massage!")
exit_code = args.func(args, CONFIGPATH)
return exit_code

except MissingSubCommand as error:
Expand Down
3 changes: 2 additions & 1 deletion devel-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pytest
pytest-cov
pytest-cov
pytest-mock
10 changes: 10 additions & 0 deletions docs/reportdaily.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ Subcommands

.. HINT: Sort the subcommands alphabetically
init
~~~~

Is required for the first time use of the programm
User can enter his data directly or he need to answer some questions provided by this command

.. code:: bash
reportdaily init
new
~~~

Expand Down
3 changes: 1 addition & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ formats = bztar, zip
[tool:pytest]
minversion = 3.0
testpaths= tests/
addopts = --cov=reportdaily tests/ --cov-report=term-missing

addopts = --cov=reportdaily tests/ --cov-report=term-missing
[options]
scripts =
bin/reportdaily.py
Expand Down
8 changes: 5 additions & 3 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import pytest
import sys

import reportdaily as rd

# import monkey patching
from unittest.mock import patch


def test_version(capsys):
"""
test if the version is correct
test if the version is correct
"""
# given the user use the --version option
cliargs = ["--version"]
Expand Down Expand Up @@ -41,7 +43,7 @@ def test_help(capsys):
@pytest.mark.parametrize("verbose_count", ["", "-v", "-vv", "-vvv", "-vvvv"])
def test_verbosity(verbose_count):
"""
Test if the verbosity option was used correctly
Test if the verbosity option was used correctly
"""
# given the user inputs the -v option to add verbosity level for the logger from -v to -vvvv with an subcommand
# example cmd -vvvv and new
Expand Down
Loading

0 comments on commit 6fdbaff

Please sign in to comment.