Skip to content

Commit

Permalink
Nested mode-parsers can be created using JSON,
Browse files Browse the repository at this point in the history
major refactoring in `clap/builder.py`, update to version 0.7.4
  • Loading branch information
Marek Marecki committed Aug 7, 2013
1 parent 33377b5 commit 9ca94eb
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 111 deletions.
23 changes: 23 additions & 0 deletions Changelog.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@

----

#### Version 0.7.4 (2013-08-07):

This version brings support for creating nested modes in JSON interfaces.
Apart from this, some refactoring had been done in `clap/builder.py`.
`clap.builder.ModesParser()` is no longer there - only object that is needed
to build an interface is `clap.builder.Builder()`.
Builder functions, and element-type recognition functions, are exposed so you can
use them directly with no need to initialize builder object.
However, I don't see a need for this - if you would wnat to translate dicts and
lists to interfaces and bother with all the stuff around them it's easier to just
code the whole interface by hand. This functionality will never be removed.

* __new__: `isparser()`, `isoption()` and `ismodesparser()` functions in `clap.builder`,
* __new__: `buildparser()` and `buildmodesparser()` functions in `clap.builder`,

* __upd__: `clap.builder.Builder()` is no longer limited to simple parsers - it can
now build also single- and nested-modes parsers.

* __rem__: `clap.builder.ModesParser()` is removed and it's functionality is now in
`clap.builder.Builder()`

----

#### Version 0.7.3 (2013-08-06):

This version debugs stuff (I hope) and let's you create simple-parser interfaces using
Expand Down
2 changes: 1 addition & 1 deletion clap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@
from clap import modes
from clap import builder

__version__ = '0.7.3'
__version__ = '0.7.4'
162 changes: 92 additions & 70 deletions clap/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,92 @@

import clap


# type recognition functions
def isoption(data):
"""Checks if given data can be treated as a representation of option.
"""
return type(data) == dict and ('short' in data or 'long' in data)

def isparser(data):
"""Checks if given data can be treated as a representation of parser.
"""
correct_type = type(data) == list
correct_contents = True
for d in data:
if not isoption(d):
correct_type = False
break
return correct_type and correct_contents

def ismodesparser(data):
"""Checks if given data can be treated as a representation of modes parser.
"""
correct_contents = True
for d in data:
if not isparser(d) or not ismodesparser(d):
correct_contents = False
break
return type(data) == dict and ('short' not in data and 'long' not in data)


# building functions
# you can use them directly to convert native Python data types
# to interfaces
def buildparser(data, argv=[]):
"""Builds parser from list.
:param data: data used to build UI
:type data: list[dict]
"""
p = clap.parser.Parser(argv=argv)
for option in data: p.add(**option)
return p

def buildmodesparser(data, argv=[]):
"""Builds modes parser from dict.
:param data: data used to build UI
:type data: dict
"""
p = clap.modes.Parser(argv=argv)
for mode in data:
if mode == '__global__': continue
element = data[mode]
if isparser(element): element = buildparser(element)
elif ismodesparser(element): element = buildmodesparser(element)
else: raise Exception('invalid element: {0}'.format(element))
p.addMode(mode, element)
if '__global__' in data:
if type(data) != list: raise TypeError('"__global__" mus be a list of options')
for option in data['__global__']: p.addOption(**option)
return p


