Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/behave/behave
Browse files Browse the repository at this point in the history
  • Loading branch information
jenisys committed Jan 10, 2021
2 parents 45126c1 + 1ebb95c commit 175af23
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 22 deletions.
25 changes: 25 additions & 0 deletions behave/compat/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,28 @@
warnings.warn(message)
# -- BACKWARD-COMPATIBLE: Better than nothing (for behave use case).
OrderedDict = dict

try:
from collections import UserDict
except ImportError: # pragma: no cover
class UserDict(object):
"""Emulate collections.UserDict class in python3."""
def __init__(self, data=None):
if data is None:
data = {}
self.data = data

def __len__(self):
return len(self.data)

def __iter__(self):
return len(self.data)

def keys(self):
return self.data.keys()

def values(self):
return self.data.values()

def items(self):
return self.data.items()
145 changes: 136 additions & 9 deletions behave/tag_matcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@
Active-tags provide a skip-if logic based on tags in feature files.
"""

from __future__ import absolute_import
from __future__ import absolute_import, print_function
import re
import operator
import six
from ._types import Unknown
from .compat.collections import UserDict


def bool_to_string(value):
"""Converts a Boolean value into its normalized string representation."""
return str(bool(value)).lower()


# -----------------------------------------------------------------------------
# CLASSES FOR: Active-Tags and ActiveTagMatchers
# -----------------------------------------------------------------------------
class TagMatcher(object):
"""Abstract base class that defines the TagMatcher protocol."""

Expand Down Expand Up @@ -220,8 +219,8 @@ def is_tag_group_enabled(self, group_category, group_tag_pairs):
# -- CASE: Empty group is always enabled (CORNER-CASE).
return True

current_value = self.value_provider.get(group_category, None)
if current_value is None and self.ignore_unknown_categories:
current_value = self.value_provider.get(group_category, Unknown)
if current_value is Unknown and self.ignore_unknown_categories:
# -- CASE: Unknown category, ignore it.
return True

Expand Down Expand Up @@ -320,6 +319,115 @@ def should_exclude_with(self, tags):
return False


# -----------------------------------------------------------------------------
# ACTIVE TAG VALUE PROVIDER CLASSES:
# -----------------------------------------------------------------------------
class IActiveTagValueProvider(object):
"""Protocol/Interface for active-tag value providers."""

def get(self, category, default=None):
return NotImplemented


class ActiveTagValueProvider(UserDict):
def __init__(self, data=None):
if data is None:
data = {}
UserDict.__init__(self, data)

@staticmethod
def use_value(value):
if callable(value):
# -- RE-EVALUATE VALUE: Each time
value_func = value
value = value_func()
return value

def __getitem__(self, name):
value = self.data[name]
return self.use_value(value)

def get(self, category, default=None):
value = self.data.get(category, default)
return self.use_value(value)

def values(self):
for value in self.data.values(self):
yield self.use_value(value)

def items(self):
for category, value in self.data.items():
yield (category, self.use_value(value))

def categories(self):
return self.keys()


class CompositeActiveTagValueProvider(ActiveTagValueProvider):
"""Provides a composite helper class to resolve active-tag values
from a list of value-providers.
"""

def __init__(self, value_providers=None):
if value_providers is None:
value_providers = []
super(CompositeActiveTagValueProvider, self).__init__()
self.value_providers = list(value_providers)

def get(self, category, default=None):
# -- FIRST: Check category cached-map (=self.data)
value = self.data.get(category, Unknown)
if value is Unknown:
# -- NOT DISCOVERED: Search over value_providers.
for value_provider in self.value_providers:
value = value_provider.get(category, Unknown)
if value is Unknown:
continue

# -- FOUND CATEGORY:
self.data[category] = value
break
# -- FOUND-CATEGORY or NOT-FOUND:
if value is Unknown:
value = default

return self.use_value(value)

# -- MORE: Provide a dict-like interface.
def keys(self):
for value_provider in self.value_providers:
try:
for category in value_provider.keys():
yield category
except AttributeError:
# -- keys() method not supported.
pass

def values(self):
for category in self.keys():
value = self.get(category)
yield value

def items(self):
for category in self.keys():
value = self.get(category)
yield (category, value)



# -----------------------------------------------------------------------------
# UTILITY FUNCTIONS:
# -----------------------------------------------------------------------------
def bool_to_string(value):
"""Converts a boolean active-tag value into its normalized
string representation.
:param value: Boolean value to use (or value converted into bool).
:returns: Boolean value converted into a normalized string.
"""
return str(bool(value)).lower()


def setup_active_tag_values(active_tag_values, data):
"""Setup/update active_tag values with dict-like data.
Only values for keys that are already present are updated.
Expand All @@ -330,3 +438,22 @@ def setup_active_tag_values(active_tag_values, data):
for category in list(active_tag_values.keys()):
if category in data:
active_tag_values[category] = data[category]


def print_active_tags(active_tag_value_provider, categories=None):
"""Print a summary of the current active-tag values."""
if categories is None:
try:
categories = list(active_tag_value_provider)
except TypeError: # TypeError: object is not iterable
categories = []

active_tag_data = active_tag_value_provider
print("ACTIVE-TAGS:")
for category in categories:
active_tag_value = active_tag_data.get(category)
print("use.with_{category}={value}".format(
category=category, value=active_tag_value))

# -- FINALLY: TRAILING NEW-LINE
print()
9 changes: 3 additions & 6 deletions features/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
# FILE: features/environemnt.py

from __future__ import absolute_import, print_function
from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
from behave.tag_matcher import \
ActiveTagMatcher, setup_active_tag_values, print_active_tags
from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
from behave import python_feature
import platform
Expand All @@ -21,11 +22,7 @@


def print_active_tags_summary():
active_tag_data = active_tag_value_provider
print("ACTIVE-TAG SUMMARY:")
print("use.with_python.version=%s" % active_tag_data.get("python.version"))
print("use.with_os=%s" % active_tag_data.get("os"))
print()
print_active_tags(active_tag_value_provider, ["python.version", "os"])


# -----------------------------------------------------------------------------
Expand Down
9 changes: 2 additions & 7 deletions issue.features/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import platform
import os.path
import six
from behave.tag_matcher import ActiveTagMatcher
from behave.tag_matcher import ActiveTagMatcher, print_active_tags
from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
# PREPARED: from behave.tag_matcher import setup_active_tag_values

Expand Down Expand Up @@ -94,12 +94,7 @@ def discover_ci_server():


def print_active_tags_summary():
active_tag_data = active_tag_value_provider
print("ACTIVE-TAG SUMMARY:")
print("use.with_python.version=%s" % active_tag_data.get("python.version"))
# print("use.with_platform=%s" % active_tag_data.get("platform"))
# print("use.with_os=%s" % active_tag_data.get("os"))
print()
print_active_tags(active_tag_value_provider, ["python.version", "os"])


# ---------------------------------------------------------------------------
Expand Down

0 comments on commit 175af23

Please sign in to comment.