diff options
author | Michal Minar <miminar@redhat.com> | 2013-09-09 13:33:15 +0200 |
---|---|---|
committer | Michal Minar <miminar@redhat.com> | 2013-09-09 15:02:08 +0200 |
commit | 7046b0ea22cc4e72a2e6cd26d56d716aa0b3b9b0 (patch) | |
tree | ff5a0595f39e80a5a574250fb0b14c9d0704f4e9 /doc | |
parent | a3e636b09062e3dc63a0ab350caadfb2759c59b7 (diff) | |
download | openlmi-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/Makefile | 162 | ||||
-rw-r--r-- | doc/command-classes.rst | 140 | ||||
-rw-r--r-- | doc/command-properties.rst | 379 | ||||
-rw-r--r-- | doc/conf.py | 242 | ||||
-rw-r--r-- | doc/index.rst | 16 | ||||
-rw-r--r-- | doc/make.bat | 190 | ||||
-rw-r--r-- | doc/script-development.rst | 450 | ||||
-rw-r--r-- | doc/usage.rst | 38 |
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 + |