diff --git a/.gitignore b/.gitignore index 2c04c4e..fb16bf5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.py[cod] __pycache__ .aliases +.related # Virtual Environment env/ diff --git a/DEVELOPMENT_NOTES.md b/DEVELOPMENT_NOTES.md index 01187b1..620dcc4 100644 --- a/DEVELOPMENT_NOTES.md +++ b/DEVELOPMENT_NOTES.md @@ -3,3 +3,14 @@ - the command module, will use both filesystem and db to perform actions - the filesystem module, deals with low-level fs operations - the db module, deals with database operations +- `make` accept params. + + Ex.: + ```sh + make params="add tests/data/.kb/data" + ``` + will execute + + ```sh + python -m kb add tests/data/.kb/data + ``` diff --git a/Makefile b/Makefile index 6bddf85..edac252 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: default, lint default: - python -m kb + python -m kb $(params) spell: codespell . --ignore-words-list=hist --skip=./.* --quiet-level=2 || true lint: diff --git a/docs/conf.py b/docs/conf.py index 0c881cd..ab817aa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,213 +11,221 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +# import os +# import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode'] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.ifconfig", + "sphinx.ext.viewcode", +] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'kb' -copyright = u'2020, gnc' +project = u"kb" +copyright = u"2020, gnc" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '0.1.6' +version = "0.1.6" # The full version, including alpha/beta/rc tags. -release = '0.1.6' +release = "0.1.6" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'kbdoc' +htmlhelp_basename = "kbdoc" # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' +# latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' +# latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'kb.tex', u'kb Documentation', - u'gnc', 'manual'), + ("index", "kb.tex", u"kb Documentation", u"gnc", "manual"), ] -latex_elements = {'papersize': 'a4paper', 'fontpkg': '\\usepackage{tgheros}', - 'fncychap': '\\usepackage[Sonny]{fncychap}'} +latex_elements = { + "papersize": "a4paper", + "fontpkg": "\\usepackage{tgheros}", + "fncychap": "\\usepackage[Sonny]{fncychap}", +} # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +# latex_preamble = '' # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'kb', u'kb Documentation', - [u'gnc'], 1) -] +man_pages = [("index", "kb", u"kb Documentation", [u"gnc"], 1)] # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} +intersphinx_mapping = {"http://docs.python.org/": None} diff --git a/kb/__main__.py b/kb/__main__.py index 05f4e39..03051ea 100755 --- a/kb/__main__.py +++ b/kb/__main__.py @@ -12,9 +12,8 @@ """ from kb.main import main +__all__ = ("main",) -__all__ = ('main',) - -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/kb/cl_parser.py b/kb/cl_parser.py index 37c1645..6edd06f 100755 --- a/kb/cl_parser.py +++ b/kb/cl_parser.py @@ -13,11 +13,12 @@ __all__ = () -import sys import argparse -from kb import __version__ +import sys from typing import Sequence +from kb import __version__ + def parse_args(args: Sequence[str]) -> argparse.Namespace: """ @@ -33,44 +34,44 @@ def parse_args(args: Sequence[str]) -> argparse.Namespace: An argparse Namespace object with the provided arguments, which can be used in a simpler format. """ - parser = argparse.ArgumentParser(prog='kb', - description='A knowledge base organizer') + parser = argparse.ArgumentParser( + prog="kb", description="A knowledge base organizer" + ) parser.add_argument( "--version", action="version", version="%(prog)s {}".format(__version__)) - subparsers = parser.add_subparsers(help='commands', dest="command") + subparsers = parser.add_subparsers(help="commands", dest="command") subparsers.required = True # Main Commands - add_parser = subparsers.add_parser( - 'add', help='Add an artifact') + add_parser = subparsers.add_parser("add", help="Add an artifact") edit_parser = subparsers.add_parser( - 'edit', help='Edit an artifact content') - list_parser = subparsers.add_parser( - 'list', help='Search for artifacts') - view_parser = subparsers.add_parser( - 'view', help='View artifacts') + "edit", help="Edit an artifact content") + list_parser = subparsers.add_parser("list", help="Search for artifacts") + view_parser = subparsers.add_parser("view", help="View artifacts") grep_parser = subparsers.add_parser( - 'grep', help='Grep through kb artifacts') + "grep", help="Grep through kb artifacts") update_parser = subparsers.add_parser( - 'update', help='Update artifact properties') - delete_parser = subparsers.add_parser( - 'delete', help='Delete artifacts') + "update", help="Update artifact properties") + delete_parser = subparsers.add_parser("delete", help="Delete artifacts") template_parser = subparsers.add_parser( - 'template', help='Manage templates for artifacts') + "template", help="Manage templates for artifacts" + ) import_parser = subparsers.add_parser( - 'import', help='Import a knowledge base') + "import", help="Import a knowledge base") export_parser = subparsers.add_parser( - 'export', help='Export the knowledge base') + "export", help="Export the knowledge base") erase_parser = subparsers.add_parser( - 'erase', help='Erase the entire kb knowledge base') + "erase", help="Erase the entire kb knowledge base" + ) sync_parser = subparsers.add_parser( - 'sync', help='Synchronize the knowledge base with a remote git repository') + "sync", help="Synchronize the knowledge base with a remote git repository") help_parser = subparsers.add_parser( - 'help', help='Show help of a particular command') + "help", help="Show help of a particular command" + ) # add parser add_parser.add_argument( @@ -80,30 +81,35 @@ def parse_args(args: Sequence[str]) -> argparse.Namespace: nargs="*", ) add_parser.add_argument( - "-t", "--title", + "-t", + "--title", help="Title of the added artifact", type=str, ) add_parser.add_argument( - "-c", "--category", + "-c", + "--category", help="Category associated to the artifact", default="default", type=str, ) add_parser.add_argument( - "-g", "--tags", + "-g", + "--tags", help=""" Tags to associate to the artifact in the form \"tag1;tag2;...;tagN\" """, type=str, ) add_parser.add_argument( - "-a", "--author", + "-a", + "--author", help="Author of the artifact", type=str, ) add_parser.add_argument( - "-s", "--status", + "-s", + "--status", help="Status of the artifact", type=str, ) @@ -113,7 +119,8 @@ def parse_args(args: Sequence[str]) -> argparse.Namespace: type=str, ) add_parser.add_argument( - "-b", "--body", + "-b", + "--body", help="Body of the artifact", type=str, ) @@ -126,36 +133,42 @@ def parse_args(args: Sequence[str]) -> argparse.Namespace: nargs="?", ) edit_parser.add_argument( - "-i", "--id", + "-i", + "--id", help="ID of the artifact to edit", type=str, ) edit_parser.add_argument( - "-t", "--title", + "-t", + "--title", help="Title to update", default=None, type=str, ) edit_parser.add_argument( - "-c", "--category", + "-c", + "--category", help="Category to update", default=None, type=str, ) edit_parser.add_argument( - "-g", "--tags", - help="Tags to update in the form \"tag1;tag2;...;tagN\"", + "-g", + "--tags", + help='Tags to update in the form "tag1;tag2;...;tagN"', default=None, type=str, ) edit_parser.add_argument( - "-a", "--author", + "-a", + "--author", help="Author to update", default=None, type=str, ) edit_parser.add_argument( - "-s", "--status", + "-s", + "--status", help="Status to update", default=None, type=str, @@ -170,13 +183,15 @@ def parse_args(args: Sequence[str]) -> argparse.Namespace: type=str, ) list_parser.add_argument( - "-c", "--category", + "-c", + "--category", help="Filter search results by specified category", default=None, type=str, ) list_parser.add_argument( - "-g", "--tags", + "-g", + "--tags", help=""" Tags associates to the artifact to search in the form \"tag1;tag2;...;tagN\" """, @@ -184,36 +199,41 @@ def parse_args(args: Sequence[str]) -> argparse.Namespace: type=str, ) list_parser.add_argument( - "-a", "--author", + "-a", + "--author", help="Filter search results by specified author", default=None, type=str, ) list_parser.add_argument( - "-s", "--status", + "-s", + "--status", help="Filter search results by specified status", default=None, type=str, ) list_parser.add_argument( - "-v", "--verbose", + "-v", + "--verbose", help="Show additional information for the provided results", - action='store_true', - dest='verbose', + action="store_true", + dest="verbose", default=False, ) list_parser.add_argument( - "-f", "--full-identifier", + "-f", + "--full-identifier", help="Print results in full-identifier mode", - action='store_true', - dest='full_identifier', + action="store_true", + dest="full_identifier", default=False, ) list_parser.add_argument( - "-n", "--no-color", + "-n", + "--no-color", help="Enabled no-color mode", - action='store_true', - dest='no_color', + action="store_true", + dest="no_color", default=False, ) @@ -225,32 +245,37 @@ def parse_args(args: Sequence[str]) -> argparse.Namespace: nargs="?", ) view_parser.add_argument( - "-i", "--id", + "-i", + "--id", help="ID of the artifact to visualize", type=str, ) view_parser.add_argument( - "-t", "--title", + "-t", + "--title", help="Title of the artifact to visualize", type=str, ) view_parser.add_argument( - "-c", "--category", + "-c", + "--category", help="Category associated to the artifact to visualize", type=str, ) view_parser.add_argument( - "-e", "--open-editor", + "-e", + "--open-editor", help="Open the file in a text editor (read-only mode)", - action='store_true', - dest='editor', + action="store_true", + dest="editor", default=False, ) view_parser.add_argument( - "-n", "--no-color", + "-n", + "--no-color", help="Enabled no-color mode", - action='store_true', - dest='no_color', + action="store_true", + dest="no_color", default=False, ) @@ -261,13 +286,15 @@ def parse_args(args: Sequence[str]) -> argparse.Namespace: type=str, ) grep_parser.add_argument( - "-c", "--category", + "-c", + "--category", help="Filter search results by specified category", default=None, type=str, ) grep_parser.add_argument( - "-g", "--tags", + "-g", + "--tags", help=""" Tags associates to the artifact to search in the form \"tag1;tag2;...;tagN\" """, @@ -275,78 +302,90 @@ def parse_args(args: Sequence[str]) -> argparse.Namespace: type=str, ) grep_parser.add_argument( - "-a", "--author", + "-a", + "--author", help="Filter search results by specified author", default=None, type=str, ) grep_parser.add_argument( - "-s", "--status", + "-s", + "--status", help="Filter search results by specified status", default=None, type=str, ) grep_parser.add_argument( - "-m", "--show-matches", + "-m", + "--show-matches", help="Show text matching the regex within the artifact ", - action='store_true', - dest='matches', + action="store_true", + dest="matches", default=False, ) grep_parser.add_argument( - "-i", "--case-insensitive", + "-i", + "--case-insensitive", help="Perform grep using a case insensitive regex", - action='store_true', - dest='case_insensitive', + action="store_true", + dest="case_insensitive", default=False, ) grep_parser.add_argument( - "-v", "--verbose", + "-v", + "--verbose", help="Show additional information for the provided results", - action='store_true', - dest='verbose', + action="store_true", + dest="verbose", default=False, ) grep_parser.add_argument( - "-n", "--no-color", + "-n", + "--no-color", help="Enabled no-color mode", - action='store_true', - dest='no_color', + action="store_true", + dest="no_color", default=False, ) # update parser update_parser.add_argument( - "-i", "--id", + "-i", + "--id", help="ID of the artifact to update", type=str, ) update_parser.add_argument( - "-t", "--title", + "-t", + "--title", help="Title to update", default=None, type=str, ) update_parser.add_argument( - "-c", "--category", + "-c", + "--category", help="Category to update", default=None, type=str, ) update_parser.add_argument( - "-g", "--tags", - help="Tags to update in the form \"tag1;tag2;...;tagN\"", + "-g", + "--tags", + help='Tags to update in the form "tag1;tag2;...;tagN"', default=None, type=str, ) update_parser.add_argument( - "-a", "--author", + "-a", + "--author", help="Author to update", default=None, type=str, ) update_parser.add_argument( - "-s", "--status", + "-s", + "--status", help="Status to update", default=None, type=str, @@ -358,61 +397,74 @@ def parse_args(args: Sequence[str]) -> argparse.Namespace: type=str, ) update_parser.add_argument( - "-e", "--edit-content", + "-e", + "--edit-content", help="Edit content of the artifact with an editor", action="store_true", dest="edit_content", ) update_parser.add_argument( - "-b", "--body", + "-b", + "--body", help="Update the body of the artifact (erases the current content)", type=str, ) # delete parser delete_parser.add_argument( - "-i", "--id", + "-i", + "--id", help="ID of the artifact", type=str, - nargs='*', + nargs="*", ) delete_parser.add_argument( - "-t", "--title", + "-t", + "--title", help="Title of the artifact to remove", default=None, type=str, ) delete_parser.add_argument( - "-c", "--category", + "-c", + "--category", help="Category associated to the artifact to remove", default=None, type=str, ) delete_parser.add_argument( - "-f", "--force", + "-f", + "--force", help="Force removal without asking for confirmation prompt", - action='store_true', + action="store_true", default=False, ) # template parser template_subparsers = template_parser.add_subparsers( - help='template commands', dest="template_command") + help="template commands", dest="template_command" + ) template_subparsers.required = True # template subcommands add_template_parser = template_subparsers.add_parser( - 'add', help='Add a template from a file') + "add", help="Add a template from a file" + ) edit_template_parser = template_subparsers.add_parser( - 'edit', help='Edit a template') + "edit", help="Edit a template" + ) list_template_parser = template_subparsers.add_parser( - 'list', help='List all templates') + "list", help="List all templates" + ) new_template_parser = template_subparsers.add_parser( - 'new', help='Create a template from starting from an example') + "new", help="Create a template from starting from an example" + ) delete_template_parser = template_subparsers.add_parser( - 'delete', help='Delete an existing template') + "delete", help="Delete an existing template" + ) apply_template_parser = template_subparsers.add_parser( - 'apply', help='Apply a template to an entire set of artifacts') + "apply", help="Apply a template to an entire set of artifacts" + ) add_template_parser.add_argument( "file", @@ -420,7 +472,8 @@ def parse_args(args: Sequence[str]) -> argparse.Namespace: type=str, ) add_template_parser.add_argument( - "-t", "--title", + "-t", + "--title", help="The title to assign to the template added from a file to kb", type=str, ) @@ -433,13 +486,14 @@ def parse_args(args: Sequence[str]) -> argparse.Namespace: "query", help="The name (or part of it) of the template to search for", type=str, - nargs='?', + nargs="?", ) list_template_parser.add_argument( - "-n", "--no-color", + "-n", + "--no-color", help="Enabled no-color mode", - action='store_true', - dest='no_color', + action="store_true", + dest="no_color", default=False, ) delete_template_parser.add_argument( @@ -460,38 +514,45 @@ def parse_args(args: Sequence[str]) -> argparse.Namespace: ) apply_template_parser.add_argument( - "-t", "--title", + "-t", + "--title", help="Title of the artifacts on which template is applied", type=str, ) apply_template_parser.add_argument( - "-c", "--category", + "-c", + "--category", help="Category of the artifacts on which template is applied", default=None, type=str, ) apply_template_parser.add_argument( - "-g", "--tags", + "-g", + "--tags", help=""" - Tags associates to the artifacts in the form \"tag1;tag2;...;tagN\" where template is applied + Tags associates to the artifacts in the form \"tag1;tag2;...;tagN\" + where template is applied """, default=None, type=str, ) apply_template_parser.add_argument( - "-a", "--author", + "-a", + "--author", help="Author of the artifacts on which template is applied", default=None, type=str, ) apply_template_parser.add_argument( - "-s", "--status", + "-s", + "--status", help="Status of the artifacts on which template is applied", default=None, type=str, ) apply_template_parser.add_argument( - "-m", "--extended-match", + "-m", + "--extended-match", help=""" Perform application query not on a strict match, for example: @@ -499,8 +560,8 @@ def parse_args(args: Sequence[str]) -> argparse.Namespace: will match all artifacts containing in their category \"cheat\", hence \"cheatsheet\", \"mycheats\",\"cheatsheets\" and so on" """, - action='store_true', - dest='extended_match', + action="store_true", + dest="extended_match", default=False, ) @@ -513,17 +574,17 @@ def parse_args(args: Sequence[str]) -> argparse.Namespace: # export parser export_parser.add_argument( - "-f", "--file", + "-f", + "--file", help="Name of the exported archive", type=str, - nargs="?" - ) + nargs="?") export_parser.add_argument( "-d", "--only-data", help="Export only notes files organized as directories (one for each category)", - action='store_true', - dest='only_data', + action="store_true", + dest="only_data", default=False, ) @@ -531,43 +592,42 @@ def parse_args(args: Sequence[str]) -> argparse.Namespace: erase_parser.add_argument( "--db", help="Only remove kb database", - action='store_true', - dest='db', + action="store_true", + dest="db", default=False, ) # sync parser sync_parser.add_argument( - 'operation', + "operation", help="""Use \"init\" to initialize the remote repo, Use \"push\" to git push (write local -> remote) the knowledge base, Use \"pull\" to git pull (retrieve remote -> local) the remote kb, Use \"info\" to show information about the repository """, - choices=['init', 'push', 'pull', 'info']) + choices=["init", "push", "pull", "info"], + ) help_parser.add_argument( - 'cmd', - help='Name of command to get help for', - nargs='?' - ) + "cmd", + help="Name of command to get help for", + nargs="?") if len(args) == 0: parser.print_help(sys.stderr) sys.exit(1) parsed_args = parser.parse_args() - if parsed_args.command == 'help': + if parsed_args.command == "help": if not parsed_args.cmd: parser.print_help(sys.stderr) else: try: subparsers.choices[parsed_args.cmd].print_help() except KeyError: - print(f'Unknown command name `{parsed_args.cmd}`') + print(f"Unknown command name `{parsed_args.cmd}`") print( - f"Valid commands are: {', '.join(subparsers.choices.keys())}" - ) + f"Valid commands are: {', '.join(subparsers.choices.keys())}") sys.exit(1) return parsed_args diff --git a/kb/commands/add.py b/kb/commands/add.py index c79447c..bb359f2 100644 --- a/kb/commands/add.py +++ b/kb/commands/add.py @@ -16,9 +16,10 @@ from pathlib import Path from subprocess import call from typing import Dict + import kb.db as db -import kb.initializer as initializer import kb.filesystem as fs +import kb.initializer as initializer from kb.entities.artifact import Artifact @@ -53,6 +54,7 @@ def add(args: Dict[str, str], config: Dict[str, str]): if args["file"]: for fname in args["file"]: if fs.is_directory(fname): + print("Error:", fname, "is a directory, ignored.") continue add_file_to_kb(conn, args, config, fname) else: @@ -81,15 +83,19 @@ def add(args: Dict[str, str], config: Dict[str, str]): body_lines = sys.stdin.readlines() art_file.writelines(body_lines) else: - shell_cmd = shlex.split( - config["EDITOR"]) + [artifact_path] + shell_cmd = shlex.split(config["EDITOR"]) + [artifact_path] call(shell_cmd) new_artifact = Artifact( - id=None, title=title, category=category, + id=None, + title=title, + category=category, path="{category}/{title}".format(category=category, title=title), tags=args["tags"], - status=args["status"], author=args["author"], template=args["template"]) + status=args["status"], + author=args["author"], + template=args["template"], + ) db.insert_artifact(conn, new_artifact) @@ -108,10 +114,7 @@ def validate(args): def add_file_to_kb( - conn, - args: Dict[str, str], - config: Dict[str, str], - fname: str + conn, args: Dict[str, str], config: Dict[str, str], fname: str ) -> None: """ Adds a file to the kb knowledge base. @@ -126,26 +129,61 @@ def add_file_to_kb( PATH_KB_DATA, the path to where artifact are stored fname - the path of the file to add to kb """ + # Title title = args["title"] or fs.get_basename(fname) - category = args["category"] or "default" - template = args["template"] or "default" + dest_fname = ( + title # enable artifact to have a title different from the corresponding file + ) + + # Template + if args["template"]: + template = args["template"] + elif fs.is_md_file(fname): + template = "markdown" + if args["title"] is None: + title = Path(fname).stem + else: + template = "default" + # Category + category = args["category"] or "default" category_path = Path(config["PATH_KB_DATA"], category) category_path.mkdir(parents=True, exist_ok=True) + # Copy + + if db.is_artifact_existing(conn, title, category): + print( + "Error: The specified artifact {title} already exist!".format(title=title) + ) + sys.exit(1) + + if fs.is_file(Path(category_path, dest_fname)): + print( + "Error: The destination file {dest_fname} already exist!".format( + dest_fname=dest_fname + ) + ) + sys.exit(1) + try: - fs.copy_file(fname, Path(category_path, title)) + fs.copy_file(fname, Path(category_path, dest_fname)) except FileNotFoundError: - print("Error: The specified file does not exist!".format(fname=fname)) + print("Error: The specified file {fname} does not exist!".format(fname=fname)) sys.exit(1) - if not db.is_artifact_existing(conn, title, category): - fs.copy_file(fname, Path(category_path, title)) + # ??? + # if not db.is_artifact_existing(conn, title, category): + # fs.copy_file(fname, Path(category_path, title)) new_artifact = Artifact( id=None, - title=title, category=category, + title=title, + category=category, path="{category}/{title}".format(category=category, title=title), tags=args["tags"], - status=args["status"], author=args["author"], template=template) + status=args["status"], + author=args["author"], + template=template, + ) db.insert_artifact(conn, new_artifact) diff --git a/kb/commands/delete.py b/kb/commands/delete.py index 77a82de..ac61183 100644 --- a/kb/commands/delete.py +++ b/kb/commands/delete.py @@ -11,13 +11,14 @@ :License: GPLv3 (see /LICENSE). """ -import sys -from typing import Dict from pathlib import Path +# import sys +from typing import Dict + import kb.db as db -import kb.initializer as initializer -import kb.history as history import kb.filesystem as fs +import kb.history as history +import kb.initializer as initializer def delete(args: Dict[str, str], config: Dict[str, str]): @@ -63,13 +64,13 @@ def delete_by_id(id: int, is_forced: bool, config: Dict[str, str]): conn = db.create_connection(config["PATH_KB_DB"]) artifact_id = history.get_artifact_id(config["PATH_KB_HIST"], id) artifact = db.get_artifact_by_id(conn, artifact_id) - + if not artifact: print("Error: Invalid artifact referenced") return - if not is_forced: - confirm = ask_confirmation(artifact.title,artifact.category) + if not is_forced: + confirm = ask_confirmation(artifact.title, artifact.category) if not artifact or not confirm: print("No artifact was removed") return @@ -86,11 +87,15 @@ def delete_by_id(id: int, is_forced: bool, config: Dict[str, str]): if fs.count_files(category_path) == 0: fs.remove_directory(category_path) - print("Artifact {category}/{title} removed!".format( - category=artifact.category, title=artifact.title)) + print( + "Artifact {category}/{title} removed!".format( + category=artifact.category, title=artifact.title + ) + ) -def delete_by_name(title: str, category: str, is_forced: bool, config: Dict[str, str]): +def delete_by_name(title: str, category: str, + is_forced: bool, config: Dict[str, str]): """ Edit the content of an artifact by name, that is title/category @@ -105,14 +110,14 @@ def delete_by_name(title: str, category: str, is_forced: bool, config: Dict[str, EDITOR - the editor program to call """ conn = db.create_connection(config["PATH_KB_DB"]) - artifacts = db.get_artifacts_by_filter(conn, title=title, - category=category, - is_strict=True) + artifacts = db.get_artifacts_by_filter( + conn, title=title, category=category, is_strict=True + ) if len(artifacts) == 1: artifact = artifacts.pop() if not is_forced: - confirm = ask_confirmation(artifact.title,artifact.category) + confirm = ask_confirmation(artifact.title, artifact.category) if not artifact or not confirm: print("No artifact was removed") return @@ -121,10 +126,14 @@ def delete_by_name(title: str, category: str, is_forced: bool, config: Dict[str, print("Artifact {}/{} removed!".format(artifact.category, artifact.title)) elif len(artifacts) > 1: print( - "There is more than one artifact with that title, please specify a category") + "There is more than one artifact with that title, " + "please specify a category" + ) else: print( - "There is no artifact with that name, please specify a correct artifact name") + "There is no artifact with that name, " + "please specify a correct artifact name" + ) def ask_confirmation(title: str, category: str): @@ -141,6 +150,8 @@ def ask_confirmation(title: str, category: str): """ answer = input( "Are you sure you want to delete {category}/{title}? [y/n]".format( - category=category, title=title)) + category=category, title=title + ) + ) - return not (answer.lower() not in ["y","yes"]) + return not (answer.lower() not in ["y", "yes"]) diff --git a/kb/commands/edit.py b/kb/commands/edit.py index 48219d1..f0c6c45 100644 --- a/kb/commands/edit.py +++ b/kb/commands/edit.py @@ -15,9 +15,10 @@ from pathlib import Path from subprocess import call from typing import Dict + import kb.db as db -import kb.initializer as initializer import kb.history as history +import kb.initializer as initializer def edit(args: Dict[str, str], config: Dict[str, str]): @@ -70,13 +71,13 @@ def edit_by_id(id: int, config: Dict[str, str]): EDITOR - the editor program to call """ conn = db.create_connection(config["PATH_KB_DB"]) - artifact = history.get_artifact( - conn, config["PATH_KB_HIST"], id) + artifact = history.get_artifact(conn, config["PATH_KB_HIST"], id) category_path = Path(config["PATH_KB_DATA"], artifact.category) - shell_cmd = shlex.split(config["EDITOR"]) + \ - [str(Path(category_path, artifact.title))] + shell_cmd = shlex.split(config["EDITOR"]) + [ + str(Path(category_path, artifact.title)) + ] call(shell_cmd) @@ -95,19 +96,24 @@ def edit_by_name(title: str, category: str, config: Dict[str, str]): EDITOR - the editor program to call """ conn = db.create_connection(config["PATH_KB_DB"]) - artifacts = db.get_artifacts_by_filter(conn, title=title, - category=category, - is_strict=True) + artifacts = db.get_artifacts_by_filter( + conn, title=title, category=category, is_strict=True + ) if len(artifacts) == 1: artifact = artifacts.pop() category_path = Path(config["PATH_KB_DATA"], artifact.category) - shell_cmd = shlex.split( - config["EDITOR"]) + [str(Path(category_path, artifact.title))] + shell_cmd = shlex.split(config["EDITOR"]) + [ + str(Path(category_path, artifact.title)) + ] call(shell_cmd) elif len(artifacts) > 1: print( - "There is more than one artifact with that title, please specify a category") + "There is more than one artifact with that title, " + "please specify a category" + ) else: print( - "There is no artifact with that name, please specify a correct artifact name") + "There is no artifact with that name, " + "please specify a correct artifact name" + ) diff --git a/kb/commands/erase.py b/kb/commands/erase.py index 61fdada..c1027ab 100644 --- a/kb/commands/erase.py +++ b/kb/commands/erase.py @@ -12,6 +12,7 @@ """ from typing import Dict + import kb.filesystem as fs @@ -41,7 +42,8 @@ def erase(args: Dict[str, str], config: Dict[str, str]): pass else: answer = input( - "Are you sure you want to erase the whole kb knowledge base ? [YES/NO]") + "Are you sure you want to erase the whole kb knowledge base ? [YES/NO]" + ) if answer.lower() == "yes": try: fs.remove_directory(config["PATH_KB"]) diff --git a/kb/commands/export.py b/kb/commands/export.py index 7eecdfb..b81b8f0 100644 --- a/kb/commands/export.py +++ b/kb/commands/export.py @@ -11,9 +11,10 @@ :License: GPLv3 (see /LICENSE). """ -import time import tarfile -from pathlib import Path +import time + +# from pathlib import Path from typing import Dict @@ -35,8 +36,8 @@ def export(args: Dict[str, str], config: Dict[str, str]): fname = fname + archive_ext if args["only_data"]: - with tarfile.open(fname, mode='w:gz') as archive: + with tarfile.open(fname, mode="w:gz") as archive: archive.add(config["PATH_KB_DATA"], arcname="kb", recursive=True) else: - with tarfile.open(fname, mode='w:gz') as archive: + with tarfile.open(fname, mode="w:gz") as archive: archive.add(config["PATH_KB"], arcname=".kb", recursive=True) diff --git a/kb/commands/grep.py b/kb/commands/grep.py index e645002..1564cd5 100644 --- a/kb/commands/grep.py +++ b/kb/commands/grep.py @@ -14,11 +14,12 @@ import sys from pathlib import Path from typing import Dict + import kb.db as db +import kb.filesystem as fs +import kb.history as history import kb.initializer as initializer import kb.printer.grep as printer -import kb.history as history -import kb.filesystem as fs def grep(args: Dict[str, str], config: Dict[str, str]): @@ -64,8 +65,10 @@ def grep(args: Dict[str, str], config: Dict[str, str]): sys.exit(0) # Get the list of artifact tuples in the form (category,title) - artifact_names = [fs.get_filename_parts_wo_prefix( - res[0], config["PATH_KB_DATA"]) for res in results] + artifact_names = [ + fs.get_filename_parts_wo_prefix(res[0], config["PATH_KB_DATA"]) + for res in results + ] # Get the set of uniq artifacts uniq_artifact_names = set(artifact_names) @@ -78,7 +81,8 @@ def grep(args: Dict[str, str], config: Dict[str, str]): for art in uniq_artifact_names: artifact = db.get_artifacts_by_filter( - conn, category="/".join(art[:-1]), title=art[-1], is_strict=True)[0] + conn, category="/".join(art[:-1]), title=art[-1], is_strict=True + )[0] if artifact: no_of_hits = filecounts[art] diff --git a/kb/commands/ingest.py b/kb/commands/ingest.py index 4a1f3ba..7674e7d 100644 --- a/kb/commands/ingest.py +++ b/kb/commands/ingest.py @@ -14,6 +14,7 @@ import tarfile from pathlib import Path from typing import Dict + import kb.filesystem as fs @@ -30,9 +31,11 @@ def ingest(args: Dict[str, str], config: Dict[str, str]): PATH_KB - the main path of KB """ if args["file"].endswith(".tar.gz"): - answer = input("You are about to import a whole knowledge base " - "are you sure you want to wipe your previous " - " kb data ? [YES/NO]") + answer = input( + "You are about to import a whole knowledge base " + "are you sure you want to wipe your previous " + " kb data ? [YES/NO]" + ) if answer.lower() == "yes": print("Previous kb knowledge base data wiped...") try: diff --git a/kb/commands/search.py b/kb/commands/search.py index 1fdc837..f2b6d16 100644 --- a/kb/commands/search.py +++ b/kb/commands/search.py @@ -12,10 +12,11 @@ """ from typing import Dict + import kb.db as db +import kb.history as history import kb.initializer as initializer import kb.printer.search as printer -import kb.history as history def search(args: Dict[str, str], config: Dict[str, str]): @@ -41,7 +42,7 @@ def search(args: Dict[str, str], config: Dict[str, str]): tags_list = None if args["tags"] and args["tags"] != "": - tags_list = args["tags"].split(';') + tags_list = args["tags"].split(";") conn = db.create_connection(config["PATH_KB_DB"]) rows = db.get_artifacts_by_filter( @@ -50,7 +51,8 @@ def search(args: Dict[str, str], config: Dict[str, str]): category=args["category"], tags=tags_list, status=args["status"], - author=args["author"]) + author=args["author"], + ) # rows.sort(key=lambda x: x[1]) artifacts = sorted(rows, key=lambda x: x.title) diff --git a/kb/commands/sync.py b/kb/commands/sync.py index f5afc8d..f372d9f 100644 --- a/kb/commands/sync.py +++ b/kb/commands/sync.py @@ -13,8 +13,9 @@ import time from typing import Dict -import kb.filesystem as fs + import git +import kb.filesystem as fs def sync(args: Dict[str, str], config: Dict[str, str]): @@ -36,12 +37,13 @@ def sync(args: Dict[str, str], config: Dict[str, str]): if is_local_git_repo_initialized(config["PATH_KB_GIT"]): print("Warning: a git repository already exists...") cancel_repo = input( - "Do you want to re-init your repository ? [Type YES in that case] ") + "Do you want to re-init your repository ? [Type YES in that case] " + ) if cancel_repo == "YES": fs.remove_directory(config["PATH_KB_GIT"]) git_init(config["PATH_KB"]) else: - print("Maybe you wanted to type \"kb sync push\" or \"kb sync pull\"!") + print('Maybe you wanted to type "kb sync push" or "kb sync pull"!') else: git_init(config["PATH_KB"]) elif operation == "push": @@ -54,7 +56,9 @@ def sync(args: Dict[str, str], config: Dict[str, str]): if not is_local_git_repo_initialized(config["PATH_KB_GIT"]): print("Warning: no local git repository has been instantiated!") cancel_repo = input( - "Do you want to remove possible local kb files and sync from a remote repository? [Type YES in that case] ") + "Do you want to remove possible local kb files " + "and sync from a remote repository? [Type YES in that case] " + ) if cancel_repo == "YES": try: fs.remove_directory(config["PATH_KB"]) @@ -81,18 +85,20 @@ def is_local_git_repo_initialized(git_path): def git_push(repo_path): try: kb_repo = git.Repo(repo_path) - kb_repo.git.add('--all') + kb_repo.git.add("--all") timestamp = time.strftime("%d/%m/%Y-%H:%M:%S") kb_repo.index.commit("kb synchronization {ts}".format(ts=timestamp)) kb_repo.remote( - name='origin').push( - refspec='{}:{}'.format( + name="origin").push( + refspec="{}:{}".format( "main", "main")) print("Repository correctly synchronized to remote!") except BaseException: - print('Some error occurred while pushing the code') - print('Check your internet connection or the existence of the remote repository') + print("Some error occurred while pushing the code") + print( + "Check your internet connection or the existence of the remote repository" + ) def git_pull(repo_path): @@ -102,13 +108,16 @@ def git_pull(repo_path): origin.pull(origin.refs[0].remote_head) print("Repository correctly synchronized from remote!") except BaseException: - print('Some error occurred while pulling the code') - print('Check your internet connection or the existence of the remote repository') + print("Some error occurred while pulling the code") + print( + "Check your internet connection or the existence of the remote repository" + ) def git_clone(repo_path): remote_repo_url = input( - "Insert the URL of the remote repo (e.g., https://github/user/mykb): ") + "Insert the URL of the remote repo (e.g., https://github/user/mykb): " + ) if remote_repo_url: git.Repo.clone_from(remote_repo_url, repo_path) print("Knowledge base correctly pulled from remote!") @@ -119,16 +128,18 @@ def git_clone(repo_path): def git_init(repo_path): print("Create a remote empty repository on github/gitlab or other git provider...") remote_repo_url = input( - "Insert the URL of the created empty remote repo (e.g., https://github/user/mykb): ") + """Insert the URL of the created empty remote repo + (e.g., https://github/user/mykb): """ + ) if remote_repo_url: local_repo = git.Repo.init(repo_path) remote = local_repo.create_remote("origin", url=remote_repo_url) - local_repo.git.add('--all') + local_repo.git.add("--all") timestamp = time.strftime("%d/%m/%Y-%H:%M:%S") local_repo.index.commit("kb synchronization {ts}".format(ts=timestamp)) print("Initialization with remote may take time...") print("Please provide remote credentials and wait...") - remote.push(refspec='{}:{}'.format("main", "main")) + remote.push(refspec="{}:{}".format("main", "main")) print("Remote repository correctly initialized!") else: print("Error: Provide a valid remote URL") diff --git a/kb/commands/template.py b/kb/commands/template.py index 31d1ebd..06efc6a 100644 --- a/kb/commands/template.py +++ b/kb/commands/template.py @@ -13,16 +13,17 @@ import shlex import sys -import toml from pathlib import Path from subprocess import call from typing import Dict, List + +import kb.config as conf import kb.db as db -import kb.initializer as initializer import kb.filesystem as fs -import kb.config as conf -from kb.entities.artifact import Artifact +import kb.initializer as initializer import kb.printer.template as printer +import toml +from kb.entities.artifact import Artifact def get_templates(templates_path: str) -> List[str]: @@ -70,7 +71,7 @@ def apply_on_set(args: Dict[str, str], config: Dict[str, str]): tags_list = None if args["tags"] and args["tags"] != "": - tags_list = args["tags"].split(';') + tags_list = args["tags"].split(";") conn = db.create_connection(config["PATH_KB_DB"]) is_query_strict = not args["extended_match"] @@ -81,7 +82,8 @@ def apply_on_set(args: Dict[str, str], config: Dict[str, str]): tags=tags_list, status=args["status"], author=args["author"], - is_strict=is_query_strict) + is_strict=is_query_strict, + ) for artifact in rows: updated_artifact = Artifact( @@ -91,7 +93,8 @@ def apply_on_set(args: Dict[str, str], config: Dict[str, str]): tags=artifact.tags, author=artifact.author, status=artifact.status, - template=args["template"]) + template=args["template"], + ) db.update_artifact_by_id(conn, artifact.id, updated_artifact) @@ -106,8 +109,8 @@ def new(args: Dict[str, str], config: Dict[str, str]): the following keys: PATH_KB_TEMPLATES - the path to where the templates of KB are stored - PATH_KB_DEFAULT_TEMPLATE - the path to where the default template of KB - is stored + PATH_KB_DEFAULT_TEMPLATE - the path to where the default template + of KB is stored EDITOR - the editor program to call """ template_path = str(Path(config["PATH_KB_TEMPLATES"]) / args["template"]) @@ -115,18 +118,18 @@ def new(args: Dict[str, str], config: Dict[str, str]): if fs.is_file(template_path): print( "ERROR: The template you inserted corresponds to an existing one. " - "Please specify another name for the new template") + "Please specify another name for the new template" + ) sys.exit(1) fs.create_directory(Path(template_path).parent) # fs.copy_file(config["PATH_KB_DEFAULT_TEMPLATE"], template_path) - with open(template_path, 'w') as tmplt: + with open(template_path, "w") as tmplt: tmplt.write("# This is an example configuration template\n\n\n") tmplt.write(toml.dumps(conf.DEFAULT_TEMPLATE)) - shell_cmd = shlex.split( - config["EDITOR"]) + [template_path] + shell_cmd = shlex.split(config["EDITOR"]) + [template_path] call(shell_cmd) @@ -183,22 +186,23 @@ def edit(args: Dict[str, str], config: Dict[str, str]): template_path = str(Path(config["PATH_KB_TEMPLATES"]) / args["template"]) if not fs.is_file(template_path): - print("ERROR: The template you want to edit does not exist. " - "Please specify a valid template to edit or create a new one") + print( + "ERROR: The template you want to edit does not exist. " + "Please specify a valid template to edit or create a new one" + ) sys.exit(1) - shell_cmd = shlex.split( - config["EDITOR"]) + [template_path] + shell_cmd = shlex.split(config["EDITOR"]) + [template_path] call(shell_cmd) COMMANDS = { - 'add': add, - 'delete': delete, - 'edit': edit, - 'list': search, - 'new': new, - 'apply': apply_on_set, + "add": add, + "delete": delete, + "edit": edit, + "list": search, + "new": new, + "apply": apply_on_set, } diff --git a/kb/commands/update.py b/kb/commands/update.py index 686adc5..4683e2e 100644 --- a/kb/commands/update.py +++ b/kb/commands/update.py @@ -12,13 +12,14 @@ """ import shlex +from pathlib import Path from subprocess import call from typing import Dict -from pathlib import Path + import kb.db as db -import kb.initializer as initializer -import kb.history as history import kb.filesystem as fs +import kb.history as history +import kb.initializer as initializer from kb.entities.artifact import Artifact @@ -57,11 +58,13 @@ def update(args: Dict[str, str], config: Dict[str, str]): # if an ID is specified, load artifact with that ID if args["id"]: - old_artifact = history.get_artifact(conn, - config["PATH_KB_HIST"], args["id"]) + old_artifact = history.get_artifact( + conn, config["PATH_KB_HIST"], args["id"]) if not old_artifact: - print("The artifact you are trying to update does not exist! " - "Please insert a valid ID...") + print( + "The artifact you are trying to update does not exist! " + "Please insert a valid ID..." + ) return None updated_artifact = Artifact( @@ -71,7 +74,8 @@ def update(args: Dict[str, str], config: Dict[str, str]): tags=args["tags"], author=args["author"], status=args["status"], - template=args["template"]) + template=args["template"], + ) db.update_artifact_by_id(conn, old_artifact.id, updated_artifact) # If either title or category has been changed, we must move the file @@ -84,35 +88,44 @@ def update(args: Dict[str, str], config: Dict[str, str]): args["category"] or old_artifact.category) fs.create_directory(new_category_path) - fs.move_file(Path(old_category_path, old_artifact.title), Path( - new_category_path, args["title"] or old_artifact.title)) + fs.move_file( + Path(old_category_path, old_artifact.title), + Path(new_category_path, args["title"] or old_artifact.title), + ) # else if a title is specified elif args["title"]: - artifact = db.get_uniq_artifact_by_filter(conn, title=args["title"], - category=args["category"], - author=args["author"], - status=args["status"], - is_strict=True) + artifact = db.get_uniq_artifact_by_filter( + conn, + title=args["title"], + category=args["category"], + author=args["author"], + status=args["status"], + is_strict=True, + ) if artifact: category_path = Path(config["PATH_KB_DATA"], artifact.category) else: print( - "There is none or more than one artifact with that title, please specify a category") + "There is none or more than one artifact with that title, " + " please specify a category" + ) if args["edit_content"] or args["body"]: if args["title"]: artifact_path = str(Path(category_path, artifact.title)) shell_cmd = shlex.split(config["EDITOR"]) + [artifact_path] elif args["id"]: - artifact_path = str(Path(config["PATH_KB_DATA"]) - / old_artifact.category - / old_artifact.title) + artifact_path = str( + Path(config["PATH_KB_DATA"]) + / old_artifact.category + / old_artifact.title + ) shell_cmd = shlex.split(config["EDITOR"]) + [artifact_path] if args["body"]: args["body"] = args["body"].replace("\\n", "\n") - with open(artifact_path, 'w') as art_file: + with open(artifact_path, "w") as art_file: art_file.write(args["body"]) else: call(shell_cmd) diff --git a/kb/commands/view.py b/kb/commands/view.py index 1ab5ef4..18ced6b 100644 --- a/kb/commands/view.py +++ b/kb/commands/view.py @@ -11,14 +11,16 @@ :License: GPLv3 (see /LICENSE). """ -import os -import platform -import sys +# import os +# import platform +# import tempfile + import shlex -import tempfile -from subprocess import call +import sys from pathlib import Path +from subprocess import call from typing import Dict + import kb.db as db import kb.filesystem as fs import kb.history as history @@ -58,11 +60,8 @@ def view(args: Dict[str, str], config: Dict[str, str]): view_by_id(args["id"], config, args["editor"], color_mode) elif args["title"]: view_by_name( - args["title"], - args["category"], - config, - args["editor"], - color_mode) + args["title"], args["category"], config, args["editor"], color_mode + ) elif args["nameid"]: if args["nameid"].isdigit(): view_by_id(args["nameid"], config, args["editor"], color_mode) @@ -75,7 +74,7 @@ def view(args: Dict[str, str], config: Dict[str, str]): color_mode) -def view_by_id(id: int, +def view_by_id(id_artifact: int, config: Dict[str, str], open_editor: bool, @@ -84,7 +83,7 @@ def view_by_id(id: int, View the content of an artifact by id. Arguments: - id: - the ID (the one you see with kb list) + id_artifact: - the ID (the one you see with kb list) associated to the artifact we want to edit config: - a configuration dictionary containing at least the following keys: @@ -98,10 +97,9 @@ def view_by_id(id: int, enabled when printed on stdout """ conn = db.create_connection(config["PATH_KB_DB"]) - artifact_id = history.get_artifact_id( - config["PATH_KB_HIST"], id) + id_artifact = history.get_artifact_id(config["PATH_KB_HIST"], id_artifact) - artifact = db.get_artifact_by_id(conn, artifact_id) + artifact = db.get_artifact_by_id(conn, id_artifact) if not artifact: sys.exit(1) @@ -127,12 +125,13 @@ def view_by_id(id: int, opener.open_non_text_file(artifact_path) -def view_by_name(title: str, - category: str, - config: Dict[str, - str], - open_editor: bool, - color_mode: bool): +def view_by_name( + title: str, + category: str, + config: Dict[str, str], + open_editor: bool, + color_mode: bool, +): """ View the content of an artifact by name, that is title/category @@ -151,9 +150,9 @@ def view_by_name(title: str, enabled when printed on stdout """ conn = db.create_connection(config["PATH_KB_DB"]) - artifacts = db.get_artifacts_by_filter(conn, title=title, - category=category, - is_strict=True) + artifacts = db.get_artifacts_by_filter( + conn, title=title, category=category, is_strict=True + ) if len(artifacts) == 1: artifact = artifacts.pop() category_path = Path(config["PATH_KB_DATA"], artifact.category) @@ -176,14 +175,18 @@ def view_by_name(title: str, opener.open_non_text_file(artifact_path) elif len(artifacts) > 1: print( - "There is more than one artifact with that title, please specify a category") + "There is more than one artifact with that title, " + "please specify a category" + ) else: print( - "There is no artifact with that name, please specify a correct artifact name") + "There is no artifact with that name, " + "please specify a correct artifact name" + ) def get_template(artifact: Artifact, config: Dict[str, str]) -> str: - """" + """ " Get template for a specific artifact. Arguments: @@ -202,5 +205,6 @@ def get_template(artifact: Artifact, config: Dict[str, str]) -> str: markers = get_markers(config["PATH_KB_DEFAULT_TEMPLATE"]) else: markers = get_markers( - str(Path(*[config["PATH_KB_TEMPLATES"]] + template.split('/')))) + str(Path(*[config["PATH_KB_TEMPLATES"]] + template.split("/"))) + ) return markers diff --git a/kb/config.py b/kb/config.py index 1d8cb03..cfaad64 100755 --- a/kb/config.py +++ b/kb/config.py @@ -14,11 +14,15 @@ __all__ = () import os -from sys import platform + +# from sys import platform from pathlib import Path + import toml -BASE_PATH = Path(os.environ.get("XDG_DATA_HOME",Path(Path.home(),".local","share")),"kb") +BASE_PATH = Path( + os.environ.get("XDG_DATA_HOME", Path(Path.home(), ".local", "share")), "kb" +) DEFAULT_CONFIG = { @@ -31,9 +35,12 @@ "PATH_KB_CONFIG": str(Path(BASE_PATH, "kb.conf.py")), "PATH_KB_TEMPLATES": str(Path(BASE_PATH, "templates")), "PATH_KB_DEFAULT_TEMPLATE": str(Path(BASE_PATH, "templates", "default")), + "PATH_KB_MARKDOWN_TEMPLATE": str(Path(BASE_PATH, "templates", "markdown")), "DB_SCHEMA_VERSION": 1, "EDITOR": os.environ.get("EDITOR", "vim"), - "INITIAL_CATEGORIES": ["default", ] + "INITIAL_CATEGORIES": [ + "default", + ], } @@ -42,6 +49,17 @@ "WARNINGS": ("^!.*", "yellow"), } +MARKDOWN_TEMPLATE = { + "MARKDOWN": "rich", + "STYLE": "paraiso-dark", + "JUSTIFY": "full", + "HYPERLINKS": False, + "PAGER": True, + "PAGER_COLOR": True, + "PADDING_VERTICAL": 0, + "PADDING_HORIZONTAL": 4, +} + def get_markers(markers_path: str): """ @@ -58,3 +76,4 @@ def get_markers(markers_path: str): print("Error: The provided file is not in the toml format") except FileNotFoundError: print("Error: The provided file does not exist or cannot be accessed") + return None diff --git a/kb/db.py b/kb/db.py index 448c49c..8cd4322 100755 --- a/kb/db.py +++ b/kb/db.py @@ -14,8 +14,10 @@ __all__ = () import sqlite3 -from pathlib import Path + +# from pathlib import Path from typing import List, Optional + import attr from kb.entities.artifact import Artifact @@ -108,9 +110,12 @@ def is_artifact_existing(conn, title: str, category: str) -> bool: otherwise False """ cur = conn.cursor() - cur.execute("""SELECT title,category + cur.execute( + """SELECT title,category FROM artifacts - WHERE title=? AND category=?""", [title, category]) + WHERE title=? AND category=?""", + [title, category], + ) result = cur.fetchone() return result is not None @@ -131,14 +136,15 @@ def insert_artifact(conn, artifact: Artifact) -> None: # Convert tags to a string with ';' separating tags tags_list = [] if artifact.tags: - tags_list = list(set(artifact.tags.split(';'))) + tags_list = list(set(artifact.tags.split(";"))) path = "" if artifact.path: path = artifact.path else: path = "{category}/{title}".format( - category=artifact.category, title=artifact.title) + category=artifact.category, title=artifact.title + ) cur = conn.cursor() if is_artifact_existing(conn, artifact.title, artifact.category): @@ -146,9 +152,9 @@ def insert_artifact(conn, artifact: Artifact) -> None: print("Run `kb update -h` to understand how to update an artifact") return - sql = '''INSERT INTO artifacts + sql = """INSERT INTO artifacts (title,category,path,tags,author,status,template) - VALUES(?,?,?,?,?,?,?)''' + VALUES(?,?,?,?,?,?,?)""" args = ( artifact.title, artifact.category, @@ -156,15 +162,16 @@ def insert_artifact(conn, artifact: Artifact) -> None: artifact.tags, artifact.author, artifact.status, - artifact.template) + artifact.template, + ) cur.execute(sql, args) last_artifact_id = cur.lastrowid for tag in tags_list: - sql = '''INSERT INTO tags + sql = """INSERT INTO tags (artifact_id,tag) - VALUES(?,?)''' + VALUES(?,?)""" args = (last_artifact_id, tag) cur.execute(sql, args) @@ -188,14 +195,15 @@ def insert_artifact_with_id(conn, artifact: Artifact, id: int) -> None: # Convert tags to a string with ';' separating tags tags_list = [] if artifact.tags: - tags_list = list(set(artifact.tags.split(';'))) + tags_list = list(set(artifact.tags.split(";"))) path = "" if artifact.path: path = artifact.path else: path = "{category}/{title}".format( - category=artifact.category, title=artifact.title) + category=artifact.category, title=artifact.title + ) cur = conn.cursor() if is_artifact_existing(conn, artifact.title, artifact.category): @@ -203,9 +211,9 @@ def insert_artifact_with_id(conn, artifact: Artifact, id: int) -> None: print("Run `kb update -h` to understand how to update an artifact") return - sql = '''INSERT INTO artifacts + sql = """INSERT INTO artifacts (id, title,category,path,tags,author,status,template) - VALUES(?,?,?,?,?,?,?,?)''' + VALUES(?,?,?,?,?,?,?,?)""" args = ( artifact.id, artifact.title, @@ -214,15 +222,16 @@ def insert_artifact_with_id(conn, artifact: Artifact, id: int) -> None: artifact.tags, artifact.author, artifact.status, - artifact.template) + artifact.template, + ) cur.execute(sql, args) last_artifact_id = cur.lastrowid for tag in tags_list: - sql = '''INSERT INTO tags + sql = """INSERT INTO tags (artifact_id,tag) - VALUES(?,?)''' + VALUES(?,?)""" args = (last_artifact_id, tag) cur.execute(sql, args) @@ -280,13 +289,13 @@ def get_artifact_by_id(conn, artifact_id: int) -> Artifact: def get_artifacts_by_filter( - conn, - title: Optional[str] = None, - category: Optional[str] = None, - tags: Optional[List[str]] = None, - author: Optional[str] = None, - status: Optional[str] = None, - is_strict: bool = False + conn, + title: Optional[str] = None, + category: Optional[str] = None, + tags: Optional[List[str]] = None, + author: Optional[str] = None, + status: Optional[str] = None, + is_strict: bool = False, ) -> List[Artifact]: """ Returns artifacts by applying a generic filter @@ -310,11 +319,13 @@ def get_artifacts_by_filter( if title is not None: artifacts_by_title = get_artifacts_by_title( - conn, query_string=title, is_strict=is_strict) + conn, query_string=title, is_strict=is_strict + ) artifact_id_list.append({art.id for art in artifacts_by_title}) if category is not None: artifacts_by_cat = get_artifacts_by_category( - conn, query_string=category, is_strict=is_strict) + conn, query_string=category, is_strict=is_strict + ) artifact_id_list.append({art.id for art in artifacts_by_cat}) if tags: artifacts_by_tags = get_artifacts_by_tags( @@ -322,11 +333,13 @@ def get_artifacts_by_filter( artifact_id_list.append({art.id for art in artifacts_by_tags}) if author: artifacts_by_author = get_artifacts_by_author( - conn, query_string=author, is_strict=is_strict) + conn, query_string=author, is_strict=is_strict + ) artifact_id_list.append({art.id for art in artifacts_by_author}) if status: artifacts_by_status = get_artifacts_by_status( - conn, query_string=status, is_strict=is_strict) + conn, query_string=status, is_strict=is_strict + ) artifact_id_list.append({art.id for art in artifacts_by_status}) if len(artifact_id_list) == 0: @@ -345,9 +358,7 @@ def get_artifacts_by_filter( def get_artifacts_by_title( - conn, - query_string: str = "", - is_strict: bool = False + conn, query_string: str = "", is_strict: bool = False ) -> List[Artifact]: """ Returns artifacts matching the string to search, @@ -378,13 +389,13 @@ def get_artifacts_by_title( def get_uniq_artifact_by_filter( - conn, - title: Optional[str] = None, - category: Optional[str] = None, - tags: Optional[List[str]] = None, - author: Optional[str] = None, - status: Optional[str] = None, - is_strict: bool = False + conn, + title: Optional[str] = None, + category: Optional[str] = None, + tags: Optional[List[str]] = None, + author: Optional[str] = None, + status: Optional[str] = None, + is_strict: bool = False, ) -> Artifact: """ Get the artifact with the specified query string @@ -406,20 +417,21 @@ def get_uniq_artifact_by_filter( - None if there is more than one artifact matching the query string or no artifact matched at all """ - artifacts = get_artifacts_by_filter(conn, title=title, - category=category, - tags=tags, - author=author, - status=status, - is_strict=is_strict) + artifacts = get_artifacts_by_filter( + conn, + title=title, + category=category, + tags=tags, + author=author, + status=status, + is_strict=is_strict, + ) if len(artifacts) == 1: return artifacts.pop() def get_artifacts_by_status( - conn, - query_string: str = "", - is_strict: bool = False + conn, query_string: str = "", is_strict: bool = False ) -> List[Artifact]: """ Returns artifacts matching the status string provided, @@ -451,9 +463,7 @@ def get_artifacts_by_status( def get_artifacts_by_author( - conn, - query_string: str = "", - is_strict: bool = False + conn, query_string: str = "", is_strict: bool = False ) -> List[Artifact]: """ Returns artifacts matching the string to search, @@ -484,9 +494,7 @@ def get_artifacts_by_author( def get_artifacts_by_category( - conn, - query_string: str = "", - is_strict: bool = False + conn, query_string: str = "", is_strict: bool = False ) -> List[Artifact]: """ Returns artifacts of the category matching the string to search, @@ -518,9 +526,7 @@ def get_artifacts_by_category( def get_artifacts_by_tags( - conn, - tags: List[str] = [], - is_strict: bool = False + conn, tags: List[str] = [], is_strict: bool = False ) -> List[Artifact]: """ Returns artifacts matching the provided list of tags, @@ -558,11 +564,7 @@ def get_artifacts_by_tags( return artifacts -def update_artifact_by_id( - conn, - artifact_id: int, - artifact: Artifact -) -> None: +def update_artifact_by_id(conn, artifact_id: int, artifact: Artifact) -> None: """ Update an artifact in the database @@ -580,9 +582,16 @@ def update_artifact_by_id( if not current_artifact: return None - update_record = (artifact_id, artifact.title, artifact.category, - artifact.path, artifact.tags, artifact.status, - artifact.author, artifact.template) + update_record = ( + artifact_id, + artifact.title, + artifact.category, + artifact.path, + artifact.tags, + artifact.status, + artifact.author, + artifact.template, + ) new_record = list() for i, elem in enumerate(attr.astuple(current_artifact)): diff --git a/kb/entities/artifact.py b/kb/entities/artifact.py index 7a77c31..78fdd7c 100644 --- a/kb/entities/artifact.py +++ b/kb/entities/artifact.py @@ -11,8 +11,10 @@ :License: GPLv3 (see /LICENSE). """ +# from typing import List, Set, Optional +from typing import Optional + import attr -from typing import List, Set, Optional @attr.s(auto_attribs=True, frozen=True, slots=True) diff --git a/kb/filesystem.py b/kb/filesystem.py index a444d73..a79165e 100755 --- a/kb/filesystem.py +++ b/kb/filesystem.py @@ -191,8 +191,10 @@ def get_temp_filepath() -> str: """ tmpfilename = None while tmpfilename is None: - random_tmp_path = str(Path(tempfile.gettempdir(), - os.urandom(24).hex())) + random_tmp_path = str( + Path( + tempfile.gettempdir(), + os.urandom(24).hex())) if not os.path.isfile(random_tmp_path): tmpfilename = random_tmp_path return tmpfilename @@ -210,14 +212,36 @@ def is_text_file(filename: str) -> bool: Returns: A boolean, True if the file is of type text. """ - txt_extensions = ("", ".conf", ".ini", ".txt", - ".md", ".rst", ".ascii", ".org", ".tex") + txt_extensions = ( + "", + ".conf", + ".ini", + ".txt", + ".md", + ".rst", + ".ascii", + ".org", + ".tex", + ) file_ext = os.path.splitext(filename)[1] return file_ext in txt_extensions +def is_md_file(filename: str) -> bool: + """ + Determines if a file is a markdown based on ".md" extension. + + Arguments: + filename - the file name/path to check + + Returns: + A boolean, True if the extension of the file is ".md". + """ + return os.path.splitext(filename)[1] == ".md" + + def get_filename_parts_wo_prefix( filename: str, prefix_to_remove: str) -> List[str]: @@ -245,9 +269,7 @@ def get_filename_parts_wo_prefix( def grep_in_files( - filelist: str, - regex: str, - case_insensitive: bool = False + filelist: str, regex: str, case_insensitive: bool = False ) -> List[str]: """ Grep recursively through a file list by trying to match @@ -290,8 +312,7 @@ def grep_in_files( def grep_in_files_uniq( filelist: str, regex: str, - case_insensitive=False -) -> List[str]: + case_insensitive=False) -> List[str]: """ Grep recursively through a list of files by trying to match a regex with the content of all the found files. diff --git a/kb/history.py b/kb/history.py index d01690b..311ece9 100644 --- a/kb/history.py +++ b/kb/history.py @@ -12,8 +12,9 @@ """ from typing import List + +from kb import db from kb.entities.artifact import Artifact -import kb.db as db def get_artifact_id(hist_file_path: str, list_id: int) -> int: @@ -34,7 +35,7 @@ def get_artifact_id(hist_file_path: str, list_id: int) -> int: The database ID corresponding to the artifact or None in case of non-valid list ID """ - with open(hist_file_path, 'r') as hfile: + with open(hist_file_path, "r") as hfile: for line in hfile: items = line.split(",") if items[0] == list_id: @@ -55,7 +56,7 @@ def write(hist_file_path: str, search_result: List) -> None: a DB query """ with open(hist_file_path, "w") as hfile: - hfile.write('view_id,db_id\n') + hfile.write("view_id,db_id\n") for view_id, result in enumerate(search_result): hfile.write("{},{}\n".format(view_id, result.id)) @@ -77,7 +78,6 @@ def get_artifact(conn, hist_file_path: str, list_id: int) -> Artifact: The artifact corresponding to list_id shown by kb list None in case of non-valid list ID """ - artifact_id = get_artifact_id( - hist_file_path, list_id) + artifact_id = get_artifact_id(hist_file_path, list_id) return db.get_artifact_by_id(conn, artifact_id) diff --git a/kb/initializer.py b/kb/initializer.py index cbc0d00..50d630f 100644 --- a/kb/initializer.py +++ b/kb/initializer.py @@ -13,10 +13,12 @@ import os from pathlib import Path + import toml + +import kb.config as conf import kb.db as db import kb.filesystem as fs -import kb.config as conf def init(config): @@ -64,6 +66,7 @@ def create_kb_files(config): templates_path = config["PATH_KB_TEMPLATES"] schema_version = config["DB_SCHEMA_VERSION"] default_template_path = str(Path(templates_path) / "default") + markdown_template_path = str(Path(templates_path) / "markdown") # Create main kb fs.create_directory(kb_path) @@ -91,9 +94,13 @@ def create_kb_files(config): fs.create_directory(category_path) # Create markers file - with open(default_template_path, 'w') as cfg: + with open(default_template_path, "w") as cfg: cfg.write(toml.dumps(conf.DEFAULT_TEMPLATE)) + # Create markers file for Markdown + with open(markdown_template_path, "w") as md_cfg: + md_cfg.write(toml.dumps(conf.MARKDOWN_TEMPLATE)) + def is_initialized(config) -> bool: """ diff --git a/kb/main.py b/kb/main.py index 07c0767..0e164f5 100755 --- a/kb/main.py +++ b/kb/main.py @@ -14,37 +14,35 @@ __all__ = () import sys -from kb.cl_parser import parse_args +from kb.cl_parser import parse_args from kb.commands.add import add -from kb.commands.search import search -from kb.commands.edit import edit -from kb.commands.update import update from kb.commands.delete import delete -from kb.commands.template import template -from kb.commands.view import view -from kb.commands.grep import grep +from kb.commands.edit import edit from kb.commands.erase import erase -from kb.commands.ingest import ingest from kb.commands.export import export +from kb.commands.grep import grep +from kb.commands.ingest import ingest +from kb.commands.search import search from kb.commands.sync import sync - +from kb.commands.template import template +from kb.commands.update import update +from kb.commands.view import view from kb.config import DEFAULT_CONFIG - COMMANDS = { - 'add': add, - 'delete': delete, - 'edit': edit, - 'update': update, - 'list': search, - 'view': view, - 'grep': grep, - 'erase': erase, - 'import': ingest, - 'export': export, - 'template': template, - 'sync': sync, + "add": add, + "delete": delete, + "edit": edit, + "update": update, + "list": search, + "view": view, + "grep": grep, + "erase": erase, + "import": ingest, + "export": export, + "template": template, + "sync": sync, } diff --git a/kb/markdown.py b/kb/markdown.py new file mode 100644 index 0000000..793d873 --- /dev/null +++ b/kb/markdown.py @@ -0,0 +1,79 @@ +# -*- encoding: utf-8 -*- +# kb v0.1.6 +# A knowledge base organizer +# Copyright © 2020, gnc. +# See /LICENSE for licensing information. + +""" +kb markdown viewer module + +:Copyright: © 2021, gnc, pliski. +:License: GPLv3 (see /LICENSE). +""" +from typing import Dict + +from rich.console import Console +from rich.markdown import Markdown +from rich.padding import Padding +from rich.panel import Panel + + +def md_print(string: str, markers: Dict[str, str]): + """ + Print an artifact parsing it as a markdown document. + + Arguments: + string - the message to be printed + markers - the configuration options for the markdown lexer + + Returns: + nothing + """ + + # defaults + style = "solarized-dark" + justify = "full" + hyperlinks = False + usepager = False + pager_color = False + padding_vertical = 0 + padding_horizontal = 0 + + # template override + if "STYLE" in markers: + style = markers["STYLE"] + + if "JUSTIFY" in markers: + justify = markers["JUSTIFY"] + + if "HYPERLINKS" in markers: + hyperlinks = markers["HYPERLINKS"] + + if "PAGER" in markers: + usepager = markers["PAGER"] + + if "PADDING_VERTICAL" in markers: + padding_vertical = markers["PADDING_VERTICAL"] + + if "PADDING_HORIZONTAL" in markers: + padding_horizontal = markers["PADDING_HORIZONTAL"] + + # This assume that you have a color capable pager as default. + # Ex. `export PAGER="less -r"` + if "PAGER_COLOR" in markers: + pager_color = markers["PAGER_COLOR"] + + # print + console = Console() + text = Markdown(string, style, justify=justify, hyperlinks=hyperlinks) + + pan = Panel(text) + out = Padding(pan, (padding_vertical, padding_horizontal)) + + if usepager: + with console.pager(styles=pager_color): + console.print(out) + else: + console.print(out) + + # console.print(locals()) diff --git a/kb/opener.py b/kb/opener.py index ffd384a..49ebe59 100644 --- a/kb/opener.py +++ b/kb/opener.py @@ -12,17 +12,17 @@ """ import os -import subprocess import platform +import subprocess def open_non_text_file(filename): """ Open a non-text file """ - if platform.system() == 'Darwin': - subprocess.Popen(['open', filename]) - elif platform.system() == 'Windows': + if platform.system() == "Darwin": + subprocess.Popen(["open", filename]) + elif platform.system() == "Windows": os.startfile(filename) else: - subprocess.Popen(['xdg-open', filename]) + subprocess.Popen(["xdg-open", filename]) diff --git a/kb/styler.py b/kb/styler.py index 9057c75..8ee3237 100644 --- a/kb/styler.py +++ b/kb/styler.py @@ -65,4 +65,4 @@ def reset() -> str: Returns: A string representing the code to reset the style and colors to default """ - return colored.attr('reset') + return colored.attr("reset") diff --git a/kb/viewer.py b/kb/viewer.py index 4b71ac8..5c355aa 100755 --- a/kb/viewer.py +++ b/kb/viewer.py @@ -13,7 +13,9 @@ import re from typing import Dict -from kb.styler import set_fg, reset + +from kb.markdown import md_print +from kb.styler import reset, set_fg def colorize_string(string, color): @@ -55,16 +57,17 @@ def colorize_row(row, markers=None): """ colored_row = row for mark in markers: - regex = re.compile(rf'{(markers[mark][0])}') + regex = re.compile(rf"{(markers[mark][0])}") color = markers[mark][1] match = regex.search(row) if match: colored_row = re.sub( - regex, colorize_string( - match.group(0).replace( - "\\", "\\\\"), color), rf'{row}') + regex, + colorize_string(match.group(0).replace("\\", "\\\\"), color), + rf"{row}", + ) row = colored_row return colored_row @@ -86,6 +89,7 @@ def colorize_output(data, markers): """ if markers is None: return data + colorized_output = list() for row in data: colorized_output.append(colorize_row(row, markers)) @@ -106,6 +110,11 @@ def view(filepath: str, markers: Dict[str, str], color: bool = True) -> None: with open(filepath) as fname: content = fname.read() + # Markdown + if "MARKDOWN" in markers: + md_print(content, markers) + return + # Print on screen with proper markers lines = content.splitlines() if color: diff --git a/requirements.txt b/requirements.txt index a0291f7..e936600 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ attrs colored toml gitpython +rich \ No newline at end of file diff --git a/setup.py b/setup.py index a0d8861..0fa8188 100755 --- a/setup.py +++ b/setup.py @@ -1,38 +1,39 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- import io -from setuptools import setup, find_packages +from setuptools import find_packages, setup -setup(name='kb-manager', - version='0.1.6', - description='A minimalist knowledge base manager', - keywords='kb', - author='gnc', - author_email='nebbionegiuseppe@gmail.com', - url='https://github.com/gnebbia/kb', - download_url='https://github.com/gnebbia/kb/archive/v0.1.6.tar.gz', - license='GPLv3', - long_description=io.open( - './docs/README.md', 'r', encoding='utf-8').read(), - long_description_content_type="text/markdown", - platforms='any', - zip_safe=False, - # http://pypi.python.org/pypi?%3Aaction=list_classifiers - classifiers=[ 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Operating System :: OS Independent', - ], - packages=find_packages(exclude=('tests',)), - include_package_data=True, - install_requires=["colored","toml","attr","attrs","gitpython"], - python_requires='>=3.6', - entry_points={ - 'console_scripts':[ - 'kb = kb.main:main', - ] - }, - ) +setup( + name="kb-manager", + version="0.1.6", + description="A minimalist knowledge base manager", + keywords="kb", + author="gnc", + author_email="nebbionegiuseppe@gmail.com", + url="https://github.com/gnebbia/kb", + download_url="https://github.com/gnebbia/kb/archive/v0.1.6.tar.gz", + license="GPLv3", + long_description=io.open("./docs/README.md", "r", encoding="utf-8").read(), + long_description_content_type="text/markdown", + platforms="any", + zip_safe=False, + # http://pypi.python.org/pypi?%3Aaction=list_classifiers + classifiers=[ + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Operating System :: OS Independent", + ], + packages=find_packages(exclude=("tests",)), + include_package_data=True, + install_requires=["colored", "toml", "attr", "attrs", "gitpython", "rich"], + python_requires=">=3.6", + entry_points={ + "console_scripts": [ + "kb = kb.main:main", + ] + }, +)