diff --git a/.gitignore b/.gitignore index 36646dd..62c28e2 100644 --- a/.gitignore +++ b/.gitignore @@ -159,6 +159,5 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -# Custom Additions -.vscode/ +# Ignore Generated Files *.html \ No newline at end of file diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..093e3e5 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,642 @@ +[MAIN] + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint +# in a server-like mode. +clear-cache-post-run=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, +# it can't be used as an escape character. +ignore-paths=/src/version.py + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.10 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata, + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=yes + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=(App)|(__init__) + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type alias names. If left empty, type +# alias names will be checked with the set naming style. +#typealias-rgx= + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + asyncSetUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=builtins.BaseException,builtins.Exception + + +[FORMAT] + +# Formatting is handled by black + + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow explicit reexports by alias from a package __init__. +allow-reexport-from-package=no + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + use-implicit-booleaness-not-comparison-to-string, + use-implicit-booleaness-not-comparison-to-zero, + line-too-long, + missing-module-docstring, + consider-using-from-import, + unspecified-encoding, + import-error + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable= + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are: text, parseable, colorized, +# json2 (improved json format), json (old json format) and msvs (visual +# studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. No available dictionaries : You need to install +# both the python package and the system dependency for enchant to work. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..4993e2c --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "ms-python.pylint", + "ms-python.black-formatter" + ], +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..5f87f68 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Default Launch", + "type": "python", + "request": "launch", + "program": "C:/Seneca/Semester5/OSD600/til-page-builder/src/til_builder_main.py", + "console": "integratedTerminal", + "justMyCode": true, + "args": [ + "./examples" + ], + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..40e593b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter", + "editor.formatOnSave": true, + }, + "editor.codeActionsOnSave": { + "source.organizeImports.ruff": true + }, + "pylint.ignorePatterns": [ + "version.py" + ], + "pylint.args": [ + "--rcfile=./.pylintrc" + ], +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b7e91e2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,272 @@ + +# Contributing to til-page-builder + +First off, thanks for taking the time to contribute! ❤️ + +All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 + +> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: +> - Star the project +> - Tweet about it +> - Refer this project in your project's readme +> - Mention the project at local meetups and tell your friends/colleagues + + +## Table of Contents + +- [I Have a Question](#i-have-a-question) +- [I Want To Contribute](#i-want-to-contribute) +- [Reporting Bugs](#reporting-bugs) +- [Suggesting Enhancements](#suggesting-enhancements) +- [Your First Code Contribution](#your-first-code-contribution) + +- [Styleguides](#styleguides) + + + + + +## I Have a Question + +> If you want to ask a question, we assume that you have read the available [Documentation](https://github.com/Amnish04/til-page-builder/blob/master/README.md). + +Before you ask a question, it is best to search for existing [Issues](https://github.com/Amnish04/til-page-builder/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first. + +If you then still feel the need to ask a question and need clarification, we recommend the following: + +- Open an [Issue](https://github.com/Amnish04/til-page-builder/issues/new). +- Provide as much context as you can about what you're running into. +- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant. + +We will then take care of the issue as soon as possible. + + + +## I Want To Contribute + +> ### Legal Notice +> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license. + +### Reporting Bugs + + +#### Before Submitting a Bug Report + +A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible. + +- Make sure that you are using the latest version. +- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://github.com/Amnish04/til-page-builder/blob/master/README.md). If you are looking for support, you might want to check [this section](#i-have-a-question)). +- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue. +- Collect information about the bug: +- Stack trace (Traceback) +- OS, Platform and Version (Windows, Linux, macOS, x86, ARM) +- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant. +- Possibly your input and the output +- Can you reliably reproduce the issue? And can you also reproduce it with older versions? + + +#### How Do I Submit a Good Bug Report? + +> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to . + + +We use GitHub issues to track bugs and errors. If you run into an issue with the project: + +- Open an [Issue](https://github.com/Amnish04/til-page-builder/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.) +- Explain the behavior you would expect and the actual behavior. +- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case. +- Provide the information you collected in the previous section. + +Once it's filed: + +- The project team will label the issue accordingly. +- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. +- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution). + + + + +### Suggesting Enhancements + +This section guides you through submitting an enhancement suggestion for til-page-builder, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions. + + +#### Before Submitting an Enhancement + +- Make sure that you are using the latest version. +- Read the [documentation](https://github.com/Amnish04/til-page-builder/blob/master/README.md) carefully and find out if the functionality is already covered, maybe by an individual configuration. +- Perform a [search](https://github.com/Amnish04/til-page-builder/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. +- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library. + + +#### How Do I Submit a Good Enhancement Suggestion? + +Enhancement suggestions are tracked as [GitHub issues](https://github.com/Amnish04/til-page-builder/issues). + +- Use a **clear and descriptive title** for the issue to identify the suggestion. +- Provide a **step-by-step description of the suggested enhancement** in as many details as possible. +- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you. +- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. +- **Explain why this enhancement would be useful** to most til-page-builder users. You may also want to point out the other projects that solved it better and which could serve as inspiration. + + + +### Your First Code Contribution + +You'll need to have the following requirements installed on your computer to be able to run this project. + +#### Project Requirements 💻 + +**1. Python** + +This tool requires Python 3 interpreter to be installed on user's system and added to the `PATH` system variable so that python files can be easily executed from the terminal. + +**2. Packages** + +This project only uses 1 external library that you need to install a package from PyPi called `yattag` +``` +pip install yattag +``` + +Alternatively, all the dependencies used by project that need to be installed are configured in the `requirements.txt` file included in the project. + +The following command installs all the dependencies configured in the requirements.txt file with corresponding versions. +``` +pip install -r requirements.txt +``` + +#### How to Run + +You need to execute the entry point of the application `src/til-builder_main.py` with required positional arguments to run this tool. + +Here's the syntax: +``` +python src/til-builder_main.py [OPTIONS] +``` + + + + + +## Installing Recommended Extenstions + +When you open the project for the first time, you'll see a popup asking you to insall recommended extensions. You should install the suggested extensions as they'll be usefull for formatting and linting your code while you're developing. This is discussed in detail in next section. + +If you miss that popup, you can always go to the extensions tab and type `@recommended` to look up and install those extensions. + +Image + + +## Styleguides + +Make sure to follow the following style standards/procedures when working on contributions. + +### Code Styling + +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) + +This project is using [black](https://github.com/psf/black) for code styling. You should have it already installed if you installed all the dependencies from `requirements.txt` file. + +If not, run the following command to install black on your system. + +``` +pip install black +``` + +Now, navigate to the root directory of the project and run: + +``` +black . +``` + +This should format all the configured files in `pyproject.toml` file. + +Even if you forget to run the formatter, the configured git hooks will automatically run it whenever you commit any changes. + +If you're using VSCode, follow this article to integrate black into your IDE. (This is optional) + +https://dev.to/adamlombard/how-to-use-the-black-python-code-formatter-in-vscode-3lo0 + +#### IDE Integration + +If you're using VSCode, all you need to do is install the official `black-formatter` extension by Microsoft. All the necessary configurations are already set up in the project workspace settings. + +### Linting + +[![linting: pylint](https://img.shields.io/badge/linting-pylint-yellowgreen)](https://github.com/pylint-dev/pylint) + +This project uses [Pylint](https://docs.pylint.org/) to enforce coding standards and prevent code smells. + +* Pylint is a static code analysis tool that checks your Python code for compliance with coding standards, style, and potential issues. +* Before submitting a pull request, please ensure that your code passes Pylint checks. This helps us maintain a high level of code quality and consistency in the project. + +#### Installation + +The tool should be automatically installed if you installed all the dependencies from `requirements.txt` file. + +If not, the following command will install `pylint` on your system. +``` +pip install pylint== +``` + +Make sure to install the version specified in [requirements.txt](./requirements.txt) + +All the **linting** settings can be found in [.pylintrc](./.pylintrc) file. +⚠️ **Do not** make any changes to that file unless approved by the project owner. + +#### Usage + +Run `Pylint` on your Python code or module using the following command: + +``` +pylint your_module.py +``` + +Replace your_module.py with the name of the Python file you want to lint. + +To run the tool on entire project, you need to pass the root directory in the command. +``` +pylint path_to_root_directory +``` + +Pylint will generate a report with suggestions, warnings, and errors. Review the output to identify areas in your code that may need improvement. + +Please adhere to the following guidelines when using Pylint: + +* Ensure that your code does not introduce new Pylint errors or warnings. + +* Address existing Pylint errors and warnings in your code before submitting a pull request. + +* If you encounter Pylint issues that you believe are false positives, feel free to discuss and provide explanations in your pull request or raise the issue with the project maintainers. + +#### IDE Integration + +If you're using VSCode, all you need to do is install the official `pylint` extension by Microsoft. All the necessary configurations are already set up in the project workspace settings. + + + + + + +## Attribution +This guide is based on the **contributing-gen**. [Make your own](https://github.com/bttger/contributing-gen)! \ No newline at end of file diff --git a/README.md b/README.md index e2a8e47..9c43b14 100644 --- a/README.md +++ b/README.md @@ -9,40 +9,21 @@ A command-line tool for authoring "Today I Learned" posts in Markdown, which can -## Requirements 💻 - -**1. Python** - -This tool requires Python 3 interpreter to be installed on user's system and added to the `PATH` system variable so that python files can be easily executed from the terminal. - -**2. Packages** - -This project only uses 1 external library that you need to install a package from PyPi called `yattag` -``` -pip install yattag -``` - -Alternatively, all the dependencies used by project that need to be installed are configured in the `requirements.txt` file included in the project. - -The following command installs all the dependencies configured in the requirements.txt file with corresponding versions. -``` -pip install -r requirements.txt -``` - ## Features Converts **text** or **markdown** files to **html** files that can be rendered as web pages. ### Common features * Parses the first line as **web page title** and top level heading if followed by 2 empty newlines. * Parses all **text blocks**, delimeted by an empty line, as **`p` tags** for the html. -* Parses **headings** just like github markdown. For example, a line starting with `# ` translates to an `h1`, `## ` to and `h2`, and so on... +* Generates and adds a **Table of Contents** at the top of every generated docuement for better reader experience. ### Exclusive to markdown +* Parses **headings** just like github markdown. For example, a line starting with `# ` translates to an `h1`, `## ` to and `h2`, and so on... * Any piece of text wrapped with `**` is converted to `strong` tag (bolded) in html, and wrapped with `*` is converted to `em` tag (italicized). ## Usage -The driver file for this tool is located at `src/til-builder_main.py`. This is the file that needs to be executed for to perform all kinds of actions that the tool supports. +The driver file for this tool is located at `src/til_builder_main.py`. This is the file that needs to be executed for to perform all kinds of actions that the tool supports. There are several ways in which you can use this tool. @@ -50,18 +31,18 @@ There are several ways in which you can use this tool. At its core, the tool takes either a text file or a folder containing files as a positional argument and then generates corresponding html files to `./til` folder by default. -**Note:** The following commands assume that you are currently located in the src folder that contains the `til-builder_main.py` driver file. +**Note:** The following commands assume that you are currently located in the src folder that contains the `til_builder_main.py` driver file. 1. Converting a file ``` -python til-builder_main.py file.txt +python til_builder_main.py file.txt ``` 2. Converting all files within a folder ``` -python til-builder_main.py +python til_builder_main.py ``` ### Flags/Options for custom behavior @@ -70,7 +51,7 @@ Here are some examples demonstrating usage of custom flags supported by the tool 1. `-v` or `--version` : Displays the program name and the version that is in use. ``` -python .\src\til-builder_main.py --version +python .\src\til_builder_main.py --version ``` Output: @@ -82,7 +63,7 @@ TIL Page Builder: 0.1.0 2. `-h` or `--help` : Displays a help message that describes usage of all kinds of commandline arguments supported. ``` -python .\src\til-builder_main.py --help +python .\src\til_builder_main.py --help ``` Output: @@ -105,7 +86,7 @@ options: For example, in order to generate the html files in a directory `./dist/html_files` ``` -python til-builder_main.py --output ./dist/html_files +python til_builder_main.py --output ./dist/html_files ``` 4. `-c` or `--config`: Allows specification of a [TOML](https://toml.io/en/) configuration file containing all required options for the program. @@ -115,28 +96,40 @@ output = "./build" lang = "fr" ``` ``` -python til-builder_main.py myfile.txt --config config.toml +python til_builder_main.py myfile.txt --config config.toml ``` Will set the output directory and language of the HTML files instead of having to use `--output` and `--lang`. Using `-c` or `--config` will override any other config flags provided. +## Examples - -## Example +### General Use Case This example demonstrates the app behavior for a `markdown` file. -For the following _markdown_ file: -![TIL Markdown](https://github.com/Amnish04/til-page-builder/assets/78865303/4096711c-caf5-49f4-aac0-8b8ce2e56647) +[**examples\til-yattag.md**](https://github.com/Amnish04/til-page-builder/blob/master/examples/til-yattag.md) +![TIL Markdown](https://github.com/Amnish04/til-page-builder/assets/78865303/eb3197a2-59e7-4058-85dd-feeeb3af8fa2) Run the command for conversion. ``` -python src/til-builder_main.py examples/til-yattag.md +python src/til_builder_main.py examples/til-yattag.md ``` -**Generated HTML** -![Sample generated TIL](https://github.com/Amnish04/til-page-builder/assets/78865303/9ccbdb5c-f2f9-40dd-959b-b906a9fb2bc4) +**Generated HTML Page** +![Generated Page](https://github.com/Amnish04/til-page-builder/assets/78865303/7dcdec5f-5a9c-4d2d-88f0-12b3285e93da) + +*** + +### Table of Contents Demo + +![TOC Demo](https://github.com/Amnish04/til-page-builder/assets/78865303/c5a141aa-2ac9-4405-b696-b5ca526492a4) + + +## Contributing + +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![linting: pylint](https://img.shields.io/badge/linting-pylint-yellowgreen)](https://github.com/pylint-dev/pylint) +Please read the [CONTRIBUTING.md](https://github.com/Amnish04/til-page-builder/blob/static-analysis-tooling/CONTRIBUTING.md) file for all the details required to get you started as an official contributor for this project. ## License diff --git a/image.png b/image.png new file mode 100644 index 0000000..1dea03c Binary files /dev/null and b/image.png differ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..39ed2de --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +[tool.black] +line-length = 88 +target-version = ['py37'] +include = '\.pyi?$' +# 'extend-exclude' excludes files or directories in addition to the defaults +exclude = '^/src/version.py' +extend-exclude = ''' +# A regex preceded with ^/ will apply only to files and directories +# in the root of the project. +( + ^/foo.py # exclude a file named foo.py in the root of the project + | .*_pb2.py # exclude autogenerated Protocol Buffer files anywhere in the project +) +''' \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 510aa3f..b086af8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ -yattag==1.15.1 \ No newline at end of file +yattag==1.15.1 +black==23.10.1 +pylint==3.0.2 \ No newline at end of file diff --git a/src/builder/html_builder.py b/src/builder/html_builder.py index 95a61cb..22754fd 100644 --- a/src/builder/html_builder.py +++ b/src/builder/html_builder.py @@ -1,36 +1,46 @@ import os -import shutil import re from yattag import Doc, indentation -from utils.commandline import CommandlineParser from models.html_file import HtmlFile from utils.helper_functions import has_txt_extension, has_md_extension +from utils.commandline import CommandlineParser import builder.line_queries as line_queries from builder.toc_generator.index import TOC, HeadingItem + class HtmlBuilder: + """Functionality to build and generate the html documents""" + TOC_PLACEHOLDER = '
' def __init__(self): self._cl_args = CommandlineParser().get_args() - self._output_path = self._cl_args.output # Output directory for files - self._document_lang = self._cl_args.lang # Language used for the document + self._output_path = self._cl_args.output # Output directory for files + self._document_lang = self._cl_args.lang # Language used for the document # TOC Object self.toc = TOC() + def generate_html_file_object(self, file_path): + """Reads file at path provided and generates an `HtmlFile` object with corresponding html - def generate_html_for_file(self, file_path): + :param file_path: _description_ + :return: _description_ + """ # Start with a clean slate self.reset() with open(file_path, "r") as file: # Create the html virtual document virtual_doc = Doc().tagtext() + + # pylint: disable=unused-variable + # This is required for tuple unpacking doc, tag, text = virtual_doc + # pylint: enable=unused-variable lines = file.readlines() @@ -43,55 +53,49 @@ def generate_html_for_file(self, file_path): page_title = os.path.basename(file_path) # HTML template - self.generate_document(virtual_doc, page_title, lines, file_path) + self.generate_document_content(virtual_doc, page_title, lines, file_path) - file_content = doc.getvalue() # Get the generated html string - file_content = file_content.replace(HtmlBuilder.TOC_PLACEHOLDER, self.toc.get_html()) # Replace TOC placeholder with actual TOC - file_content = indentation.indent(file_content) # Indent the html + file_content = doc.getvalue() # Get the generated html string + file_content = file_content.replace( + HtmlBuilder.TOC_PLACEHOLDER, self.toc.get_html() + ) # Replace TOC placeholder with actual TOC + file_content = indentation.indent(file_content) # Indent the html gen_file_path = f"{self._output_path}/{os.path.basename(file_path)}" return HtmlFile(gen_file_path, file_content) - - - def generate_files(self, files_to_be_generated): - # Delete the output directory if it already exists - if os.path.isdir(self._output_path): - # Delete the folder structure - shutil.rmtree(self._output_path) - - # Generate the output directory - os.makedirs(self._output_path, exist_ok=True) - - # Generate an html file for each text file - for file in files_to_be_generated: - file.generate_html_file() def reset(self): + """Reset the state of the builder""" self.toc.clear() - - def generate_document(self, virtual_doc, page_title, lines, input_file_path): + def generate_document_content( + self, virtual_doc, page_title, lines, input_file_path + ): doc, tag, text = virtual_doc line_cursor_position = 0 - doc.asis('') - with tag('html', lang=self._document_lang): - with tag('head'): - doc.stag('meta', charset="utf-8") - with tag('title'): + doc.asis("") + with tag("html", lang=self._document_lang): + with tag("head"): + doc.stag("meta", charset="utf-8") + with tag("title"): text(self._neutralize_newline_character(page_title)) - doc.stag('meta', name="viewport", content="width=device-width, initial-scale=1") + doc.stag( + "meta", + name="viewport", + content="width=device-width, initial-scale=1", + ) - with tag('body'): + with tag("body"): # Add an h1 if the title is present if has_txt_extension(input_file_path) and self._is_title_present(lines): - with tag('h1'): + with tag("h1"): text(self._neutralize_newline_character(page_title)) line_cursor_position += 3 # Placeholder for TOC - with tag('div', id='toc-placeholder'): + with tag("div", id="toc-placeholder"): doc.asis() # Continue with the remaining content @@ -99,20 +103,25 @@ def generate_document(self, virtual_doc, page_title, lines, input_file_path): text_block = "" # Club paragraph content - while line_cursor_position < len(lines) and lines[line_cursor_position] != '\n': + while ( + line_cursor_position < len(lines) + and lines[line_cursor_position] != "\n" + ): text_block += lines[line_cursor_position] line_cursor_position += 1 - + # Neutralize extra spaces/newlines from content text_block = self._neutralize_newline_character(text_block).strip() line_cursor_position += 1 if len(text_block) > 1: if has_md_extension(input_file_path): - self._process_text_block_for_markdown(virtual_doc, text_block) + self._process_text_block_for_markdown( + virtual_doc, text_block + ) else: # No additional processing for text files - with tag('p'): + with tag("p"): text(text_block) def _process_text_block_for_markdown(self, virtual_doc, text_block): @@ -122,69 +131,76 @@ def _process_text_block_for_markdown(self, virtual_doc, text_block): :param virtual_doc: An instance of Yattag virtual document :param text_block: Block of text to be processed """ + # pylint: disable=unused-variable doc, tag, text = virtual_doc - plain_text_block = text_block + # pylint: enable=unused-variable # Move from inner to outer tags when processing # Inline elements # Use regEx to replace markdown bold format (*** ***) with html tags - text_block = re.sub(r'\*\*\*(.*?)\*\*\*', r'\1', text_block) + text_block = re.sub( + r"\*\*\*(.*?)\*\*\*", r"\1", text_block + ) # Use regEx to replace markdown bold format (** **) with html tags - text_block = re.sub(r'\*\*(.*?)\*\*', r'\1', text_block) + text_block = re.sub(r"\*\*(.*?)\*\*", r"\1", text_block) # Use regEx to replace markdown italic format (* * or _ _) with html tags - text_block = re.sub(r'(\*|_)(.*?)\1', r'\2', text_block) + text_block = re.sub(r"(\*|_)(.*?)\1", r"\2", text_block) # Block level elements - if line_queries.is_h1(text_block, ): + if line_queries.is_h1( + text_block, + ): formatted_block = text_block.replace(line_queries.H1_TOKEN, "") - with tag('h1', id=HeadingItem.generate_heading_id(formatted_block)): + with tag("h1", id=HeadingItem.generate_heading_id(formatted_block)): doc.asis(formatted_block) elif line_queries.is_h2(text_block): formatted_block = text_block.replace(line_queries.H2_TOKEN, "") - with tag('h2', id=HeadingItem.generate_heading_id(formatted_block)): + with tag("h2", id=HeadingItem.generate_heading_id(formatted_block)): doc.asis(formatted_block) self.toc.add_item(HeadingItem(formatted_block)) elif line_queries.is_h3(text_block): formatted_block = text_block.replace(line_queries.H3_TOKEN, "") - with tag('h3', id=HeadingItem.generate_heading_id(formatted_block)): + with tag("h3", id=HeadingItem.generate_heading_id(formatted_block)): doc.asis(formatted_block) self.toc.add_item(HeadingItem(formatted_block)) elif line_queries.is_h4(text_block): formatted_block = text_block.replace(line_queries.H4_TOKEN, "") - with tag('h4', id=HeadingItem.generate_heading_id(formatted_block)): + with tag("h4", id=HeadingItem.generate_heading_id(formatted_block)): doc.asis(formatted_block) self.toc.add_item(HeadingItem(formatted_block)) elif line_queries.is_h5(text_block): formatted_block = text_block.replace(line_queries.H5_TOKEN, "") - with tag('h5', id=HeadingItem.generate_heading_id(formatted_block)): + with tag("h5", id=HeadingItem.generate_heading_id(formatted_block)): doc.asis(formatted_block) self.toc.add_item(HeadingItem(formatted_block)) elif line_queries.is_h6(text_block): formatted_block = text_block.replace(line_queries.H6_TOKEN, "") - with tag('h6', id=HeadingItem.generate_heading_id(formatted_block)): + with tag("h6", id=HeadingItem.generate_heading_id(formatted_block)): doc.asis(formatted_block) self.toc.add_item(HeadingItem(formatted_block)) else: - with tag('p'): + with tag("p"): doc.asis(text_block) - - def _is_title_present(self, lines): """Takes the list of lines in a file, returns True if the first line is title""" - return lines[0] != '\n' and \ - len(lines) > 1 and lines[1] == '\n' and \ - len(lines) > 2 and lines[2] == '\n' + return ( + lines[0] != "\n" + and len(lines) > 1 + and lines[1] == "\n" + and len(lines) > 2 + and lines[2] == "\n" + ) def _neutralize_newline_character(self, line): """Removes any line delimeters like line feeds (\n) or carriage returns (\r\n), returns the cleaned line""" - cleaned_line = line.replace('\r\n', '').replace('\n', '') + cleaned_line = line.replace("\r\n", "").replace("\n", "") return cleaned_line diff --git a/src/builder/line_queries.py b/src/builder/line_queries.py index 64db0fc..9299f96 100644 --- a/src/builder/line_queries.py +++ b/src/builder/line_queries.py @@ -10,21 +10,26 @@ # Functions + def is_h1(line): return line.startswith(H1_TOKEN) + def is_h2(line): return line.startswith(H2_TOKEN) + def is_h3(line): return line.startswith(H3_TOKEN) + def is_h4(line): return line.startswith(H4_TOKEN) + def is_h5(line): return line.startswith(H5_TOKEN) + def is_h6(line): return line.startswith(H6_TOKEN) - diff --git a/src/builder/toc_generator/heading_item.py b/src/builder/toc_generator/heading_item.py index 4b34b5f..f985ca4 100644 --- a/src/builder/toc_generator/heading_item.py +++ b/src/builder/toc_generator/heading_item.py @@ -1,8 +1,16 @@ +"""This module defined HeadingItem class""" + from typing import List import hashlib + class HeadingItem: - def __init__(self, value: str, children: List['HeadingItem'] = []): + """Represents a heading item in a TOC (Table of contents)""" + + # Class Variables + CHILDREN_DEFAULT_VALUE = [] + + def __init__(self, value: str, children: List["HeadingItem"] = None): """Constructor :param id: id of the corresponding html element @@ -11,7 +19,9 @@ def __init__(self, value: str, children: List['HeadingItem'] = []): """ self.id = HeadingItem.generate_heading_id(value) self.value = value - self.children = children + self.children = ( + HeadingItem.CHILDREN_DEFAULT_VALUE if children is None else children + ) @staticmethod def generate_heading_id(text_content: str): @@ -28,11 +38,10 @@ def generate_heading_id(text_content: str): # Get the hexadecimal representation of the hash return sha512_hash.hexdigest() - + def get_html(self) -> str: return f"""
  • {self.value}
  • """ - \ No newline at end of file diff --git a/src/builder/toc_generator/index.py b/src/builder/toc_generator/index.py index 91b921d..ce5338c 100644 --- a/src/builder/toc_generator/index.py +++ b/src/builder/toc_generator/index.py @@ -1,9 +1,10 @@ from typing import List from builder.toc_generator.heading_item import HeadingItem + class TOC: - def __init__(self, items: List['HeadingItem'] = []): - self.items = items + def __init__(self, items: List["HeadingItem"] = None): + self.items = [] if items is None else items def add_item(self, item: HeadingItem): self.items.append(item) @@ -15,6 +16,6 @@ def get_html(self): {''.join(list(map(lambda item: item.get_html(), self.items)))} """ - + def clear(self): - self.items = [] \ No newline at end of file + self.items = [] diff --git a/src/models/html_file.py b/src/models/html_file.py index fb9646d..d138490 100644 --- a/src/models/html_file.py +++ b/src/models/html_file.py @@ -1,14 +1,47 @@ import os -# A virtual representation of the file to be generated +import shutil +from typing import List +from utils.commandline import CommandlineParser + + class HtmlFile: + """A virtual representation of the html file to be generated""" + def __init__(self, file_path, file_content): + """ + :param file_path: Write + :param file_content: _description_ + """ + self.file_path = file_path self.file_content = file_content # Write the file to the disk based on path and content def generate_html_file(self): """Creates a new file on the disk, or overrides existing, based on `self.file_path` and `self.file_content`""" - write_path = self.file_path.replace(f'{os.path.splitext(self.file_path)[1]}', '.html') + write_path = self.file_path.replace( + f"{os.path.splitext(self.file_path)[1]}", ".html" + ) with open(write_path, "w") as file: file.write(self.file_content) + + @staticmethod + def generate_files(files_to_be_generated: List["HtmlFile"]): + """Writes the provided HtmlFile(s) to the disk + + :param files_to_be_generated: A list of HtmlFile objects to be written to disk + """ + cl_args = CommandlineParser().get_args() + + # Delete the output directory if it already exists + if os.path.isdir(cl_args.output): + # Delete the folder structure + shutil.rmtree(cl_args.output) + + # Generate the output directory + os.makedirs(cl_args.output, exist_ok=True) + + # Generate an html file for each text file + for file in files_to_be_generated: + file.generate_html_file() diff --git a/src/til-builder_main.py b/src/til-builder_main.py deleted file mode 100644 index 83fb3b9..0000000 --- a/src/til-builder_main.py +++ /dev/null @@ -1,63 +0,0 @@ -import os -import pathlib -from utils.helper_functions import has_txt_extension, has_md_extension -from utils.commandline import CommandlineParser -import version - -from builder.html_builder import HtmlBuilder - -class App: - def __init__(self): - self.cl_args = CommandlineParser().get_args() - self.html_builder = HtmlBuilder() - - def run(self): - """Entry point for the app""" - - # Check for exit commands (Return if any branch is entered) - if self.cl_args.version: - print(f"{version.__name__}: {version.__version__}") - exit(0) - - # Execute parse functions - input_path = self.cl_args.input_path - if input_path is None: - print("You need to specify a file or folder of text files that need to be converted!") - exit(-1) - - # Check if the pathname is a directory - if os.path.isdir(input_path): - # Create a Path object for the directory - directory = pathlib.Path(input_path) - - # Use the rglob() method to get a list of all files in the directory and its subdirectories - files_in_directory = list(filter(lambda file_path: - os.path.isfile(file_path) and has_txt_extension(file_path) or has_md_extension(file_path), - list(map(lambda file_path: str(file_path.absolute()).replace('\\', '/'), - list(directory.rglob('*')))))) - - files_to_be_generated = [] - - for file in files_in_directory: - files_to_be_generated.append(self.html_builder.generate_html_for_file(file)) - - self.html_builder.generate_files(files_to_be_generated) - - elif os.path.isfile(input_path): - # Check if a text file is supplied - if has_txt_extension(input_path) or has_md_extension(input_path): - self.html_builder.generate_files([self.html_builder.generate_html_for_file(input_path)]) - else: - print(f"Only text and markdown files are supported. {input_path} is not a text or markdown file!") - exit(-1) - else: - print(f"'{input_path}' does not exist or is neither a file nor a directory.") - exit(-1) - - # Everything went well - exit(0) - - -if __name__ == "__main__": - app = App() - app.run() diff --git a/src/til_builder_main.py b/src/til_builder_main.py new file mode 100644 index 0000000..a340d7d --- /dev/null +++ b/src/til_builder_main.py @@ -0,0 +1,89 @@ +"""Entry point of the application""" + +import os +import sys +import pathlib +from utils.helper_functions import has_txt_extension, has_md_extension +from utils.commandline import CommandlineParser +from models.html_file import HtmlFile +import version + +from builder.html_builder import HtmlBuilder + + +class App: + def __init__(self): + self.cl_args = CommandlineParser().get_args() + self.html_builder = HtmlBuilder() + + def run(self): + """Entry point for the app""" + + # Check for exit commands (Return if any branch is entered) + if self.cl_args.version: + print(f"{version.__app_name__}: {version.__version__}") + sys.exit(0) + + # Execute parse functions + input_path = self.cl_args.input_path + if input_path is None: + print( + "You need to specify a file or folder of text files that need to be converted!" + ) + sys.exit(-1) + + # Check if the pathname is a directory + if os.path.isdir(input_path): + # Create a Path object for the directory + directory = pathlib.Path(input_path) + + # Use the rglob() method to get a list of all files in the directory and its subdirectories + files_in_directory = list( + filter( + lambda file_path: os.path.isfile(file_path) + and has_txt_extension(file_path) + or has_md_extension(file_path), + list( + map( + lambda file_path: str(file_path.absolute()).replace( + "\\", "/" + ), + list(directory.rglob("*")), + ) + ), + ) + ) + + files_to_be_generated = [] + + for file in files_in_directory: + files_to_be_generated.append( + self.html_builder.generate_html_file_object(file) + ) + + HtmlFile.generate_files(files_to_be_generated) + + elif os.path.isfile(input_path): + # Check if a text file is supplied + if has_txt_extension(input_path) or has_md_extension(input_path): + HtmlFile.generate_files( + [self.html_builder.generate_html_file_object(input_path)] + ) + else: + print( + f"Only text and markdown files are supported. {input_path} is not a text or markdown file!" + ) + sys.exit(-1) + else: + print( + f"'{input_path}' does not exist or is neither a file nor a directory." + ) + sys.exit(-1) + + # Everything went well + sys.exit(0) + + +if __name__ == "__main__": + app = App() + app.run() diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/commandline.py b/src/utils/commandline.py index eb18eee..4f5b21f 100644 --- a/src/utils/commandline.py +++ b/src/utils/commandline.py @@ -1,6 +1,8 @@ import argparse +import sys import tomli + class CommandlineParser: _instance = None @@ -13,13 +15,13 @@ def __new__(cls): cls._instance = super(CommandlineParser, cls).__new__(cls) cls._instance._initialize() return cls._instance - + def _initialize(self): # Create an ArgumentParser object self._parser = argparse.ArgumentParser( - prog='TIL Page Builder', - description='Converts text files for TIL posts to HTML files for publishing on the web.', - ) + prog="TIL Page Builder", + description="Converts text files for TIL posts to HTML files for publishing on the web.", + ) self._setup_arguments() def get_args(self): @@ -30,41 +32,45 @@ def _setup_arguments(self): # Config file self._parser.add_argument( - '-c','--config', - metavar='config', + "-c", + "--config", + metavar="config", type=str, - help="Uses a provided config file to set any valid options for the program." + help="Uses a provided config file to set any valid options for the program.", ) # Input file or folder containing files self._parser.add_argument( - 'input_path', + "input_path", default=None, - nargs='?', - help="The path to a text file or a folder containing files to be converted to corresponding html file(s)" + nargs="?", + help="The path to a text file or a folder containing files to be converted to corresponding html file(s)", ) # Output directory self._parser.add_argument( - '-o', '--output', + "-o", + "--output", default="./til", type=str, - help="Generates the html files in the provided directory, by default it is './til'" + help="Generates the html files in the provided directory, by default it is './til'", ) # Lang attribute self._parser.add_argument( - '-l', '--lang', + "-l", + "--lang", default="en-CA", type=str, - help="Indicates the language to use when generating the lang attribute on the root element" + help="Indicates the language to use when generating the lang attribute on the root element", ) # Display project version self._parser.add_argument( - '-v', '--version', - action='store_true', - help="Show the name and version of the project" + "-v", + "--version", + action="store_true", + help="Show the name and version of the project", ) # on/off flag # Parse the command-line arguments @@ -72,17 +78,22 @@ def _setup_arguments(self): self._process_config_file() - def _process_config_file(self): """Process the config file if provided""" if self._cl_args.config is not None: - with open(self._cl_args.config, 'rb') as config_file: # Opening config file + with open(self._cl_args.config, "rb") as config_file: # Opening config file try: - toml_vals = tomli.load(config_file) # Loading configurations from TOML file + toml_vals = tomli.load( + config_file + ) # Loading configurations from TOML file except tomli.TOMLDecodeError: - raise Exception("Error: Unable to decode configuration file contents") + print("Error: Unable to decode configuration file contents") + sys.exit(1) except: - raise Exception("Error: Invalid configuration file") + print("Error: Invalid configuration file") + sys.exit(1) - for (key,value) in toml_vals.items(): - setattr(self._cl_args, key, value) # Setting each attribute in arg namespace from tomli dictionary + for key, value in toml_vals.items(): + setattr( + self._cl_args, key, value + ) # Setting each attribute in arg namespace from tomli dictionary diff --git a/src/utils/helper_functions.py b/src/utils/helper_functions.py index 839274d..07411dd 100644 --- a/src/utils/helper_functions.py +++ b/src/utils/helper_functions.py @@ -1,9 +1,17 @@ import os + def has_txt_extension(file_path): + # pylint: disable=unused-variable file_name, file_extension = os.path.splitext(file_path) - return file_extension.lower() == '.txt' + # pylint: enable=unused-variable + + return file_extension.lower() == ".txt" + def has_md_extension(file_path): + # pylint: disable=unused-variable file_name, file_extension = os.path.splitext(file_path) - return file_extension.lower() == '.md' \ No newline at end of file + # pylint: enable=unused-variable + + return file_extension.lower() == ".md" diff --git a/src/version.py b/src/version.py index a9ba9bf..6513452 100644 --- a/src/version.py +++ b/src/version.py @@ -1,2 +1,2 @@ -__name__ = "TIL Page Builder" +__app_name__ = "TIL Page Builder" __version__ = "0.1.0"