qed 2.1.1 → 2.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.
Files changed (67) hide show
  1. data/COPYING +622 -344
  2. data/DIARY.rdoc +117 -0
  3. data/HISTORY +36 -0
  4. data/PROFILE +16 -0
  5. data/README.rdoc +69 -36
  6. data/REQUIRE +9 -0
  7. data/ROADMAP +12 -0
  8. data/VERSION +5 -0
  9. data/demo/01_demos.rdoc +56 -0
  10. data/demo/02_advice.rdoc +158 -0
  11. data/demo/03_helpers.rdoc +42 -0
  12. data/demo/04_fixtures.rdoc +29 -0
  13. data/demo/05_quote.rdoc +24 -0
  14. data/demo/07_toplevel.rdoc +42 -0
  15. data/demo/08_cross_script.rdoc +27 -0
  16. data/demo/09_cross_script.rdoc +27 -0
  17. data/demo/10_constant_lookup.rdoc +16 -0
  18. data/demo/applique/constant.rb +2 -0
  19. data/demo/applique/env.rb +5 -0
  20. data/demo/applique/fileutils.rb +1 -0
  21. data/demo/applique/markup.rb +10 -0
  22. data/demo/applique/quote.rb +4 -0
  23. data/demo/applique/toplevel.rb +15 -0
  24. data/demo/fixtures/data.txt +1 -0
  25. data/demo/fixtures/table.yml +5 -0
  26. data/demo/helpers/advice.rb +40 -0
  27. data/demo/helpers/sample.rb +4 -0
  28. data/demo/helpers/toplevel.rb +6 -0
  29. data/eg/hello_world.rdoc +15 -0
  30. data/{demo/error.rdoc → eg/view_error.rdoc} +0 -0
  31. data/{demo → eg}/website.rdoc +0 -0
  32. data/lib/qed.rb +20 -1
  33. data/lib/qed/advice.rb +4 -30
  34. data/lib/qed/advice/events.rb +6 -3
  35. data/lib/qed/advice/patterns.rb +37 -19
  36. data/lib/qed/applique.rb +85 -0
  37. data/lib/qed/command.rb +3 -5
  38. data/lib/qed/evaluator.rb +52 -56
  39. data/lib/qed/package.yml +5 -0
  40. data/lib/qed/parser.rb +149 -0
  41. data/lib/qed/profile.yml +16 -0
  42. data/lib/qed/reporter/{base.rb → abstract.rb} +17 -19
  43. data/lib/qed/reporter/bullet.rb +14 -16
  44. data/lib/qed/reporter/dotprogress.rb +7 -6
  45. data/lib/qed/reporter/html.rb +21 -3
  46. data/lib/qed/reporter/verbatim.rb +28 -26
  47. data/lib/qed/scope.rb +98 -82
  48. data/lib/qed/script.rb +21 -69
  49. data/lib/qed/session.rb +44 -3
  50. data/script/qedoc +2 -0
  51. data/script/test +2 -0
  52. metadata +74 -28
  53. data/doc/qedoc/index.html +0 -515
  54. data/doc/qedoc/jquery.js +0 -19
  55. data/meta/authors +0 -1
  56. data/meta/created +0 -1
  57. data/meta/description +0 -2
  58. data/meta/homepage +0 -1
  59. data/meta/name +0 -1
  60. data/meta/released +0 -1
  61. data/meta/repository +0 -1
  62. data/meta/requires +0 -5
  63. data/meta/ruby +0 -2
  64. data/meta/suite +0 -1
  65. data/meta/summary +0 -1
  66. data/meta/title +0 -1
  67. data/meta/version +0 -1
@@ -1,11 +1,12 @@
1
1
  module QED
2
2
 
3
- # = Patter Advice (When)
3
+ # = Pattern Advice (When)
4
4
  #
