diff --git a/examples/cipher/cipher.py b/examples/cipher/cipher.py index 26a07756..83610a5d 100644 --- a/examples/cipher/cipher.py +++ b/examples/cipher/cipher.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/examples/cipher/cipher_test.py b/examples/cipher/cipher_test.py index 285c42cd..d2fb5c5f 100644 --- a/examples/cipher/cipher_test.py +++ b/examples/cipher/cipher_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/examples/diff/diff.py b/examples/diff/diff.py index be5b8dab..d321acec 100644 --- a/examples/diff/diff.py +++ b/examples/diff/diff.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/examples/diff/diff_test.py b/examples/diff/diff_test.py index f73732e2..81a513c3 100644 --- a/examples/diff/diff_test.py +++ b/examples/diff/diff_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/examples/diff/difffull.py b/examples/diff/difffull.py index 38918dee..b765e3f2 100644 --- a/examples/diff/difffull.py +++ b/examples/diff/difffull.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/examples/identity/identity.py b/examples/identity/identity.py index 1dec9032..45143883 100644 --- a/examples/identity/identity.py +++ b/examples/identity/identity.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/examples/widget/collector.py b/examples/widget/collector.py index dc66b690..f37ecc7a 100644 --- a/examples/widget/collector.py +++ b/examples/widget/collector.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/examples/widget/collector_test.py b/examples/widget/collector_test.py index c4c13e7d..274cf382 100644 --- a/examples/widget/collector_test.py +++ b/examples/widget/collector_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/examples/widget/widget.py b/examples/widget/widget.py index c4bb83d5..bf1cbeb2 100644 --- a/examples/widget/widget.py +++ b/examples/widget/widget.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/examples/widget/widget_test.py b/examples/widget/widget_test.py index 6e2188d6..a5cd7188 100644 --- a/examples/widget/widget_test.py +++ b/examples/widget/widget_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/fire/__init__.py b/fire/__init__.py index e9c87141..ab0874f8 100644 --- a/fire/__init__.py +++ b/fire/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/fire/completion.py b/fire/completion.py index ff18e3e8..62af5a44 100644 --- a/fire/completion.py +++ b/fire/completion.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -221,7 +221,7 @@ def Completions(component, verbose=False): return [str(index) for index in range(len(component))] if inspect.isgenerator(component): - # TODO: There are currently no commands available for generators. + # TODO(dbieber): There are currently no commands available for generators. return [] return [ @@ -276,7 +276,7 @@ def _Commands(component, depth=3): return for member_name, member in _Members(component): - # TODO: Also skip components we've already seen. + # TODO(dbieber): Also skip components we've already seen. member_name = _FormatForCommand(member_name) yield (member_name,) diff --git a/fire/completion_test.py b/fire/completion_test.py index 5da360a9..47d80b4a 100644 --- a/fire/completion_test.py +++ b/fire/completion_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/fire/core.py b/fire/core.py index 4083caa8..9784a596 100644 --- a/fire/core.py +++ b/fire/core.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -237,7 +237,7 @@ def _IsHelpShortcut(component_trace, remaining_args): def _PrintResult(component_trace, verbose=False): """Prints the result of the Fire call to stdout in a human readable way.""" - # TODO: Design human readable deserializable serialization method + # TODO(dbieber): Design human readable deserializable serialization method # and move serialization to it's own module. result = component_trace.GetResult() @@ -298,7 +298,7 @@ def _ComponentVisible(component, verbose=False): def _OneLineResult(result): """Returns result serialized to a single line string.""" - # TODO: Ensure line is fewer than eg 120 characters. + # TODO(dbieber): Ensure line is fewer than eg 120 characters. if isinstance(result, six.string_types): return str(result).replace('\n', ' ') @@ -404,20 +404,12 @@ def _Fire(component, args, context, name=None): isclass = inspect.isclass(component) try: - target = component.__name__ - filename, lineno = inspectutils.GetFileAndLine(component) - - component, consumed_args, remaining_args, capacity = _CallCallable( - component, remaining_args) - - # Update the trace. - if isclass: - component_trace.AddInstantiatedClass( - component, target, consumed_args, filename, lineno, capacity) - else: - component_trace.AddCalledRoutine( - component, target, consumed_args, filename, lineno, capacity) - + component, remaining_args = _CallAndUpdateTrace( + component, + remaining_args, + component_trace, + treatment='class' if isclass else 'routine', + target=component.__name__) except FireError as error: component_trace.AddError(error, initial_args) return component_trace @@ -454,7 +446,7 @@ def _Fire(component, args, context, name=None): else: # The target isn't present in the dict as a string, but maybe it is as # another type. - # TODO: Consider alternatives for accessing non-string keys. + # TODO(dbieber): Consider alternatives for accessing non-string keys. found_target = False for key, value in component.items(): if target == str(key): @@ -487,8 +479,19 @@ def _Fire(component, args, context, name=None): component, target, consumed_args, filename, lineno) except FireError as error: - component_trace.AddError(error, initial_args) - return component_trace + if not callable(component): + component_trace.AddError(error, initial_args) + return component_trace + + # If we can't access the member, we try to treat component as a callable + try: + component, remaining_args = _CallAndUpdateTrace(component, + remaining_args, + component_trace, + treatment='callable') + except FireError as error: + component_trace.AddError(error, initial_args) + return component_trace if used_separator: # Add back in the arguments from after the separator. @@ -569,6 +572,48 @@ def _GetMember(component, args): raise FireError('Could not consume arg:', arg) +def _CallAndUpdateTrace(component, args, component_trace, treatment='class', + target=None): + """Call the component and update FireTrace. + + The component could a class, a routine, or a callable object. This function + will attempt to call the callable and add corresponding action trace to + component_trace if the invocation is successful. If not, raise FireError. + + Args: + component: The component to call + args: Args for calling the component + component_trace: FireTrace object that contains action trace + treatment: Type of treatment used. Indicating whether we treat the component + as a class, a routine, or a callable. + target: Target in FireTrace element, default is None. If the value is None, + the component itself will be used as target. + Returns: + component: The object that is the result of the callable call. + remaining_args: The remaining args that haven't been consumed yet. + """ + if not target: + target = component + filename, lineno = inspectutils.GetFileAndLine(component) + component, consumed_args, remaining_args, capacity = _CallCallable( + component.__call__ if treatment == 'callable' else component, args) + + # TODO(joejoevictor): Consolidate AddInstantiatedClass, AddCalledRoutine, and + # AddCalledCallable into one method since the only different between those + # methods is the 'action' attribute of the FireTraceElement they created. + if treatment == 'class': + component_trace.AddInstantiatedClass( + component, target, consumed_args, filename, lineno, capacity) + elif treatment == 'routine': + component_trace.AddCalledRoutine( + component, target, consumed_args, filename, lineno, capacity) + else: + component_trace.AddCalledCallable( + component, target, consumed_args, filename, lineno, capacity) + + return component, remaining_args + + def _CallCallable(fn, args): """Calls the function fn by consuming args from args. diff --git a/fire/core_test.py b/fire/core_test.py index 0af8f216..adfdd514 100644 --- a/fire/core_test.py +++ b/fire/core_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -72,7 +72,7 @@ def testInteractiveModeVariablesWithName(self, mock_embed): self.assertEqual(variables['D'], tc.WithDefaults) self.assertIsInstance(variables['trace'], trace.FireTrace) - # TODO: Use parameterized tests to break up repetitive tests. + # TODO(dbieber): Use parameterized tests to break up repetitive tests. def testHelpWithClass(self): with self.assertRaisesFireExit(0, 'Usage:.*ARG1'): core.Fire(tc.InstanceVars, command=['--', '--help']) @@ -134,12 +134,20 @@ def testPrintEmptyDict(self): with self.assertOutputMatches(stdout='{}', stderr=None): core.Fire(tc.EmptyDictOutput, command=['nothing_printable']) - def testPrintDict(self): + def testPrintOrderedDict(self): with self.assertOutputMatches(stdout=r'A:\s+A\s+2:\s+2\s+', stderr=None): core.Fire(tc.OrderedDictionary, command=['non_empty']) with self.assertOutputMatches(stdout='{}'): core.Fire(tc.OrderedDictionary, command=['empty']) + def testCallable(self): + with self.assertOutputMatches(stdout=r'foo:\s+foo\s+', stderr=None): + core.Fire(tc.CallableWithKeywordArgument(), command=['--foo=foo']) + with self.assertOutputMatches(stdout=r'foo\s+', stderr=None): + core.Fire(tc.CallableWithKeywordArgument(), command=['print_msg', 'foo']) + with self.assertOutputMatches(stdout=r'', stderr=None): + core.Fire(tc.CallableWithKeywordArgument(), command=[]) + if __name__ == '__main__': testutils.main() diff --git a/fire/decorators.py b/fire/decorators.py index 168312d4..3fcc4b97 100644 --- a/fire/decorators.py +++ b/fire/decorators.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/fire/decorators_test.py b/fire/decorators_test.py index 372cee60..cc7d6203 100644 --- a/fire/decorators_test.py +++ b/fire/decorators_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/fire/fire_import_test.py b/fire/fire_import_test.py index 8589b79e..c5975681 100644 --- a/fire/fire_import_test.py +++ b/fire/fire_import_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/fire/fire_test.py b/fire/fire_test.py index 8f1f53ec..dd8527b5 100644 --- a/fire/fire_test.py +++ b/fire/fire_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/fire/helputils.py b/fire/helputils.py index eecd25ca..162ad382 100644 --- a/fire/helputils.py +++ b/fire/helputils.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/fire/helputils_test.py b/fire/helputils_test.py index cf8014f3..416ce7a7 100644 --- a/fire/helputils_test.py +++ b/fire/helputils_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Google Inc. +# Copyright (C) 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ def testHelpStringObject(self): self.assertIn('Type: NoDefaults', helpstring) self.assertIn('String form: