mirror of
https://github.com/YunoHost/moulinette.git
synced 2024-09-03 20:06:31 +02:00
Try to improve time execution and continue refactoring
* Revert to classes centralization into actionsmap.py * Try to optimize conditions and loops * Revisit Package class and get directories from a file generated at build * Early refactoring of i18n * Move yunohost library into /lib
This commit is contained in:
parent
9104024fa1
commit
9c9ccc1271
27 changed files with 660 additions and 632 deletions
16
bin/yunohost
16
bin/yunohost
|
@ -3,25 +3,21 @@
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os.path
|
import os.path
|
||||||
import gettext
|
|
||||||
|
|
||||||
# Run from source
|
# Run from source
|
||||||
basedir = os.path.abspath(os.path.dirname(__file__) +'/../')
|
basedir = os.path.abspath('%s/../' % os.path.dirname(__file__))
|
||||||
if os.path.isdir(basedir +'/src'):
|
if os.path.isdir('%s/src' % basedir):
|
||||||
sys.path.append(basedir +'/src')
|
sys.path.append('%s/src' % basedir)
|
||||||
|
|
||||||
from moulinette import init, cli, MoulinetteError
|
from moulinette import init, cli, MoulinetteError
|
||||||
from moulinette.helpers import YunoHostError, colorize
|
from moulinette.helpers import YunoHostError, colorize
|
||||||
|
|
||||||
gettext.install('yunohost')
|
|
||||||
|
|
||||||
|
|
||||||
## Main action
|
## Main action
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Run from source (prefix and libdir set to None)
|
# Run from source
|
||||||
init('yunohost', prefix=None, libdir=None,
|
init(_from_source=True)
|
||||||
cachedir=os.path.join(basedir, 'cache'))
|
|
||||||
|
|
||||||
# Additional arguments
|
# Additional arguments
|
||||||
use_cache = True
|
use_cache = True
|
||||||
|
@ -39,7 +35,7 @@ if __name__ == '__main__':
|
||||||
raise YunoHostError(17, _("YunoHost is not correctly installed, please execute 'yunohost tools postinstall'"))
|
raise YunoHostError(17, _("YunoHost is not correctly installed, please execute 'yunohost tools postinstall'"))
|
||||||
|
|
||||||
# Execute the action
|
# Execute the action
|
||||||
cli(args, use_cache)
|
cli(['yunohost'], args, use_cache)
|
||||||
except MoulinetteError as e:
|
except MoulinetteError as e:
|
||||||
print(e.colorize())
|
print(e.colorize())
|
||||||
sys.exit(e.code)
|
sys.exit(e.code)
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os.path
|
import os.path
|
||||||
import gettext
|
|
||||||
|
|
||||||
# Run from source
|
# Run from source
|
||||||
basedir = os.path.abspath(os.path.dirname(__file__) +'/../')
|
basedir = os.path.abspath(os.path.dirname(__file__) +'/../')
|
||||||
|
@ -12,8 +11,6 @@ if os.path.isdir(basedir +'/src'):
|
||||||
|
|
||||||
from moulinette import init, api
|
from moulinette import init, api
|
||||||
|
|
||||||
gettext.install('yunohost')
|
|
||||||
|
|
||||||
|
|
||||||
## Callbacks for additional routes
|
## Callbacks for additional routes
|
||||||
|
|
||||||
|
@ -31,9 +28,8 @@ def is_installed():
|
||||||
## Main action
|
## Main action
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Run from source (prefix and libdir set to None)
|
# Run from source
|
||||||
init('yunohost', prefix=None, libdir=None,
|
init(_from_source=True)
|
||||||
cachedir=os.path.join(basedir, 'cache'))
|
|
||||||
|
|
||||||
# Additional arguments
|
# Additional arguments
|
||||||
use_cache = True
|
use_cache = True
|
||||||
|
@ -45,5 +41,5 @@ if __name__ == '__main__':
|
||||||
# TODO: Add log argument
|
# TODO: Add log argument
|
||||||
|
|
||||||
# Rune the server
|
# Rune the server
|
||||||
api(6787, {('GET', '/installed'): is_installed}, use_cache)
|
api(['yunohost'], 6787, {('GET', '/installed'): is_installed}, use_cache)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
0
src/moulinette/extra/__init__.py → lib/yunohost/__init__.py
Normal file → Executable file
0
src/moulinette/extra/__init__.py → lib/yunohost/__init__.py
Normal file → Executable file
|
@ -1,7 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
__title__ = 'moulinette'
|
__title__ = 'moulinette'
|
||||||
__version__ = '695'
|
__version__ = '0.1'
|
||||||
__author__ = ['Kload',
|
__author__ = ['Kload',
|
||||||
'jlebleu',
|
'jlebleu',
|
||||||
'titoko',
|
'titoko',
|
||||||
|
@ -31,12 +31,10 @@ __all__ = [
|
||||||
|
|
||||||
from .core import MoulinetteError
|
from .core import MoulinetteError
|
||||||
|
|
||||||
curr_namespace = None
|
|
||||||
|
|
||||||
|
|
||||||
## Package functions
|
## Package functions
|
||||||
|
|
||||||
def init(namespace=None, **kwargs):
|
def init(**kwargs):
|
||||||
"""Package initialization
|
"""Package initialization
|
||||||
|
|
||||||
Initialize directories and global variables. It must be called
|
Initialize directories and global variables. It must be called
|
||||||
|
@ -44,30 +42,33 @@ def init(namespace=None, **kwargs):
|
||||||
functions.
|
functions.
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
- namespace -- The namespace to initialize and use
|
- **kwargs -- See core.Package
|
||||||
- **kwargs -- See helpers.Package
|
|
||||||
|
|
||||||
At the end, the global variable 'pkg' will contain a Package
|
At the end, the global variable 'pkg' will contain a Package
|
||||||
instance. See helpers.Package for available methods and variables.
|
instance. See core.Package for available methods and variables.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import sys
|
||||||
import __builtin__
|
import __builtin__
|
||||||
from .core import Package
|
from .core import Package, install_i18n
|
||||||
|
|
||||||
global curr_namespace
|
|
||||||
curr_namespace = namespace
|
|
||||||
|
|
||||||
__builtin__.__dict__['pkg'] = Package(**kwargs)
|
__builtin__.__dict__['pkg'] = Package(**kwargs)
|
||||||
|
|
||||||
|
# Initialize internationalization
|
||||||
|
install_i18n()
|
||||||
|
|
||||||
|
# Add library directory to python path
|
||||||
|
sys.path.append(pkg.libdir)
|
||||||
|
|
||||||
|
|
||||||
## Easy access to interfaces
|
## Easy access to interfaces
|
||||||
|
|
||||||
def api(port, routes={}, use_cache=True):
|
def api(namespaces, port, routes={}, use_cache=True):
|
||||||
"""Web server (API) interface
|
"""Web server (API) interface
|
||||||
|
|
||||||
Run a HTTP server with the moulinette for an API usage.
|
Run a HTTP server with the moulinette for an API usage.
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
|
- namespaces -- The list of namespaces to use
|
||||||
- port -- Port to run on
|
- port -- Port to run on
|
||||||
- routes -- A dict of additional routes to add in the form of
|
- routes -- A dict of additional routes to add in the form of
|
||||||
{(method, uri): callback}
|
{(method, uri): callback}
|
||||||
|
@ -79,18 +80,19 @@ def api(port, routes={}, use_cache=True):
|
||||||
from .actionsmap import ActionsMap
|
from .actionsmap import ActionsMap
|
||||||
from .interface.api import MoulinetteAPI
|
from .interface.api import MoulinetteAPI
|
||||||
|
|
||||||
amap = ActionsMap('api', use_cache=use_cache)
|
amap = ActionsMap('api', namespaces, use_cache)
|
||||||
moulinette = MoulinetteAPI(amap, routes)
|
moulinette = MoulinetteAPI(amap, routes)
|
||||||
|
|
||||||
run(moulinette.app, port=port)
|
run(moulinette.app, port=port)
|
||||||
|
|
||||||
def cli(args, use_cache=True):
|
def cli(namespaces, args, use_cache=True):
|
||||||
"""Command line interface
|
"""Command line interface
|
||||||
|
|
||||||
Execute an action with the moulinette from the CLI and print its
|
Execute an action with the moulinette from the CLI and print its
|
||||||
result in a readable format.
|
result in a readable format.
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
|
- namespaces -- The list of namespaces to use
|
||||||
- args -- A list of argument strings
|
- args -- A list of argument strings
|
||||||
- use_cache -- False if it should parse the actions map file
|
- use_cache -- False if it should parse the actions map file
|
||||||
instead of using the cached one
|
instead of using the cached one
|
||||||
|
@ -98,7 +100,7 @@ def cli(args, use_cache=True):
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
from .actionsmap import ActionsMap
|
from .actionsmap import ActionsMap
|
||||||
from .helpers import YunoHostError, pretty_print_dict
|
from .helpers import pretty_print_dict
|
||||||
|
|
||||||
lock_file = '/var/run/moulinette.lock'
|
lock_file = '/var/run/moulinette.lock'
|
||||||
|
|
||||||
|
@ -112,7 +114,7 @@ def cli(args, use_cache=True):
|
||||||
os.system('chmod 400 '+ lock_file)
|
os.system('chmod 400 '+ lock_file)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
amap = ActionsMap('cli', use_cache=use_cache)
|
amap = ActionsMap('cli', namespaces, use_cache)
|
||||||
pretty_print_dict(amap.process(args))
|
pretty_print_dict(amap.process(args))
|
||||||
except KeyboardInterrupt, EOFError:
|
except KeyboardInterrupt, EOFError:
|
||||||
raise MoulinetteError(125, _("Interrupted"))
|
raise MoulinetteError(125, _("Interrupted"))
|
||||||
|
|
|
@ -1,21 +1,478 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import pickle
|
import argparse
|
||||||
import yaml
|
import yaml
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
|
import cPickle as pickle
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from . import __version__, curr_namespace, MoulinetteError
|
from . import __version__
|
||||||
from .extra.parameters import extraparameters_list
|
from .core import MoulinetteError
|
||||||
|
|
||||||
## Extra parameters Parser
|
## Interfaces' Actions map Parser --------------------------------------
|
||||||
|
|
||||||
|
class _AMapParser(object):
|
||||||
|
"""Actions map's base Parser
|
||||||
|
|
||||||
|
Each interfaces must implement a parser class derived from this
|
||||||
|
class. It is used to parse the main parts of the actions map (i.e.
|
||||||
|
general arguments, categories and actions).
|
||||||
|
|
||||||
class ExtraParser(object):
|
|
||||||
"""
|
"""
|
||||||
Global parser for the extra parameters.
|
|
||||||
|
## Optional variables
|
||||||
|
# Each parser classes can overwrite these variables.
|
||||||
|
|
||||||
|
"""Either it will parse general arguments, or not"""
|
||||||
|
parse_general_arguments = True
|
||||||
|
|
||||||
|
|
||||||
|
## Virtual methods
|
||||||
|
# Each parser classes can implement these methods.
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_arg_name(name, full):
|
||||||
|
"""Format argument name
|
||||||
|
|
||||||
|
Format agument name depending on its 'full' parameters and return
|
||||||
|
a list to use it as option string for the argument parser.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- name -- The argument name
|
||||||
|
- full -- The argument's 'full' parameter
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A list of option strings
|
||||||
|
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("derived class '%s' must override this method" % \
|
||||||
|
self.__class__.__name__)
|
||||||
|
|
||||||
|
def add_general_parser(self, **kwargs):
|
||||||
|
"""Add a parser for general arguments
|
||||||
|
|
||||||
|
Create and return an argument parser for general arguments.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An ArgumentParser based object
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not self.parse_general_arguments:
|
||||||
|
msg = "doesn't parse general arguments"
|
||||||
|
else:
|
||||||
|
msg = "must override this method"
|
||||||
|
raise NotImplementedError("derived class '%s' %s" % \
|
||||||
|
(self.__class__.__name__, msg))
|
||||||
|
|
||||||
|
def add_category_parser(self, name, **kwargs):
|
||||||
|
"""Add a parser for a category
|
||||||
|
|
||||||
|
Create a new category and return a parser for it.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- name -- The category name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A BaseParser based object
|
||||||
|
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("derived class '%s' must override this method" % \
|
||||||
|
self.__class__.__name__)
|
||||||
|
|
||||||
|
def add_action_parser(self, name, **kwargs):
|
||||||
|
"""Add a parser for an action
|
||||||
|
|
||||||
|
Create a new action and return an argument parser for it.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- name -- The action name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An ArgumentParser based object
|
||||||
|
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("derived class '%s' must override this method" % \
|
||||||
|
self.__class__.__name__)
|
||||||
|
|
||||||
|
def parse_args(self, args, **kwargs):
|
||||||
|
"""Parse arguments
|
||||||
|
|
||||||
|
Convert argument variables to objects and assign them as
|
||||||
|
attributes of the namespace.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- args -- Arguments string or dict (TODO)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The populated namespace
|
||||||
|
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("derived class '%s' must override this method" % \
|
||||||
|
self.__class__.__name__)
|
||||||
|
|
||||||
|
# CLI Actions map Parser
|
||||||
|
|
||||||
|
class CLIAMapParser(_AMapParser):
|
||||||
|
"""Actions map's CLI Parser
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, parser=None):
|
||||||
|
self._parser = parser or argparse.ArgumentParser()
|
||||||
|
self._subparsers = self._parser.add_subparsers()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_arg_name(name, full):
|
||||||
|
if name[0] == '-' and full:
|
||||||
|
return [name, full]
|
||||||
|
return [name]
|
||||||
|
|
||||||
|
def add_general_parser(self, **kwargs):
|
||||||
|
return self._parser
|
||||||
|
|
||||||
|
def add_category_parser(self, name, category_help=None, **kwargs):
|
||||||
|
"""Add a parser for a category
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- category_help -- A brief description for the category
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new CLIParser object for the category
|
||||||
|
|
||||||
|
"""
|
||||||
|
parser = self._subparsers.add_parser(name, help=category_help)
|
||||||
|
return self.__class__(parser)
|
||||||
|
|
||||||
|
def add_action_parser(self, name, action_help, **kwargs):
|
||||||
|
"""Add a parser for an action
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- action_help -- A brief description for the action
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new argparse.ArgumentParser object for the action
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self._subparsers.add_parser(name, help=action_help)
|
||||||
|
|
||||||
|
def parse_args(self, args, **kwargs):
|
||||||
|
return self._parser.parse_args(args)
|
||||||
|
|
||||||
|
# API Actions map Parser
|
||||||
|
|
||||||
|
class _HTTPArgumentParser(object):
|
||||||
|
"""Argument parser for HTTP requests
|
||||||
|
|
||||||
|
Object for parsing HTTP requests into Python objects. It is based
|
||||||
|
on argparse.ArgumentParser class and implements some of its methods.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
# Initialize the ArgumentParser object
|
||||||
|
self._parser = argparse.ArgumentParser(usage='',
|
||||||
|
prefix_chars='@',
|
||||||
|
add_help=False)
|
||||||
|
self._parser.error = self._error
|
||||||
|
|
||||||
|
self._positional = [] # list(arg_name)
|
||||||
|
self._optional = {} # dict({arg_name: option_strings})
|
||||||
|
|
||||||
|
def set_defaults(self, **kwargs):
|
||||||
|
return self._parser.set_defaults(**kwargs)
|
||||||
|
|
||||||
|
def get_default(self, dest):
|
||||||
|
return self._parser.get_default(dest)
|
||||||
|
|
||||||
|
def add_argument(self, *args, **kwargs):
|
||||||
|
action = self._parser.add_argument(*args, **kwargs)
|
||||||
|
|
||||||
|
# Append newly created action
|
||||||
|
if len(action.option_strings) == 0:
|
||||||
|
self._positional.append(action.dest)
|
||||||
|
else:
|
||||||
|
self._optional[action.dest] = action.option_strings
|
||||||
|
|
||||||
|
return action
|
||||||
|
|
||||||
|
def parse_args(self, args):
|
||||||
|
arg_strings = []
|
||||||
|
|
||||||
|
## Append an argument to the current one
|
||||||
|
def append(arg_strings, value, option_string=None):
|
||||||
|
# TODO: Process list arguments
|
||||||
|
if isinstance(value, bool):
|
||||||
|
# Append the option string only
|
||||||
|
if option_string is not None:
|
||||||
|
arg_strings.append(option_string)
|
||||||
|
elif isinstance(value, str):
|
||||||
|
if option_string is not None:
|
||||||
|
arg_strings.append(option_string)
|
||||||
|
arg_strings.append(value)
|
||||||
|
else:
|
||||||
|
arg_strings.append(value)
|
||||||
|
|
||||||
|
return arg_strings
|
||||||
|
|
||||||
|
# Iterate over positional arguments
|
||||||
|
for dest in self._positional:
|
||||||
|
if dest in args:
|
||||||
|
arg_strings = append(arg_strings, args[dest])
|
||||||
|
|
||||||
|
# Iterate over optional arguments
|
||||||
|
for dest, opt in self._optional.items():
|
||||||
|
if dest in args:
|
||||||
|
arg_strings = append(arg_strings, args[dest], opt[0])
|
||||||
|
return self._parser.parse_args(arg_strings)
|
||||||
|
|
||||||
|
def _error(self, message):
|
||||||
|
# TODO: Raise a proper exception
|
||||||
|
raise MoulinetteError(1, message)
|
||||||
|
|
||||||
|
class APIAMapParser(_AMapParser):
|
||||||
|
"""Actions map's API Parser
|
||||||
|
|
||||||
|
"""
|
||||||
|
parse_general_arguments = False
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._parsers = {} # dict({(method, path): _HTTPArgumentParser})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def routes(self):
|
||||||
|
"""Get current routes"""
|
||||||
|
return self._parsers.keys()
|
||||||
|
|
||||||
|
|
||||||
|
## Implement virtual methods
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_arg_name(name, full):
|
||||||
|
if name[0] != '-':
|
||||||
|
return [name]
|
||||||
|
if full:
|
||||||
|
return [full.replace('--', '@', 1)]
|
||||||
|
if name.startswith('--'):
|
||||||
|
return [name.replace('--', '@', 1)]
|
||||||
|
return [name.replace('-', '@', 1)]
|
||||||
|
|
||||||
|
def add_category_parser(self, name, **kwargs):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def add_action_parser(self, name, api=None, **kwargs):
|
||||||
|
"""Add a parser for an action
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- api -- The action route (e.g. 'GET /' )
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new _HTTPArgumentParser object for the route
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not api:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Validate action route
|
||||||
|
m = re.match('(GET|POST|PUT|DELETE) (/\S+)', api)
|
||||||
|
if not m:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Check if a parser already exists for the route
|
||||||
|
key = (m.group(1), m.group(2))
|
||||||
|
if key in self.routes:
|
||||||
|
raise ValueError("A parser for '%s' already exists" % key)
|
||||||
|
|
||||||
|
# Create and append parser
|
||||||
|
parser = _HTTPArgumentParser()
|
||||||
|
self._parsers[key] = parser
|
||||||
|
|
||||||
|
# Return the created parser
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def parse_args(self, args, route, **kwargs):
|
||||||
|
"""Parse arguments
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- route -- The action route (e.g. 'GET /' )
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Retrieve the parser for the route
|
||||||
|
if route not in self.routes:
|
||||||
|
raise MoulinetteError(22, "No parser for '%s %s' found" % key)
|
||||||
|
|
||||||
|
return self._parsers[route].parse_args(args)
|
||||||
|
|
||||||
|
"""
|
||||||
|
The dict of interfaces names and their associated parser class.
|
||||||
|
|
||||||
|
"""
|
||||||
|
actionsmap_parsers = {
|
||||||
|
'api': APIAMapParser,
|
||||||
|
'cli': CLIAMapParser
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Extra parameters ----------------------------------------------------
|
||||||
|
|
||||||
|
# Extra parameters definition
|
||||||
|
|
||||||
|
class _ExtraParameter(object):
|
||||||
|
"""
|
||||||
|
Argument parser for an extra parameter.
|
||||||
|
|
||||||
|
It is a pure virtual class that each extra parameter classes must
|
||||||
|
implement.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, iface):
|
||||||
|
# TODO: Add conn argument which contains authentification object
|
||||||
|
self.iface = iface
|
||||||
|
|
||||||
|
|
||||||
|
## Required variables
|
||||||
|
# Each extra parameters classes must overwrite these variables.
|
||||||
|
|
||||||
|
"""The extra parameter name"""
|
||||||
|
name = None
|
||||||
|
|
||||||
|
|
||||||
|
## Optional variables
|
||||||
|
# Each extra parameters classes can overwrite these variables.
|
||||||
|
|
||||||
|
"""A list of interface for which the parameter doesn't apply"""
|
||||||
|
skipped_iface = {}
|
||||||
|
|
||||||
|
|
||||||
|
## Virtual methods
|
||||||
|
# Each extra parameters classes can implement these methods.
|
||||||
|
|
||||||
|
def __call__(self, parameter, arg_name, arg_value):
|
||||||
|
"""
|
||||||
|
Parse the argument
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- parameter -- The value of this parameter for the action
|
||||||
|
- arg_name -- The argument name
|
||||||
|
- arg_value -- The argument value
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The new argument value
|
||||||
|
|
||||||
|
"""
|
||||||
|
return arg_value
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate(value, arg_name):
|
||||||
|
"""
|
||||||
|
Validate the parameter value for an argument
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- value -- The parameter value
|
||||||
|
- arg_name -- The argument name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The validated parameter value
|
||||||
|
|
||||||
|
"""
|
||||||
|
return value
|
||||||
|
|
||||||
|
class AskParameter(_ExtraParameter):
|
||||||
|
"""
|
||||||
|
Ask for the argument value if possible and needed.
|
||||||
|
|
||||||
|
The value of this parameter corresponds to the message to display
|
||||||
|
when asking the argument value.
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = 'ask'
|
||||||
|
skipped_iface = { 'api' }
|
||||||
|
|
||||||
|
def __call__(self, message, arg_name, arg_value):
|
||||||
|
# TODO: Fix asked arguments ordering
|
||||||
|
if arg_value:
|
||||||
|
return arg_value
|
||||||
|
|
||||||
|
# Ask for the argument value
|
||||||
|
ret = raw_input(colorize(message + ': ', 'cyan'))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate(klass, value, arg_name):
|
||||||
|
# Allow boolean or empty string
|
||||||
|
if isinstance(value, bool) or (isinstance(value, str) and not value):
|
||||||
|
logging.debug("value of '%s' extra parameter for '%s' argument should be a string" \
|
||||||
|
% (klass.name, arg_name))
|
||||||
|
value = arg_name
|
||||||
|
elif not isinstance(value, str):
|
||||||
|
raise TypeError("Invalid type of '%s' extra parameter for '%s' argument" \
|
||||||
|
% (klass.name, arg_name))
|
||||||
|
return value
|
||||||
|
|
||||||
|
class PasswordParameter(AskParameter):
|
||||||
|
"""
|
||||||
|
Ask for the password argument value if possible and needed.
|
||||||
|
|
||||||
|
The value of this parameter corresponds to the message to display
|
||||||
|
when asking the password.
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = 'password'
|
||||||
|
|
||||||
|
def __call__(self, message, arg_name, arg_value):
|
||||||
|
if arg_value:
|
||||||
|
return arg_value
|
||||||
|
|
||||||
|
# Ask for the password
|
||||||
|
pwd1 = getpass.getpass(colorize(message + ': ', 'cyan'))
|
||||||
|
pwd2 = getpass.getpass(colorize('Retype ' + message + ': ', 'cyan'))
|
||||||
|
if pwd1 != pwd2:
|
||||||
|
raise MoulinetteError(22, _("Passwords don't match"))
|
||||||
|
return pwd1
|
||||||
|
|
||||||
|
class PatternParameter(_ExtraParameter):
|
||||||
|
"""
|
||||||
|
Check if the argument value match a pattern.
|
||||||
|
|
||||||
|
The value of this parameter corresponds to a list of the pattern and
|
||||||
|
the message to display if it doesn't match.
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = 'pattern'
|
||||||
|
|
||||||
|
def __call__(self, arguments, arg_name, arg_value):
|
||||||
|
pattern = arguments[0]
|
||||||
|
message = arguments[1]
|
||||||
|
|
||||||
|
if arg_value is not None and not re.match(pattern, arg_value):
|
||||||
|
raise MoulinetteError(22, message)
|
||||||
|
return arg_value
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate(value, arg_name):
|
||||||
|
# Tolerate string type
|
||||||
|
if isinstance(value, str):
|
||||||
|
logging.warning("value of 'pattern' extra parameter for '%s' argument should be a list" % arg_name)
|
||||||
|
value = [value, _("'%s' argument is not matching the pattern") % arg_name]
|
||||||
|
elif not isinstance(value, list) or len(value) != 2:
|
||||||
|
raise TypeError("Invalid type of 'pattern' extra parameter for '%s' argument" % arg_name)
|
||||||
|
return value
|
||||||
|
|
||||||
|
"""
|
||||||
|
The list of available extra parameters classes. It will keep to this list
|
||||||
|
order on argument parsing.
|
||||||
|
|
||||||
|
"""
|
||||||
|
extraparameters_list = {AskParameter, PasswordParameter, PatternParameter}
|
||||||
|
|
||||||
|
# Extra parameters argument Parser
|
||||||
|
|
||||||
|
class ExtraArgumentParser(object):
|
||||||
|
"""
|
||||||
|
Argument validator and parser for the extra parameters.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- iface -- The running interface
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, iface):
|
def __init__(self, iface):
|
||||||
|
@ -78,69 +535,64 @@ class ExtraParser(object):
|
||||||
return arg_value
|
return arg_value
|
||||||
|
|
||||||
|
|
||||||
## Main class
|
## Main class ----------------------------------------------------------
|
||||||
|
|
||||||
class ActionsMap(object):
|
class ActionsMap(object):
|
||||||
"""
|
"""Validate and process actions defined into an actions map
|
||||||
Validate and process action defined into the actions map.
|
|
||||||
|
|
||||||
The actions map defines features and their usage of the main
|
The actions map defines the features and their usage of the main
|
||||||
application. It is composed by categories which contain one or more
|
application. It is composed by categories which contain one or more
|
||||||
action(s). Moreover, the action can have specific argument(s).
|
action(s). Moreover, the action can have specific argument(s).
|
||||||
|
|
||||||
Keyword arguments:
|
This class allows to manipulate one or several actions maps
|
||||||
|
associated to a namespace. If no namespace is given, it will load
|
||||||
|
all available namespaces.
|
||||||
|
|
||||||
- interface -- Interface type that requires the actions map.
|
Keyword arguments:
|
||||||
Possible value is one of:
|
- interface -- The type of interface which needs the actions map.
|
||||||
|
Possible values are:
|
||||||
- 'cli' for the command line interface
|
- 'cli' for the command line interface
|
||||||
- 'api' for an API usage (HTTP requests)
|
- 'api' for an API usage (HTTP requests)
|
||||||
|
- namespaces -- The list of namespaces to use
|
||||||
- use_cache -- False if it should parse the actions map file
|
- use_cache -- False if it should parse the actions map file
|
||||||
instead of using the cached one.
|
instead of using the cached one.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, interface, use_cache=True):
|
def __init__(self, interface, namespaces=[], use_cache=True):
|
||||||
self.use_cache = use_cache
|
self.use_cache = use_cache
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Retrieve the interface parser
|
# Retrieve the interface parser
|
||||||
mod = __import__('interface.%s' % interface,
|
self._parser_class = actionsmap_parsers[interface]
|
||||||
globals=globals(), level=1,
|
except KeyError:
|
||||||
fromlist=['actionsmap_parser'])
|
|
||||||
parser = getattr(mod, 'actionsmap_parser')
|
|
||||||
except (AttributeError, ImportError):
|
|
||||||
raise MoulinetteError(22, _("Invalid interface '%s'" % interface))
|
raise MoulinetteError(22, _("Invalid interface '%s'" % interface))
|
||||||
else:
|
|
||||||
self._parser_class = parser
|
|
||||||
|
|
||||||
logging.debug("initializing ActionsMap for the '%s' interface" % interface)
|
logging.debug("initializing ActionsMap for the '%s' interface" % interface)
|
||||||
|
|
||||||
|
if len(namespaces) == 0:
|
||||||
|
namespaces = self.get_namespaces()
|
||||||
actionsmaps = {}
|
actionsmaps = {}
|
||||||
namespaces = self.get_actionsmap_namespaces()
|
|
||||||
if curr_namespace and curr_namespace in namespaces:
|
|
||||||
namespaces = [curr_namespace]
|
|
||||||
|
|
||||||
# Iterate over actions map namespaces
|
# Iterate over actions map namespaces
|
||||||
for n in namespaces:
|
for n in namespaces:
|
||||||
logging.debug("loading '%s' actions map namespace" % n)
|
logging.debug("loading '%s' actions map namespace" % n)
|
||||||
|
|
||||||
if use_cache:
|
if use_cache:
|
||||||
# Attempt to load cache if it exists
|
try:
|
||||||
cache_file = '%s/%s.pkl' % (pkg.cachedir('actionsmap'), n)
|
# Attempt to load cache
|
||||||
if os.path.isfile(cache_file):
|
with open('%s/actionsmap/%s.pkl' % (pkg.cachedir, n)) as f:
|
||||||
with open(cache_file, 'r') as f:
|
|
||||||
actionsmaps[n] = pickle.load(f)
|
actionsmaps[n] = pickle.load(f)
|
||||||
else:
|
# TODO: Switch to python3 and catch proper exception
|
||||||
|
except IOError:
|
||||||
self.use_cache = False
|
self.use_cache = False
|
||||||
actionsmaps = self.generate_cache(namespaces)
|
actionsmaps = self.generate_cache(namespaces)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
am_file = '%s/%s.yml' % (pkg.datadir('actionsmap'), n)
|
with open('%s/actionsmap/%s.yml' % (pkg.datadir, n)) as f:
|
||||||
with open(am_file, 'r') as f:
|
|
||||||
actionsmaps[n] = yaml.load(f)
|
actionsmaps[n] = yaml.load(f)
|
||||||
|
|
||||||
# Generate parsers
|
# Generate parsers
|
||||||
self.extraparser = ExtraParser(interface)
|
self.extraparser = ExtraArgumentParser(interface)
|
||||||
self.parser = self._construct_parser(actionsmaps)
|
self.parser = self._construct_parser(actionsmaps)
|
||||||
|
|
||||||
def process(self, args, **kwargs):
|
def process(self, args, **kwargs):
|
||||||
|
@ -152,6 +604,8 @@ class ActionsMap(object):
|
||||||
- **kwargs -- Additional interface arguments
|
- **kwargs -- Additional interface arguments
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# Check moulinette status
|
||||||
|
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
arguments = vars(self.parser.parse_args(args, **kwargs))
|
arguments = vars(self.parser.parse_args(args, **kwargs))
|
||||||
arguments = self._parse_extra_parameters(arguments)
|
arguments = self._parse_extra_parameters(arguments)
|
||||||
|
@ -170,11 +624,12 @@ class ActionsMap(object):
|
||||||
else:
|
else:
|
||||||
# Process the action
|
# Process the action
|
||||||
return func(**arguments)
|
return func(**arguments)
|
||||||
|
return {}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_actionsmap_namespaces():
|
def get_namespaces():
|
||||||
"""
|
"""
|
||||||
Retrieve actions map namespaces from a given path
|
Retrieve available actions map namespaces
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A list of available namespaces
|
A list of available namespaces
|
||||||
|
@ -182,7 +637,7 @@ class ActionsMap(object):
|
||||||
"""
|
"""
|
||||||
namespaces = []
|
namespaces = []
|
||||||
|
|
||||||
for f in os.listdir(pkg.datadir('actionsmap')):
|
for f in os.listdir('%s/actionsmap' % pkg.datadir):
|
||||||
if f.endswith('.yml'):
|
if f.endswith('.yml'):
|
||||||
namespaces.append(f[:-4])
|
namespaces.append(f[:-4])
|
||||||
return namespaces
|
return namespaces
|
||||||
|
@ -201,20 +656,19 @@ class ActionsMap(object):
|
||||||
"""
|
"""
|
||||||
actionsmaps = {}
|
actionsmaps = {}
|
||||||
if not namespaces:
|
if not namespaces:
|
||||||
namespaces = klass.get_actionsmap_namespaces()
|
namespaces = klass.get_namespaces()
|
||||||
|
|
||||||
# Iterate over actions map namespaces
|
# Iterate over actions map namespaces
|
||||||
for n in namespaces:
|
for n in namespaces:
|
||||||
logging.debug("generating cache for '%s' actions map namespace" % n)
|
logging.debug("generating cache for '%s' actions map namespace" % n)
|
||||||
|
|
||||||
# Read actions map from yaml file
|
# Read actions map from yaml file
|
||||||
am_file = pkg.datafile('actionsmap/%s.yml' % n)
|
am_file = '%s/actionsmap/%s.yml' % (pkg.datadir, n)
|
||||||
with open(am_file, 'r') as f:
|
with open(am_file, 'r') as f:
|
||||||
actionsmaps[n] = yaml.load(f)
|
actionsmaps[n] = yaml.load(f)
|
||||||
|
|
||||||
# Cache actions map into pickle file
|
# Cache actions map into pickle file
|
||||||
cache_file = pkg.cachefile('actionsmap/%s.pkl' % n, make_dir=True)
|
with pkg.open_cache('%s.pkl' % n, subdir='actionsmap') as f:
|
||||||
with open(cache_file, 'w') as f:
|
|
||||||
pickle.dump(actionsmaps[n], f)
|
pickle.dump(actionsmaps[n], f)
|
||||||
|
|
||||||
return actionsmaps
|
return actionsmaps
|
||||||
|
@ -291,41 +745,41 @@ class ActionsMap(object):
|
||||||
for n, actionsmap in actionsmaps.items():
|
for n, actionsmap in actionsmaps.items():
|
||||||
if 'general_arguments' in actionsmap:
|
if 'general_arguments' in actionsmap:
|
||||||
# Parse general arguments
|
# Parse general arguments
|
||||||
if top_parser.parse_general:
|
if top_parser.parse_general_arguments:
|
||||||
parser = top_parser.add_general_parser()
|
parser = top_parser.add_general_parser()
|
||||||
for an, ap in actionsmap['general_arguments'].items():
|
for an, ap in actionsmap['general_arguments'].items():
|
||||||
if 'version' in ap:
|
# Replace version number
|
||||||
ap['version'] = ap['version'].replace('%version%',
|
version = ap.get('version', None)
|
||||||
__version__)
|
if version:
|
||||||
|
ap['version'] = version.replace('%version%',
|
||||||
|
__version__)
|
||||||
argname = top_parser.format_arg_name(an, ap.pop('full', None))
|
argname = top_parser.format_arg_name(an, ap.pop('full', None))
|
||||||
parser.add_argument(*argname, **ap)
|
parser.add_argument(*argname, **ap)
|
||||||
del actionsmap['general_arguments']
|
del actionsmap['general_arguments']
|
||||||
|
|
||||||
# Parse categories
|
# Parse categories
|
||||||
for cn, cp in actionsmap.items():
|
for cn, cp in actionsmap.items():
|
||||||
if 'actions' not in cp:
|
try:
|
||||||
|
actions = cp.pop('actions')
|
||||||
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
actions = cp.pop('actions')
|
|
||||||
|
|
||||||
# Add category parser
|
# Add category parser
|
||||||
if top_parser.parse_category:
|
cat_parser = top_parser.add_category_parser(cn, **cp)
|
||||||
cat_parser = top_parser.add_category_parser(cn, **cp)
|
|
||||||
else:
|
|
||||||
cat_parser = top_parser
|
|
||||||
|
|
||||||
# Parse actions
|
# Parse actions
|
||||||
if not top_parser.parse_action:
|
|
||||||
continue
|
|
||||||
for an, ap in actions.items():
|
for an, ap in actions.items():
|
||||||
arguments = ap.pop('arguments', {})
|
arguments = ap.pop('arguments', {})
|
||||||
|
|
||||||
# Add action parser
|
# Add action parser
|
||||||
parser = cat_parser.add_action_parser(an, **ap)
|
parser = cat_parser.add_action_parser(an, **ap)
|
||||||
if not parser:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Store action information
|
try:
|
||||||
parser.set_defaults(_info=(n, cn, an))
|
# Store action information
|
||||||
|
parser.set_defaults(_info=(n, cn, an))
|
||||||
|
except AttributeError:
|
||||||
|
# No parser for the action
|
||||||
|
break
|
||||||
|
|
||||||
# Add action arguments
|
# Add action arguments
|
||||||
for argn, argp in arguments.items():
|
for argn, argp in arguments.items():
|
||||||
|
|
|
@ -5,115 +5,126 @@ import sys
|
||||||
import gettext
|
import gettext
|
||||||
from .helpers import colorize
|
from .helpers import colorize
|
||||||
|
|
||||||
|
# Package manipulation -------------------------------------------------
|
||||||
|
|
||||||
|
def install_i18n(namespace=None):
|
||||||
|
"""Install internationalization
|
||||||
|
|
||||||
|
Install translation based on the package's default gettext domain or
|
||||||
|
on 'namespace' if provided.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- namespace -- The namespace to initialize i18n for
|
||||||
|
|
||||||
|
"""
|
||||||
|
if namespace:
|
||||||
|
try:
|
||||||
|
t = gettext.translation(namespace, pkg.localedir)
|
||||||
|
except IOError:
|
||||||
|
# TODO: Log error
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
t.install()
|
||||||
|
else:
|
||||||
|
gettext.install('moulinette', pkg.localedir)
|
||||||
|
|
||||||
class Package(object):
|
class Package(object):
|
||||||
"""Package representation and easy access
|
"""Package representation and easy access methods
|
||||||
|
|
||||||
Initialize directories and variables for the package and give them
|
Initialize directories and variables for the package and give them
|
||||||
easy access.
|
easy access.
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
- prefix -- The installation prefix
|
- _from_source -- Either the package is running from source or
|
||||||
- libdir -- The library directory; usually, this would be
|
not (only for debugging)
|
||||||
prefix + '/lib' (or '/lib64') when installed
|
|
||||||
- cachedir -- The cache directory; usually, this would be
|
|
||||||
'/var/cache' when installed
|
|
||||||
- destdir -- The destination prefix only if it's an installation
|
|
||||||
|
|
||||||
'prefix' and 'libdir' arguments should be empty in order to run
|
|
||||||
package from source.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, prefix, libdir, cachedir, destdir=None):
|
def __init__(self, _from_source=False):
|
||||||
if not prefix and not libdir:
|
if _from_source:
|
||||||
# Running from source directory
|
import sys
|
||||||
basedir = os.path.abspath(os.path.dirname(sys.argv[0]) +'/../')
|
basedir = os.path.abspath(os.path.dirname(sys.argv[0]) +'/../')
|
||||||
self._datadir = os.path.join(basedir, 'data')
|
|
||||||
self._libdir = os.path.join(basedir, 'src')
|
# Set local directories
|
||||||
self._cachedir = cachedir
|
self._datadir = '%s/data' % basedir
|
||||||
|
self._libdir = '%s/lib' % basedir
|
||||||
|
self._localedir = '%s/po' % basedir
|
||||||
|
self._cachedir = '%s/cache' % basedir
|
||||||
else:
|
else:
|
||||||
self._datadir = os.path.join(prefix, 'share/moulinette')
|
import package
|
||||||
self._libdir = os.path.join(libdir, 'moulinette')
|
|
||||||
self._cachedir = os.path.join(cachedir, 'moulinette')
|
|
||||||
|
|
||||||
# Append library path to python's path
|
# Set system directories
|
||||||
sys.path.append(self._libdir)
|
self._datadir = package.datadir
|
||||||
self._destdir = destdir or None
|
self._libdir = package.libdir
|
||||||
|
self._localedir = package.localedir
|
||||||
|
self._cachedir = package.cachedir
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
if name[0] == '_' and self.__dict__.has_key(name):
|
||||||
|
# Deny reassignation of package directories
|
||||||
|
raise TypeError("cannot reassign constant '%s'")
|
||||||
|
self.__dict__[name] = value
|
||||||
|
|
||||||
|
|
||||||
## Easy access to directories and files
|
## Easy access to package directories
|
||||||
|
|
||||||
def datadir(self, subdir=None, **kwargs):
|
@property
|
||||||
"""Return the path to a data directory"""
|
def datadir(self):
|
||||||
return self.get_dir(self._datadir, subdir, **kwargs)
|
"""Return the data directory of the package"""
|
||||||
|
return self._datadir
|
||||||
|
|
||||||
def datafile(self, filename, **kwargs):
|
@property
|
||||||
"""Return the path to a data file"""
|
def libdir(self):
|
||||||
return self.get_file(self._datadir, filename, **kwargs)
|
"""Return the lib directory of the package"""
|
||||||
|
return self._libdir
|
||||||
|
|
||||||
def libdir(self, subdir=None, **kwargs):
|
@property
|
||||||
"""Return the path to a lib directory"""
|
def localedir(self):
|
||||||
return self.get_dir(self._libdir, subdir, **kwargs)
|
"""Return the locale directory of the package"""
|
||||||
|
return self._localedir
|
||||||
|
|
||||||
def libfile(self, filename, **kwargs):
|
@property
|
||||||
"""Return the path to a lib file"""
|
def cachedir(self):
|
||||||
return self.get_file(self._libdir, filename, **kwargs)
|
"""Return the cache directory of the package"""
|
||||||
|
return self._cachedir
|
||||||
def cachedir(self, subdir=None, **kwargs):
|
|
||||||
"""Return the path to a cache directory"""
|
|
||||||
return self.get_dir(self._cachedir, subdir, **kwargs)
|
|
||||||
|
|
||||||
def cachefile(self, filename, **kwargs):
|
|
||||||
"""Return the path to a cache file"""
|
|
||||||
return self.get_file(self._cachedir, filename, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
## Standard methods
|
## Additional methods
|
||||||
|
|
||||||
def get_dir(self, basedir, subdir=None, make_dir=False):
|
def get_cachedir(self, subdir='', make_dir=True):
|
||||||
"""Get a directory path
|
"""Get the path to a cache directory
|
||||||
|
|
||||||
Return a path composed by a base directory and an optional
|
Return the path to the cache directory from an optional
|
||||||
subdirectory. The path will be created if needed.
|
subdirectory and create it if needed.
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
- basedir -- The base directory
|
- subdir -- A cache subdirectory
|
||||||
- subdir -- An optional subdirectory
|
- make_dir -- False to not make directory if it not exists
|
||||||
- make_dir -- True if it should create needed directory
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Retrieve path
|
path = os.path.join(self.cachedir, subdir)
|
||||||
path = basedir
|
|
||||||
if self._destdir:
|
|
||||||
path = os.path.join(self._destdir, path)
|
|
||||||
if subdir:
|
|
||||||
path = os.path.join(path, subdir)
|
|
||||||
|
|
||||||
# Create directory
|
|
||||||
if make_dir and not os.path.isdir(path):
|
if make_dir and not os.path.isdir(path):
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def get_file(self, basedir, filename, **kwargs):
|
def open_cache(self, filename, subdir='', mode='w'):
|
||||||
"""Get a file path
|
"""Open a cache file and return a stream
|
||||||
|
|
||||||
Return the path of the filename in the specified directory. This
|
Attempt to open in 'mode' the cache file 'filename' from the
|
||||||
directory will be created if needed.
|
default cache directory and in the subdirectory 'subdir' if
|
||||||
|
given. Directories are created if needed and a stream is
|
||||||
|
returned if the file can be written.
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
- basedir -- The base directory of the file
|
- filename -- The cache filename
|
||||||
- filename -- The filename or a path relative to basedir
|
- subdir -- A subdirectory which contains the file
|
||||||
- **kwargs -- Additional arguments for Package.get_dir
|
- mode -- The mode in which the file is opened
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Check for a directory in filename
|
return open('%s/%s' % (self.get_cachedir(subdir), filename), mode)
|
||||||
subdir = os.path.dirname(filename) or None
|
|
||||||
if subdir:
|
|
||||||
filename = os.path.basename(filename)
|
|
||||||
|
|
||||||
# Get directory path
|
|
||||||
dirpath = self.get_dir(basedir, subdir, **kwargs)
|
|
||||||
return os.path.join(dirpath, filename)
|
|
||||||
|
|
||||||
|
# Moulinette core classes ----------------------------------------------
|
||||||
|
|
||||||
class MoulinetteError(Exception):
|
class MoulinetteError(Exception):
|
||||||
"""Moulinette base exception
|
"""Moulinette base exception
|
||||||
|
|
|
@ -1,159 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import getpass
|
|
||||||
import re
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from .. import MoulinetteError
|
|
||||||
from ..helpers import colorize
|
|
||||||
|
|
||||||
class _ExtraParameter(object):
|
|
||||||
"""
|
|
||||||
Argument parser for an extra parameter.
|
|
||||||
|
|
||||||
It is a pure virtual class that each extra parameter classes must
|
|
||||||
implement.
|
|
||||||
|
|
||||||
"""
|
|
||||||
def __init__(self, iface):
|
|
||||||
# TODO: Add conn argument which contains authentification object
|
|
||||||
self.iface = iface
|
|
||||||
|
|
||||||
|
|
||||||
## Required variables
|
|
||||||
# Each extra parameters classes must overwrite these variables.
|
|
||||||
|
|
||||||
"""The extra parameter name"""
|
|
||||||
name = None
|
|
||||||
|
|
||||||
|
|
||||||
## Optional variables
|
|
||||||
# Each extra parameters classes can overwrite these variables.
|
|
||||||
|
|
||||||
"""A list of interface for which the parameter doesn't apply"""
|
|
||||||
skipped_iface = {}
|
|
||||||
|
|
||||||
|
|
||||||
## Virtual methods
|
|
||||||
# Each extra parameters classes can implement these methods.
|
|
||||||
|
|
||||||
def __call__(self, parameter, arg_name, arg_value):
|
|
||||||
"""
|
|
||||||
Parse the argument
|
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
- parameter -- The value of this parameter for the action
|
|
||||||
- arg_name -- The argument name
|
|
||||||
- arg_value -- The argument value
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The new argument value
|
|
||||||
|
|
||||||
"""
|
|
||||||
return arg_value
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def validate(value, arg_name):
|
|
||||||
"""
|
|
||||||
Validate the parameter value for an argument
|
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
- value -- The parameter value
|
|
||||||
- arg_name -- The argument name
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The validated parameter value
|
|
||||||
|
|
||||||
"""
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
## Extra parameters definitions
|
|
||||||
|
|
||||||
class AskParameter(_ExtraParameter):
|
|
||||||
"""
|
|
||||||
Ask for the argument value if possible and needed.
|
|
||||||
|
|
||||||
The value of this parameter corresponds to the message to display
|
|
||||||
when asking the argument value.
|
|
||||||
|
|
||||||
"""
|
|
||||||
name = 'ask'
|
|
||||||
skipped_iface = { 'api' }
|
|
||||||
|
|
||||||
def __call__(self, message, arg_name, arg_value):
|
|
||||||
# TODO: Fix asked arguments ordering
|
|
||||||
if arg_value:
|
|
||||||
return arg_value
|
|
||||||
|
|
||||||
# Ask for the argument value
|
|
||||||
ret = raw_input(colorize(message + ': ', 'cyan'))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def validate(klass, value, arg_name):
|
|
||||||
# Allow boolean or empty string
|
|
||||||
if isinstance(value, bool) or (isinstance(value, str) and not value):
|
|
||||||
logging.debug("value of '%s' extra parameter for '%s' argument should be a string" \
|
|
||||||
% (klass.name, arg_name))
|
|
||||||
value = arg_name
|
|
||||||
elif not isinstance(value, str):
|
|
||||||
raise TypeError("Invalid type of '%s' extra parameter for '%s' argument" \
|
|
||||||
% (klass.name, arg_name))
|
|
||||||
return value
|
|
||||||
|
|
||||||
class PasswordParameter(AskParameter):
|
|
||||||
"""
|
|
||||||
Ask for the password argument value if possible and needed.
|
|
||||||
|
|
||||||
The value of this parameter corresponds to the message to display
|
|
||||||
when asking the password.
|
|
||||||
|
|
||||||
"""
|
|
||||||
name = 'password'
|
|
||||||
|
|
||||||
def __call__(self, message, arg_name, arg_value):
|
|
||||||
if arg_value:
|
|
||||||
return arg_value
|
|
||||||
|
|
||||||
# Ask for the password
|
|
||||||
pwd1 = getpass.getpass(colorize(message + ': ', 'cyan'))
|
|
||||||
pwd2 = getpass.getpass(colorize('Retype ' + message + ': ', 'cyan'))
|
|
||||||
if pwd1 != pwd2:
|
|
||||||
raise MoulinetteError(22, _("Passwords don't match"))
|
|
||||||
return pwd1
|
|
||||||
|
|
||||||
class PatternParameter(_ExtraParameter):
|
|
||||||
"""
|
|
||||||
Check if the argument value match a pattern.
|
|
||||||
|
|
||||||
The value of this parameter corresponds to a list of the pattern and
|
|
||||||
the message to display if it doesn't match.
|
|
||||||
|
|
||||||
"""
|
|
||||||
name = 'pattern'
|
|
||||||
|
|
||||||
def __call__(self, arguments, arg_name, arg_value):
|
|
||||||
pattern = arguments[0]
|
|
||||||
message = arguments[1]
|
|
||||||
|
|
||||||
if arg_value is not None and not re.match(pattern, arg_value):
|
|
||||||
raise MoulinetteError(22, message)
|
|
||||||
return arg_value
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def validate(value, arg_name):
|
|
||||||
# Tolerate string type
|
|
||||||
if isinstance(value, str):
|
|
||||||
logging.warning("value of 'pattern' extra parameter for '%s' argument should be a list" % arg_name)
|
|
||||||
value = [value, _("'%s' argument is not matching the pattern") % arg_name]
|
|
||||||
elif not isinstance(value, list) or len(value) != 2:
|
|
||||||
raise TypeError("Invalid type of 'pattern' extra parameter for '%s' argument" % arg_name)
|
|
||||||
return value
|
|
||||||
|
|
||||||
"""
|
|
||||||
The list of available extra parameters classes. It will keep to this list
|
|
||||||
order on argument parsing.
|
|
||||||
|
|
||||||
"""
|
|
||||||
extraparameters_list = {AskParameter, PasswordParameter, PatternParameter}
|
|
|
@ -1,25 +1,15 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import ldap
|
||||||
try:
|
|
||||||
import ldap
|
|
||||||
except ImportError:
|
|
||||||
sys.stderr.write('Error: Yunohost CLI Require LDAP lib\n')
|
|
||||||
sys.stderr.write('apt-get install python-ldap\n')
|
|
||||||
sys.exit(1)
|
|
||||||
import ldap.modlist as modlist
|
import ldap.modlist as modlist
|
||||||
import yaml
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import getpass
|
import getpass
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import argparse
|
|
||||||
import gettext
|
import gettext
|
||||||
import getpass
|
import getpass
|
||||||
if not __debug__:
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
win = []
|
win = []
|
||||||
|
|
||||||
|
|
114
src/moulinette/interface/__init__.py
Executable file → Normal file
114
src/moulinette/interface/__init__.py
Executable file → Normal file
|
@ -1,114 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
class BaseParser(object):
|
|
||||||
"""Actions map's base Parser
|
|
||||||
|
|
||||||
Each interfaces must implement a parser class derived from this
|
|
||||||
class. It is used to parse the main parts of the actions map (i.e.
|
|
||||||
general arguments, categories and actions).
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
## Optional variables
|
|
||||||
# Each parser classes can overwrite these variables.
|
|
||||||
|
|
||||||
"""Either it will parse general arguments, or not"""
|
|
||||||
parse_general = True
|
|
||||||
|
|
||||||
"""Either it will parse categories, or not"""
|
|
||||||
parse_category = True
|
|
||||||
|
|
||||||
"""Either it will parse actions, or not"""
|
|
||||||
parse_action = True
|
|
||||||
|
|
||||||
|
|
||||||
## Virtual methods
|
|
||||||
# Each parser classes can implement these methods.
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def format_arg_name(name, full):
|
|
||||||
"""Format argument name
|
|
||||||
|
|
||||||
Format agument name depending on its 'full' parameters and return
|
|
||||||
a list to use it as option string for the argument parser.
|
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
- name -- The argument name
|
|
||||||
- full -- The argument's 'full' parameter
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A list of option strings
|
|
||||||
|
|
||||||
"""
|
|
||||||
raise NotImplementedError("derived class '%s' must override this method" % \
|
|
||||||
self.__class__.__name__)
|
|
||||||
|
|
||||||
def add_general_parser(self, **kwargs):
|
|
||||||
"""Add a parser for general arguments
|
|
||||||
|
|
||||||
Create and return an argument parser for general arguments.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
An ArgumentParser based object
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not self.parse_general:
|
|
||||||
msg = "doesn't parse general arguments"
|
|
||||||
else:
|
|
||||||
msg = "must override this method"
|
|
||||||
raise NotImplementedError("derived class '%s' %s" % \
|
|
||||||
(self.__class__.__name__, msg))
|
|
||||||
|
|
||||||
def add_category_parser(self, name, **kwargs):
|
|
||||||
"""Add a parser for a category
|
|
||||||
|
|
||||||
Create a new category and return a parser for it.
|
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
- name -- The category name
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A BaseParser based object
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not self.parse_categories:
|
|
||||||
msg = "doesn't parse categories"
|
|
||||||
else:
|
|
||||||
msg = "must override this method"
|
|
||||||
raise NotImplementedError("derived class '%s' %s" % \
|
|
||||||
(self.__class__.__name__, msg))
|
|
||||||
|
|
||||||
def add_action_parser(self, name, **kwargs):
|
|
||||||
"""Add a parser for an action
|
|
||||||
|
|
||||||
Create a new action and return an argument parser for it.
|
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
- name -- The action name
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
An ArgumentParser based object
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not self.parse_general:
|
|
||||||
msg = "doesn't parse actions"
|
|
||||||
else:
|
|
||||||
msg = "must override this method"
|
|
||||||
raise NotImplementedError("derived class '%s' %s" % \
|
|
||||||
(self.__class__.__name__, msg))
|
|
||||||
|
|
||||||
def parse_args(self, args, **kwargs):
|
|
||||||
"""Parse arguments
|
|
||||||
|
|
||||||
Convert argument variables to objects and assign them as
|
|
||||||
attributes of the namespace.
|
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
- args -- Arguments string or dict (TODO)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The populated namespace
|
|
||||||
|
|
||||||
"""
|
|
||||||
raise NotImplementedError("derived class '%s' must override this method" % \
|
|
||||||
self.__class__.__name__)
|
|
|
@ -1,160 +1,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import re
|
|
||||||
import argparse
|
|
||||||
import os.path
|
|
||||||
from bottle import Bottle, request, response, HTTPResponse
|
from bottle import Bottle, request, response, HTTPResponse
|
||||||
from beaker.middleware import SessionMiddleware
|
from beaker.middleware import SessionMiddleware
|
||||||
|
|
||||||
from . import BaseParser
|
from ..core import MoulinetteError
|
||||||
from .. import MoulinetteError
|
|
||||||
from ..helpers import YunoHostError, YunoHostLDAP
|
from ..helpers import YunoHostError, YunoHostLDAP
|
||||||
|
|
||||||
## API arguments Parser
|
# API moulinette interface ---------------------------------------------
|
||||||
|
|
||||||
class _HTTPArgumentParser(object):
|
|
||||||
"""Argument parser for HTTP requests
|
|
||||||
|
|
||||||
Object for parsing HTTP requests into Python objects. It is based
|
|
||||||
on argparse.ArgumentParser class and implements some of its methods.
|
|
||||||
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
# Initialize the ArgumentParser object
|
|
||||||
self._parser = argparse.ArgumentParser(usage='',
|
|
||||||
prefix_chars='@',
|
|
||||||
add_help=False)
|
|
||||||
self._parser.error = self._error
|
|
||||||
|
|
||||||
self._positional = [] # list(arg_name)
|
|
||||||
self._optional = {} # dict({arg_name: option_strings})
|
|
||||||
|
|
||||||
def set_defaults(self, **kwargs):
|
|
||||||
return self._parser.set_defaults(**kwargs)
|
|
||||||
|
|
||||||
def get_default(self, dest):
|
|
||||||
return self._parser.get_default(dest)
|
|
||||||
|
|
||||||
def add_argument(self, *args, **kwargs):
|
|
||||||
action = self._parser.add_argument(*args, **kwargs)
|
|
||||||
|
|
||||||
# Append newly created action
|
|
||||||
if len(action.option_strings) == 0:
|
|
||||||
self._positional.append(action.dest)
|
|
||||||
else:
|
|
||||||
self._optional[action.dest] = action.option_strings
|
|
||||||
|
|
||||||
return action
|
|
||||||
|
|
||||||
def parse_args(self, args):
|
|
||||||
arg_strings = []
|
|
||||||
|
|
||||||
## Append an argument to the current one
|
|
||||||
def append(arg_strings, value, option_string=None):
|
|
||||||
# TODO: Process list arguments
|
|
||||||
if isinstance(value, bool):
|
|
||||||
# Append the option string only
|
|
||||||
if option_string is not None:
|
|
||||||
arg_strings.append(option_string)
|
|
||||||
elif isinstance(value, str):
|
|
||||||
if option_string is not None:
|
|
||||||
arg_strings.append(option_string)
|
|
||||||
arg_strings.append(value)
|
|
||||||
else:
|
|
||||||
arg_strings.append(value)
|
|
||||||
|
|
||||||
return arg_strings
|
|
||||||
|
|
||||||
# Iterate over positional arguments
|
|
||||||
for dest in self._positional:
|
|
||||||
if dest in args:
|
|
||||||
arg_strings = append(arg_strings, args[dest])
|
|
||||||
|
|
||||||
# Iterate over optional arguments
|
|
||||||
for dest, opt in self._optional.items():
|
|
||||||
if dest in args:
|
|
||||||
arg_strings = append(arg_strings, args[dest], opt[0])
|
|
||||||
return self._parser.parse_args(arg_strings)
|
|
||||||
|
|
||||||
def _error(self, message):
|
|
||||||
# TODO: Raise a proper exception
|
|
||||||
raise MoulinetteError(1, message)
|
|
||||||
|
|
||||||
class APIParser(BaseParser):
|
|
||||||
"""Actions map's API Parser
|
|
||||||
|
|
||||||
"""
|
|
||||||
parse_category = False
|
|
||||||
parse_general = False
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._parsers = {} # dict({(method, path): _HTTPArgumentParser})
|
|
||||||
|
|
||||||
@property
|
|
||||||
def routes(self):
|
|
||||||
"""Get current routes"""
|
|
||||||
return self._parsers.keys()
|
|
||||||
|
|
||||||
|
|
||||||
## Implement virtual methods
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def format_arg_name(name, full):
|
|
||||||
if name[0] != '-':
|
|
||||||
return [name]
|
|
||||||
if full:
|
|
||||||
return [full.replace('--', '@', 1)]
|
|
||||||
if name.startswith('--'):
|
|
||||||
return [name.replace('--', '@', 1)]
|
|
||||||
return [name.replace('-', '@', 1)]
|
|
||||||
|
|
||||||
def add_action_parser(self, name, api=None, **kwargs):
|
|
||||||
"""Add a parser for an action
|
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
- api -- The action route (e.g. 'GET /' )
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A new _HTTPArgumentParser object for the route
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not api:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Validate action route
|
|
||||||
m = re.match('(GET|POST|PUT|DELETE) (/\S+)', api)
|
|
||||||
if not m:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Check if a parser already exists for the route
|
|
||||||
key = (m.group(1), m.group(2))
|
|
||||||
if key in self.routes:
|
|
||||||
raise ValueError("A parser for '%s' already exists" % key)
|
|
||||||
|
|
||||||
# Create and append parser
|
|
||||||
parser = _HTTPArgumentParser()
|
|
||||||
self._parsers[key] = parser
|
|
||||||
|
|
||||||
# Return the created parser
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def parse_args(self, args, route, **kwargs):
|
|
||||||
"""Parse arguments
|
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
- route -- The action route (e.g. 'GET /' )
|
|
||||||
|
|
||||||
"""
|
|
||||||
# Retrieve the parser for the route
|
|
||||||
if route not in self.routes:
|
|
||||||
raise MoulinetteError(22, "No parser for '%s %s' found" % key)
|
|
||||||
|
|
||||||
return self._parsers[route].parse_args(args)
|
|
||||||
|
|
||||||
actionsmap_parser = APIParser
|
|
||||||
|
|
||||||
|
|
||||||
## API moulinette interface
|
|
||||||
|
|
||||||
class _APIAuthPlugin(object):
|
class _APIAuthPlugin(object):
|
||||||
"""
|
"""
|
||||||
|
@ -189,7 +41,7 @@ class _APIAuthPlugin(object):
|
||||||
session_opts = {
|
session_opts = {
|
||||||
'session.type': 'file',
|
'session.type': 'file',
|
||||||
'session.cookie_expires': True,
|
'session.cookie_expires': True,
|
||||||
'session.data_dir': pkg.cachedir('session', make_dir=True),
|
'session.data_dir': pkg.get_cachedir('session'),
|
||||||
'session.secure': True
|
'session.secure': True
|
||||||
}
|
}
|
||||||
self._app = SessionMiddleware(app, session_opts)
|
self._app = SessionMiddleware(app, session_opts)
|
||||||
|
@ -361,11 +213,11 @@ class MoulinetteAPI(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if category is None:
|
if category is None:
|
||||||
with open(pkg.datafile('doc/resources.json')) as f:
|
with open('%s/doc/resources.json' % pkg.datadir) as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(pkg.datafile('doc/%s.json' % category)) as f:
|
with open('%s/doc/%s.json' % (pkg.datadir, category)) as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
except IOError:
|
except IOError:
|
||||||
return 'unknown'
|
return 'unknown'
|
||||||
|
|
|
@ -1,35 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import argparse
|
class MoulinetteCLI(object):
|
||||||
from . import BaseParser
|
# TODO: Implement this class
|
||||||
|
pass
|
||||||
## CLI arguments Parser
|
|
||||||
|
|
||||||
class CLIParser(BaseParser):
|
|
||||||
"""Actions map's CLI Parser
|
|
||||||
|
|
||||||
"""
|
|
||||||
def __init__(self, parser=None):
|
|
||||||
self._parser = parser or argparse.ArgumentParser()
|
|
||||||
self._subparsers = self._parser.add_subparsers()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def format_arg_name(name, full):
|
|
||||||
if name[0] == '-' and full:
|
|
||||||
return [name, full]
|
|
||||||
return [name]
|
|
||||||
|
|
||||||
def add_general_parser(self, **kwargs):
|
|
||||||
return self._parser
|
|
||||||
|
|
||||||
def add_category_parser(self, name, category_help=None, **kwargs):
|
|
||||||
parser = self._subparsers.add_parser(name, help=category_help)
|
|
||||||
return CLIParser(parser)
|
|
||||||
|
|
||||||
def add_action_parser(self, name, action_help, **kwargs):
|
|
||||||
return self._subparsers.add_parser(name, help=action_help)
|
|
||||||
|
|
||||||
def parse_args(self, args, **kwargs):
|
|
||||||
return self._parser.parse_args(args)
|
|
||||||
|
|
||||||
actionsmap_parser = CLIParser
|
|
||||||
|
|
15
src/moulinette/package.py
Normal file
15
src/moulinette/package.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Public constants defined during build
|
||||||
|
|
||||||
|
"""Package's data directory (e.g. /usr/share/moulinette)"""
|
||||||
|
datadir = '/usr/share/moulinette'
|
||||||
|
|
||||||
|
"""Package's library directory (e.g. /usr/lib/moulinette)"""
|
||||||
|
libdir = '/usr/lib/moulinette'
|
||||||
|
|
||||||
|
"""Locale directory for the package (e.g. /usr/lib/moulinette/locale)"""
|
||||||
|
localedir = '/usr/lib/moulinette/locale'
|
||||||
|
|
||||||
|
"""Cache directory for the package (e.g. /var/cache/moulinette)"""
|
||||||
|
cachedir = '/var/cache/moulinette'
|
15
src/moulinette/package.py.in
Normal file
15
src/moulinette/package.py.in
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Public constants defined during build
|
||||||
|
|
||||||
|
"""Package's data directory (e.g. /usr/share/moulinette)"""
|
||||||
|
datadir = %PKGDATADIR%
|
||||||
|
|
||||||
|
"""Package's library directory (e.g. /usr/lib/moulinette)"""
|
||||||
|
libdir = %PKGLIBDIR%
|
||||||
|
|
||||||
|
"""Locale directory for the package (e.g. /usr/lib/moulinette/locale)"""
|
||||||
|
localedir = %PKGLOCALEDIR%
|
||||||
|
|
||||||
|
"""Cache directory for the package (e.g. /var/cache/moulinette)"""
|
||||||
|
cachedir = %PKGCACHEDIR%
|
Loading…
Add table
Reference in a new issue