diff --git a/01-Getting-Started/index.md b/01-Getting-Started/index.md new file mode 100644 index 00000000..9e47a0c2 --- /dev/null +++ b/01-Getting-Started/index.md @@ -0,0 +1,103 @@ +{theme=documentation} + +\include{"../include.md"} + +> A long time ago in a galaxy full of plugin-engines ... + +# Geting Started with Miniflask {section-contentlist=False} +## Miniflask is a **small** research-oriented **plugin**-engine for **python**. + + + +Quick Start +=========== + +For a quick look into miniflask, just install it using +```shell +pip install miniflask +``` + + +Short Example +============= + + +**Module Definition** +Let's start a new project first by creating a new directory: +```shell +> ls +main.py +modules/ + module1/__init__.py + module2/__init__.py + module1/__init__.py +``` + +Let's define a simple module, `modules/module1/__init__.py`: +```python +def main(state, event): + print("main event called by module1") + +def register(mf): + mf.register_event('main', main, unique=False) +``` + +Let's define another module, `modules/module2/__init__.py`: +```python +def main(state, event): + print("main event called by module2") + print("it uses a variable var:", state["var"]) + +def register(mf): + mf.register_defaults({ + "var": 42 + }) + mf.register_event('main', main, unique=False) +``` + + + +Our main.py looks like this: +```python +import miniflask + +# initialize miniflask +mf = miniflask.init(module_dirs="./modules") +mf.parse_args() + +# check if all requested modules are loaded +if not mf.halt_parse: + + # call event "main" if exists + if hasattr(mf.event, 'main'): + mf.event.main() + else: + print("There is nothing to do.") +``` + + +**Usage**: +Now, we can use our program in the following ways: +```sh +> python main.py +There is nothing to do. +``` + +```sh +> python main.py module1 +main event called by module1 +``` + +```sh +> python main.py module2,module1 +main event called by module2 +it uses a variable var: 42 +main event called by module1 +``` + +```sh +> python main.py module2,module1 --module2.var 9001 +main event called by module2 +it uses a variable var: 9001 +main event called by module1 +``` diff --git a/02-Launching-MiniFlask/index.md b/02-Launching-MiniFlask/index.md new file mode 100644 index 00000000..d8cf5d56 --- /dev/null +++ b/02-Launching-MiniFlask/index.md @@ -0,0 +1,38 @@ +{theme=documentation} + +# Launching MiniFlask +Things to do in the launch file. + +\include{"../include.md"} + + +## Launch file + +The first step in creating a new MiniFlask project is to create the launch file. The launch file initializes MiniFlask and tells it where modules are located. +**Typically, you will never need to touch this file ever again!** + +The following function calls have to be included: +**Example file:** do.py +```python +import miniflask + +# tells MiniFlask where the modules are located +# simply feed it a list of module folders +mf = miniflask.init(["module_collection_1","module_collection_2"]) + +# Parses arguments from the command line and runs the modules that have been specified +# as entrypoints. The run command first launches the `init` event if it exists and then +# the `main` events of all modules. If multiple modules with a main method are loaded, +# the main methods are executed in the order modules are specified using the CLI. +mf.run() +``` + +## Executing the launch file +In order to start our MiniFlask project, simply execute the `do.py` file and list all modules to load separated by a comma. To overwrite the default arguments, simply pass the new parameters precising the modules after the modules. + +```python +> python main.py module2,module1 --module2.var 9001 +main event called by module2 +it uses a variable var: 9001 +main event called by module1 +``` diff --git a/03-Modules/01-Module-Declaration.md b/03-Modules/01-Module-Declaration.md new file mode 100644 index 00000000..cc901c68 --- /dev/null +++ b/03-Modules/01-Module-Declaration.md @@ -0,0 +1,82 @@ +{theme=documentation} + +\include{"../include.md"} + +# Register Modules {section-contentlist=False} +Telling MiniFlask what exists. + +### Initialize Miniflask +\include{"../08-API/02-miniflask-Instance/01-miniflask.init.md"} + +\main + + +**Example Launch File** +```python +import miniflask + +# tell miniflask where to look for modules +mf = miniflask.init(module_dirs="./modules") + +# parse CLI-args & run the main event +mf.run() +``` + +Project structure may be very project specific. Miniflask allows various way to organize your projects. +Other possibilities are to define modules directly or to specify multiple distinct folders that contain miniflask modules. + +\examples + +### What is a Module? +> A miniflask module is a self-contained functional unit with a persistent variable store. In principle, it's a singleton class. + +**Module Declaration** +- **A MiniFlask module is a folder that contains at least these two files**: + - `__init__.py` containing a method called `register(mf)`. + The method `register(mf)` is called whenever a module is loaded. + It's purpose is to tell miniflask in which way the module is to be used. + - `.module` *(empty file)* + + +### Unique & Short Module Identifiers +When working with miniflask modules we will want to reference modules uniquely. +The unique module identifier is just the path to the folder (starting with the base directory defined during initialization), where the module seperator is a dot (`.`). + + +**Example** +For instance, we could organize the folder `./modules` like this: +```shell +> ls +main.py +modules/ + module1/__init__.py + ... /.module + module2/__init__.py + ... /.module + ... /.noshortid + subfolder1/module3/__init__.py + ... / ... /.module + subfolder1/module4/__init__.py + ... / ... /.module + ... and so on ... +``` + +Further, we initialized miniflask using the line +```py +mf = miniflask.init({ + "mods": "./modules", +}) +``` +by specifying the name `mods` for our main repository contained in the folder `./modules/`. + + +Every module in this repository can now be referenced in two ways: \block[ + | **Type** | unique module identifier | (short) module identifier | + | ---------- | -------------------- | --------------------- | + | **Description** | unique identification by specifying the whole module path | any subset of the unique identifier if only one module matches that description | + | **Availability** | Always | Not available if \block[ +- two modules would share the same short identifier +- module-folder contains the file `.noshortid` + ] | + | **Example** | `module1`,\n (has no shorter id)\n `subfolder1.module3` or also `mods.module3`,\n `subfolder1.module4` or also `mods.module4`| `mods.module1`\n`mods.module2`\n`mods.subfolder1.module3`\n`mods.subfolder1.module4` | + ] diff --git a/03-Modules/02-Register-Settings.md b/03-Modules/02-Register-Settings.md new file mode 100644 index 00000000..2fd8bde8 --- /dev/null +++ b/03-Modules/02-Register-Settings.md @@ -0,0 +1,108 @@ +{theme=documentation} + +\include{"../include.md"} + +\include{"../08-API/02-miniflask-Instance/08-register_defaults.md"} + +# Register Module Settings: + \shortdescr + +\main + + +## Example +A typical used case are variables that can be overridden freely using CLI. +Example file: `modules/module1/__init__.py` +\examples + +## Overriding these using the CLI +Whenever we load `module1`, e.g. by using +```shell +python main.py module1 +``` +we also load these variables into the argument parser of miniflask. +For the example above, we list all arguments that can be used for `module1`: +```shell +> python main.py module1 -h +usage: main.py modulelist [optional arguments] + +optional arguments: + -h, --help show this help message and exit + --module1.variableA int + --module1.variableB string + --module1.variableC + --no-module1.variableC + --module1.variableD [int] +``` + +Thus, to turn `variableC` off, we can just add `--no-module1.variableC` to the above command. + +## Overriding Booleans +For a boolean typed variable, the following arguments are possible: + +| **Set to True** | **Set to False** | +| ---------------- | ---------------- | +| \block[ +``` +--module.var +--module.var true +--module.var TrUe (well, boolean values are case insensitive) +--module.var yes +--module.var y +--module.var t +--module.var 1 +``` +] | \block[ +``` +--no-module.var +--module.var false +--module.var FalSE (well, boolean values are case-insensitive) +--module.var no +--module.var n +--module.var f +--module.var 0 +``` +] | + +**Overriding Lists of basic types**: +List arguments are just as easy as +``` +--module.var 1 2 3 4 5 +``` + + + +## Required Arguments +Set any basic data type (`str`, `bool`, `int`, `float`) or list of any basic data type as default value to register an argument that has to be specified by the CLI. +```python +def register(mf): + mf.register_defaults({ + "variableA": int, + "variableB": str, + "variableC": bool, + "variableD": [float] # only lists of same type are supported + }) +``` + + + + +## Nested Arguments +Given nested modules, it is possible to group variables in the CLI using the folllowing syntax: +```python +--module1.submodule [ --var1 42 --var2 43 ] +``` +or +```python +--module1 [ --submodule [ --var1 42 --var2 43 ] ] +``` + +These calls would translate to the following: +``` +--module1.submodule.var1 42 +--module1.submodule.var2 43 +``` + + + + diff --git a/03-Modules/03-Register-Events.md b/03-Modules/03-Register-Events.md new file mode 100644 index 00000000..62a0fc8c --- /dev/null +++ b/03-Modules/03-Register-Events.md @@ -0,0 +1,112 @@ +{theme=documentation} + +\include{"../include.md"} + +# Register Events +Who doesn't love functional interfaces? ;) + +Module function can be attached to hooks so that they are called automatically upon reaching the specified hook. Events can be attached to a hook when registering an event. + +### Minimal Example +**Example File:** `modules/module1/__init__.py` +```python +def main(state, event): + print("This is the main-event of module1") + +def register(mf): + mf.register_event('main', main, unique=False) +``` + +**Example File:** `modules/module2/__init__.py` +```python +def main(state, event): + print("This is the main-event of module2") + +def notneeded(state, event, someargument): + print("Event was called. Argument:" + someargument) + return "notneeded() result" + +def register(mf): + mf.register_event('main', main, unique=False) + mf.register_event('notneeded', notneeded, unique=False) +``` + +**Example File:** `main.py` +```python +import miniflask + +# initialize miniflask +mf = miniflask.init(module_dirs="./modules") +state, event = mf.state, mf.event +mf.parse_args() +mf.event.main() +``` + +--- + +### Register Events +Every Event-Method can be called with two arguments `state` and `event` (**only as first two arguments**), **plus** the arguments that are passed when the event is called. + +There are two type of events: + +**Unique**: +Can be defined by exactly one module.\n Typically unique events are called and are expected to return exactly one result. +`mf.register_event('eventname', fn)` + +**Nonunique** +Can be defined by arbitrary many module. The results of all such events of loaded modules are collected and returned in a list. +`mf.register_event('eventname', fn, unique=False)` + +### Transform events (before/after event call) + +Sometimes, it is useful to call an event just before or after a given event *event0*. This is useful when observing what the effects of *event0* are or when applying a modifyier before *event0*. This can be done without defining a new event by simply registering the event in the following way: + +` mf.register_event('before_event0', fn, unique=False)` or `mf.register_event('after_event0',fn)`. + +In its definition, the transform function takes all arguments of the original function followed by any supplementary arguments for the transformation call. The transform function returns the arguments of the original function. + +```python +def before_blub(state, event, *args, **kwargs): + #*args are the original arguments of blub + #**kwargs are supplementary arguments of the transform function + ... + return args +``` + + + +### Outervar +In some scenarios it may be useful to define a module to depend completely on another module. +In this case we can inherit the scope of some variables from the callee, by setting their default to `miniflask.event.outervar`. +**Note:** This is miniflasks version of friend-classes known from OOP, as such it should also be used with caution and only for strong dependencies between modules that also are too loose for a well-defined interfaces. + +**Example File:** `modules/module2/__init__.py` +```python +from miniflask.event import outervar + +def main(state, event, varA=outervar): + print("This variable is queried from the callee scope:", varA) + +def register(mf): + mf.register_event('main', main, unique=False) +``` + +**Example File:** `main.py` +```python +import miniflask + +# initialize miniflask +mf = miniflask.init(module_dirs="./modules") +state, event = mf.state, mf.event +mf.parse_args() +varA=42 +mf.event.main() +``` + + + +### Call Events +\include{"../08-API/04-event/01-event calling.md"} +\shortdescr +\main + diff --git a/03-Modules/04-Module-State.md b/03-Modules/04-Module-State.md new file mode 100644 index 00000000..1ce0f1de --- /dev/null +++ b/03-Modules/04-Module-State.md @@ -0,0 +1,41 @@ +{theme=documentation} + +\include{"../include.md"} + +# State +Global variables, but fancy. + +## Using the module' variable scope +\include{"../08-API/05-state/01-... is a local dict.md"} + +\main + +**Examples**: +\examples + + +## Use/change other modules' variable scope + +\include{"../08-API/05-state/03-scope.md"} + +\shortdescr +\main + +{.alert} +This makes sense for inheritance like module dependencies. + +**Examples**: +\examples + + + +## Temporary Changes +\include{"../08-API/05-state/04-temporary.md"} + +\shortdescr +\main + +**Examples**: +\examples + + diff --git a/03-Modules/05-Module-Dependencies.md b/03-Modules/05-Module-Dependencies.md new file mode 100644 index 00000000..256f5a0f --- /dev/null +++ b/03-Modules/05-Module-Dependencies.md @@ -0,0 +1,47 @@ +{theme=documentation} + +\include{"../include.md"} + +# Module Dependencies {section-contentlist=False} +Presets for modules + +## Default Modules +With `register_default_module` you can register a module to be loaded automatically, if a regular expression is not matched in the list of loaded moduls. + +Typically, modules will be ordered in folderes based on distinct functionality. +In the following example, `module2` requires at least one module in the folder `some/folder/` to be loaded. If none such module was found by the end of the module loading-phase the default module will be loaded. + +**Example File:** `modules/module2/__init__.py` +```python +def register(mf): + mf.register_default_module("some.folder.defaultmodule", required_id="some\folder\.*") +``` +It is also possible to load a module only if a required event does not exist. + +```python +def register(mf): + mf.register_default_module("some.folder.defaultmodule", required_event="this_event_is_required") +``` + +## Plain Dependencies +With `load` you can load any other module if the current module was loaded. + +**Example File:** `modules/module2/__init__.py` +```python +def register(mf): + mf.load('some.othermodule') + mf.load(['list.of.modules1', 'list.of.modules2']) +``` +## Child modules + +It is possible to load a module as a child of another module. + +```python +mf.load_as_child(module1,module2) +``` + +..loads module2 as a child of module1. module2 then uses the identifier `module1.module2`. The variables of the second module then have to be accessed through the new global identifier. + +{.alert} +Note: Main methods of child modules will not be called automatically. + diff --git a/03-Modules/06-Settings-Dependencies.md b/03-Modules/06-Settings-Dependencies.md new file mode 100644 index 00000000..576810cd --- /dev/null +++ b/03-Modules/06-Settings-Dependencies.md @@ -0,0 +1,101 @@ +{theme=documentation} + +\include{"../include.md"} + + + +## Dependencies +In some cases global dependencies are needed. + + +### Common Scopes +Groups of Modules often share variables. (Either by functionality, or, by conceptional classification). +The method `register_defaults` has the optional argument `scope`. This variable defaults to the unique module id of the current module. +Overwriting the variable allows for shared or global settings. + +**Example File:** `modules/module2/__init__.py` +```python +def register(mf): + mf.set_sccope("common") + mf.register_defaults({ + "varA": 42, + }) +``` +This variable is identified in the global scope by `common.varA` instead of `module2.varA`. +**Note**: `mf.set_scope()` sets the scope globally (also in the event states). + +**Alternative**: +```python + mf.register_defaults({ + "varA": 42, + }, scope="common") +``` +This variable is identified in the global scope by `common.varA` instead of `module2.varA`. + +\n + + + +#### Global Settings +Global variables are just a special case of common scopes. + +**Example File:** `modules/module2/__init__.py` +```python +def register(mf): + mf.register_globals({ + "global_var": "this variable is global", + }) +``` + +**Note**: `mf.register_globals(...)` is equivalent to a call of `mf.register_defaults(...,scope="")`. +\n + + + +### Overwrite Settings of other Modules +A module can be used as a preset of other modules with predefined settings. + +**Example File:** `modules/module2/__init__.py` +```python +def register(mf): + mf.load("module1") # module dependency + mf.overwrite_defaults({ + "module1.variableB": "overwrites variable of module1", + }) +``` + +**Note**: `mf.overwrite_defaults(...)` is equivalent to a call of `mf.register_defaults(...,overwrite=True)`. +In case any variable to overwrite is not known, the method will throw an Exception (`ValueError`). +**Note**: `mf.register_defaults` would also overwrite variables, however, it would not raise an Exception if the variable is not known. +\n + + +### Module Helpers +Each registered variable can be changed using cli arguments (see above). This behaviour can be suppressed as follows. + +**Example File:** `modules/module2/__init__.py` +```python +def register(mf): + + class A(): + pass + mf.register_helpers({ + "variable": A(), # cannot be changed using argument parsers + }) +``` + +**Note**: `mf.register_helpers(...)` is equivalent to a call of `mf.register_defaults(...,cliargs=False)`. +\n + + +### Settings-Dependent Settings +On variable definition, all variables can be initialized using a lambda. This lambda retrieves the global `state`- and `event`-Objects to allow for settings dependdent settings + +**Example File:** `modules/module2/__init__.py` +```python +def register(mf): + mf.register_defaults({ + # unless anything is overwritten in the cli, this variable would be 420 + "variable": lambda state,event: state["module1.variableA"] * 10 + }) +``` diff --git a/03-Modules/index.md b/03-Modules/index.md new file mode 100644 index 00000000..cffbff14 --- /dev/null +++ b/03-Modules/index.md @@ -0,0 +1,2 @@ + +\redirect{"01-Module-Declaration.md"} diff --git a/06-Predefined-Modules/index.md b/06-Predefined-Modules/index.md new file mode 100644 index 00000000..e8e6f056 --- /dev/null +++ b/06-Predefined-Modules/index.md @@ -0,0 +1,137 @@ +{theme=documentation} + +\include{"../include.md"} + + +# Predefined Modules {section-contentlist=False} +MiniFlask comes with some simple Modules (that can be included at any time) + + +### Module `modules` +Yes, the first module is called `modules`. +It simply lists all available modules in a directory. + +**Example Output:** +{.customsyntax}$$ +Load Module ... \literal{miniflask.modules.modules} +\title{.} +├── flavours +│ └── \title{somedeps} +├── data +│ ├── \title{batches} +│ │ \meta{! dataloader} +│ └── \title{augment} +│ \meta{! dataset_augment} +├── datasets +│ ├── \title{cifar10} +│ │ \meta{! dataset} +│ └── \title{imagenet} +│ \meta{! dataset} +├── loops +│ └── \title{main_loop} +│ \meta{> main} +├── models +│ ├── \title{model2} +│ └── \title{model1} +└── \title{module3} +-------------------------------------------------- +There is nothing to do. +$$ + +--- + +### Module `events` +This module shows all registered events of all available modules. + +**Example Output:** +{.customsyntax}$$ +Load Module ...\literal{ miniflask.modules.events} + +\title{Available events} +\meta{! dataloader} used in \literal{data.batches} +\meta{! dataset used} in \literal{datasets.cifar10, datasets.imagenet} +\meta{! dataset_augment} used in \literal{data.augment} +\meta{> init} used in \literal{miniflask.modules.settings} +\meta{! main} used in \literal{loops.main_loop} +\meta{--------------------------------------------------} +There is nothing to do. +$$ + +--- + +### Module `settings` +This module shows all registered events of all available modules. + +**Example Output:** +{.customsyntax}$$ +Folder│\title{module}│\literal{variable} = value +————————————————————————————— +data│\title{augment}│\literal{fn} = transform + │\title{batches}│\literal{size} = 4 +loops│\title{main_loop}│\literal{epoch} = 2 +-------------------------------------------------- +$$ + +--- + + +### Module `definitions` +This module extends `settings` by showing also the code-locations of the setting-definitions. + +**Example Output:** +{.customsyntax}$$ +Folder│\title{module}│\literal{variable} = value +————————————————————————————— +data│\title{augment}│\literal{fn} = transform + │ │ definition in line 10 in file 'data/augment/__init__.py' + │\title{batches}│\literal{size} = 4 + │ │ definition in line 10 in file 'data/augment/__init__.py' +loops│\title{main_loop}│\literal{epoch} = 2 + │ │ definition in line 42 in file 'loops/main_loop/__init__.py' +-------------------------------------------------- +$$ + +--- + + +### Module `info` +Calls `modules` and then `events`. +(Finishes the program afterwards.) + +**Example Output:** +{.customsyntax}$$ +Load Module ... \literal{miniflask.modules.modules} +\title{.} +├── flavours +│ └── \title{somedeps} +├── data +│ ├── \title{batches} +│ │ \meta{! dataloader} +│ └── \title{augment} +│ \meta{! dataset_augment} +├── datasets +│ ├── \title{cifar10} +│ │ \meta{! dataset} +│ └── \title{imagenet} +│ \meta{! dataset} +├── loops +│ └── \title{main_loop} +│ \meta{> main} +├── models +│ ├── \title{model2} +│ └── \title{model1} +└── \title{module3} +-------------------------------------------------- + +Load Module ...\literal{ miniflask.modules.events} + +\title{Available events} +\meta{! dataloader} used in \literal{data.batches} +\meta{! dataset used} in \literal{datasets.cifar10, datasets.imagenet} +\meta{! dataset_augment} used in \literal{data.augment} +\meta{> init} used in \literal{miniflask.modules.settings} +\meta{! main} used in \literal{loops.main_loop} +\meta{--------------------------------------------------} +$$ + + diff --git a/07-Design-Patterns/index.md b/07-Design-Patterns/index.md new file mode 100644 index 00000000..6644fc3b --- /dev/null +++ b/07-Design-Patterns/index.md @@ -0,0 +1,5 @@ +{theme=documentation} + +\include{"../include.md"} + +# Nothing there yet. diff --git a/08-API/01-Method-Overview/index.md b/08-API/01-Method-Overview/index.md new file mode 100644 index 00000000..cca033a8 --- /dev/null +++ b/08-API/01-Method-Overview/index.md @@ -0,0 +1,22 @@ +{theme=documentation} + +\include{"../include.md"} + +**The API is structured as follows**: +1. [**Miniflask Instance**](../../08-API/02-miniflask-Instance/) + covers the API of the caller script (or entrypoint) into miniflask modules as explained in the documentation part [Launching MiniFlask](../../02-Launching-MiniFlask/). +2. [**register(mf) Object**](../../08-API/03-register(mf\)-Object) + is the instance that is passed into every `def register(mf)` call upon module registration. +3. [**event**](../../08-API/04-event) + is the object that enables calls to any other loaded module. +3. [**state**](../../08-API/05-state) + manages the local and global variables of any loaded module. + + + +---- + +**Full List of Contents**: +\listdocuments{dir="..", !collapseother} + + diff --git a/08-API/include.md b/08-API/include.md new file mode 100644 index 00000000..f7399253 --- /dev/null +++ b/08-API/include.md @@ -0,0 +1,15 @@ +{theme=documentation} +\include{"../version.md"} \newcommand{Output}[ +# Results in {.collapsible} +%{arg0}% +] \newcommand{title title subtitle}[ +# %{title}% {section-contentlist=False} + +# %{subtitle}% {section-contentlist=False} +] \component{topright}[ +- [\icon{fork}Github](http://www.github.com/da-h/miniflask) +] \component{"brand"}[ + {versionstr={"Version: " %{version}%}} + + [*MiniFlask -- API Reference* ({verbatim=%{versionstr}%}` `)](https://da-h.github.io/miniflask) +] diff --git a/08-API/index.md b/08-API/index.md new file mode 100644 index 00000000..6e2633a2 --- /dev/null +++ b/08-API/index.md @@ -0,0 +1 @@ +\redirect{"01-Method-Overview"} diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d1dfd801 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 David Hartmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/grep_docstrings.py b/grep_docstrings.py new file mode 100644 index 00000000..b18fb53c --- /dev/null +++ b/grep_docstrings.py @@ -0,0 +1,160 @@ +import sys +import re +import pathlib +from textwrap import dedent +from inspect import getmembers, isfunction, signature + +sys.path.insert(0, 'src/') +from miniflask import __version__ # noqa: E402 +from miniflask.miniflask import miniflask, miniflask_wrapper # noqa: E402, F401 +from miniflask.event import event # noqa: E402, F401 +from miniflask.state import state # noqa: E402, F401 + +classes = [ + ("miniflask Instance", miniflask), + ("register(mf) Object", miniflask_wrapper), + ("event", event), + ("state", state) +] + +section_re = re.compile(r"\s*([a-zA-Z\-]+):") +fns_done = [] + +# update version.md +dest = "./docs" +with open("%s/version.md" % dest, "w") as f: + f.write("{version=\"%s\"}" % __version__) + +for i, (clsname, cls) in enumerate(classes): # noqa: C901 + j = 0 + + members = getmembers(cls, isfunction) + + # resort __init__ to front + init_index = [n for n, m in members].index('__init__') + if init_index >= 0: + members = [members[init_index]] + members[:init_index] + members[init_index + 1:] + + for name, fn in members: + if fn in fns_done: + continue + + # skip non-miniflask funcitons (from derived classes) + if not fn.__module__.startswith("miniflask"): + continue + + # skip private api + # if name.startswith("_"): + # continue + + doc = fn.__doc__ + + if not doc: + continue + fns_done.append(fn) + j += 1 + + first_line, doc = doc.split("\n", 1) + first_line = first_line.strip() + if first_line: + name = first_line + if name.startswith("!"): + name = name[1:] + skipsignature = True + else: + skipsignature = False + doc = dedent(doc).split("\n") + + # convert doc-format to luke format + # --------------------------------- + luke_doc = ["{theme=documentation "] + + # name of first line is display name of function + luke_doc.append("fname='%s'" % name) + + # get signature + if not skipsignature: + sig = signature(fn) + sig_str = ", ".join(['%s="%s"' % (name, param.default) if isinstance(param.default, str) else str(param) for name, param in sig.parameters.items() if name != "self"]) + luke_doc.append("signature='%s'" % sig_str) + luke_doc.append("fnamewithsig='%s(%s)'" % (name if not first_line else first_line, sig_str)) + + # second line is description + luke_doc.append("shortdescr='%s'" % doc[0]) + + # collect topics (starting with word, ending with colon + topics = {"main": []} + current_topic = topics["main"] + for line in doc[2:]: + + match = section_re.match(line) + if match: + # print("-> Topic="+match[1].lower()) + current_topic = topics[match[1].lower()] = [] + continue + + current_topic.append(line) + + # convert topics to luke variables + for t, text in topics.items(): + luke_doc.append("%s=[\n%s\n]" % (t, "\n".join(text))) + + luke_doc.append("}") + + luke_doc.append(""" + \\ifexists{filename_current}[][\\include{"../include.md"} +# %{fname}% +## \\shortdescr + +---- + +\\main + +\\ifexists{note}[ +### Note {.alert} +\\note +] + +\\ifexists{fnamewithsig}[ +### Method Signature +```python {verbatim=%{fnamewithsig}%} +``` +] + +\\ifexists{returns}[ +## Returns +\\returns +] + +\\ifexists{args}[ +## Arguments +\\args +] + +\\ifexists{examples}[ +## Examples +\\examples +] + +\\ifexists{appendix}[ + +--- + +\\appendix +] +] + """) + + # create file for that function + # ----------- + cls_dir = pathlib.Path(dest) / "08-API" / ("%02d-%s" % (i + 2, clsname.replace(" ", "-"))) + cls_dir.mkdir(parents=True, exist_ok=True) + print(str(cls_dir / ("%02d-%s.md" % (j, name)))) + with open(cls_dir / ("%02d-%s.md" % (j, name)), "w") as f: + f.write("\n".join(luke_doc)) + + # create index (pointing to first function) + # ------------ + if not (cls_dir / "index.md").exists(): + with open(cls_dir / "index.md", "w") as f: + f.write("\\redirect{\"%02d-%s.md\"}" % (j, name)) diff --git a/include.md b/include.md new file mode 100644 index 00000000..822c53cc --- /dev/null +++ b/include.md @@ -0,0 +1,19 @@ +{theme=documentation code-syntax=markdown} + +\newcommand{Output}[ +# Results in {.collapsible} +%{arg0}% +] + + +\newcommand{title title subtitle}[ +# %{title}% {section-contentlist=False} + +# %{subtitle}% {section-contentlist=False} +] + + +\component{brand}[[*MiniFlask*](http://da-h.github.io/miniflask)] +\component{topright}[ +- [\icon{fork}Github](http://www.github.com/da-h/miniflask) +] diff --git a/index.md b/index.md new file mode 100644 index 00000000..5be529c8 --- /dev/null +++ b/index.md @@ -0,0 +1 @@ +\redirect{"01-Getting-Started/"}