wukong 3.0.0.pre3 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/README.md +689 -50
- data/bin/wu-local +1 -74
- data/diagrams/wu_local.dot +39 -0
- data/diagrams/wu_local.dot.png +0 -0
- data/examples/loadable.rb +2 -0
- data/examples/string_reverser.rb +7 -0
- data/lib/hanuman/stage.rb +2 -2
- data/lib/wukong.rb +21 -10
- data/lib/wukong/dataflow.rb +2 -5
- data/lib/wukong/doc_helpers.rb +14 -0
- data/lib/wukong/doc_helpers/dataflow_handler.rb +29 -0
- data/lib/wukong/doc_helpers/field_handler.rb +91 -0
- data/lib/wukong/doc_helpers/processor_handler.rb +29 -0
- data/lib/wukong/driver.rb +11 -1
- data/lib/wukong/local.rb +40 -0
- data/lib/wukong/local/event_machine_driver.rb +27 -0
- data/lib/wukong/local/runner.rb +98 -0
- data/lib/wukong/local/stdio_driver.rb +44 -0
- data/lib/wukong/local/tcp_driver.rb +47 -0
- data/lib/wukong/logger.rb +16 -7
- data/lib/wukong/plugin.rb +48 -0
- data/lib/wukong/processor.rb +57 -15
- data/lib/wukong/rake_helper.rb +6 -0
- data/lib/wukong/runner.rb +151 -128
- data/lib/wukong/runner/boot_sequence.rb +123 -0
- data/lib/wukong/runner/code_loader.rb +52 -0
- data/lib/wukong/runner/deploy_pack_loader.rb +75 -0
- data/lib/wukong/runner/help_message.rb +42 -0
- data/lib/wukong/spec_helpers.rb +4 -12
- data/lib/wukong/spec_helpers/integration_tests.rb +150 -0
- data/lib/wukong/spec_helpers/{integration_driver_matchers.rb → integration_tests/integration_test_matchers.rb} +28 -62
- data/lib/wukong/spec_helpers/integration_tests/integration_test_runner.rb +97 -0
- data/lib/wukong/spec_helpers/shared_examples.rb +19 -10
- data/lib/wukong/spec_helpers/unit_tests.rb +134 -0
- data/lib/wukong/spec_helpers/{processor_methods.rb → unit_tests/unit_test_driver.rb} +42 -8
- data/lib/wukong/spec_helpers/{spec_driver_matchers.rb → unit_tests/unit_test_matchers.rb} +6 -32
- data/lib/wukong/spec_helpers/unit_tests/unit_test_runner.rb +54 -0
- data/lib/wukong/version.rb +1 -1
- data/lib/wukong/widget/filters.rb +134 -8
- data/lib/wukong/widget/processors.rb +64 -5
- data/lib/wukong/widget/reducers/bin.rb +68 -18
- data/lib/wukong/widget/reducers/count.rb +12 -0
- data/lib/wukong/widget/reducers/group.rb +48 -5
- data/lib/wukong/widget/reducers/group_concat.rb +30 -2
- data/lib/wukong/widget/reducers/moments.rb +4 -4
- data/lib/wukong/widget/reducers/sort.rb +53 -3
- data/lib/wukong/widget/serializers.rb +37 -12
- data/lib/wukong/widget/utils.rb +1 -1
- data/spec/spec_helper.rb +20 -2
- data/spec/wukong/driver_spec.rb +2 -0
- data/spec/wukong/local/runner_spec.rb +40 -0
- data/spec/wukong/local_spec.rb +6 -0
- data/spec/wukong/logger_spec.rb +49 -0
- data/spec/wukong/processor_spec.rb +22 -0
- data/spec/wukong/runner_spec.rb +128 -8
- data/spec/wukong/widget/filters_spec.rb +28 -10
- data/spec/wukong/widget/processors_spec.rb +5 -5
- data/spec/wukong/widget/reducers/bin_spec.rb +14 -14
- data/spec/wukong/widget/reducers/count_spec.rb +1 -1
- data/spec/wukong/widget/reducers/group_spec.rb +7 -6
- data/spec/wukong/widget/reducers/moments_spec.rb +2 -2
- data/spec/wukong/widget/reducers/sort_spec.rb +1 -1
- data/spec/wukong/widget/serializers_spec.rb +84 -88
- data/spec/wukong/wu-local_spec.rb +109 -0
- metadata +43 -20
- data/bin/wu-server +0 -70
- data/lib/wukong/boot.rb +0 -96
- data/lib/wukong/configuration.rb +0 -8
- data/lib/wukong/emitter.rb +0 -22
- data/lib/wukong/server.rb +0 -119
- data/lib/wukong/spec_helpers/integration_driver.rb +0 -157
- data/lib/wukong/spec_helpers/processor_helpers.rb +0 -89
- data/lib/wukong/spec_helpers/spec_driver.rb +0 -28
- data/spec/wukong/local_runner_spec.rb +0 -31
- data/spec/wukong/wu_local_spec.rb +0 -125
data/lib/wukong/runner.rb
CHANGED
@@ -1,144 +1,167 @@
|
|
1
|
+
require_relative("runner/code_loader")
|
2
|
+
require_relative("runner/deploy_pack_loader")
|
3
|
+
require_relative("runner/boot_sequence")
|
4
|
+
|
1
5
|
module Wukong
|
2
|
-
module CommandlineRunner
|
3
|
-
|
4
|
-
def exit_with_status(status, options = {})
|
5
|
-
warn options[:msg] if options[:msg]
|
6
|
-
@env.dump_help if options[:show_help]
|
7
|
-
exit(status)
|
8
|
-
end
|
9
|
-
|
10
|
-
def env= settings
|
11
|
-
@env = settings
|
12
|
-
end
|
13
6
|
|
14
|
-
|
15
|
-
|
7
|
+
# A base class which handles
|
8
|
+
#
|
9
|
+
# * requiring any necessary code like deploy packs or code from command-line arguments
|
10
|
+
# * having all plugins configure settings as necessary
|
11
|
+
# * resolving settings
|
12
|
+
# * having all plugins boot from now resolved settings
|
13
|
+
# * parsing command-line arguments
|
14
|
+
# * instantiating and handing over control to a driver which runs the actual code
|
15
|
+
class Runner
|
16
|
+
|
17
|
+
include Logging
|
18
|
+
include CodeLoader
|
19
|
+
include DeployPackLoader
|
20
|
+
include BootSequence
|
21
|
+
|
22
|
+
# The settings object that will be configured and booted from.
|
23
|
+
# All plugins will configure this object.
|
24
|
+
attr_accessor :settings
|
25
|
+
|
26
|
+
# Create a new Runner with the given +settings+.
|
27
|
+
#
|
28
|
+
# Uses an empty Configliere::Param object if no +settings+ are
|
29
|
+
# given.
|
30
|
+
#
|
31
|
+
# @param [Configliere::Param] settings
|
32
|
+
def initialize settings=Configliere::Param.new
|
33
|
+
self.settings = settings
|
16
34
|
end
|
17
35
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
36
|
+
# Instantiates a new Runner and boot it up.
|
37
|
+
#
|
38
|
+
# Will rescue any Wukong::Error with a logged error message and
|
39
|
+
# exit.
|
40
|
+
def self.run(settings=Configliere::Param.new)
|
41
|
+
begin
|
42
|
+
new(settings).boot!
|
43
|
+
rescue Wukong::Error => e
|
44
|
+
die(e.message, 127)
|
23
45
|
end
|
46
|
+
end
|
24
47
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
48
|
+
# The parsed command-line arguments.
|
49
|
+
#
|
50
|
+
# Will raise an error if +boot+ hasn't been called yet.
|
51
|
+
#
|
52
|
+
# @return [Array<String>]
|
53
|
+
def args
|
54
|
+
settings.rest
|
55
|
+
end
|
29
56
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
57
|
+
# The root directory we should consider ourselves to be running
|
58
|
+
# in.
|
59
|
+
#
|
60
|
+
# Defaults to the root directory of a deploy pack if we're running
|
61
|
+
# inside one, else just returns `Dir.pwd`.
|
62
|
+
#
|
63
|
+
# @return [String]
|
64
|
+
def root
|
65
|
+
in_deploy_pack? ? deploy_pack_dir : Dir.pwd
|
66
|
+
end
|
37
67
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
68
|
+
# Convenience method for setting the usage message of a Runner.
|
69
|
+
#
|
70
|
+
# @param [String, nil] msg set the usage message
|
71
|
+
# @return [String] the usage message
|
72
|
+
def self.usage msg=nil
|
73
|
+
return @usage unless msg
|
74
|
+
@usage = msg
|
75
|
+
end
|
76
|
+
|
77
|
+
# Convenience method for setting the description message of a Runner.
|
78
|
+
#
|
79
|
+
# @param [String, nil] msg set the description message
|
80
|
+
# @return [String] the description message
|
81
|
+
def self.description msg=nil
|
82
|
+
return @description unless msg
|
83
|
+
@description = msg
|
84
|
+
end
|
42
85
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
86
|
+
# Kill this process with the given error `message` and exit
|
87
|
+
# `code`.
|
88
|
+
#
|
89
|
+
# @param [String] message
|
90
|
+
# @param [Integer] code.
|
91
|
+
def self.die(message=nil, code=127)
|
92
|
+
log.error(message) if message
|
93
|
+
exit(code)
|
94
|
+
end
|
49
95
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
96
|
+
# Return the name of the program this Runner is running.
|
97
|
+
#
|
98
|
+
# This is passed to plugins which can configure settings
|
99
|
+
# appropriately. Defaults to the name of the currently running
|
100
|
+
# process.
|
101
|
+
#
|
102
|
+
# @return [String]
|
103
|
+
def program_name
|
104
|
+
@program_name || File.basename($0)
|
105
|
+
end
|
54
106
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
107
|
+
# Explicitly set the name of the program this Runner is running.
|
108
|
+
#
|
109
|
+
# This is useful for unit tests in which the name of the currently
|
110
|
+
# running process may be different from the runner command being
|
111
|
+
# tested (`rspec` vs. `wu-local`).
|
112
|
+
#
|
113
|
+
# @param [String] name
|
114
|
+
def program_name= name
|
115
|
+
@program_name = name
|
116
|
+
end
|
66
117
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
runner.run(*settings.rest)
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
class LocalRunner
|
79
|
-
include CommandlineRunner
|
80
|
-
base_configuration
|
81
|
-
|
82
|
-
usage 'usage: wu-local PROCESSOR|FLOW [ --param=value | -p value | --param | -p]'
|
83
|
-
desc <<EOF
|
84
|
-
wu-local is a tool for running Wukong processors and flows locally on
|
85
|
-
the command-line. Use wu-local by passing it a processor and feeding
|
86
|
-
in some data:
|
87
|
-
|
88
|
-
$ echo 'UNIX is Clever and Fun...' | wu-local tokenizer.rb
|
89
|
-
UNIX
|
90
|
-
is
|
91
|
-
Clever
|
92
|
-
and
|
93
|
-
Fun
|
94
|
-
|
95
|
-
If your processors have named fields you can pass them in as
|
96
|
-
arguments:
|
97
|
-
|
98
|
-
$ echo 'UNIX is clever and fun...' | wu-local tokenizer.rb --min_length=4
|
99
|
-
UNIX
|
100
|
-
Clever
|
101
|
-
|
102
|
-
You can chain processors and calls to wu-local together:
|
103
|
-
|
104
|
-
$ echo 'UNIX is clever and fun...' | wu-local tokenizer.rb --min_length=4 | wu-local downcaser.rb
|
105
|
-
unix
|
106
|
-
clever
|
107
|
-
|
108
|
-
Which is a good way to develop a combined data flow which you can
|
109
|
-
again test locally:
|
110
|
-
|
111
|
-
$ echo 'UNIX is clever and fun...' | wu-local tokenize_and_downcase_big_words.rb
|
112
|
-
unix
|
113
|
-
clever
|
114
|
-
EOF
|
115
|
-
|
116
|
-
add_param :run, description: "Name of the processor or dataflow to use. Defaults to basename of the given path.", flag: 'r'
|
117
|
-
add_param :tcp_server, description: "Run locally as a server using provided TCP port", default: false, flag: 't'
|
118
|
-
|
119
|
-
def run *args
|
120
|
-
arg = args.first
|
121
|
-
case
|
122
|
-
when arg.nil?
|
123
|
-
exit_with_status(1, show_help: true, msg: "Must pass a processor name or path to a processor file. Got <#{arg}>")
|
124
|
-
when Wukong.registry.registered?(arg.to_sym)
|
125
|
-
processor = arg.to_sym
|
126
|
-
when File.exist?(arg)
|
127
|
-
load arg
|
128
|
-
processor = @env.run || File.basename(arg, '.rb')
|
129
|
-
else
|
130
|
-
exit_with_status(2, show_help: true, msg: "Must pass a processor name or path to a processor file. Got <#{arg}>")
|
131
|
-
end
|
132
|
-
run_em_server(processor, @env)
|
118
|
+
# Return the usage message for this runner.
|
119
|
+
#
|
120
|
+
# @return [String] the usage message
|
121
|
+
def usage
|
122
|
+
["usage: #{program_name} [ --param=val | --param | -p val | -p ]", self.class.usage].compact.join(' ')
|
133
123
|
end
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
124
|
+
|
125
|
+
# Return the description text for this runner.
|
126
|
+
#
|
127
|
+
# @return [String] the description text
|
128
|
+
def description
|
129
|
+
self.class.description
|
130
|
+
end
|
131
|
+
|
132
|
+
# Is there a processor or dataflow registered with the given
|
133
|
+
# `name`?
|
134
|
+
#
|
135
|
+
# @param [String] name
|
136
|
+
# @return [true, false]
|
137
|
+
def registered? name
|
138
|
+
name && Wukong.registry.registered?(name.to_sym)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Retrieve the dataflow registered under a given `name`.
|
142
|
+
#
|
143
|
+
# @param [String,Symbol] name
|
144
|
+
# @return [Wukong::Processor, Wukong::Dataflow, nil]
|
145
|
+
def dataflow_class_for(name)
|
146
|
+
builder = (Wukong.registry.retrieve(name.to_sym) or return)
|
147
|
+
builder.for_class
|
148
|
+
end
|
149
|
+
|
150
|
+
# Is the given `name` a registered as a processor?
|
151
|
+
#
|
152
|
+
# @param [String,Symbol] name
|
153
|
+
# @return [true, false]
|
154
|
+
def processor?(name)
|
155
|
+
registered?(name) && dataflow_class_for(name).ancestors.include?(Wukong::Processor)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Is the given `name` a registered as a dataflow?
|
159
|
+
#
|
160
|
+
# @param [String,Symbol] name
|
161
|
+
# @return [true, false]
|
162
|
+
def dataflow?(name)
|
163
|
+
registered?(name) && dataflow_class_for(name).ancestors.include?(Wukong::Dataflow)
|
141
164
|
end
|
142
165
|
|
143
|
-
end
|
166
|
+
end
|
144
167
|
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require_relative('help_message')
|
2
|
+
module Wukong
|
3
|
+
class Runner
|
4
|
+
|
5
|
+
# The boot sequence of a runner consists of the following phases,
|
6
|
+
# each corresponding to a method provided by this module.
|
7
|
+
#
|
8
|
+
# * #load -- loads all application code
|
9
|
+
# * #configure -- configures settings from core Wukong, any loaded plugins, and any application code
|
10
|
+
# * #resolve -- resolves settings
|
11
|
+
# * #setup -- boots core Wukong and all loaded plugins
|
12
|
+
# * #validate -- validates command-line args
|
13
|
+
# * #run -- starts the runner running
|
14
|
+
#
|
15
|
+
# Each method can be separately overriden, allowing for a lot of
|
16
|
+
# customizability for different kinds of runners.
|
17
|
+
module BootSequence
|
18
|
+
|
19
|
+
include HelpMessage
|
20
|
+
|
21
|
+
# Boot this Runner, calling in order:
|
22
|
+
#
|
23
|
+
# * #load
|
24
|
+
# * #configure
|
25
|
+
# * #resolve
|
26
|
+
# * #setup
|
27
|
+
# * #validate
|
28
|
+
# * #run or #die
|
29
|
+
#
|
30
|
+
# If `override_settings` is passed then merge it over the
|
31
|
+
# Runner's usual settings (this is useful for unit tests where
|
32
|
+
# settings are injected in ways different from the usual
|
33
|
+
# workflow).
|
34
|
+
#
|
35
|
+
# @param [Configliere::Param] override_settings
|
36
|
+
def boot!(override_settings=nil)
|
37
|
+
load
|
38
|
+
configure
|
39
|
+
resolve
|
40
|
+
setup
|
41
|
+
settings.merge!(override_settings) if override_settings
|
42
|
+
|
43
|
+
case
|
44
|
+
when help_given? then dump_help_and_exit!
|
45
|
+
when validate then run
|
46
|
+
else
|
47
|
+
die("Invalid arguments")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Loads all code necessary for this Runner to perform, including:
|
54
|
+
#
|
55
|
+
# * any code associated with being inside of a deploy pack
|
56
|
+
# * any code passed in as (unknown rest) arguments on the command-line
|
57
|
+
def load
|
58
|
+
load_deploy_pack
|
59
|
+
load_args
|
60
|
+
end
|
61
|
+
|
62
|
+
# Endows the settings with everything it needs, including usage,
|
63
|
+
# description, and any define's provided by this Runner class or
|
64
|
+
# any plugins.
|
65
|
+
def configure
|
66
|
+
settings.use(:commandline)
|
67
|
+
settings.description = description if description
|
68
|
+
u = usage
|
69
|
+
settings.define_singleton_method(:usage){ u } if u
|
70
|
+
Wukong.configure_plugins(settings, program_name)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Resolves the settings.
|
74
|
+
#
|
75
|
+
# Rescues some of the annoying RuntimeErrors thrown by
|
76
|
+
# Configliere...
|
77
|
+
def resolve
|
78
|
+
begin
|
79
|
+
strip_help_param!
|
80
|
+
settings.resolve!
|
81
|
+
true
|
82
|
+
rescue RuntimeError, SystemExit => e
|
83
|
+
raise Error.new(e)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Performs any setup code necessary before run.
|
88
|
+
#
|
89
|
+
# Boots all plugins by default. If you override this code, make
|
90
|
+
# sure to either call `super` or boot plugins yourself.
|
91
|
+
def setup
|
92
|
+
Wukong.boot_plugins(settings, root)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Validates the command-line args. Raise a Wukong::Error in
|
96
|
+
# this method to terminate execution with a specific or custom
|
97
|
+
# error.
|
98
|
+
#
|
99
|
+
# Return false-like to terminate with a generic argument error.
|
100
|
+
#
|
101
|
+
# @return [true, false]
|
102
|
+
def validate
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
# Run this runner.
|
107
|
+
#
|
108
|
+
# You'll want to override this method in your own Runner class.
|
109
|
+
def run
|
110
|
+
end
|
111
|
+
|
112
|
+
# Kill this runner with the given error `message` and exit
|
113
|
+
# `code`.
|
114
|
+
#
|
115
|
+
# @param [String] message
|
116
|
+
# @param [Integer] code
|
117
|
+
def die message=nil, code=126
|
118
|
+
self.class.die(message, code)
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Wukong
|
2
|
+
class Runner
|
3
|
+
|
4
|
+
# Defines methods to help a Runner class load code passed in
|
5
|
+
# dynamically on the command-line.
|
6
|
+
#
|
7
|
+
# The default behavior of code in this module is to load any Ruby
|
8
|
+
# files (ending with `.rb`) passed in the command-line.
|
9
|
+
module CodeLoader
|
10
|
+
|
11
|
+
# Loads all code, whether from a deploy pack or additionally
|
12
|
+
# passed on the command line.
|
13
|
+
def load_args
|
14
|
+
(args_to_load || []).each do |path|
|
15
|
+
load_ruby_file(path)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# Load any additional code that we found out about on the
|
22
|
+
# command-line.
|
23
|
+
#
|
24
|
+
# @return [Array<String>] paths to load culled from the ARGV.
|
25
|
+
def args_to_load
|
26
|
+
ruby_file_args || []
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns all pre-resolved arguments which are Ruby files.
|
30
|
+
#
|
31
|
+
# @return [Array<String>]
|
32
|
+
def ruby_file_args
|
33
|
+
ARGV.find_all { |arg| arg.to_s =~ /\.rb$/ && arg.to_s !~ /^--/ }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Loads a single Ruby file, capturing LoadError and SyntaxError
|
37
|
+
# and raising Wukong::Error instead (so it can be easily captured
|
38
|
+
# by the Runner).
|
39
|
+
#
|
40
|
+
# @param [String] path
|
41
|
+
# @raise [Wukong::Error] if there is an error
|
42
|
+
def load_ruby_file path
|
43
|
+
return unless path
|
44
|
+
begin
|
45
|
+
Kernel.load path
|
46
|
+
rescue LoadError, SyntaxError => e
|
47
|
+
raise Error.new(e)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|