Skip to content

Commit

Permalink
refactory: split robotframework's logic into more files
Browse files Browse the repository at this point in the history
  • Loading branch information
xyb committed May 3, 2020
1 parent cbb3d07 commit 9f87267
Show file tree
Hide file tree
Showing 15 changed files with 229 additions and 212 deletions.
3 changes: 3 additions & 0 deletions DebugLibrary/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from .keywords import DebugKeywords
from .version import VERSION

"""A debug library and REPL for RobotFramework."""


class DebugLibrary(DebugKeywords):
"""Debug Library for RobotFramework."""

ROBOT_LIBRARY_SCOPE = 'GLOBAL'
ROBOT_LIBRARY_VERSION = VERSION
2 changes: 1 addition & 1 deletion DebugLibrary/cmdcompleter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from prompt_toolkit.completion import Completer, Completion

from .robotutils import parse_keyword
from .robotkeyword import parse_keyword


class CmdCompleter(Completer):
Expand Down
38 changes: 21 additions & 17 deletions DebugLibrary/debugcmd.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import os

from robot.api import logger
from robot.errors import ExecutionFailed, HandlerExecutionFailed

from .cmdcompleter import CmdCompleter
from .prompttoolkitcmd import PromptToolkitCmd
from .robotutils import (SELENIUM_WEBDRIVERS, get_builtin_libs, get_keywords,
get_lib_keywords, get_libs, get_libs_dict,
get_robot_instance, match_libs,
reset_robotframework_exception, run_keyword,
start_selenium_commands)
from .robotapp import get_robot_instance, reset_robotframework_exception
from .robotkeyword import get_keywords, get_lib_keywords, run_keyword
from .robotlib import get_builtin_libs, get_libs, get_libs_dict, match_libs
from .robotselenium import SELENIUM_WEBDRIVERS, start_selenium_commands
from .robotvar import assign_variable
from .styles import (DEBUG_PROMPT_STYLE, get_debug_prompt_tokens, print_error,
print_output)

Expand All @@ -20,21 +21,23 @@ def run_robot_command(robot_instance, command):
if not command:
return

result = ''
try:
result = run_keyword(robot_instance, command)
if result:
head, message = result
print_output(head, message)
except ExecutionFailed as exc:
print_error('! keyword:', command)
print_error('!', exc.message)
print_error('! execution failed:', str(exc))
except HandlerExecutionFailed as exc:
print_error('! keyword:', command)
print_error('!', exc.full_message)
print_error('! handler execution failed:', exc.full_message)
except Exception as exc:
print_error('! keyword:', command)
print_error('! FAILED:', repr(exc))

if result:
head, message = result
print_output(head, message)


class DebugCmd(PromptToolkitCmd):
"""Interactive debug shell for robotframework."""
Expand Down Expand Up @@ -100,8 +103,7 @@ def get_completer(self):
'Keyword[{0}.]: {1}'.format(keyword['lib'], keyword['doc']),
))

cmd_completer = CmdCompleter(commands, self)
return cmd_completer
return CmdCompleter(commands, self)