5
- # This class tracks When advice defined by demo scripts
6
- # and helpers. It is instantiated in Scope, so that
7
- # the advice methods will have access to the same
8
- # local binding and the demo scripts themselves.
5
+ # This class encapsulates "When" advice on plain text.
6
+ #
7
+ # Matches are evaluated in Scope context, via #instance_exec,
8
+ # so that the advice methods will have access to the same
9
+ # scope as the demonstrandum themselves.
9
10
  #
10
11
  class Patterns
11
12
 
@@ -16,33 +17,50 @@ module QED
16
17
  end
17
18
 
18
19
  #
19
- def add(pattern, &procedure)
20
- @when << [pattern, procedure]
20
+ def add(patterns, &procedure)
21
+ @when << [patterns, procedure]
21
22
  end
22
23
 
23
24
  #
24
- def call(match, *args)
25
- @when.each do |(pattern, proc)|
26
- case pattern
27
- when Regexp
28
- regex = pattern
29
- else
30
- regex = when_string_to_regexp(pattern)
25
+ def call(scope, section)
26
+ match = section.text
27
+ args = section.args
28
+
29
+ @when.each do |(patterns, proc)|
30
+ compare = match
31
+ matched = true
32
+ params = []
33
+ patterns.each do |pattern|
34
+ case pattern
35
+ when Regexp
36
+ regex = pattern
37
+ else
38
+ regex = when_string_to_regexp(pattern)
39
+ end
40
+ if md = regex.match(compare)
41
+ params.concat(md[1..-1])
42
+ compare = md.post_match
43
+ else
44
+ matched = false
45
+ break
46
+ end
31
47
  end
32
- if md = regex.match(match)
33
- proc.call(*md[1..-1])
48
+ if matched
49
+ params += args
50
+ #proc.call(*params)
51
+ scope.instance_exec(*params, &proc)
34
52
  end
35
53
  end
36
54
  end
37
55
 
38
56
  private
39
57
 
40
- #
58
+ # TODO: Now that we can use multi-patterns, we might not need this any more.
41
59
  def when_string_to_regexp(str)
42
60
  str = str.split(/(\(\(.*?\)\))(?!\))/).map{ |x|
43
- x =~ /\A\(\((.*)\)\)\z/ ? $1 : Regexp.escape(x)
61
+ x =~ /\A\(\((.*)\)\)\Z/ ? $1 : Regexp.escape(x)
44
62
  }.join
45
- str = str.gsub(/(\\\ )+/, '\s+')
63
+ str = str.gsub(/\\\s+/, '\s+')
46
64
  Regexp.new(str, Regexp::IGNORECASE)
47
65
 
48
66
  #rexps = []
@@ -0,0 +1,85 @@
1
+ require 'qed/advice'
2
+
3
+ module QED
4
+
5
+ # The Applique is the environment of libraries required by
6
+ # and the rules to apply to demonstrandum. The applique is
7
+ # defined by a set of scripts located in the +applique+
8
+ # directory of the upper-most test directory relative to
9
+ # the tests run and below the root of a project. All
10
+ # applique scripts are loaded at the start of a test
11
+ # session. Thus all demos belong to one and only one
12
+ # applique, and all the scripts in an applique must be
13
+ # compatible/consistant. For two demos to have separate
14
+ # applique they must be kept in separate directores.
15
+
16
+ class Applique < Module
17
+
18
+ #
19
+ def initialize
20
+ super()
21
+ extend self
22
+ @__advice__ = Advice.new
23
+ end
24
+
25
+ #
26
+ def initialize_copy(other)
27
+ @__advice__ = other.__advice__.dup
28
+ end
29
+
30
+ # Redirect missing constants to Object class
31
+ # to simulate TOPLEVEL.
32
+ #
33
+ # TODO: Clean backtrace when constant is not found.
34
+ def const_missing(name)
35
+ Object.const_get(name)
36
+ end
37
+
38
+ #
39
+ def __advice__
40
+ @__advice__
41
+ end
42
+
43
+ # Because patterns are mathced against HTML documents
44
+ # HTML special charaters +<+, +>+ and +&+ should not be
45
+ # used.
46
+ def When(*patterns, &procedure)
47
+ if patterns.size == 1 && Symbol === patterns.first
48
+ __advice__.events.add(:"#{patterns.first}", &procedure)
49
+ else
50
+ __advice__.patterns.add(patterns, &procedure)
51
+ end
52
+ end
53
+
54
+ # Before advice.
55
+ def Before(type=:code, &procedure)
56
+ __advice__.events.add(:"before_#{type}", &procedure)
57
+ end
58
+
59
+ # After advice.
60
+ def After(type=:code, &procedure)
61
+ __advice__.events.add(:"after_#{type}", &procedure)
62
+ end
63
+
64
+ # Code match-and-transform procedure.
65
+ #
66
+ # This is useful to transform human readable code examples
67
+ # into proper exectuable code. For example, say you want to
68
+ # run shell code, but want to make if look like typical
69
+ # shelle examples:
70
+ #
71
+ # $ cp fixture/a.rb fixture/b.rb
72
+ #
73
+ # You can use a transform to convert lines starting with '$'
74
+ # into executable Ruby using #system.
75
+ #
76
+ # system('cp fixture/a.rb fixture/b.rb')
77
+ #
78
+ #def Transform(pattern=nil, &procedure)
79
+ #
80
+ #end
81
+
82
+ end
83
+
84
+ end
85
+
data/lib/qed/command.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  require 'qed'
4
4
  require 'optparse'
