Intermake
=========
___APPLICATION FRONTEND VIA REFLECTION___

* Please go to the [Usage Tutorial](#Usage-tutorial) section for how to **use** an Iɴᴛᴇʀᴍᴀᴋᴇ application
* Please go to the [Development Tutorial](#Development-tutorial) section for how to **create** an Iɴᴛᴇʀᴍᴀᴋᴇ application

[](toc)

What is Intermake?
------------------

Iɴᴛᴇʀᴍᴀᴋᴇ is a library that provides a front-end to Pʏᴛʜᴏɴ functions.
The following frontends are supported:

* `ARG`: Call from **command line arguments**
* `CLI`: Interactive terminal-like shell (**CLI**)
* `PYI`: Interactive **Pʏᴛʜᴏɴ shell** (PYI)
* `GUI`: Graphical user interface (**GUI**)
* `PYS`: Call from any **Pʏᴛʜᴏɴ script** or application
* `???`: **Custom** front-ends are also supported


### Rationale ###

Unlike other utilities, Iɴᴛᴇʀᴍᴀᴋᴇ:
* Requires minimal setup – an `@command` decorator
* Isn't intrusive
* Generates help and documentation _automatically_ – uses [PEP-257](https://www.python.org/dev/peps/pep-0257/) documentation
* Generates an _appropriate_ UI _automatically_:
    * Uses [PEP-484](https://www.python.org/dev/peps/pep-0484/) annotations
    * Converts types automatically
        * A function, requiring an `int`, receives an `int`, not the `str` the user typed
        * A function, requiring a `MyOwnClass`, receives a `MyOwnClass`
    * Supports annotation hints:
        * Function defaults
        * The `typing` library: `List[T]`, `Union[T, U]`, `Optional[T]`, etc.
        * Includes custom-annotations: `Filename[extension]` (str), `Dirname` (str), `Nonzero[T]` (T)
* Abstracts common concepts – scroll bars, feedback to user, questions to user, etc.



Usage Tutorial
--------------

Where `sample` is the name of your application, use the following Bash commands to launch it:

| Mode                           | Command                                          |
|--------------------------------|--------------------------------------------------|
| Command-line arguments         | `sample "<commands>"`                            |
| Command line interface         | `sample` <br/> ...or... <br/> `bio42 "ui cli"`   |
| Graphical user interface       | `sample "ui gui"`                                |
| Pʏᴛʜᴏɴ interactive shell       | `sample "ui pyi"`                                |
| Interface for Pʏᴛʜᴏɴ scripting | `python` <br/> ...then... <br/> `import sample`  |

Iɴᴛᴇʀᴍᴀᴋᴇ provides an ***extensive, context-dependent, in-line help*** system.

Let's explore the CLI mode. First, launch your application from the command line in the default (CLI) mode.

```bash
$   sample
```

Once inside `sample`, use the `help` command to get started.

```bash
$   help
    ECO help
    INF   _help____________________________________________
          Aliases: basic_help
    
                        You are in command-line mode.
    
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
```


Development Tutorial
--------------------

### Implementation ###

Get right in there with the full implementation of a simple Iɴᴛᴇʀᴍᴀᴋᴇ application, called Sᴀᴍᴩʟᴇ! It has two amazing functions "`say_hello`" and "`do_some_work`".
Paste the following contents into the appropriate files:

[`sample/sample/__init__.py`]
```python
from intermake import MCMD, MENV, command

@command()
def say_hello():
    """
    Says hello
    """
    MCMD.print( "hello" )

@command()
def do_some_work( count : int ):
    """
    Does nothing.
    
    :param count: The number of times to do nothing.
    """
    with MCMD.action( "Doing some work for you!", count ) as action:
        for n in range( count ):
            action.increment()

MENV.name = "sample"
```

[`sample/sample/__main__.py`]:
```python
import sample
import intermake

if __name__ == "__main__":
    intermake.start()
```

**_That's all there is!_** The next section describes what we actually did!

### Explanation ###

1. We used the `@command()` decorator to expose our function through Iɴᴛᴇʀᴍᴀᴋᴇ.
2. We properly documented our functions using PEP-287 doc comments.
3. We properly annotated our function parameters using PEP-484.
4. We used the `MCMD` field to obtain the abstraction to the current UI.
    * We called `MCMD.print` to print a message.
    * We called `MCMD.action` to show a progress bar.
5. We set the application name `MENV.name`.
    * (Iɴᴛᴇʀᴍᴀᴋᴇ doesn't impose any branding, so if we don't do this our application just gets called "`Untitled`".)
6. Finally, we called `intermake.start` to start Iɴᴛᴇʀᴍᴀᴋᴇ with the appropriate UI.

### Running our application ###

This is Pʏᴛʜᴏɴ boilerplate stuff, but if you don't already know:
* Do a quick-and-dirty registration with Pʏᴛʜᴏɴ by running:

```bash
export PYTHONPATH=$PYTHONPATH:/path/to/sample`.
alias sample="python3 -m sample"
```

Now you can try out the various modes as follows. They all do the same thing, in different ways.

#### CLI mode ####

```bash
BASH   <<< sample
SAMPLE <<< say_hello
       <<< do_some_work count=10000
       <<< exit
```

#### ARG mode ####

```bash
BASH   <<< sample "say_hello : do_some_work count=10000"
```

#### GUI mode ####

```bash
BASH   <<< sample "ui gui"
SAMPLE <<< *click say_hello*
       <<< *click run*
       <<< *click do_some_work*
       <<< *set count to 10000*
       <<< *click run*
       <<< *close the window*
```

#### PYI mode ####

```bash
BASH   <<< sample "ui pyi"
PYTHON <<< say_hello()
       <<< do_some_work(10000)
```

#### PYS mode ####

```python
PYTHON <<< import sample
       <<< sample.say_hello()
       <<< sample.do_some_work(10000)
```

Adding support for new types
----------------------------

* Iɴᴛᴇʀᴍᴀᴋᴇ uses Eᴅɪᴛᴏʀɪᴜᴍ to supply the Qᴛ GUI editor. To add GUI support for new types, call `editorium.register` (see the `editorium` module itself for details).
* Iɴᴛᴇʀᴍᴀᴋᴇ uses SᴛʀɪɴɢCᴏᴇʀᴄɪᴏɴ to supply the CLI translations from text. To add support for translation from text to new types, call `stringcoercion.register` (see the `stringcoercion` module itself for details).  
    
Advanced plugins
----------------

In our example we provided a _plugin_ through the `@command` decorator.
`@command` also allows us to register *classes*, if those classes are subclasses of the `Plugin` type.
The `plugins` directory of `intermake` contains the set of in-built plugins, which may be used as a reference-point for creating your own.

***Please see the doc-comments on `@command` and `Plugin` for full details.***

Customising
-----------

The example above set only the application title, more customisations are available via the `MENV` field, notably the `root` field, that exposes an application hierarchy derived from `IVisualisable`:

```python
MENV.root = my_root
```

Once a root is set, the user can navigate your hierarchy using UNIX/DOS like commands:

```bash
$   sample
$   cd plugins
```

The `MENV` field is a `__Environment` object defined in `environment.py`.

***Please see the doc-comments on `__Environment` and `IVisualisable` for full details.***

System requirements
-------------------

* Iɴᴛᴇʀᴍᴀᴋᴇ uses type annotations, which require at least Pʏᴛʜᴏɴ 3.6.
    * This must be installed first. 

* Supported platforms
    * Windows 7,8
        * Iɴᴛᴇʀᴍᴀᴋᴇ uses Cᴏʟᴏʀᴀᴍᴀ to add support for ANSI-escape sequences
        * Iɴᴛᴇʀᴍᴀᴋᴇ instructs the user on how to enable UTF-8
    * Windows 10
    * Ubuntu
    * MacOS
        * Beware that this ships with a legacy version of Pʏᴛʜᴏɴ 2 by default, you'll need to update!

* Terminal environment requisites (`CLI`/`ARG`/`PYI`/`PYS`)
    * ANSI escape sequences
        * _If not supported_: Weird characters in console output
    * ANSI colours (optional)
        * _If not supported_: No colours will be shown
    * UTF8 (optional)
        * _If not supported_: Weird/missing data in console output.
            * Note: Pʏᴛʜᴏɴ itself must still be notified via the `PYTHONIOENCODING` environment variable, e.g. `export PYTHONIOENCODING=ascii:replace`. If this is not done the application will crash (Iɴᴛᴇʀᴍᴀᴋᴇ supplements the Python error with a more helpful error description).
    * _readline_
        * _If not supported_: Up/down will not work to invoke command history in console. 

* Graphical user interface requisites (`GUI`)
    * _PyQt5_.
        * Note: at the time of writing, some versions of Ubuntu ship with a broken _Qt_/_PyQt5_/_Sip_ install, giving a `killed 9` or `segfault` error on GUI startup. This will require re-installation of _Qt_/_PyQt_/_Sip_ by the user.
        * _If not supported_: Cannot start GUI. Iɴᴛᴇʀᴍᴀᴋᴇ's UI components are isolated so the console modes should still work fine providing the application itself also isolates its GUI (which it should).
    
Troubleshooting
---------------

### Generally weird errors ###

User errors:

* Iɴᴛᴇʀᴍᴀᴋᴇ requires at least Pʏᴛʜᴏɴ 3.6.
    * (This is especially problematic on Mac, which for some reason ships with a legacy version of Pʏᴛʜᴏɴ 2) 

### Unicode errors ###

User errors:

* You're using Pʏᴛʜᴏɴ2, Iɴᴛᴇʀᴍᴀᴋᴇ requires at least Pʏᴛʜᴏɴ3.6.
* You've changed the terminal encoding. Check the solutions for Ubuntu and Windows below, regardless of your platform.

On Ubuntu:

* Problem: The terminal is using an implicit encoding `LC_ALL=C`? Pʏᴛʜᴏɴ can't handle this.
    * Solution - Use UTF8. Call `export LC_ALL="en_GB.utf8"` from the command line.
        * Replace the `en_GB` with your own locale, e.g. `es.utf8` or `zh_CN.utf8`.

On Windows:

* Problem: `cmd.exe` or _PowerShell_ with an `ASCII`-only font.
    * Solution - Change your font to a Unicode one.
    * Quick workaround - call `set PYTHONIOENCODING=ascii:replace` from the command line
    
On Mac:

* No known problems


### Segmentation fault, killed 9, or GUI fails to run ###

On Ubuntu:

* Problem: At the time of writing, some Linux systems have a corrupt installation of PyQt and/or Qt.
    * Solution: Build PyQt and/or Qt properly from source yourself, see: 
        * [riverbankcomputing.com](https://riverbankcomputing.com/software/pyqt/intro)
        * [qt.io](https://www.qt.io/)
    * Workaround: Just use your Iɴᴛᴇʀᴍᴀᴋᴇ application from one of the CLI modes.
    
On Windows or Mac:

* No known problems

On other systems (e.g. Android):

* Problem: Qt is not installed.
    * Solution: See if you can find a Qt installation for your system.
    * Workaround: Use your Iɴᴛᴇʀᴍᴀᴋᴇ application from one of the CLI modes.
    
### An Iɴᴛᴇʀᴍᴀᴋᴇ application doesn't start for some other reason ###

User errors:

* Problem: Requisite libraries not installed
    * Solution: Please follow the installation instructions included with the application.
    
### General errors ###

Coding errors:

* Problem: A Iɴᴛᴇʀᴍᴀᴋᴇ error occurs.
    * Solution: Report it so it can be fixed. Please include the debug information in your bug report. 
        * GUI: Iɴᴛᴇʀᴍᴀᴋᴇ writes debugging information to the console (`stderr`) whenever it encounters an error in the GUI.
          Launch the GUI from the command line (e.g. `./xxxx "ui gui"`) to see this information.
        * CLI: If an error occurs in the CLI version, details can be retrieved using the `error` command.
            * You can instruct all errors to be dumped by default via the `set` command.
    * Solution: Fix it yourself! All Iɴᴛᴇʀᴍᴀᴋᴇ code is heavily documented in-line using standard Pʏᴛʜᴏɴ documentation. 
    
### Hard to read text in CLI mode ###

Iɴᴛᴇʀᴍᴀᴋᴇ does its best to use colours to distinguish on-screen items, and expects standard ANSI/DOS colours.
If you're getting unreadable items, such as yellow text on a white background, the colours should be changed in your terminal preferences (not the Iɴᴛᴇʀᴍᴀᴋᴇ application itself). Modify your terminal profile/theme or simply turn off ANSI colour support.


### Locked environment ###

Intermake runs as a singleton instance, that is, you cannot define two Intermake applications using the same piece of Python code.
More than one application simply doesn't make sense: which application would the user interact with?
Assuming just one instance simplifies a lot of things, namely, you don't need to specify the application every time you define a plugin.

Normally, you can only set `MENV.name` once. If you try to change the name more than once, an error is raised.
This way, if you accidentally import another `Intermake` application into the current one, you'll get an error, rather than ending up with some weird chimerical hybrid of both applications.

That said, applications can build on top of each other, providing increased levels of functionality.

* To add plugins to an existing application:
    * You can add plugins to an existing application without problem, simply import the existing application's module, then use `@command` to register your own plugins.
* To extend an existing application, replacing the name, version, root object, etc.
    * Import the existing application's module, then call `MENV.unlock` to let Intermake know the name change is not a problem.
* To provide a new application, that can also extend an existing application. 
    * Check `MENV.is_locked`. If this is `True`, another Intermake application is already setup, proceed to add plugins as required but don't redefine the Intermake environment.

If you get a locked environment error, you've tried to `import` another Intermake application module, and that application is trying to overwrite the current one. This might be because:
* You've using `import` on an unreleated Intermake application.
* You're trying to `unpickle` data from that application.
* You've not checked `MENV.is_locked`.
* You've not called `MENV.unlock`.


Meta
----

```ini
type= library
author= Martin Rusilowicz
language= python3
created= 2017
host= bitbucket,pypi
```