def do_selenium(self, arg):
"""Start a selenium webdriver and open url in browser you expect.
Expand All @@ -121,7 +123,8 @@ def complete_selenium(self, text, line, begin_idx, end_idx):
"""Complete selenium command."""
if len(line.split()) == 3:
command, url, driver_name = line.lower().split()
return [d for d in SELENIUM_WEBDRIVERS if d.startswith(driver_name)]
return [driver for driver in SELENIUM_WEBDRIVERS
if driver.startswith(driver_name)]
elif len(line.split()) == 2 and line.endswith(' '):
return SELENIUM_WEBDRIVERS
return []
Expand All @@ -143,9 +146,9 @@ def do_libs(self, args):
for lib in get_libs():
print_output(' {}'.format(lib.name), lib.version)
if lib.doc:
print(' {}'.format(lib.doc.split('\n')[0]))
logger.console(' {}'.format(lib.doc.split('\n')[0]))
if '-s' in args:
print(' {}'.format(lib.source))
logger.console(' {}'.format(lib.source))
print_output('<', 'Builtin libraries:')
for name in sorted(get_builtin_libs()):
print_output(' ' + name, '')
Expand Down Expand Up @@ -199,8 +202,9 @@ def do_docs(self, kw_name):
for lib in get_libs():
for keyword in get_lib_keywords(lib, long_format=True):
if keyword['name'].lower() == kw_name.lower():
print(keyword['doc'])
logger.console(keyword['doc'])
return
print("could not find documentation for keyword {}".format(kw_name))

print_error('< not find keyword', kw_name)

do_d = do_docs
4 changes: 0 additions & 4 deletions DebugLibrary/keywords.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@

from .debugcmd import DebugCmd
from .styles import print_output
from .version import VERSION
from .webdriver import get_remote_url, get_session_id, get_webdriver_remote


class DebugKeywords(object):
"""Debug Keywords for RobotFramework."""

ROBOT_LIBRARY_SCOPE = 'GLOBAL'
ROBOT_LIBRARY_VERSION = VERSION

def debug(self):
"""Open a interactive shell, run any RobotFramework keywords.
Expand Down
File renamed without changes.
3 changes: 1 addition & 2 deletions DebugLibrary/prompttoolkitcmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ class BaseCmd(cmd.Cmd):
"""Basic REPL tool."""

def emptyline(self):
"""By default Cmd runs last command if an empty line is entered.
Disable it."""
"""Do not repeat last command if press enter only."""

def do_exit(self, arg):
"""Exit the interpreter. You can also use the Ctrl-D shortcut."""
Expand Down
24 changes: 24 additions & 0 deletions DebugLibrary/robotapp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

from robot.api import logger
from robot.libraries.BuiltIn import BuiltIn
from robot.running.signalhandler import STOP_SIGNAL_MONITOR


def get_robot_instance():
"""Get robotframework builtin instance as context."""
return BuiltIn()


def reset_robotframework_exception():
"""Resume RF after press ctrl+c during keyword running."""
if STOP_SIGNAL_MONITOR._signal_count:
STOP_SIGNAL_MONITOR._signal_count = 0
STOP_SIGNAL_MONITOR._running_keyword = True
logger.info('Reset last exception of DebugLibrary')


def assign_variable(robot_instance, variable_name, args):
"""Assign a robotframework variable."""
variable_value = robot_instance.run_keyword(*args)
robot_instance._variables.__setitem__(variable_name, variable_value)
return variable_value
76 changes: 76 additions & 0 deletions DebugLibrary/robotkeyword.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import re

from robot.libdocpkg.model import LibraryDoc

from .memoize import memoize
from .robotlib import ImportedLibraryDocBuilder, get_libs
from .robotvar import assign_variable

try:
from robot.variables.search import is_variable
except ImportError:
from robot.variables import is_var as is_variable # robotframework < 3.2

KEYWORD_SEP = re.compile(' +|\t')


def parse_keyword(command):
"""Split a robotframework keyword string."""
return KEYWORD_SEP.split(command)


@memoize
def get_lib_keywords(library, long_format=False):
"""Get keywords of imported library."""
lib = ImportedLibraryDocBuilder().build(library)
keywords = []
for keyword in lib.keywords:
if long_format:
doc = keyword.doc
else:
doc = keyword.doc.split('\n')[0]
keywords.append({
'name': keyword.name,
'lib': library.name,
'doc': doc,
})
return keywords


def get_keywords():
"""Get all keywords of libraries."""
for lib in get_libs():
yield from get_lib_keywords(lib)


def run_keyword(robot_instance, keyword):
"""Run a keyword in robotframewrk environment."""
if not keyword:
return

keyword_args = parse_keyword(keyword)
keyword = keyword_args[0]
args = keyword_args[1:]

is_comment = keyword.strip().startswith('#')
if is_comment:
return

