Revision: 6713f7c7c6da
Author:   Pekka Klärck
Date:     Mon Feb 20 00:26:57 2012
Log: libdoc: Initial implementation to show library information on console.

This implementation supports following usages:

  robot.libdoc MyLib show [keywords]
  robot.libdoc MyLib list [keywords]
  robot.libdoc MyLib search [patterns]
  robot.libdoc MyLib version

Todo:
- Discuss usage. I'm not sure are list and search needed separately.
- Code review.
- Add some more tests.
- Documentation.
http://code.google.com/p/robotframework/source/detail?r=6713f7c7c6da

Added:
 /atest/robot/libdoc/console_viewer.txt
 /src/robot/libdocpkg/consoleviewer.py
Modified:
 /atest/robot/libdoc/LibDocLib.py
 /atest/robot/libdoc/invalid_usage.txt
 /src/robot/libdoc.py
 /src/robot/libdocpkg/__init__.py

=======================================
--- /dev/null
+++ /atest/robot/libdoc/console_viewer.txt      Mon Feb 20 00:26:57 2012
@@ -0,0 +1,51 @@
+*** Settings ***
+Force Tags        regression    pybot    jybot
+Resource          libdoc_resource.txt
+
+*** Test Cases ***
+List all keywords
+    Run Libdoc And Verify Output    Dialogs list
+    ...   Execute Manual Step
+    ...   Get Selection From User
+    ...   Get Value From User
+    ...   Pause Execution
+
+List some keywords
+ Run Libdoc And Verify Output ${TESTDATADIR}/resource.txt LIST KW? CURDIR
+    ...   curdir
+    ...   kw 3
+    ...   kw 4
+    ...   kw 5
+
+Show whole library
+    ${output}=    Run Libdoc    Dialogs show
+    Should Start With    ${output}    Dialogs\n=======\nVersion:
+    Should Contain    ${output}    Execute Manual Step\n----
+    Should Contain    ${output}    Get Selection From User\n----
+    Should Contain    ${output}    Get Value From User\n----
+    Should Contain    ${output}    Pause Execution\n----
+
+Show intro only
+    ${output}=    Run Libdoc    Dialogs SHOW intro
+    Should Start With    ${output}    Dialogs\n=======\nVersion:
+    Should Not Contain    ${output}    Execute Manual Step\n----
+    Should Not Contain    ${output}    Get Selection From User\n----
+    Should Not Contain    ${output}    Get Value From User\n----
+    Should Not Contain    ${output}    Pause Execution\n----
+
+Show intro and keywords
+ ${output}= Run Libdoc ${TESTDATADIR}/resource.txt SHOW NONASC* INTRO
+    Should Start With    ${output}    resource\n========\nNamed arguments:
+    ${expected} =    Catenate    SEPARATOR=\n
+    ...    non ascii doc
+    ...    -------------
+    ...    Arguments:${SPACE * 2}[]
+    ...    ${EMPTY}
+    ...    Hyvää yötä.
+    ...    ${EMPTY}
+    ...    Спасибо!
+    Should Contain    ${output}    ${expected}
+
+Show version
+ Run Libdoc And Verify Output ${TESTDATADIR}/module.py version 0.1-alpha + Run Libdoc And Verify Output ${TESTDATADIR}/resource.txt version N/A
=======================================
--- /dev/null
+++ /src/robot/libdocpkg/consoleviewer.py       Mon Feb 20 00:26:57 2012
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+
+#  Copyright 2008-2012 Nokia Siemens Networks Oyj
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import textwrap
+
+from robot import utils
+from robot.errors import DataError
+
+
+class ConsoleViewer(object):
+
+    def __init__(self, libdoc):
+        self._libdoc = libdoc
+
+    @classmethod
+    def handles(cls, method):
+        return hasattr(cls, method.lower())
+
+    def view(self, method, *args):
+        try:
+            getattr(self, method.lower())(*args)
+        except AttributeError:
+            raise DataError("Cannot view '%s'." % method)
+        except TypeError:
+ raise DataError("Wrong number of arguments to view '%s'." % method)
+
+    def list(self, *included):
+        for kw in KeywordMatcher(self._libdoc).list(included):
+            self._console(kw.name)
+
+    def show(self, *included):
+        if not included or any(utils.eq('intro', inc) for inc in included):
+            self._show_intro(self._libdoc)
+            if self._libdoc.inits:
+                self._show_inits(self._libdoc)
+        for kw in KeywordMatcher(self._libdoc).list(included):
+            self._show_keyword(kw)
+
+    def search(self, *patterns):
+        for kw in KeywordMatcher(self._libdoc).search(patterns):
+            self._console(kw.name)
+
+    def version(self):
+        self._console(self._libdoc.version or 'N/A')
+
+    def _console(self, msg):
+        print utils.encode_output(msg)
+
+    def _show_intro(self, lib):
+        self._header(lib.name, underline='=')
+        named_args = 'supported' if lib.named_args else 'not supported'
+        self._data([('Version', lib.version), ('Scope', lib.scope),
+                    ('Named arguments', named_args)])
+        self._doc(lib.doc)
+
+    def _show_inits(self, lib):
+        self._header('Importing', underline='-')
+        for init in lib.inits:
+            self._show_keyword(init, show_name=False)
+
+    def _show_keyword(self, kw, show_name=True):
+        if show_name:
+            self._header(kw.name, underline='-')
+        self._data([('Arguments', '[%s]' % ', '.join(kw.args))])
+        self._doc(kw.doc)
+
+    def _header(self, name, underline):
+        self._console('%s\n%s' % (name, underline * len(name)))
+
+    def _data(self, items):
+        ljust = max(len(name) for name, _ in items) + 3
+        for name, value in items:
+            if value:
+                text = '%s%s' % ((name+':').ljust(ljust), value)
+ self._console(self._wrap(text, subsequent_indent=' '*ljust))
+
+    def _doc(self, doc):
+        self._console('')
+        for line in doc.splitlines():
+            self._console(self._wrap(line))
+        if doc:
+            self._console('')
+
+    def _wrap(self, text, width=78, **config):
+        return '\n'.join(textwrap.wrap(text, width=width, **config))
+
+
+class KeywordMatcher(object):
+
+    def __init__(self, libdoc):
+        self._keywords = libdoc.keywords
+
+    def list(self, patterns):
+        for kw in self._keywords:
+            if not patterns or self._matches(kw.name, patterns):
+                yield kw
+
+    def _matches(self, item, patterns):
+        return any(utils.matches(item, p) for p in patterns)
+
+    def search(self, patterns):
+        patterns = ['*%s*' % p for p in patterns]
+        for kw in self._keywords:
+ if self._matches(kw.name, patterns) or self._matches(kw.doc, patterns):
+                yield kw
=======================================
--- /atest/robot/libdoc/LibDocLib.py    Thu Feb 16 03:42:26 2012
+++ /atest/robot/libdoc/LibDocLib.py    Mon Feb 20 00:26:57 2012
@@ -4,6 +4,7 @@
 from subprocess import call, STDOUT

 from robot.api import logger