5
5
  require 'shellwords'
6
- require 'tilt'
7
6
 
8
7
  module QED
9
8
 
@@ -17,7 +16,7 @@ module QED
17
16
  # Default location of demonstrations if no
18
17
  # specific files or locations given. This
19
18
  # is use in Dir.glob.
20
- DEFAULT_DEMOS_LOCATION = '{qed}'
19
+ DEFAULT_DEMO_LOCATION = '{demo,demos}'
21
20
 
22
21
  # Initialize and execute.
23
22
  def self.execute
@@ -156,12 +155,11 @@ module QED
156
155
  end
157
156
 
158
157
  #
159
-
160
158
  def demos
161
159
  files = self.files
162
- types = Tilt.mappings.keys
160
+ types = %w{qed rdoc md markdown} #Tilt.mappings.keys
163
161
  if files.empty?
164
- files << DEFAULT_DEMOS_LOCATION
162
+ files << DEFAULT_DEMO_LOCATION
165
163
  end
166
164
  files = files.map do |pattern|
167
165
  Dir[pattern]
data/lib/qed/evaluator.rb CHANGED
@@ -1,27 +1,15 @@
1
1
  module QED
2
2
 
3
- require 'tilt'
4
- require 'nokogiri'
5
3
  require 'qed/scope'
6
4
 
7
- # = Demo Script Evaluator
8
- #
9
- #--
10
- # TODO: Currently the Evaluator class uses #traverse to work
11
- # thru the HTML document and trigger events accordingly. This
12
- # works well enough for simple HTML documents --the kind produced
13
- # by typical wiki-markup formats. However, for complex HTML it
14
- # it will not produce ideal output (although the code segements
15
- # should still run just fine). To counter this weakness, we will
16
- # have to swtich to a more complex SAX parser in the future.
17
- #--
5
+ # = Demonstrandum Evaluator
18
6
  class Evaluator
19
7
 
20
8
  #
21
9
  def initialize(script, *observers)
22
10
  @script = script
23
11
  @file = script.file
24
- @root = script.root
12
+ @ast = script.parse
25
13
  @scope = script.scope
26
14
  @binding = script.binding
27
15
  @advice = script.advice
@@ -31,75 +19,78 @@ module QED
31
19
 
32
20
  #
33
21
  def run
34
- Dir.chdir(File.dirname(@file)) do
35
- advise!(:before_document, @script)
36
- @root.traverse do |element|
37
- call_tag(element)
38
- end
39
- advise!(:after_document, @script)
40
- end
41
- end
42
-
43
- #
44
- def call_tag(element)
45
- advise!(:tag, element)
46
- __send__("tag_#{element.name}", element)
22
+ advise!(:before_document, @script)
23
+ process
24
+ advise!(:after_document, @script)
47
25
  end