variable_name = keyword.rstrip('= ')
if is_variable(variable_name):
variable_only = not args
if variable_only:
display_value = ['Log to console', keyword]
robot_instance.run_keyword(*display_value)
else:
variable_value = assign_variable(
robot_instance,
variable_name,
args,
)
echo = '{0} = {1!r}'.format(variable_name, variable_value)
return ('#', echo)
else:
output = robot_instance.run_keyword(keyword, *args)
if output:
return ('<', repr(output))
41 changes: 41 additions & 0 deletions DebugLibrary/robotlib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import re

from robot.libdocpkg.model import LibraryDoc
from robot.libdocpkg.robotbuilder import KeywordDocBuilder, LibraryDocBuilder
from robot.libraries import STDLIBS
from robot.running.namespace import IMPORTER


def get_builtin_libs():
"""Get robotframework builtin library names."""
return list(STDLIBS)


def get_libs():
"""Get imported robotframework library names."""
return sorted(IMPORTER._library_cache._items, key=lambda _: _.name)


def get_libs_dict():
"""Get imported robotframework libraries as a name -> lib dict"""
return {lib.name: lib for lib in IMPORTER._library_cache._items}


def match_libs(name=''):
"""Find libraries by prefix of library name, default all"""
libs = [_.name for _ in get_libs()]
matched = [_ for _ in libs if _.lower().startswith(name.lower())]
return matched


class ImportedLibraryDocBuilder(LibraryDocBuilder):

def build(self, lib):
libdoc = LibraryDoc(
name=lib.name,
doc=self._get_doc(lib),
doc_format=lib.doc_format,
)
libdoc.inits = self._get_initializers(lib)
libdoc.keywords = KeywordDocBuilder().build_keywords(lib)
return libdoc
27 changes: 27 additions & 0 deletions DebugLibrary/robotselenium.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

from .robotkeyword import parse_keyword

SELENIUM_WEBDRIVERS = ['firefox', 'chrome', 'ie',
'opera', 'safari', 'phantomjs', 'remote']


def start_selenium_commands(arg):
"""Start a selenium webdriver and open url in browser you expect.
arg: [<url> or google] [<browser> or firefox]
"""
yield 'import library SeleniumLibrary'

# Set defaults, overriden if args set
url = 'http://www.google.com/'
browser = 'firefox'
if arg:
args = parse_keyword(arg)
if len(args) == 2:
url, browser = args
else:
url = arg
if '://' not in url:
url = 'http://' + url

yield 'open browser %s %s' % (url, browser)
Loading

7 comments on commit 9f87267

@stdedos
Copy link
Contributor

@stdedos stdedos commented on 9f87267 May 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, for your information, that there are some forks that have fixed other bugs.

If you are interested, it could be worth while to add them to the main branch "before this one"

@xyb
Copy link
Owner Author

@xyb xyb commented on 9f87267 May 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stdedos Good idea, I will check if I can merge some forks into the main branch. Thank you very much for your notice! I did this refactoring to make it easier for contributors to add new features. If you think whose fork deserves priority attention, please let me know.

@stdedos
Copy link
Contributor

@stdedos stdedos commented on 9f87267 May 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From an old list of mine:

https://github.com/DanielPBak/robotframework-debuglibrary
https://github.com/jollychang/robotframework-debuglibrary
https://github.com/peritus/robotframework-debuglibrary

But I don't have my generating-script available somewhere here and I didn't do any deeper digging, so YMMV.

I have already asked Github to add a +/- commits on the forks screen, but it does not seem to get traction.
Feel free to bug the Github support team:
image

@xyb
Copy link
Owner Author

@xyb xyb commented on 9f87267 May 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stdedos I have reviewed three forks you pointed and confirm there's nothing to merge back into. DanielPBak try to implement step debugging but not finished, so I spent some time research and done it in version v2.1.0

@stdedos
Copy link
Contributor

@stdedos stdedos commented on 9f87267 May 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is so then it is so! Thank you for spending time :-D

I do think, however, someone had changed the default filename extension for the throwaway file you use to initiate Robot (must be *.robot instead of *.txt)
I haven't checked if you had done it already though

@xyb
Copy link
Owner Author

@xyb xyb commented on 9f87267 May 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stdedos changed already:

@stdedos
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, okay - you stashed the change in that commit 😛

Please sign in to comment.