# builder object - use it to build from JSON
class Builder():
"""This object responsible for building interfaces.
"""This object is used to convert JSON representations to CLAP interfaces.
It can build simple parsers, single-level mode parsers and nested mode parsers.
Before initialization, make sure that the ui file you are trying to use is
present because it is loaded immediately.
**Notice**: Builder will not format the input for you. It must be done explicitly.
f = clap.formater(Formater(sys.argv[1:])
f.format()
builder = clap.builder.Builder(path='/home/user/.stuff/ui.json', argv=list(f))
The code above is a useful snippet.
"""
def __init__(self, path, argv=[]):
"""Initialization of a builder.
:param path: path to JSON representing interface
:type path: str
:param argv: formated input list
:type argv: list
"""
self.path = path
self.argv = argv
self.data = None
Expand All @@ -33,13 +115,13 @@ def _load(self):
def _applyhandlers(self):
"""Replaces type names in JSON with their callback functions.
"""
for i, option in enumerate(self.data):
if 'arguments' in option:
for n, name in enumerate(option['arguments']): option['arguments'][n] = self.types[name]
self.data[i] = option
for p in self.data: self.data[p] = self._applyhandlersto(self.data[p])

def _applyhandlersto(self, parser):
"""Replaces type names in given parser-list with their callback functions.
:param parser: list of options
:type parsre: list[dict]
"""
for i, option in enumerate(parser):
if 'arguments' in option:
Expand All @@ -55,73 +137,13 @@ def addTypeHandler(self, name, callback):
def build(self):
"""Builds the interface.
"""
if isparser(self.data): self.interface = buildparser(self._applyhandlersto(self.data), argv=self.argv)
if isparser(self.data):
self.interface = buildparser(self._applyhandlersto(self.data), argv=self.argv)
elif ismodesparser(self.data):
self._applyhandlers()
self.interface = buildmodesparser(data=self.data, argv=self.argv)

def get(self):
"""Returns built interface.
"""
return self.interface


class ModesParser(Builder):
def _applyhandlers(self):
"""Replaces type names in JSON with their callback functions.
"""
for p in self.data:
parser = self.data[p]
for i, option in enumerate(parser):
if 'arguments' in option:
for n, name in enumerate(option['arguments']): option['arguments'][n] = self.types[name]
parser[i] = option
self.data[p] = parser

def build(self):
"""Applies type handlers to options and
converts loaded JSON to actual interface.
"""
self.interface = clap.modes.Parser(argv=self.argv)
self._applyhandlers()
for mode in self.data:
if mode == '__global__': continue
self.interface.addMode(mode, buildparser(self.data[mode]))
if '__global__' in self.data:
for option in self.data['__global__']: self.interface.addOption(**option)


def buildparser(data, argv=[]):
"""Builds parser from dict.
:param data: data used to build UI
:type data: dict
"""
p = clap.parser.Parser(argv=argv)
for option in data: p.add(**option)
return p


def isoption(data):
"""Checks if given data can be treated as a representation of option.
"""
return type(data) == dict and ('short' in data or 'long' in data)


def isparser(data):
"""Checks if given data can be treated as a representation of parser.
"""
correct_type = type(data) == list
correct_contents = True
for d in data:
if not isoption(d):
correct_type = False
break
return correct_type and correct_contents


def ismodesparser(data):
"""Checks if given data can be treated as a representation of modes parser.
"""
correct_contents = True
for d in data:
if not isparser(d) or not ismodesparser(d):
correct_contents = False
break
return type(data) == dict and ('short' not in data and 'long' not in data)
18 changes: 6 additions & 12 deletions manual/builder.mdown
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,16 @@

Since version 0.7.2 it is possible to build user interface entirely in JSON and
use CLAP to load it.
Every feature that can be used when creating interfaces using Python API is also
available using JSON builders.
Since 0.7.4 every feature that can be used when creating interfaces using Python API is also
available using JSON builder.


----

#### Building

Parser is defined as a JSON list containing dictionaries describing options.
Syntax for options is:

* key: name of the parameter as would be passed to `parser.Parser.add()` via
Python API,
* value: value as would be passed with the parameter,

Only thing that needs to be set up in Python are *type-handlers* that are not built-in
Python data types. These are the callback functions which are used for `arguments`
parameter in `parser.Parser.add()`. In JSON they are referred to as strings and need
Only thing that needs to be set up in native Python are *type-handlers* that are not built-in
Python data types.
These *type-handlers* are the callback functions which are used for `arguments`
parameter in `clap.parser.Parser.add()`. In JSON they are referred to as strings and need
to be replaced by real functions. `str`, `int` and `float` are always available.
55 changes: 27 additions & 28 deletions manual/dynamic_interfaces.mdown
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,33 @@ be easier to spot interface updates.

### Limitations of JSON interfaces

Currently (as of version 0.7.2) builder does not support nested modes so to achieve
something like:
There is no method to access Python data types from JSON because it would violate
the syntax of JSON.

nature laws physics --disable
^ ^ ^ ^
mode | | | |
| | |
mode --------| | |
| |
mode -------------------| |
|
option ------------------------------|


is not possible and you have to code it by hand in Python.
But fear not - I'm working on this issue.

----

### Handling option types

There is no method to access Python types straghtly from JSON because it would violate
the syntax of JSON and the interface wouldn't be built successfully.
Virtually every type can be accessed without much hassle - via *type-handlers*.
In JSON types are represented as strings - `"str"` or `"int"` for example.

However, virtually every type can be accessed without much hassle - via *type-handlers*.
A *handler* as a string - `str`, `int` or `foo` or `bar` - associated with some callback
function.
`Builder()` object has `addTypeHandler()` method which is used for assigning such strings
`Builder()` object has `addTypeHandler()` method which is used for assigning strings
to their callback functions.

**USAGE**: `Builder().addTypeHandler('string-identifer', some.callback.function)`

Type handler is added to builder like this:

builder = clap.builder.Builder('/path/to/fancy/ui.json', argv=[...])
builder.addTypeHandler('url', datatypes.url)
builder.addTypeHandler('str', str)
builder.addTypeHandler('float', float)
...

You have to add handler for every data type used in your interface or it won't be built.

where `datatypes.url()` is function validating URLs.
Note that you don't've to add type handlers for `str`, `int` and `float` types.

Note that you don't've to add type handlers for `str`, `int` and `float` types
as they are handled implicitly.

----

Expand All @@ -59,8 +45,8 @@ as they are handled implicitly.
#### 1. Option

Options are represented in JSON as dictionaries and appended to their parsers using
the handy feature of `parser.add(**option)` dictionary expansion (I cannot force myself
to memorize the name of it but it's handy).
the handy feature of dictionary expansion (or something like this I cannot force myself
to memorize the name) - `parser.add(**option)`.

Example option (covering all possible fields):

Expand All @@ -75,6 +61,9 @@ Example option (covering all possible fields):
"not_with": ["--spam"],
}


`isoption()` function in `clap.builder` is used to check if a dict is valid option.

----

#### 2. Parser
Expand Down Expand Up @@ -146,5 +135,15 @@ Example:
]
}

Nested modes are created by assigning another dict representing mode parser to a mode name.

Not that `__global__` mode must be a simple list of option and cannot be a modes-parser.


----

### Reference implementations

Unfortunatelly, there is now way to define nested modes.
In main directory of CLAP library there are files containing example in their names.
They are reference implementations of builders and example JSON interfaces which you can read and
make yourself more knowledgeable about usage of CLAP.
Loading

0 comments on commit 9ca94eb

Please sign in to comment.