graffle 0.1.9 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/History.txt +9 -0
  2. data/Manifest.txt +15 -8
  3. data/Rakefile.hoe +1 -2
  4. data/bin/interpret-intentions.rb +51 -0
  5. data/examples/objects with notes.rb +1 -0
  6. data/examples/old-style-rails-workflow-test.expected +11 -0
  7. data/examples/old-style-rails-workflow-test.rb +43 -0
  8. data/examples/rails-workflow-test.rb +7 -6
  9. data/examples/sheet with key.expected +5 -0
  10. data/examples/sheet with key.graffle +0 -0
  11. data/examples/sheet with key.rb +38 -0
  12. data/graffle.tmproj +242 -75
  13. data/lib/graffle.rb +11 -2
  14. data/lib/graffle/nodoc/hacks.rb +1 -0
  15. data/lib/graffle/point.rb +1 -0
  16. data/lib/graffle/stereotypes.rb +34 -3
  17. data/lib/graffle/version.rb +3 -1
  18. data/lib/graphical_tests_for_rails.rb +11 -9
  19. data/lib/graphical_tests_for_rails/jumbled-string-canonicalizer.rb +117 -0
  20. data/lib/graphical_tests_for_rails/orderings.rb +1 -2
  21. data/lib/graphical_tests_for_rails/picture-applier.rb +257 -0
  22. data/lib/graphical_tests_for_rails/stereotype-extensions.rb +111 -0
  23. data/lib/graphical_tests_for_rails/text-appliers.rb +10 -84
  24. data/lib/graphical_tests_for_rails/user-intention.rb +250 -0
  25. data/test/abstract-graphic-tests.rb +10 -0
  26. data/test/container-tests.rb +29 -0
  27. data/test/graphical_tests_for_rails/jumbled-string-canonicalizer-tests.rb +174 -0
  28. data/test/graphical_tests_for_rails/new-style-picture-applier-tests.rb +286 -0
  29. data/test/graphical_tests_for_rails/old-style-picture-applier-tests.rb +129 -0
  30. data/test/graphical_tests_for_rails/user-intention-tests.rb +389 -0
  31. data/test/graphical_tests_for_rails/util.rb +9 -0
  32. data/test/sheet-tests.rb +21 -0
  33. data/test/text-tests.rb +6 -0
  34. metadata +25 -26
  35. data/design-notes/graphical-tests-for-rails-objects.graffle +0 -644
  36. data/lib/graphical_tests_for_rails/graphic-volunteers.rb +0 -75
  37. data/lib/graphical_tests_for_rails/picture-appliers.rb +0 -225
  38. data/lib/graphical_tests_for_rails/volunteer-pool.rb +0 -115
  39. data/test/graphical_tests_for_rails/deprecated-graphic-interpreter-tests.rb +0 -121
  40. data/test/graphical_tests_for_rails/graphic-volunteer-tests.rb +0 -218
  41. data/test/graphical_tests_for_rails/picture-applier-tests.rb +0 -215
  42. 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 < AbstractTextApplier
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 < ArgsFromQuotedText
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 < AbstractTextApplier
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