summaryrefslogtreecommitdiffstats
path: root/doc
diff options
context:
space:
mode:
authorMichal Minar <miminar@redhat.com>2013-09-09 13:33:15 +0200
committerMichal Minar <miminar@redhat.com>2013-09-09 15:02:08 +0200
commit7046b0ea22cc4e72a2e6cd26d56d716aa0b3b9b0 (patch)
treeff5a0595f39e80a5a574250fb0b14c9d0704f4e9 /doc
parenta3e636b09062e3dc63a0ab350caadfb2759c59b7 (diff)
downloadopenlmi-scripts-7046b0ea22cc4e72a2e6cd26d56d716aa0b3b9b0.tar.gz
openlmi-scripts-7046b0ea22cc4e72a2e6cd26d56d716aa0b3b9b0.tar.xz
openlmi-scripts-7046b0ea22cc4e72a2e6cd26d56d716aa0b3b9b0.zip
added documentation
documentation is composed of two parts: 1. usage of lmi meta-command 2. writing custom scripts
Diffstat (limited to 'doc')
-rw-r--r--doc/Makefile162
-rw-r--r--doc/command-classes.rst140
-rw-r--r--doc/command-properties.rst379
-rw-r--r--doc/conf.py242
-rw-r--r--doc/index.rst16
-rw-r--r--doc/make.bat190
-rw-r--r--doc/script-development.rst450
-rw-r--r--doc/usage.rst38
8 files changed, 1617 insertions, 0 deletions
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..387690e
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,162 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext mof generated
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+%.png: %.dia
+ dia -e $@ $<
+
+%.svg: %.uml plantuml.cfg
+ plantuml -config plantuml.cfg -Tsvg $<
+
+figures:
+
+
+html: figures
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/AnacondaStorageProvider.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/AnacondaStorageProvider.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/AnacondaStorageProvider"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/AnacondaStorageProvider"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/doc/command-classes.rst b/doc/command-classes.rst
new file mode 100644
index 0000000..07575dd
--- /dev/null
+++ b/doc/command-classes.rst
@@ -0,0 +1,140 @@
+.. _command_classes:
+
+Command classes
+===============
+Before reading this, please make sure you're familiar with
+:ref:`command_wrappers_overview`.
+
+We focus here on commands intended for subclassing in command wrapper modules.
+*OpenLMI Scripts* defines and uses other kinds of commands internally. But
+the script developer does not need to know about them.
+
+.. _end-point_commands:
+
+End-point commands
+------------------
+Were already introduced before (see :ref:`end-point_commands_introduction`).
+We'll dive into details here.
+
+Every end-point command allows to verify and transform options parsed by
+docopt_ before they are passed to associated function. This can happen in
+methods:
+
+``verify_options(self, options)``
+ Taking pre-processed options dictionary as a first argument.
+ Properties affecting this pre-processing can be found in
+ :ref:`pre_processing_properties`. This method shall check option values or
+ their combination and raise ``lmi.scripts.common.errors.LmiInvalidOptions``
+ if any inconsistency discovered.
+
+ Example usage: ::
+
+ class FileLister(command.LmiInstanceLister):
+ DYNAMIC_PROPERTIES = True
+
+ def verify_options(self, options):
+ file_types = { 'all', 'file', 'directory', 'symlink'
+ , 'fifo', 'device'}
+ if ( options['--type'] is not None
+ and options['--type'] not in file_types):
+ raise errors.LmiInvalidOptions(
+ 'invalid file type given, must be one of %s' %
+ file_types)
+
+``transform_options(self, options)``
+ Taking verified options dictionary. It modifies this dictionary in
+ arbitrary way in place. Its return value is ignored.
+
+ Example usage: ::
+
+ class Lister(command.LmiLister):
+ COLUMNS = ('Device', 'Name', "ElementName", "Type")
+
+ def transform_options(self, options):
+ """
+ Rename 'device' option to 'devices' parameter name for better
+ readability.
+ """
+ options['<devices>'] = options.pop('<device>')
+
+Above methods can be used to process options in a way, that any script library
+function can be called. In a case we need more control over what is called or
+when we want to decide at runtime which function shall be call, we may override
+``execute()`` method instead. Example of this may be found at
+:ref:`associating_a_function`.
+
+.. _lmi_check_result:
+
+``LmiCheckResult``
+~~~~~~~~~~~~~~~~~~
+This command invokes associated function on hosts in session, collects results
+from them and compares them to an expected value. It does not produce any
+output, when all returned values are expected.
+
+This command class is very useful when wrapping up some CIM class's method
+such as ``LMI_Service::StartService()``. Example can be seen in
+:ref:`property_descriptions`.
+
+Its specific properties are listed in :ref:`lmi_check_result_properties`.
+
+.. _lmi_lister:
+
+``LmiLister``
+~~~~~~~~~~~~~
+Prints a table like data. It expects associated function to return its result
+in form: ::
+
+ [row1, row2, ...]
+
+Where ``rowX`` is a tuple containing row values. Each such row is ``list`` or
+``tuple`` of the same length. There is a property ``COLUMNS`` defining column
+names [#]_ (see :ref:`lmi_lister_properties`). Generator is prefered over
+a ``list`` of rows. If ``COLUMNS`` property is omitted, returned value shall
+take the following form instead: ::
+
+ (columns, data)
+
+Where ``columns`` has the same meaning as ``COLUMNS`` as a class property and
+``data`` is the result of previous case [#]_.
+
+.. _lmi_instance_lister:
+
+``LmiInstanceLister``
+~~~~~~~~~~~~~~~~~~~~~
+Is a variant of ``LmiLister``. Instead of rows being tuples, here they are
+instances of some CIM class. Instead of using ``COLUMNS`` property for
+specifying columns labels, ``PROPERTIES`` is used for the same purpose here.
+Its primary use is in specifying, which properties of instances shall be
+rendered in which column. This is described in detail in
+:ref:`lmi_instance_lister_properties`.
+
+The expected output of associated function is therefore: ::
+
+ [instance1, instance2, ...]
+
+Again, usage of generators is preferred.
+
+.. _lmi_show_instance:
+
+``LmiShowInstance``
+~~~~~~~~~~~~~~~~~~~
+Renders a single instance of some CIM class. It's rendered in a form of
+two-column table. Where the first column contains property names and
+the second their corresponding values. Rendering is controlled in the same
+way as for ``LmiInstanceLister`` (see :ref:`lmi_show_instance_properties`).
+
+.. seealso::
+
+ General and class specific properties in :ref:`command_properties`.
+
+.. ****************************************************************************
+
+.. _CIM: http://dmtf.org/standards/cim
+.. _OpenLMI: http://fedorahosted.org/openlmi/
+.. _openlmi-tools: http://fedorahosted.org/openlmi/wiki/shell
+.. _docopt: http://docopt.org/
+
+-------------------------------------------------------------------------------
+
+.. [#] Having the same lenght as each row in returned data.
+.. [#] Generator or a ``list`` of rows.
diff --git a/doc/command-properties.rst b/doc/command-properties.rst
new file mode 100644
index 0000000..cad41dd
--- /dev/null
+++ b/doc/command-properties.rst
@@ -0,0 +1,379 @@
+.. _command_properties:
+
+Command properties
+==================
+
+As noted before in :ref:`end-point_commands`, command at first tries to
+process input arguments, calls an associated function and then renders its
+result. We'll now introduce properties affecting this process.
+
+Command class properties are written in their bodies and handled by their
+metaclasses. After being processed, they are removed from class. So they are
+not accessible as class attributes or from their instances.
+
+.. _pre_processing_properties:
+
+Options pre-processing
+----------------------
+Influencing properties:
+
+ * ``OPT_NO_UNDERSCORES`` (opt_no_underscores_)
+ * ``ARG_ARRAY_SUFFIX`` (arg_array_suffix_)
+ * ``OWN_USAGE`` (own_usage_)
+
+docopt_ will make a dictionary of options based on usage string such
+as the one above (:ref:`usage_string`). Options dictionary matching this
+example looks like this: ::
+
+ { 'list' : bool
+ , '--all' : bool
+ , '--disabled' : bool
+ , 'start' : bool
+ , '<service>' : str
+ }
+
+Values of this dictionary are passed to an associated function as arguments
+with names created out of matching keys. Since argument names can not contain
+characters such as `'<'`, `'>'`, `'-'`, etc. These need to be replaced.
+Process of renaming of these options can be described by the following pseudo
+algorithm:
+
+.. _options_transform_algorithm:
+
+ 1. arguments enclosed in brackets are un-surrounded -- brackets get
+ removed ::
+
+ "<service>" -> "service"
+
+ 2. arguments written in upper case are made lower cased ::
+
+ "FILE" -> "file"
+
+ 3. prefix of short and long options made of dashes shall be replaced with
+ single underscore ::
+
+ "-a" -> "_a"
+ "--all" -> "_all"
+
+ 4. any not-empty sequence of characters not allowed in python's identitier
+ shall be replaced with a single underscore ::
+
+ "_long-option" -> "_long_option"
+ "special--cmd-#2" -> "special_cmd_2"
+
+Points 3 and 4 could be merged into one. But we separate them due to effects
+of ``OPT_NO_UNDERSCORES`` property described below.
+
+Property descriptions
+~~~~~~~~~~~~~~~~~~~~~
+.. _opt_no_underscores:
+
+``OPT_NO_UNDERSCORES`` : ``bool`` (defaults to ``False``)
+ Modifies point 3 of options pre-processing. It causes the prefix of dashes
+ to be completely removed with no replacement: ::
+
+ "--long-options" -> "long-option"
+
+ This may not be save if there is a command with the same name as the
+ option being removed. Setting this property to ``True`` will cause
+ overwriting the command with the value of option. A warning shall be
+ echoed if such a case occurs.
+
+.. _arg_array_suffix:
+
+``ARG_ARRAY_SUFFIX`` : ``str`` (defaults to ``""``)
+ Adds additional point (5) to `options_transform_algorithm`_. All
+ repeatable arguments, resulting in a ``list`` of items are renamed to
+ ``<original_name><suffix>`` [#]_. Repeatable argument in usage string
+ looks like this: ::
+
+ """
+ Usage: %(cmd)s start <service> ...
+ """
+
+ Causing all of the ``<service>`` arguments being loaded into a ``list``
+ object.
+
+.. _own_usage:
+
+``OWN_USAGE`` : ``bool`` (defaults to ``False``)
+ Says, whether the documentation string of this class is a usage string.
+ Each command in hierarchy can have its own usage string.
+
+ This can also be assigned a usage string directly: ::
+
+ class MySubcommand(LmiCheckResult):
+ """
+ Class doc string.
+ """
+ OWN_USAGE = "Usage: %(cmd)s --opt1 --opt1 <file> <args> ..."
+ EXPECT = 0
+
+ But using a boolean value is more readable: ::
+
+ class MySubcommand(LmiCheckResult):
+ """
+ Usage: %(cmd)s --opt1 --opt1 <file> <args> ...
+ """
+ OWN_USAGE = True
+ EXPECT = 0
+
+ .. note::
+
+ Using own usage strings in subcommands of top-level commands is not
+ recommended. It brings a lot of redundancy and may prove problematic
+ to modify while keeping consistency among hierarchically nested
+ usages.
+
+ Therefore try to have just one usage string in a top-level command.
+ And one top-level command in a single module. Resulting in one usage
+ string per one command wrappers module. This makes it easier to read
+ and modify.
+
+.. _associating_a_function:
+
+Associating a function
+----------------------
+Influencing properties:
+
+ * ``CALLABLE`` (callable_)
+
+When command is invoked, it's method ``execute()`` will get verified and
+transformed options as a positional and keyword arguments. This method
+shall pass them to an associated function residing in script library and
+return its result on completition.
+
+One way to associate a function is to use ``CALLABLE`` property. The other
+is to define very own ``execute()`` method like this: ::
+
+ class Lister(command.LmiInstanceLister):
+ PROPERTIES = ('Name', "Started", 'Status')
+
+ def execute(self, ns, _all, _disabled, _oneshot):
+ kind = 'enabled'
+ if _all:
+ kind = 'all'
+ elif _disabled:
+ kind = 'disabled'
+ elif _oneshot:
+ kind = 'oneshot'
+ for service_inst in service.list_services(ns, kind):
+ yield service_inst
+
+This approach allows to access properties of command and application allowing
+for more advanced approaches to options handling, verifying and result
+post-processing.
+
+.. _property_descriptions:
+
+Property descriptions
+~~~~~~~~~~~~~~~~~~~~~
+.. _callable:
+
+``CALLABLE`` : ``str`` (defaults to ``None``)
+ This is a mandatory option if ``execute()`` method is not overriden.
+ It may be a string composed of a full path of module and its callable
+ delimited with ``':'``: ::
+
+ CALLABLE = 'lmi.scripts.service:start'
+
+ Causes function ``start()`` of ``'lmi.scripts.service'`` module to be
+ associated with command.
+
+ Callable may also be assigned directly like this: ::
+
+ from lmi.scripts import service
+ class Start(command.LmiCheckResult):
+ CALLABLE = service.start
+ EXPECT = 0
+
+ The first variant (by assigning string) comes handy, if the particular
+ module of associated function is not yet imported. Thus delaying the
+ import until the point of function's invocation - if the execution comes
+ to this point at all. In short it speeds up execution of ``lmi``
+ meta-command by reducing number of module imports, that are not needed.
+
+.. _function_invocation:
+
+Function invocation
+-------------------
+Influencing properties:
+
+ * ``NAMESPACE`` (namespace_)
+
+Property descriptions
+~~~~~~~~~~~~~~~~~~~~~
+
+.. _namespace:
+
+``NAMESPACE`` : ``str`` (defaults to ``None``)
+ This property affects the first argument passed to an associated function.
+ Various values have different impact:
+
+ +-----------+---------------------------------------+-------------------+
+ | Value | Value of first argument. | Its type |
+ +===========+=======================================+===================+
+ | ``None`` | Same impact as value ``"root/cimv2"`` | ``LMINamespace`` |
+ +-----------+---------------------------------------+-------------------+
+ | ``False`` | Raw connection object | ``LMIConnection`` |
+ +-----------+---------------------------------------+-------------------+
+ | any path | Namespace object with given path | ``LMINamespace`` |
+ +-----------+---------------------------------------+-------------------+
+
+ This usually won't need any modification. Sometimes perhaps associated
+ function might want to access more than one namespace, in that case
+ an instance of ``LMIConnection`` might provide more useful.
+
+ Namespace can also be overriden globally in a configuration file or with
+ an option on command line.
+
+Command specific properties
+---------------------------
+Each command class can have its own specific properties. Let's take a look on
+them.
+
+``LmiCommandMultiplexer``
+~~~~~~~~~~~~~~~~~~~~~~~~~
+.. _commands:
+
+``COMMANDS`` : ``dict`` (mandatory)
+ Dictionary assigning subcommands to their names listed in usage string.
+ Example follows: ::
+
+ class MyCommand(LmiCommandMultiplexer):
+ '''
+ My command description.
+
+ Usage: %(cmd)s mycommand (subcmd1 | subcmd2)
+ '''
+ COMMANDS = {'subcmd1' : Subcmd1, 'subcmd2' : Subcmd2}
+ OWN_USAGE = True
+
+ Where ``Subcmd1`` and ``Subcmd2`` are some other ``LmiBaseCommand``
+ subclasses. Documentation string must be parseable with docopt_.
+
+ ``COMMANDS`` property will be translated to ``child_commands()`` class
+ method by ``lmi.scripts.common.command.meta.MultiplexerMetaClass``.
+
+.. _lmi_lister_properties:
+
+``LmiLister`` properties
+~~~~~~~~~~~~~~~~~~~~~~~~
+.. _columns:
+
+``COLUMNS`` : ``tuple`` (mandatory)
+ Column names. It's a tuple with name for each column. Each row of data
+ shall then contain the same number of items as this tuple. If omitted,
+ associated function is expected to provide them in the first row of
+ returned list. It's translated to ``get_columns()`` class method.
+
+.. _lmi_instance_commands_properties:
+.. _lmi_show_instance_properties:
+.. _lmi_instance_lister_properties:
+
+``LmiShowInstance`` and ``LmiInstanceLister`` properties
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+These two classes expect as a result of their associated function an instance
+or a list of instances of some CIM class. They take care of rendering them to
+standard output. Thus their properties affect the way, how their properties
+are rendered.
+
+.. _properties:
+
+``PROPERTIES`` : ``tuple``
+ Property names in the same order as the properties shall be listed. Items
+ of this tuple can take multiple forms:
+
+ Property Name : ``str``
+ Will be used for the name of column/property in output table and the
+ same name will be used when obtaining the value from instance. Thus
+ this form may be used only if the property name of instance can appear
+ as a name of column.
+
+ (Column Name, Property Name) : ``(str, str)``
+ This pair allows to render value of property under different name
+ (*Column Name*).
+
+ (Column Name, getter) : ``(str, callable)``
+ This way allows the value to be arbitrarily computed. The second
+ item is a callable taking one and only argument -- the instance of
+ class to be rendered.
+
+ Example below shows different ways of rendering attributes for instances
+ of ``LMI_Service`` CIM class: ::
+
+ class Show(command.LmiShowInstance):
+ CALLABLE = 'lmi.scripts.service:get_instance'
+ PROPERTIES = (
+ 'Name',
+ ('Enabled', lambda i: i.EnabledDefault == 2),
+ ('Active', 'Started'))
+
+ First property will be shown with the same label as the name of property.
+ Second one modifies the value of ``EnabledDefault`` from integer to
+ ``bool`` representing enabled state. The last one uses different label for
+ property name ``Started``.
+
+.. _dynamic_properties:
+
+``DYNAMIC_PROPERTIES`` : ``bool`` (defaults to ``False``)
+ Whether the associated function is expected to return the properties tuple
+ itself. If ``True``, the result of associated function must be in form: ::
+
+ (properties, data)
+
+ Where ``properties`` have the same inscription and meaning as a
+ ``PROPERTIES`` property of class.
+
+ Otherwise only the ``data`` is expexted which has different meaning
+ for ``LmiShowInstance`` and ``LmiInstanceLister``.
+
+.. note::
+
+ Omitting both ``PROPERTIES`` and ``DYNAMIC_PROPERTIES`` makes the
+ ``LmiShowInstance`` render all attributes of instance. For
+ ``LmiInstanceLister`` this is not allowed, either ``DYNAMIC_PROPERTIES``
+ must be ``True`` or ``PROPERTIES`` must be filled.
+
+
+.. _lmi_check_result_properties:
+
+``LmiCheckResult`` properties
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+This command typically does not produce any output if operation succeeds.
+The operation succeeds if the result of associated function is
+expected. There are more ways, how to say, what is an expected result.
+One way is to use ``EXPECT`` property. The other is to provide very own
+definition of ``check_result()`` method.
+
+.. _expect:
+
+``EXPECT``: (mandatory)
+ Any value can be assigned to this property. This value is then expected
+ to be returned by associated function. Unexpected result is treated
+ as an error.
+
+ A callable object assigned here has special meaning. This object must
+ accept exactly two parameters:
+
+ 1. options - Dictionary with parsed command line options returned by
+ docopt_ after being processed by ``transform_options()``.
+ 2. result - Return value of associated function.
+
+.. seealso::
+
+ Docopt_ home page and its git: http://github.org/docopt/docopt.
+
+-------------------------------------------------------------------------------
+
+.. [#] Angle brackets here just mark the boundaries of name components. They
+ have nothing to do with arguments.
+
+.. ****************************************************************************
+
+.. _CIM: http://dmtf.org/standards/cim
+.. _OpenLMI: http://fedorahosted.org/openlmi/
+.. _openlmi-tools: http://fedorahosted.org/openlmi/wiki/shell
+.. _docopt: http://docopt.org/
+.. _docopt-git: http://github.org/docopt
+
diff --git a/doc/conf.py b/doc/conf.py
new file mode 100644
index 0000000..787394c
--- /dev/null
+++ b/doc/conf.py
@@ -0,0 +1,242 @@
+# -*- coding: utf-8 -*-
+#
+# OpenLMI Scripts documentation build configuration file, created by
+# sphinx-quickstart on Thu Sep 5 12:50:18 2013.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.todo', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'OpenLMI Scripts'
+copyright = u'2013, Michal Minář'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.2.0'
+# The full version, including alpha/beta/rc tags.
+release = '0.2.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'OpenLMIScriptsdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index_new', 'OpenLMIScripts.tex', u'OpenLMI Scripts Documentation',
+ u'Michal Minář', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index_new', 'openlmiscripts', u'OpenLMI Scripts Documentation',
+ [u'Michal Minář'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ ('index_new', 'OpenLMIScripts', u'OpenLMI Scripts Documentation',
+ u'Michal Minář', 'OpenLMIScripts', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
diff --git a/doc/index.rst b/doc/index.rst
new file mode 100644
index 0000000..4dbeffc
--- /dev/null
+++ b/doc/index.rst
@@ -0,0 +1,16 @@
+OpenLMI Scripts documentation
+=============================
+
+Client-side python modules and command line utilities. Contains python
+libraries for interfacing with OpenLMI providers through local or remote CIMOM
+with WBEM as a protocol in between. It also contains `lmi` meta-command
+allowing to instrument these libraries from command line.
+
+Content:
+
+.. toctree::
+ :maxdepth: 2
+
+ usage
+ configuration
+ script-development
diff --git a/doc/make.bat b/doc/make.bat
new file mode 100644
index 0000000..c9db9bc
--- /dev/null
+++ b/doc/make.bat
@@ -0,0 +1,190 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
+set I18NSPHINXOPTS=%SPHINXOPTS% source
+if NOT "%PAPER%" == "" (
+ set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+ set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+ :help
+ echo.Please use `make ^<target^>` where ^<target^> is one of
+ echo. html to make standalone HTML files
+ echo. dirhtml to make HTML files named index.html in directories
+ echo. singlehtml to make a single large HTML file
+ echo. pickle to make pickle files
+ echo. json to make JSON files
+ echo. htmlhelp to make HTML files and a HTML help project
+ echo. qthelp to make HTML files and a qthelp project
+ echo. devhelp to make HTML files and a Devhelp project
+ echo. epub to make an epub
+ echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+ echo. text to make text files
+ echo. man to make manual pages
+ echo. texinfo to make Texinfo files
+ echo. gettext to make PO message catalogs
+ echo. changes to make an overview over all changed/added/deprecated items
+ echo. linkcheck to check all external links for integrity
+ echo. doctest to run all doctests embedded in the documentation if enabled
+ goto end
+)
+
+if "%1" == "clean" (
+ for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+ del /q /s %BUILDDIR%\*
+ goto end
+)
+
+if "%1" == "html" (
+ %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+ goto end
+)
+
+if "%1" == "dirhtml" (
+ %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+ goto end
+)
+
+if "%1" == "singlehtml" (
+ %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+ goto end
+)
+
+if "%1" == "pickle" (
+ %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the pickle files.
+ goto end
+)
+
+if "%1" == "json" (
+ %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the JSON files.
+ goto end
+)
+
+if "%1" == "htmlhelp" (
+ %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+ goto end
+)
+
+if "%1" == "qthelp" (
+ %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+ echo.^> qcollectiongenerator %BUILDDIR%\qthelp\OpenLMIScripts.qhcp
+ echo.To view the help file:
+ echo.^> assistant -collectionFile %BUILDDIR%\qthelp\OpenLMIScripts.ghc
+ goto end
+)
+
+if "%1" == "devhelp" (
+ %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished.
+ goto end
+)
+
+if "%1" == "epub" (
+ %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The epub file is in %BUILDDIR%/epub.
+ goto end
+)
+
+if "%1" == "latex" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "text" (
+ %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The text files are in %BUILDDIR%/text.
+ goto end
+)
+
+if "%1" == "man" (
+ %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The manual pages are in %BUILDDIR%/man.
+ goto end
+)
+
+if "%1" == "texinfo" (
+ %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+ goto end
+)
+
+if "%1" == "gettext" (
+ %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+ goto end
+)
+
+if "%1" == "changes" (
+ %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.The overview file is in %BUILDDIR%/changes.
+ goto end
+)
+
+if "%1" == "linkcheck" (
+ %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+ goto end
+)
+
+if "%1" == "doctest" (
+ %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+ goto end
+)
+
+:end
diff --git a/doc/script-development.rst b/doc/script-development.rst
new file mode 100644
index 0000000..5298208
--- /dev/null
+++ b/doc/script-development.rst
@@ -0,0 +1,450 @@
+Script Development
+==================
+This provides a general overview on what script is, how is it written
+and is interfaced with.
+
+Prerequisities
+--------------
+Reader should be familiar about a CIM_ (Common Information Model). He should
+have a general idea about, what OpenLMI_ is and what it does. He should get
+familiar with ``lmishell``, which is a python binary shipped with
+openlmi-tools_.
+
+Also user should be acquinted in writing unix's command line utilities help
+strings [#]_.
+
+Introduction
+------------
+By a *script* in this document we mean:
+
+ * Python library exploiting ``lmishell`` for instrumenting CIM providers
+ through a CIMOM broker. It resides in ``lmi.scripts.<profile_name>``
+ package, where ``<profile_name>`` corresponds to a DMTF profile or an
+ arbitrary set of ``OpenLMI`` providers.
+ * Command wrappers for this library as a set of classes inheriting from
+ ``lmi.scripts.common.command.base.LmiBaseCommand``. These may create a
+ hierarchy of nested subcommands. They are the entry points of ``lmi``
+ meta-command to the wrapped functionality of library.
+
+Command wrappers are part of the library usually grouped in a single
+module named after the ``lmi`` subcommand or ``cmd``: ::
+
+ lmi.scripts.<profile_name>.cmd
+
+Writing a library
+-----------------
+Library shall consist of a set of functions taking a namespace or
+connection object as a first argument. There are no special requirements
+on how to divide these functions into submodules. Use common sense. Smaller
+scripts can have all functionality in
+``lmi/scripts/<profile_name>/__init__.py`` module. With wrappers usually
+contained in ``lmi/scripts/<profile_name>/cmd.py``.
+
+Library should be written with an ease of use in mind. Functions should
+represent possible use cases of what can be done with particular
+providers instead of wrapping 1-to-1 a CIM class's methods in python
+functions.
+
+Any function that shall be called by a command wrapper must accect
+a ``namespace`` argument, which by a convention is called as ``ns``.
+It's an instance of ``lmi.shell.LMINamespace`` providing quick access to
+represented CIM namespace [#]_ and its classes. It's also possible to specify
+that function shall be passed a raw ``lmi.shell.LMIConnection`` object.
+For details see :ref:`function_invocation`.
+
+Service example
+~~~~~~~~~~~~~~~
+Suppose we have a service profile and we want to write a python interface
+for. Real provider implementation can be found at ``src/service`` directory
+in upstream git [#]_. For more information please refer to `service description`_.
+
+As you may see, this implements single CIM class ``LMI_Service`` with a few
+useful methods such as:
+
+ * ``StartService()``
+ * ``StopService()``
+
+We'd like our users to provide a way, how to list system services, get a
+details for one of them and allow to start, stop and restart them.
+
+Simplified [#]_ version of some of these functions may look like this: ::
+
+ def list_services(ns, kind='enabled'):
+ for service in sorted(ns.LMI_Service.instances(),
+ key=lambda i: i.Name):
+ if kind == 'disabled' and service.EnabledDefault != \
+ ns.LMI_Service.EnabledDefaultValues.Disabled:
+ continue
+ if kind == 'oneshot' and service.EnabledDefault != \
+ ns.LMI_Service.EnabledDefaultValues.NotApplicable:
+ continue
+ if kind == 'enabled' and service.EnabledDefault != \
+ ns.LMI_Service.EnabledDefaultValues.Enabled:
+ # list only enabled
+ continue
+ yield service
+
+It yields instances of ``LMI_Service`` class. There is no need to use
+exclusively ``yield`` statement instead of ``return``. We prefer to use it in
+enumerating functions because of memory usage reduce (which is possible to
+occur in the future, when underlying components will also allow for lazy
+evaluation). Moreover user may limit the number of instances listed, reducing
+the number of instances evaluated.
+
+::
+
+ from lmi.shell import LMIInstanceName
+ from lmi.scripts.common import get_logger
+ from lmi.scripts.common.errors import LmiFailed
+
+ LOG = get_logger(__name__)
+
+ def start_service(ns, service):
+ if isinstance(service, basestring):
+ # let's accept service as a string
+ inst = ns.LMI_Service.first_instance(key="Name", value=service)
+ name = service
+ else: # or as LMIInstance or LMIInstanceName
+ inst = service
+ name = inst.path['Name']
+ if inst is None:
+ raise LmiFailed('No such service "%s"' % name)
+ if isinstance(inst, LMIInstanceName):
+ # we need LMIInstance
+ inst = inst.to_instance()
+ res = inst.StartService()
+ if res == 0:
+ LOG().debug('started service "%s" on hostname "%s"',
+ name, ns.hostname)
+ return res
+
+In similar fashion, ``stop_service``, ``restart_service`` and others could be
+written.
+
+``ns`` argument typically points to ``root/cimv2`` namespace, which is the
+main implementation namespace for ``OpenLMI`` providers. One could also write
+these functions operating upon a connection object like this: ::
+
+ def get_instance(c, service):
+ inst = c.root.cimv2.LMI_Service.first_instance(
+ key="Name", value=service)
+ if inst is None:
+ raise LmiFailed('No such service "%s"' % service)
+ return inst
+
+User can then easily access any other namespace he may need. Command classes
+need to be informed about, what wrapped function expects. This will be
+explained later in more detail (see :ref:`function_invocation`).
+
+The ``LOG`` variable provides acces to the logger of this module. Messages
+logged in this way end up in a log file [#]_ and console. Implicitly only
+warnings and higher priority messages are logged into a console. This is
+controllable with ``lmi`` parameters.
+
+Useful informations should not be rendered or printed by these functions
+directly. Wrapper commands shall post-process instances or data returned,
+render useful information and print it on standard output stream.
+
+If operation fails due to some not-so-unexpected error, please use
+``lmi.scripts.common.errors.LmiFailed`` exception with human readable
+description.
+
+For more *real world* examples, take a look on scripts already present in our
+`upstream git`_.
+
+.. _command_wrappers_overview:
+
+Command wrappers overview
+-------------------------
+They are a set of commands wrapping up library's functionality in a set of
+commands creating a tree invocable by ``lmi`` meta-command. All commands are
+subclasses of ``lmi.scripts.common.command.base.LmiBaseCommand``.
+
+Behaviour of commands is controlled by class properties such as these: ::
+
+ class Show(command.LmiShowInstance):
+ CALLABLE = 'lmi.scripts.service:get_instance'
+ PROPERTIES = (
+ 'Name',
+ 'Caption',
+ ('Enabled', lambda i: i.EnabledDefault == 2),
+ ('Active', 'Started'),
+ 'Status')
+
+Example above contains definition of **show** command for instances of
+``LMI_Service``. Its associated function is ``get_instance()`` located in
+``lmi.scripts.service`` module [#]_. Properties used will be described in
+detail later (see :ref:`lmi_instance_commands_properties`). Let's just say,
+that ``PROPERTIES`` specify a way, how the instance is rendered.
+
+Top-level commands
+~~~~~~~~~~~~~~~~~~
+Are entry points of a script library. They are direct subcommands of ``lmi``.
+For example: ::
+
+ $ lmi help
+ $ lmi service list
+ $ lmi sw show openlmi-providers
+
+``help``, ``service`` and ``sw`` are top-level commands. One script library
+(such a ``service`` above) can provide one or more of them. They need to be
+listed in a ``setup.py`` script in ``entry_points`` argument of ``setup()``
+function. More details will be noted later in `Writing setup.py`_.
+
+They contain usage string which is a documentation and prescription of
+command-line arguments in one string. This string is printed, when user
+requests command's help: ::
+
+ $ lmi help
+
+.. _usage_string:
+
+Usage string
+^^^^^^^^^^^^
+looks like this: ::
+
+ """
+ System service management.
+
+ Usage:
+ %(cmd)s list [--all | --disabled]
+ %(cmd)s start <service>
+
+ Options:
+ --all List all services available.
+ --disabled List only disabled services.
+ """
+
+Format of this string is very important, it's parsed by a docopt_ command line
+parser, generating options dictionary for commands. Please refer to its
+documentation for details.
+
+.. note::
+
+ There is one deviation to *classical* usage string. It's the use of
+ ``%(cmd)s`` formatting mark. This is replaced with full command's name.
+ Full name means that all subcommands and binary name prefixing current
+ command on command line are part of it. So for example full name of
+ command **list** in a following string passed to command line: ::
+
+ lmi sw list pkgs
+
+ is ``lmi sw list``.
+
+ If parsing **sw** usage, it is just ``lmi sw``.
+
+ Please use this notation instead of writing your own usages completely.
+ Although it may work from command line, it won't work in interactive
+ mode without ``%(cmd)s`` being used.
+
+.. _end-point_commands_introduction:
+
+End-point commands
+~~~~~~~~~~~~~~~~~~
+Are associated with one or more function of script library. They handle the
+following:
+
+ 1. call docopt_ parser on command line arguments
+ 2. make some name pre-processing on them (see
+ :ref:`pre_processing_properties`)
+ 3. verify them (see :ref:`end-point_commands`)
+ 4. transform them (see :ref:`end-point_commands`)
+ 5. pass them to associated function
+ 6. collect results
+ 7. render them and print them
+
+Developper of command wrappers need to be familiar with each step. We will
+describe them later in details.
+
+There are following end-point commands available for subclassing:
+
+ * ``LmiCheckResult`` (see :ref:`lmi_check_result`)
+ * ``LmiLister`` (see :ref:`lmi_lister`)
+ * ``LmiInstanceLister`` (see :ref:`lmi_instance_lister`)
+ * ``LmiShowInstance`` (see :ref:`lmi_show_instance`)
+
+They differ in a way, how they render the result obtained from associated
+function.
+
+These are listed in depth in :ref:`end-point_commands`.
+
+.. _command_multiplexers_introduction:
+
+Command multiplexers
+~~~~~~~~~~~~~~~~~~~~
+Provide a way, how to group multiple commands under one. Suppose you want to
+list packages, repositories and files. All of these use cases need different
+arguments, and render different informations so logically these they should
+be independent end-point commands. What binds them together is the user's
+wish to *list* something. He may wish for other things like *show*, *add*,
+*remove* etc. Having all combination of these wishes and things would
+generate a lot of commands under the top-level one. Let's instead group them
+under particular *wish* like this:
+
+ * ``sw list packages``
+ * ``sw list repositories``
+ * ``sw list files``
+ * ``sw show package``
+
+To reflect it in our commands definition hierarchy, we need to use
+``LmiCommandMultiplexer`` command. ::
+
+ class Lister(command.LmiCommandMultiplexer):
+ """ List information about packages, repositories or files. """
+ COMMANDS = {
+ 'packages' : PkgLister,
+ 'repositories' : RepoLister,
+ 'files' : FileLister
+ }
+
+Where ``COMMANDS`` property maps subcommand classes to their names as will
+be passed on command line. Each command multiplexer consumes one command
+argument from command line, representing the subcommand and passes the rest
+of options to it. In this way we can create arbitrarily tall command trees.
+
+Top-level command is nothing else than a subclass of ``LmiCommandMultiplexer``.
+
+Command wrappers module
+~~~~~~~~~~~~~~~~~~~~~~~
+Usually consists of:
+
+ 1. license header
+ 2. usage dostring - parseable by docopt_
+ 3. end-point command wrappers
+ 4. single top-level command
+
+The top-level command is usally defined like this: ::
+
+ Service = command.register_subcommands(
+ 'Service', __doc__,
+ { 'list' : Lister
+ , 'show' : Show
+ , 'start' : Start
+ , 'stop' : Stop
+ , 'restart' : Restart
+ },
+ )
+
+Where the ``__doc__`` is a usage string (see usage_string_) and module's doc
+string at the same time. It's mentioned in point 2. ``Service`` is a name,
+which will be listed in ``entry_points`` dictionary described in section below
+(entry_points_). The global variable's name we assign to should be the same
+as a value of the first argument to ``register_subcommands()``. The last
+argument here is the dictionary mapping all subcommands of **service** to
+their names [#]_.
+
+Egg structure
+~~~~~~~~~~~~~
+Script library is distributed as an python egg. Making it easy to distribute
+it and install either to system or user directory.
+
+Following tree shows directory structure of *service* egg residing in
+`upstream git`_: ::
+
+ commands/service
+ ├── lmi
+ │ ├── __init__.py
+ │ └── scripts
+ │ ├── __init__.py
+ │ └── service
+ │ ├── cmd.py
+ │ └── __init__.py
+ ├── README.md
+ └── setup.py
+
+This library then can be imported with: ::
+
+ from lmi.scripts import service
+
+``commands/service/scripts/service`` must be a package (directory with
+``__init__.py``) because ``lmi.scripts`` is a namespace package. It can have
+arbitrary number of modules and subpackages. The care should be taken to make
+the API easy to use and learn though.
+
+Writing ``setup.py``
+--------------------
+Follows a minimal example of ``setup.py`` script for service library. ::
+
+ from setuptools import setup, find_packages
+ setup(
+ name="openlmi-scripts-service",
+ version="0.1.0",
+ description='LMI command for system service administration.',
+ url='https://github.com/openlmi/openlmi-scripts',
+ platforms=['Any'],
+ license="BSD",
+ install_requires=['openlmi-scripts'],
+ namespace_packages=['lmi', 'lmi.scripts'],
+ packages=['lmi', 'lmi.scripts', 'lmi.scripts.service'],
+
+ entry_points={
+ 'lmi.scripts.cmd': [
+ 'service = lmi.scripts.service.cmd:Service',
+ ],
+ },
+ )
+
+.. _entry_points:
+
+The most notable argument here is ``entry_points`` which is a dictionary
+containing python namespaces, where plugins are registered. In this case, we
+register single top-level command (see `Top-level commands`_) called
+``service`` in ``lmi.scripts.cmd`` namespace. This particular namespace is
+used by ``lmi`` meta-command when searching of registered user commands.
+``Service`` is a command multiplexer, created with a call to
+``register_subcommands()`` grouping end-point commands together.
+
+Next example shows setup with more top-level commands
+(of storage scripts library): ::
+
+ entry_points={
+ 'lmi.scripts.cmd': [
+ 'fs = lmi.scripts.storage.fs_cmd:Fs',
+ 'partition = lmi.scripts.storage.partition_cmd:Partition',
+ 'raid = lmi.scripts.storage.raid_cmd:Raid',
+ 'lv = lmi.scripts.storage.lv_cmd:Lv',
+ 'vg = lmi.scripts.storage.vg_cmd:Vg',
+ 'storage = lmi.scripts.storage.storage_cmd:Storage',
+ 'mount = lmi.scripts.storage.mount_cmd:Mount',
+ ],
+ },
+
+Detailed description
+--------------------
+These pages provide more details of some aspects:
+
+.. toctree::
+ :maxdepth: 2
+
+ command-classes
+ command-properties
+
+-------------------------------------------------------------------------------
+
+.. seealso::
+
+ Docopt_ documentation.
+
+ :ref:`command_classes`
+
+ :ref:`command_properties`
+
+-------------------------------------------------------------------------------
+
+.. [#] Described by a POSIX.
+.. [#] Default namespace is ``"root/cimv2"``.
+.. [#] ssh://git.fedorahosted.org/git/openlmi-providers.git/
+.. [#] Simplified here means, that there are no documentation strings
+ and no type checking.
+.. [#] If logging to a file is enabled in configuration.
+.. [#] Precisely in an ``__init__.py`` module of this package.
+.. [#] These names must exactly match the names in usage strings.
+
+.. ****************************************************************************
+
+.. _CIM: http://dmtf.org/standards/cim
+.. _OpenLMI: http://fedorahosted.org/openlmi/
+.. _openlmi-tools: http://fedorahosted.org/openlmi/wiki/shell
+.. _docopt: http://docopt.org/
+.. _`service description`: https://fedorahosted.org/openlmi/wiki/service
+.. _`upstream git`: https://github.com/openlmi/openlmi-scripts
diff --git a/doc/usage.rst b/doc/usage.rst
new file mode 100644
index 0000000..fbfab75
--- /dev/null
+++ b/doc/usage.rst
@@ -0,0 +1,38 @@
+`lmi` meta-command usage
+========================
+``lmi`` meta-command is a command line utility build on top of client-side
+libraries. Each library for particular set of providers can declare one or
+more commands that will be registered with ``lmi`` meta-command and will be
+available to user at command line.
+
+Running from command line
+-------------------------
+It can run single command given on command line like this: ::
+
+ lmi -h ${hostname} service list --all
+
+Running in interactive mode
+---------------------------
+Or it can be run in interactive mode when command is omitted: ::
+
+ lmi -h ${hostname}
+ lmi> sw search django
+ ...
+ lmi> sw install python-django
+ ...
+ lmi> exit
+
+Getting help
+------------
+For detailed help run: ::
+
+ lmi --help
+
+To get a list of available commands with short descriptions: ::
+
+ lmi help
+
+For help on a particular registered command: ::
+
+ lmi help service
+