48
26
 
49
- # T A G S
50
-
51
27
  #
52
- def tag_a(element)
53
- case element['href']
54
- when /qed:\/\/(.*?)$/
55
- file = $1
56
- case File.extname(file)
57
- when '.rb'
58
- import!(file)
59
- else
60
- Script.new(file, scope).run
28
+ def process
29
+ @ast.each do |section|
30
+ case section.type
31
+ when :code
32
+ evaluate_code(section)
33
+ when :text
34
+ evaluate_text(section)
61
35
  end
62
36
  end
63
37
  end
64
38
 
65
39
  #
66
- def tag_pre(element)
67
- advise!(:before_code, element, @file)
40
+ def evaluate_code(section)
41
+ advise!(:before_code, section, @file)
68
42
  begin
69
- eval(element.text, @binding, @file, element.line)
70
- pass!(element)
43
+ advise!(:code, section)
44
+ eval(section.text, @binding, @file, section.line)
45
+ #@scope.module_eval(section.text, @file, section.line)
46
+ pass!(section)
71
47
  rescue Assertion => exception
72
- fail!(element, exception)
48
+ fail!(section, exception)
73
49
  rescue Exception => exception
74
- error!(element, exception)
50
+ error!(section, exception)
75
51
  end
76
- advise!(:after_code, element, @file)
52
+ advise!(:after_code, section, @file)
77
53
  end
78
54
 
79
55
  #
80
- def tag_p(element)
81
- advise!(:when, element.text)
56
+ def evaluate_text(section)
57
+ advise!(:text, section)
58
+ evaluate_links(section)
59
+ advise!(:when, section)
82
60
  end
83
61
 
84
62
  #
85
- def method_missing(s, *a)
86
- super(s, *a) unless /^tag/ =~ s.to_s
63
+ def evaluate_links(section)
64
+ section.text.scan(/\[qed:\/\/(.*?)\]/) do |match|
65
+ file = $1
66
+ # relative to demo script
67
+ if File.exist?(File.join(@script.directory,file))
68
+ file = File.join(@script.directory,file)
69
+ end
70
+ # ruby or another demo
71
+ case File.extname(file)
72
+ when '.rb'
73
+ import!(file)
74
+ else
75
+ Script.new(@script.applique, file, @script.scope).run
76
+ end
77
+ end
87
78
  end
88
79
 
89
80
  #
90
- def pass!(element)
91
- advise!(:pass, element)
81
+ def pass!(section)
82
+ advise!(:pass, section)
92
83
  end
93
84
 
94
85
  #
95
- def fail!(element, exception)
96
- advise!(:fail, element, exception)
86
+ def fail!(section, exception)
87
+ advise!(:fail, section, exception)
97
88
  #raise exception
98
89
  end
99
90
 
100
91
  #
101
- def error!(element, exception)
102
- advise!(:error, element, exception)
92
+ def error!(section, exception)
93
+ advise!(:error, section, exception)
103
94
  #raise exception
104
95
  end
105
96
 
@@ -114,7 +105,7 @@ module QED
114
105
  def advise!(signal, *args)
115
106
  @observers.each{ |o| o.update(signal, *args) }
116
107
  #@scope.__advice__.call(signal, *args)
117
- @advice.call(signal, *args)
108
+ @advice.call(@scope, signal, *args)
118
109
  end
119
110
 
120
111
  #
@@ -122,6 +113,11 @@ module QED
122
113
  # @advice.call_when(match)
123
114
  #end
124
115
 
116
+ ##
117
+ #def method_missing(s, *a)
118
+ # super(s, *a) unless /^tag/ =~ s.to_s
119
+ #end
120
+
125
121
  end
126
122
 
127
123
  end
