graffle 0.1.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +9 -0
- data/Manifest.txt +15 -8
- data/Rakefile.hoe +1 -2
- data/bin/interpret-intentions.rb +51 -0
- data/examples/objects with notes.rb +1 -0
- data/examples/old-style-rails-workflow-test.expected +11 -0
- data/examples/old-style-rails-workflow-test.rb +43 -0
- data/examples/rails-workflow-test.rb +7 -6
- data/examples/sheet with key.expected +5 -0
- data/examples/sheet with key.graffle +0 -0
- data/examples/sheet with key.rb +38 -0
- data/graffle.tmproj +242 -75
- data/lib/graffle.rb +11 -2
- data/lib/graffle/nodoc/hacks.rb +1 -0
- data/lib/graffle/point.rb +1 -0
- data/lib/graffle/stereotypes.rb +34 -3
- data/lib/graffle/version.rb +3 -1
- data/lib/graphical_tests_for_rails.rb +11 -9
- data/lib/graphical_tests_for_rails/jumbled-string-canonicalizer.rb +117 -0
- data/lib/graphical_tests_for_rails/orderings.rb +1 -2
- data/lib/graphical_tests_for_rails/picture-applier.rb +257 -0
- data/lib/graphical_tests_for_rails/stereotype-extensions.rb +111 -0
- data/lib/graphical_tests_for_rails/text-appliers.rb +10 -84
- data/lib/graphical_tests_for_rails/user-intention.rb +250 -0
- data/test/abstract-graphic-tests.rb +10 -0
- data/test/container-tests.rb +29 -0
- data/test/graphical_tests_for_rails/jumbled-string-canonicalizer-tests.rb +174 -0
- data/test/graphical_tests_for_rails/new-style-picture-applier-tests.rb +286 -0
- data/test/graphical_tests_for_rails/old-style-picture-applier-tests.rb +129 -0
- data/test/graphical_tests_for_rails/user-intention-tests.rb +389 -0
- data/test/graphical_tests_for_rails/util.rb +9 -0
- data/test/sheet-tests.rb +21 -0
- data/test/text-tests.rb +6 -0
- metadata +25 -26
- data/design-notes/graphical-tests-for-rails-objects.graffle +0 -644
- data/lib/graphical_tests_for_rails/graphic-volunteers.rb +0 -75
- data/lib/graphical_tests_for_rails/picture-appliers.rb +0 -225
- data/lib/graphical_tests_for_rails/volunteer-pool.rb +0 -115
- data/test/graphical_tests_for_rails/deprecated-graphic-interpreter-tests.rb +0 -121
- data/test/graphical_tests_for_rails/graphic-volunteer-tests.rb +0 -218
- data/test/graphical_tests_for_rails/picture-applier-tests.rb +0 -215
- data/test/graphical_tests_for_rails/text-applier-tests.rb +0 -111
@@ -0,0 +1,111 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Brian Marick on 2007-07-31.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
# These extensions to Graffle allow code here to give
|
7
|
+
# stereotyped names to visual thingees and generate method
|
8
|
+
# calls from them.
|
9
|
+
#
|
10
|
+
# TODO: Should these be moved into Graffle proper? On the one
|
11
|
+
# hand, some of them (esp. the queries) could be useful. On the other,
|
12
|
+
# they have weird names (especially in the more specific modules.)
|
13
|
+
|
14
|
+
# These methods are tested via the user-intention-tests.
|
15
|
+
|
16
|
+
|
17
|
+
require 'graffle/stereotypes'
|
18
|
+
|
19
|
+
module Graffle
|
20
|
+
|
21
|
+
module AbstractGraphic
|
22
|
+
# These are utilities of use when you're handed an
|
23
|
+
# abstract graphic and want to know something about it.
|
24
|
+
|
25
|
+
def is_labeled_line? # :nodoc:
|
26
|
+
return false unless behaves_like?(LineGraphic)
|
27
|
+
has_label?
|
28
|
+
end
|
29
|
+
|
30
|
+
def has_line_label_note? # :nodoc:
|
31
|
+
is_labeled_line? && label.has_note?
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_line_label_content? # :nodoc:
|
35
|
+
is_labeled_line? && label.has_content?
|
36
|
+
end
|
37
|
+
|
38
|
+
def has_line_note? # :nodoc:
|
39
|
+
behaves_like?(LineGraphic) && has_note?
|
40
|
+
end
|
41
|
+
|
42
|
+
def has_shaped_graphic_note? # :nodoc:
|
43
|
+
behaves_like?(ShapedGraphic) && has_note?
|
44
|
+
end
|
45
|
+
|
46
|
+
def has_shaped_graphic_content? # :nodoc:
|
47
|
+
behaves_like?(ShapedGraphic) && has_content?
|
48
|
+
end
|
49
|
+
|
50
|
+
def has_any_content? # :nodoc:
|
51
|
+
return true if behaves_like?(ShapedGraphic) && has_content?
|
52
|
+
has_line_label_content?
|
53
|
+
end
|
54
|
+
|
55
|
+
def has_any_note? # :nodoc:
|
56
|
+
return true if has_note?
|
57
|
+
has_line_label_note?
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def any_content_lines # :nodoc:
|
62
|
+
if has_shaped_graphic_content?
|
63
|
+
shaped_graphic_content_lines
|
64
|
+
elsif has_line_label_content?
|
65
|
+
line_label_content_lines
|
66
|
+
else
|
67
|
+
user_is_bewildered("#{self.inspect} doesn't have content.")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
def any_note_lines # :nodoc:
|
73
|
+
if has_shaped_graphic_note?
|
74
|
+
shaped_graphic_note_lines
|
75
|
+
elsif has_line_note?
|
76
|
+
line_note_lines
|
77
|
+
elsif has_line_label_note?
|
78
|
+
line_label_note_lines
|
79
|
+
else
|
80
|
+
user_is_bewildered("#{self.inspect} doesn't have content.")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
module ShapedGraphic
|
86
|
+
def shaped_graphic_note_lines # :nodoc: # can be automatically generated
|
87
|
+
note.as_lines
|
88
|
+
end
|
89
|
+
|
90
|
+
def shaped_graphic_content_lines # :nodoc: # can be automatically generated
|
91
|
+
content.as_lines
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
module LineGraphic
|
96
|
+
def line_note_lines # :nodoc: # can be called by generated code
|
97
|
+
note.as_lines
|
98
|
+
end
|
99
|
+
|
100
|
+
def line_label_content_lines # :nodoc # can be called by generated code
|
101
|
+
label.content.as_lines
|
102
|
+
end
|
103
|
+
|
104
|
+
def line_label_note_lines # :nodoc: # can be called by generated code
|
105
|
+
label.note.as_lines
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
@@ -1,44 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
#
|
3
|
-
# Created by Brian Marick on 2007-07-21.
|
2
|
+
# Created by Brian Marick on 2007-07-23.
|
4
3
|
# Copyright (c) 2007. All rights reserved.
|
4
|
+
"prevent the above from infecting rdoc"
|
5
5
|
|
6
6
|
module GraphicalTestsForRails
|
7
7
|
|
8
8
|
# Just a wrapper module so that classes are grouped nicely in rdoc.
|
9
9
|
# You don't have to include this - that happens if you require
|
10
10
|
# graphical_tests_for_rails.rb.
|
11
|
-
module TextAppliers
|
12
|
-
|
13
|
-
# This is the base class for objects that know how to convert text
|
14
|
-
# into a series of messages and send them to some target.
|
15
|
-
class AbstractTextApplier
|
16
|
-
|
17
|
-
# apply is given an array of text (_lines_). It is to convert
|
18
|
-
# that text into messages and send them to _target_.
|
19
|
-
#
|
20
|
-
def apply(lines, target)
|
21
|
-
message_sends = lines.collect {|line| message_and_args_from(line) }
|
22
|
-
message_sends.each do |m|
|
23
|
-
Fluid.log << readable(*m)
|
24
|
-
target.send(*m)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
# message_and_args_from is given a string that is derived from some
|
29
|
-
# text. It must answer an array whose first element is a
|
30
|
-
# message name and whose remaining elements are appropriate message
|
31
|
-
# arguments. Must be defined in any subclass.
|
32
|
-
def message_and_args_from; subclass_responsibility; end
|
33
|
-
|
34
|
-
private
|
35
|
-
def readable(message, *args)
|
36
|
-
inspected = args.collect { |a| a.inspect }
|
37
|
-
"+ #{message}(#{inspected.join(', ')})"
|
38
|
-
end
|
39
|
-
|
40
|
-
|
41
|
-
end
|
11
|
+
module TextAppliers # :nodoc: all
|
42
12
|
|
43
13
|
|
44
14
|
# The arguments to apply are quoted within the _string_.
|
@@ -48,30 +18,7 @@ module GraphicalTestsForRails
|
|
48
18
|
# means this:
|
49
19
|
# target.paul_logs_in_with("john", "j")
|
50
20
|
#
|
51
|
-
class ArgsFromQuotedText
|
52
|
-
|
53
|
-
def message_and_args_from(string)
|
54
|
-
quote_marker = '%<<<==-'
|
55
|
-
|
56
|
-
string = string.gsub(/(\w)'(\w)/, "#{quote_marker}\\1\\2#{quote_marker}")
|
57
|
-
tokens = string.split(/['"],?/, allow_trailing_null_fields = -1)
|
58
|
-
message = tokens[0].strip.downcase
|
59
|
-
args = tokens[1..-1]
|
60
|
-
|
61
|
-
|
62
|
-
message.gsub!(/#{quote_marker}(.)(.)#{quote_marker}/, "\\1\\2")
|
63
|
-
message.gsub!(/\s+/,'_')
|
64
|
-
message.gsub!(/-/, '_')
|
65
|
-
message.gsub!(/\W/, '')
|
66
|
-
|
67
|
-
|
68
|
-
args = args.enum_slice(2).collect { |e| e[0] }
|
69
|
-
args = args.collect do | arg |
|
70
|
-
arg.gsub(/#{quote_marker}(.)(.)#{quote_marker}/, "\\1'\\2")
|
71
|
-
end
|
72
|
-
|
73
|
-
[message] + args
|
74
|
-
end
|
21
|
+
class ArgsFromQuotedText
|
75
22
|
end
|
76
23
|
|
77
24
|
# Like ArgsFromQuotedText, but the first word (typically a name)
|
@@ -82,17 +29,7 @@ module GraphicalTestsForRails
|
|
82
29
|
# target.logs_in_with("paul", "john", "j")
|
83
30
|
#
|
84
31
|
|
85
|
-
class PrefixNameAndArgsFromQuotedText
|
86
|
-
def message_and_args_from(string)
|
87
|
-
user_claims(string =~ /^\s*(\w+)\s+(.*)$/) {
|
88
|
-
"'#{string}' cannot be split into a name and an action."
|
89
|
-
}
|
90
|
-
name = $1.downcase
|
91
|
-
rest = super($2)
|
92
|
-
message = rest.shift
|
93
|
-
[message, name, *rest]
|
94
|
-
end
|
95
|
-
|
32
|
+
class PrefixNameAndArgsFromQuotedText
|
96
33
|
end
|
97
34
|
|
98
35
|
|
@@ -103,33 +40,22 @@ module GraphicalTestsForRails
|
|
103
40
|
# applier.apply("HOME")
|
104
41
|
# turns into this:
|
105
42
|
# applier.assert_on("home")
|
106
|
-
class TextAsNameOfPage
|
43
|
+
class TextAsNameOfPage
|
107
44
|
|
108
45
|
# When producing a method call, objects of this class will
|
109
46
|
# prepend the _message_ to
|
110
47
|
# a single argument it constructs.
|
111
48
|
def initialize(message)
|
49
|
+
user_claims(message == 'assert_on_page') {
|
50
|
+
"At the moment, a TextAsNameOfPage must be given assert_on_page."
|
51
|
+
}
|
112
52
|
@message = message
|
113
53
|
end
|
114
|
-
|
115
|
-
# All of the text in the _string_ is expected to be the name
|
116
|
-
# of a page. It is downcased and treated as the single argument to
|
117
|
-
# the message passed to +new+.
|
118
|
-
def message_and_args_from(string)
|
119
|
-
[@message, string.downcase.strip]
|
120
|
-
end
|
121
54
|
end
|
122
55
|
|
123
56
|
class TextApplierThatDoesNothing # :nodoc:
|
124
|
-
def message_and_args_from(string)
|
125
|
-
[:object_id] # no_op
|
126
|
-
end
|
127
|
-
|
128
|
-
def apply(lines, target)
|
129
|
-
# do nothing, don't even bother sending a no-op
|
130
|
-
end
|
131
57
|
end
|
132
|
-
|
58
|
+
|
133
59
|
end
|
134
60
|
|
135
61
|
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Brian Marick on 2007-07-31.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
require 'enumerator'
|
7
|
+
require 'graphical_tests_for_rails/stereotype-extensions'
|
8
|
+
require 'graphical_tests_for_rails/jumbled-string-canonicalizer'
|
9
|
+
|
10
|
+
module GraphicalTestsForRails
|
11
|
+
class UserIntention # :nodoc:
|
12
|
+
include GraphicalTestsForRails
|
13
|
+
|
14
|
+
module Names
|
15
|
+
USER_MEANS_LINE_LABEL_NOTE = "line_label_note"
|
16
|
+
USER_MEANS_LINE_LABEL_CONTENT = "line_label_content"
|
17
|
+
USER_MEANS_LINE_NOTE = "line_note"
|
18
|
+
USER_MEANS_SHAPED_GRAPHIC_NOTE = "shaped_graphic_note"
|
19
|
+
USER_MEANS_SHAPED_GRAPHIC_CONTENT = "shaped_graphic_content"
|
20
|
+
USER_MEANS_ANY_CONTENT = "any_content"
|
21
|
+
USER_MEANS_ANY_NOTE = "any_note"
|
22
|
+
|
23
|
+
USER_MEANS_CONTAINS_IGNORE = "contains_ignore"
|
24
|
+
|
25
|
+
USER_MEANS_LINES_WITH_ONE_WORD = "line_with_one_word"
|
26
|
+
USER_MEANS_LINE_ENDS_WITH_ELLIPSES = "ends_with_ellipses"
|
27
|
+
USER_MEANS_REMAINING_LINES = "catch_all_for_lines"
|
28
|
+
|
29
|
+
USER_MEANS_ACTOR_ACTION_WITH_QUOTE_ARGS = "user_action_quote_args"
|
30
|
+
USER_MEANS_CLAIM_WITH_QUOTE_ARGS = "claim_quote_args"
|
31
|
+
USER_MEANS_PAGE_NAME = "page_name"
|
32
|
+
USER_MEANS_DO_NOTHING = "do_nothing"
|
33
|
+
|
34
|
+
end
|
35
|
+
include Names
|
36
|
+
|
37
|
+
DISAMBIGUATE_BY_WORD_COUNT = proc { |a, b|
|
38
|
+
# The 'compact' is needed to get rid of non-matching alternatives
|
39
|
+
# in something like /(foo)|(bar)/.
|
40
|
+
words = proc { | x | - x.raw_reason.compact.length }
|
41
|
+
words[a] <=> words[b]
|
42
|
+
}
|
43
|
+
|
44
|
+
LINE_LABEL_REGEXP = /\b(line)\s+(labels?)\b/
|
45
|
+
NOTE_REGEXP = /\b(notes?)|(annotations?)\b/
|
46
|
+
CONTENT_REGEXP = /\b(contents?)\b/
|
47
|
+
LINE_REGEXP = /\b(lines?)\b/
|
48
|
+
SHAPED_GRAPHIC_REGEXP = /\b(shaped\s+graphics?)|(shapes?)\b/
|
49
|
+
|
50
|
+
PLACE = {
|
51
|
+
[LINE_LABEL_REGEXP, NOTE_REGEXP] => USER_MEANS_LINE_LABEL_NOTE,
|
52
|
+
[LINE_LABEL_REGEXP, CONTENT_REGEXP] => USER_MEANS_LINE_LABEL_CONTENT,
|
53
|
+
[LINE_REGEXP, NOTE_REGEXP] => USER_MEANS_LINE_NOTE,
|
54
|
+
[SHAPED_GRAPHIC_REGEXP, NOTE_REGEXP] => USER_MEANS_SHAPED_GRAPHIC_NOTE,
|
55
|
+
[SHAPED_GRAPHIC_REGEXP, CONTENT_REGEXP] => USER_MEANS_SHAPED_GRAPHIC_CONTENT,
|
56
|
+
[CONTENT_REGEXP] => USER_MEANS_ANY_CONTENT,
|
57
|
+
[NOTE_REGEXP] => USER_MEANS_ANY_NOTE,
|
58
|
+
}
|
59
|
+
PLACER = JumbledStringCanonicalizer.new(PLACE) {
|
60
|
+
disambiguate &DISAMBIGUATE_BY_WORD_COUNT
|
61
|
+
no_match_message { | source | "'#{source}' does not refer to any graphic."}
|
62
|
+
}
|
63
|
+
|
64
|
+
TEXTS = {
|
65
|
+
[/\b(ignore)\b/] => USER_MEANS_CONTAINS_IGNORE,
|
66
|
+
}
|
67
|
+
TEXTER = JumbledStringCanonicalizer.new(TEXTS) {
|
68
|
+
default nil
|
69
|
+
}
|
70
|
+
|
71
|
+
|
72
|
+
QUOTED_ARGS_REGEXPS = [/\b(quote\w*)\b/, /\b(arg\w*)\b/]
|
73
|
+
ACTIONS = {
|
74
|
+
[/\b(skip)|(ignore)\b/] => USER_MEANS_DO_NOTHING,
|
75
|
+
[/\b(user)\s+(actions?)\b/] + QUOTED_ARGS_REGEXPS => USER_MEANS_ACTOR_ACTION_WITH_QUOTE_ARGS,
|
76
|
+
[/\b(claims?)\b/] + QUOTED_ARGS_REGEXPS => USER_MEANS_CLAIM_WITH_QUOTE_ARGS,
|
77
|
+
[/\b(names?)\b/, /\b(pages?)\b/] => USER_MEANS_PAGE_NAME,
|
78
|
+
}
|
79
|
+
ACTIONER = JumbledStringCanonicalizer.new(ACTIONS) {
|
80
|
+
no_match_message { | source | "'#{source}' doesn't say what should happen in the test."}
|
81
|
+
multiple_match_message { | source, pretty_reasons, raw_reasons |
|
82
|
+
pretty_reasons = pretty_reasons.sort_by { |r| r.length }
|
83
|
+
"'#{source}' implies conflicting instructions:\n" +
|
84
|
+
"One is from these words: #{pretty_reasons[0]}\n" +
|
85
|
+
pretty_reasons[1..-1].collect { |r| "Another is from these: #{r}" }.join("\n")
|
86
|
+
}
|
87
|
+
|
88
|
+
}
|
89
|
+
|
90
|
+
OTHER_KIND_REGEXP = '(\b(other)\s+(kinds?)\s+(of)\b)'
|
91
|
+
OTHERWISE_REGEXP = '(\botherwise\b)'
|
92
|
+
LINE_GUARDS = {
|
93
|
+
[/\b(with)\s+.*\b(one)\s+(word)\b/] => USER_MEANS_LINES_WITH_ONE_WORD,
|
94
|
+
[/\b(ellipses)\b/] => USER_MEANS_LINE_ENDS_WITH_ELLIPSES,
|
95
|
+
[/\b(text\.\.\.*)/] => USER_MEANS_LINE_ENDS_WITH_ELLIPSES,
|
96
|
+
[/#{OTHERWISE_REGEXP}|#{OTHER_KIND_REGEXP}/] => USER_MEANS_REMAINING_LINES,
|
97
|
+
}
|
98
|
+
GUARDER = JumbledStringCanonicalizer.new(LINE_GUARDS) {
|
99
|
+
default nil
|
100
|
+
}
|
101
|
+
|
102
|
+
attr_reader :place, :text, :action, :line_guard, :source
|
103
|
+
|
104
|
+
def to_s
|
105
|
+
retval = "'#{source}' produces:\n"
|
106
|
+
retval += "\t#{action.value} if #{place.value}"
|
107
|
+
retval += " && #{text.value}" if text.value
|
108
|
+
retval += " && #{line_guard.value}" if line_guard.value
|
109
|
+
retval
|
110
|
+
end
|
111
|
+
def inspect; to_s; end
|
112
|
+
|
113
|
+
def self.understand(instruction)
|
114
|
+
prog1(new) { | i | i.understand(instruction) }
|
115
|
+
end
|
116
|
+
|
117
|
+
def understand(instruction)
|
118
|
+
@source = instruction
|
119
|
+
understand_place(instruction)
|
120
|
+
understand_text(instruction)
|
121
|
+
understand_action(instruction)
|
122
|
+
understand_line_guard(instruction)
|
123
|
+
end
|
124
|
+
|
125
|
+
def understand_place(instruction)
|
126
|
+
@place = PLACER.canonicalize(instruction)
|
127
|
+
end
|
128
|
+
|
129
|
+
def understand_text(instruction)
|
130
|
+
@text = TEXTER.canonicalize(instruction)
|
131
|
+
end
|
132
|
+
|
133
|
+
def understand_action(instruction)
|
134
|
+
@action = ACTIONER.canonicalize(instruction)
|
135
|
+
end
|
136
|
+
|
137
|
+
def understand_line_guard(instruction)
|
138
|
+
@line_guard = GUARDER.canonicalize(instruction)
|
139
|
+
end
|
140
|
+
|
141
|
+
def applies_to_graphic?(graphic)
|
142
|
+
graphic.send('has_' + place.value + '?')
|
143
|
+
end
|
144
|
+
|
145
|
+
def lines_from(graphic)
|
146
|
+
lines = graphic.send(place.value + '_lines')
|
147
|
+
lines.collect { |l| l.strip }.find_all { |l| l != '' }
|
148
|
+
end
|
149
|
+
|
150
|
+
def applies_to_text?(lines)
|
151
|
+
return true if text.value.nil?
|
152
|
+
send(text.value + '?', lines)
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
def contains_ignore?(lines)
|
157
|
+
lines.any? { |l| l.strip.ends_with?("ignore") }
|
158
|
+
end
|
159
|
+
|
160
|
+
def applies_to_line?(line)
|
161
|
+
return true if line_guard.value.nil?
|
162
|
+
send(line_guard.value + '_acceptable?', line)
|
163
|
+
end
|
164
|
+
|
165
|
+
def line_with_one_word_acceptable?(line)
|
166
|
+
line.split(' ').length == 1
|
167
|
+
end
|
168
|
+
|
169
|
+
def ends_with_ellipses_acceptable?(line)
|
170
|
+
line.strip =~ /\.\.+$/
|
171
|
+
end
|
172
|
+
|
173
|
+
def catch_all_for_lines_acceptable?(line)
|
174
|
+
true
|
175
|
+
end
|
176
|
+
|
177
|
+
def apply(string, target, log = [])
|
178
|
+
Applier.new(target, log).send("apply_" + action.value, string)
|
179
|
+
end
|
180
|
+
|
181
|
+
class Applier
|
182
|
+
def initialize(target, log)
|
183
|
+
@target = target
|
184
|
+
@log = log
|
185
|
+
end
|
186
|
+
|
187
|
+
def do_it(*message_send)
|
188
|
+
@log << readable(*message_send)
|
189
|
+
@target.send(*message_send)
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
def apply_user_action_quote_args(string)
|
194
|
+
user_claims(string =~ /^\s*(\w+)\s+(.*)$/) {
|
195
|
+
"'#{string}' cannot be split into a name and an action."
|
196
|
+
}
|
197
|
+
name = $1.downcase
|
198
|
+
rest = message_and_quote_args($2)
|
199
|
+
message = rest.shift
|
200
|
+
do_it(message, name, *rest)
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
def apply_claim_quote_args(string)
|
205
|
+
do_it(*message_and_quote_args(string))
|
206
|
+
end
|
207
|
+
|
208
|
+
def apply_page_name(string)
|
209
|
+
do_it('assert_on_page', string.downcase.strip)
|
210
|
+
end
|
211
|
+
|
212
|
+
def apply_do_nothing(string)
|
213
|
+
@log << "# '#{string}' ignored."
|
214
|
+
end
|
215
|
+
|
216
|
+
private
|
217
|
+
def message_and_quote_args(string)
|
218
|
+
quote_marker = '%<<<==-'
|
219
|
+
|
220
|
+
string = string.gsub(/(\w)'(\w)/, "#{quote_marker}\\1\\2#{quote_marker}")
|
221
|
+
tokens = string.split(/['"],?/, allow_trailing_null_fields = -1)
|
222
|
+
message = tokens[0].strip.downcase
|
223
|
+
args = tokens[1..-1]
|
224
|
+
|
225
|
+
|
226
|
+
message.gsub!(/#{quote_marker}(.)(.)#{quote_marker}/, "\\1\\2")
|
227
|
+
message.gsub!(/\s+/,'_')
|
228
|
+
message.gsub!(/-/, '_')
|
229
|
+
message.gsub!(/\W/, '')
|
230
|
+
|
231
|
+
|
232
|
+
args = args.enum_slice(2).collect { |e| e[0] }
|
233
|
+
args = args.collect do | arg |
|
234
|
+
arg.gsub(/#{quote_marker}(.)(.)#{quote_marker}/, "\\1'\\2")
|
235
|
+
end
|
236
|
+
|
237
|
+
[message] + args
|
238
|
+
end
|
239
|
+
|
240
|
+
def readable(message, *args)
|
241
|
+
inspected = args.collect { |a| a.inspect }
|
242
|
+
"+ #{message}(#{inspected.join(', ')})"
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
|
247
|
+
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|