+from robot.utils import decode_output

 ROBOT_SRC = join(dirname(abspath(__file__)), '..', '..', '..', 'src')

@@ -25,4 +26,4 @@
         stdout.seek(0)
         output = stdout.read().replace('\r\n', '\n')
         logger.info(output)
-        return output
+        return decode_output(output)
=======================================
--- /atest/robot/libdoc/invalid_usage.txt       Thu Feb 16 03:42:26 2012
+++ /atest/robot/libdoc/invalid_usage.txt       Mon Feb 20 00:26:57 2012
@@ -10,22 +10,26 @@
 *** Test Cases ***

 No arguments
-    ${EMPTY}          Expected 2 arguments, got 0.
-
-Too many arguments
-    arg1 arg2 arg3    Expected 2 arguments, got 3.
+    ${EMPTY}          Expected at least 2 arguments, got 0.
+
+Too many arguments when creating output
+ MyLib out.xml extra Only two arguments allowed when writing output.
+
+Too many arguments with version
+    Dialogs version extra    Wrong number of arguments to view 'version'.

 Invalid option
     --invalid         option --invalid not recognized

 Invalid format
- --format XXX BuiltIn ${OUTHTML} Format must be either 'HTML' or 'XML', got 'XXX'. + -f XXX BuiltIn out.html Format must be either 'HTML' or 'XML', got 'XXX'. + BuiltIn out.ext Format must be either 'HTML' or 'XML', got 'EXT'.

 Non-existing library
- NonExistingLib ${OUTHTML} Importing test library 'NonExistingLib' failed: * + NonExistingLib out.html Importing test library 'NonExistingLib' failed: *

 Invalid resource
-    ${CURDIR}/invalid_usage.txt ${OUTHTML}
+    ${CURDIR}/invalid_usage.txt out.html
     ...   [ ERROR ] *: Non-existing setting 'Force Tags'.
     ...   [ ERROR ] *: Non-existing setting 'Test Template'.
... Resource file '*' contains a test case table which is not allowed.
=======================================
--- /src/robot/libdoc.py        Thu Feb 16 05:37:32 2012
+++ /src/robot/libdoc.py        Mon Feb 20 00:26:57 2012
@@ -72,22 +72,34 @@
     import pythonpathsetter   # running libdoc.py as script

 from robot.utils import Application
-from robot.libdocpkg import LibraryDocumentation
+from robot.errors import DataError
+from robot.libdocpkg import LibraryDocumentation, ConsoleViewer


 class LibDoc(Application):

     def __init__(self):
-        Application.__init__(self, USAGE, arg_limits=2, auto_version=False)
+ Application.__init__(self, USAGE, arg_limits=(2,), auto_version=False)
+
+    def validate(self, options, arguments):
+        if len(arguments) > 2 and not ConsoleViewer.handles(arguments[1]):
+ raise DataError('Only two arguments allowed when writing output.')
+        return options, arguments

     def main(self, args, argument=None, name='', version='', format=None):
-        lib_or_res, outfile = args
+        lib_or_res, output = args[:2]
         libdoc = LibraryDocumentation(lib_or_res, argument, name, version)
-        libdoc.save(outfile, self._get_format(format, outfile))
-        self.console(os.path.abspath(outfile))
+        if ConsoleViewer.handles(output):
+            ConsoleViewer(libdoc).view(output, *args[2:])
+        else:
+            libdoc.save(output, self._get_format(format, output))
+            self.console(os.path.abspath(output))

     def _get_format(self, format, output):
-        return format if format else os.path.splitext(output)[1][1:]
+ format = (format if format else os.path.splitext(output)[1][1:]).upper()
+        if format in ['HTML', 'XML']:
+            return format
+ raise DataError("Format must be either 'HTML' or 'XML', got '%s'." % format)


 def libdoc_cli(args):
=======================================
--- /src/robot/libdocpkg/__init__.py    Tue Feb 14 05:38:50 2012
+++ /src/robot/libdocpkg/__init__.py    Mon Feb 20 00:26:57 2012
@@ -13,6 +13,7 @@
 #  limitations under the License.

 from .builder import DocumentationBuilder
+from .consoleviewer import ConsoleViewer


 def LibraryDocumentation(library_or_resource, arguments=None, name=None,

Reply via email to