@@ -0,0 +1,5 @@
1
+ name : qed
2
+ major: 2
3
+ minor: 2
4
+ patch: 0
5
+ date : 2010-06-20
data/lib/qed/parser.rb ADDED
@@ -0,0 +1,149 @@
1
+ module QED
2
+
3
+ # The parser breaks down a demonstandum into
4
+ # structured object to passed thru the script
5
+ # evaluator.
6
+ #
7
+ # Technically is defines it's own markup language
8
+ # but for interoperability sake it ...
9
+ class Parser
10
+
11
+ #
12
+ def initialize(file)
13
+ @lines = File.readlines(file).to_a
14
+ @ast = []
15
+ end
16
+
17
+ #
18
+ attr :ast
19
+
20
+ #
21
+ def parse
22
+ state = :text
23
+ linein = 0
24
+
25
+ text = ''
26
+
27
+ @lines.each_with_index do |line, lineno|
28
+ if /^\S/ =~ line
29
+ if state == :code
30
+ add_section(:code, text, linein)
31
+ linein = lineno
32
+ text = ''
33
+ end
34
+ state = :text
35
+ text << line
36
+ else
37
+ if state == :text
38
+ next if text.strip.empty?
39
+ add_section(:text, text, linein)
40
+ linein = lineno
41
+ text = ''
42
+ end
43
+ state = :code
44
+ text << line
45
+ end
46
+ end
47
+ add_section(state, text, linein)
48
+ @ast.reject!{ |sect| sect.type == :code && sect.text.strip.empty? }
49
+ return @ast
50
+ end
51
+
52
+ #
53
+ def add_section(state, text, lineno)
54
+ case state
55
+ when :code
56
+ if ast.last.raw?
57
+ @ast.last << text #clean_quote(text)
58
+ else
59
+ @ast << CodeSection.new(text, lineno)
60
+ end
61
+ else
62
+ @ast << TextSection.new(text, lineno)
63
+ #cont = (/\.\.\.\s*^/ =~ text ? true : false)
64
+ end
65
+ end
66
+
67
+ # TODO: We need to preserve the indentation for the verbatim reporter.
68
+ #def clean_quote(text)
69
+ # text = text.tabto(0).chomp.sub(/\A\n/,'')
70
+ # if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(text)
71
+ # text = md[1]
72
+ # end
73
+ # text.rstrip
74
+ #end
75
+
76
+ #
77
+ class Section
78
+ attr :text
79
+ attr :line
80
+ def initialize(text, line)
81
+ @text = text
82
+ @line = line
83
+ end
84
+ end
85
+
86
+ #
87
+ class TextSection < Section
88
+ attr :args
89
+ attr :cont
90
+ def initialize(text, line, *args)
91
+ @text = text
92
+ @line = line
93
+ @args = args
94
+ @cont = []
95
+ end
96
+ def <<(text)
97
+ @cont << clean_continuation(text)
98
+ @args << block_continuation(text)
99
+ end
100
+ def type
101
+ :text
102
+ end
103
+ # TODO: Use ':' or '...' ?
104
+ def raw?
105
+ #/\:\s*\Z/m =~ text
106
+ /\.\.\.\s*\Z/m =~ text
107
+ end
108
+
109
+ # Clean up the text, removing unccesseary white lines and triple
110
+ # quote brackets, but keep indention intact.
111
+ def clean_continuation(text)
112
+ text = text.chomp.sub(/\A\n/,'')
113
+ if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(text)
114
+ text = md[1]
115
+ end
116
+ text.rstrip
117
+ end
118
+
119
+ # Block the text, removing white lines, triple quote brackets
120
+ # and indention.
121
+ def block_continuation(text)
122
+ text = text.tabto(0).chomp.sub(/\A\n/,'')
123
+ if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(text)
124
+ text = md[1]
125
+ end
126
+ text.rstrip
127
+ end
128
+ end
129
+
130
+ #
131
+ class CodeSection < Section
132
+ #attr :args
133
+ def intialize(text, line) #, *args)
134
+ @text = text
135
+ @line = line
136
+ #@args = args
137
+ end
138
+ #def <<(arg)
139
+ # @args << arg
140
+ #end
141
+ def type
142
+ :code
143
+ end
144
+ end
145
+
146
+ end
147
+
148
+ end
149
+