waw 0.2.2

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 (199) hide show
  1. data/LICENCE.rdoc +25 -0
  2. data/README.rdoc +32 -0
  3. data/bin/waw +32 -0
  4. data/bin/waw-profile +26 -0
  5. data/bin/waw-start +26 -0
  6. data/bin/wspec +33 -0
  7. data/layouts/empty/Rakefile +14 -0
  8. data/layouts/empty/config.ru +5 -0
  9. data/layouts/empty/config/commons.cfg +31 -0
  10. data/layouts/empty/config/devel.cfg +21 -0
  11. data/layouts/empty/ignore +7 -0
  12. data/layouts/empty/logs/dontforgetme +0 -0
  13. data/layouts/empty/resources/messages.rs +1 -0
  14. data/layouts/empty/test/wspec/site_respond.wspec +3 -0
  15. data/layouts/empty/test/wspec/test_all.rb +13 -0
  16. data/layouts/empty/waw.deploy +1 -0
  17. data/layouts/empty/waw.routing +6 -0
  18. data/layouts/static/dependencies +1 -0
  19. data/layouts/static/public/.wawaccess +18 -0
  20. data/layouts/static/public/css/style.css +0 -0
  21. data/layouts/static/public/images/dontforgetme +0 -0
  22. data/layouts/static/public/js/project.js +0 -0
  23. data/layouts/static/public/pages/.wawaccess +30 -0
  24. data/layouts/static/public/pages/404.wtpl +1 -0
  25. data/layouts/static/public/pages/index.wtpl +5 -0
  26. data/layouts/static/public/templates/.wawaccess +9 -0
  27. data/layouts/static/public/templates/layout.wtpl +17 -0
  28. data/layouts/static/test/wspec/static_pages_are_served.wspec +21 -0
  29. data/layouts/static/waw.routing +8 -0
  30. data/lib/waw.rb +99 -0
  31. data/lib/waw/commands/command.rb +115 -0
  32. data/lib/waw/commands/profile_command.rb +66 -0
  33. data/lib/waw/commands/start_command.rb +59 -0
  34. data/lib/waw/config.rb +110 -0
  35. data/lib/waw/controller.rb +25 -0
  36. data/lib/waw/controllers/action/action.rb +91 -0
  37. data/lib/waw/controllers/action/action_utils.rb +30 -0
  38. data/lib/waw/controllers/action/js_generation.rb +116 -0
  39. data/lib/waw/controllers/action_controller.rb +133 -0
  40. data/lib/waw/controllers/error/backtrace.rb +54 -0
  41. data/lib/waw/controllers/error_handler.rb +62 -0
  42. data/lib/waw/controllers/json_controller.rb +31 -0
  43. data/lib/waw/controllers/no_cache.rb +22 -0
  44. data/lib/waw/controllers/static/match.rb +80 -0
  45. data/lib/waw/controllers/static/waw_access.rb +235 -0
  46. data/lib/waw/controllers/static/waw_access_dsl.rb +48 -0
  47. data/lib/waw/controllers/static_controller.rb +37 -0
  48. data/lib/waw/default_config.cfg +14 -0
  49. data/lib/waw/environment_utils.rb +57 -0
  50. data/lib/waw/errors.rb +4 -0
  51. data/lib/waw/ext.rb +3 -0
  52. data/lib/waw/ext/core.rb +4 -0
  53. data/lib/waw/ext/core/hash.rb +47 -0
  54. data/lib/waw/ext/core/logger.rb +10 -0
  55. data/lib/waw/ext/core/module.rb +20 -0
  56. data/lib/waw/ext/core/object.rb +29 -0
  57. data/lib/waw/ext/rack.rb +19 -0
  58. data/lib/waw/ext/rack/builder.rb +43 -0
  59. data/lib/waw/ext/rack/delegator.rb +51 -0
  60. data/lib/waw/ext/rack/urlmap.rb +55 -0
  61. data/lib/waw/ext/wlang.rb +1 -0
  62. data/lib/waw/ext/wlang/hosted_language.rb +21 -0
  63. data/lib/waw/fullstate.rb +8 -0
  64. data/lib/waw/fullstate/on_class.rb +37 -0
  65. data/lib/waw/fullstate/on_instance.rb +27 -0
  66. data/lib/waw/fullstate/variable.rb +36 -0
  67. data/lib/waw/kern.rb +6 -0
  68. data/lib/waw/kern/app.rb +48 -0
  69. data/lib/waw/kern/empty/waw.deploy +0 -0
  70. data/lib/waw/kern/empty/waw.routing +1 -0
  71. data/lib/waw/kern/freezed_state.rb +32 -0
  72. data/lib/waw/kern/hooks.rb +53 -0
  73. data/lib/waw/kern/lifecycle.rb +248 -0
  74. data/lib/waw/kern/living_state.rb +87 -0
  75. data/lib/waw/kern/utils.rb +27 -0
  76. data/lib/waw/resource_collection.rb +100 -0
  77. data/lib/waw/restart.rb +32 -0
  78. data/lib/waw/routing.rb +43 -0
  79. data/lib/waw/routing/action_routing.rb +78 -0
  80. data/lib/waw/routing/dsl.rb +45 -0
  81. data/lib/waw/routing/feedback.rb +23 -0
  82. data/lib/waw/routing/form_validation_feedback.rb +36 -0
  83. data/lib/waw/routing/javascript.rb +17 -0
  84. data/lib/waw/routing/redirect.rb +26 -0
  85. data/lib/waw/routing/refresh.rb +17 -0
  86. data/lib/waw/routing/routing_rule.rb +16 -0
  87. data/lib/waw/scope_utils.rb +69 -0
  88. data/lib/waw/session.rb +51 -0
  89. data/lib/waw/testing.rb +1 -0
  90. data/lib/waw/tools/mail.rb +4 -0
  91. data/lib/waw/tools/mail/mail.rb +119 -0
  92. data/lib/waw/tools/mail/mail_agent.rb +123 -0
  93. data/lib/waw/tools/mail/mailbox.rb +62 -0
  94. data/lib/waw/tools/mail/template.rb +38 -0
  95. data/lib/waw/utils/dsl_helper.rb +116 -0
  96. data/lib/waw/validation.rb +175 -0
  97. data/lib/waw/validation/and_validator.rb +27 -0
  98. data/lib/waw/validation/array_validations.rb +38 -0
  99. data/lib/waw/validation/boolean_validator.rb +32 -0
  100. data/lib/waw/validation/comparison_validations.rb +45 -0
  101. data/lib/waw/validation/date_validator.rb +31 -0
  102. data/lib/waw/validation/default_validator.rb +30 -0
  103. data/lib/waw/validation/dsl_ruby_extensions.rb +11 -0
  104. data/lib/waw/validation/errors.rb +17 -0
  105. data/lib/waw/validation/ext.rb +3 -0
  106. data/lib/waw/validation/file_validator.rb +30 -0
  107. data/lib/waw/validation/float_validator.rb +19 -0
  108. data/lib/waw/validation/helpers.rb +67 -0
  109. data/lib/waw/validation/integer_validator.rb +16 -0
  110. data/lib/waw/validation/isin_validator.rb +24 -0
  111. data/lib/waw/validation/mandatory_validator.rb +17 -0
  112. data/lib/waw/validation/missing_validator.rb +17 -0
  113. data/lib/waw/validation/not_validator.rb +20 -0
  114. data/lib/waw/validation/or_validator.rb +34 -0
  115. data/lib/waw/validation/regexp_validator.rb +29 -0
  116. data/lib/waw/validation/same_validator.rb +16 -0
  117. data/lib/waw/validation/signature.rb +157 -0
  118. data/lib/waw/validation/size_validations.rb +44 -0
  119. data/lib/waw/validation/string_validator.rb +15 -0
  120. data/lib/waw/validation/validator.rb +48 -0
  121. data/lib/waw/wawgen.rb +2 -0
  122. data/lib/waw/wawgen/create.rb +166 -0
  123. data/lib/waw/wawgen/project.rb +25 -0
  124. data/lib/waw/wspec.rb +5 -0
  125. data/lib/waw/wspec/browser.rb +240 -0
  126. data/lib/waw/wspec/dsl.rb +201 -0
  127. data/lib/waw/wspec/html_analysis.rb +136 -0
  128. data/lib/waw/wspec/html_analysis/tag.rb +56 -0
  129. data/lib/waw/wspec/runner.rb +70 -0
  130. data/lib/waw/wspec/scenario.rb +35 -0
  131. data/lib/waw/wspec/suite.rb +54 -0
  132. data/test/bricks/error_handler/config/test.cfg +2 -0
  133. data/test/bricks/error_handler/logs/webapp.log +1411 -0
  134. data/test/bricks/error_handler/test/error_handler.wspec +16 -0
  135. data/test/bricks/error_handler/waw.deploy +1 -0
  136. data/test/bricks/error_handler/waw.routing +27 -0
  137. data/test/integration/waw_create_integration_test.rb +24 -0
  138. data/test/spec/assumptions_spec.rb +30 -0
  139. data/test/spec/controllers/action_controller_spec.rb +14 -0
  140. data/test/spec/controllers/static/waw_access_spec.rb +112 -0
  141. data/test/spec/environment_utils_spec.rb +15 -0
  142. data/test/spec/ext/core/hash_spec.rb +58 -0
  143. data/test/spec/fixtures.rb +41 -0
  144. data/test/spec/fixtures/action/config/default.cfg +2 -0
  145. data/test/spec/fixtures/action/lib/action_controller_test.rb +12 -0
  146. data/test/spec/fixtures/action/waw.deploy +1 -0
  147. data/test/spec/fixtures/action/waw.routing +6 -0
  148. data/test/spec/fixtures/empty/waw.deploy +0 -0
  149. data/test/spec/fixtures/empty/waw.routing +0 -0
  150. data/test/spec/fullstate/on_class_spec.rb +59 -0
  151. data/test/spec/fullstate/on_instance_spec.rb +59 -0
  152. data/test/spec/fullstate/session_spec.rb +43 -0
  153. data/test/spec/fullstate/variable_spec.rb +55 -0
  154. data/test/spec/resource_collection_spec.rb +50 -0
  155. data/test/spec/test_all.rb +9 -0
  156. data/test/spec/tools/mail/mail_agent_spec.rb +116 -0
  157. data/test/spec/tools/mail/mail_spec.rb +56 -0
  158. data/test/spec/tools/mail/mailbox_spec.rb +57 -0
  159. data/test/spec/tools/mail/template_spec.rb +47 -0
  160. data/test/spec/validation/array_validation_spec.rb +63 -0
  161. data/test/spec/validation/array_validator_spec.rb +17 -0
  162. data/test/spec/validation/date_validation_spec.rb +35 -0
  163. data/test/spec/validation/default_validation_spec.rb +37 -0
  164. data/test/spec/validation/disjuctive_validation_spec.rb +33 -0
  165. data/test/spec/validation/errors_spec.rb +37 -0
  166. data/test/spec/validation/file_validator_spec.rb +34 -0
  167. data/test/spec/validation/mail_validation_spec.rb +51 -0
  168. data/test/spec/validation/missing_validation_spec.rb +43 -0
  169. data/test/spec/validation/same_validation_spec.rb +24 -0
  170. data/test/spec/validation/signature_intuition_spec.rb +37 -0
  171. data/test/spec/validation/signature_spec.rb +164 -0
  172. data/test/spec/validation/validation_spec.rb +28 -0
  173. data/test/spec/wspec/html_analysis/tag_spec.rb +38 -0
  174. data/test/spec/wspec/html_analysis_spec.rb +170 -0
  175. data/test/unit/test_all.rb +8 -0
  176. data/test/unit/waw/app_test.rb +126 -0
  177. data/test/unit/waw/app_test/config/commons.cfg +2 -0
  178. data/test/unit/waw/app_test/config/devel.cfg +1 -0
  179. data/test/unit/waw/config_test.rb +54 -0
  180. data/test/unit/waw/controllers/action_controller_test.rb +76 -0
  181. data/test/unit/waw/controllers/action_test.rb +35 -0
  182. data/test/unit/waw/controllers/example_action_controller_test.rb +24 -0
  183. data/test/unit/waw/controllers/multiple_action_controller_test.rb +78 -0
  184. data/test/unit/waw/controllers/static/example/css/example.css +1 -0
  185. data/test/unit/waw/controllers/static/example/index.html +1 -0
  186. data/test/unit/waw/controllers/static/example/js/example.js +1 -0
  187. data/test/unit/waw/controllers/static/example/pages/hello.wtpl +1 -0
  188. data/test/unit/waw/controllers/static/waw_access_test.rb +76 -0
  189. data/test/unit/waw/ext/rack_test.rb +74 -0
  190. data/test/unit/waw/resource_collection_test.rb +49 -0
  191. data/test/unit/waw/resources.txt +4 -0
  192. data/test/unit/waw/routing/routing_test.rb +26 -0
  193. data/test/unit/waw/utils/dsl_helper_test.rb +79 -0
  194. data/test/unit/waw/utils/dsl_helper_test_extensions1.rb +4 -0
  195. data/test/unit/waw/validation/signature_test.rb +193 -0
  196. data/test/unit/waw/validation_test.rb +319 -0
  197. data/test/unit/waw/wspec/html_analysis_test.html +81 -0
  198. data/test/unit/waw/wspec/html_analysis_test.rb +26 -0
  199. metadata +272 -0
@@ -0,0 +1,44 @@
1
+ module Waw
2
+ module Validation
3
+ # Validation linked to the length of the string
4
+ module SizeValidations
5
+ module Methods
6
+
7
+ # Checks that it has a size
8
+ def has_size?(val) val.respond_to?(:size) end
9
+
10
+ # Builds a validator that verifies that the length is greater than
11
+ # a specified value
12
+ def >(value)
13
+ Validator.new {|*vals| Validation.argument_safe{ vals.all?{|val| has_size?(val) and (val.size > value)} }}
14
+ end
15
+
16
+ # Builds a validator that verifies that the length is greater-or-equal to
17
+ # a specified value
18
+ def >=(value)
19
+ Validator.new {|*vals| Validation.argument_safe{ vals.all?{|val| has_size?(val) and (val.size >= value)} }}
20
+ end
21
+
22
+ # Builds a validator that verifies that the length is less than
23
+ # a specified value
24
+ def <(value)
25
+ Validator.new {|*vals| Validation.argument_safe{ vals.all?{|val| has_size?(val) and (val.size < value)} }}
26
+ end
27
+
28
+ # Builds a validator that verifies that the length is less-or-equal to
29
+ # a specified value
30
+ def <=(value)
31
+ Validator.new {|*vals| Validation.argument_safe{ vals.all?{|val| has_size?(val) and (val.size <= value)} }}
32
+ end
33
+
34
+ # Builds a validator that verifies that the length is equal to a
35
+ # specified value
36
+ def ==(value)
37
+ Validator.new {|*vals| Validation.argument_safe{ vals.all?{|val| has_size?(val) and (val.size == value)} }}
38
+ end
39
+
40
+ end
41
+ extend Methods
42
+ end # module SizeValidations
43
+ end # module Validation
44
+ end # module Waw
@@ -0,0 +1,15 @@
1
+ module Waw
2
+ module Validation
3
+ class StringValidator < Validator
4
+
5
+ def validate(*values)
6
+ values.all?{|v| String===v}
7
+ end
8
+
9
+ def convert_and_validate(*values)
10
+ [validate(*values), values]
11
+ end
12
+
13
+ end
14
+ end # module Validation
15
+ end # module Waw
@@ -0,0 +1,48 @@
1
+ module Waw
2
+ module Validation
3
+ #
4
+ # A validator reusable class.
5
+ #
6
+ class Validator
7
+ include ::Waw::Validation::Helpers
8
+
9
+ # Creates a validator instance that takes a block as validation code
10
+ def initialize(&block)
11
+ @block = block
12
+ end
13
+
14
+ # Calls the block installed at initialization time
15
+ def validate(*values)
16
+ raise "Missing validation block on #{self}" unless @block
17
+ @block.call(*values)
18
+ end
19
+ def ===(*args)
20
+ validate(*args)
21
+ end
22
+
23
+ # Converts and validate
24
+ def convert_and_validate(*values)
25
+ validate(*values) ? [true, values] : [false, values]
26
+ end
27
+ def =~(*args)
28
+ convert_and_validate(*args)
29
+ end
30
+
31
+ # Negates this validator
32
+ def not
33
+ NotValidator.new(self)
34
+ end
35
+
36
+ # Creates a validator by disjunction
37
+ def |(validator)
38
+ OrValidator.new(self, Waw::Validation.to_validator(validator))
39
+ end
40
+
41
+ # Creates a validator by conjunction
42
+ def &(validator)
43
+ AndValidator.new(self, Waw::Validation.to_validator(validator))
44
+ end
45
+
46
+ end # class Validator
47
+ end # module Validation
48
+ end # module Waw
@@ -0,0 +1,2 @@
1
+ require 'waw/wawgen/project'
2
+ require 'waw/wawgen/create'
@@ -0,0 +1,166 @@
1
+ require 'optparse'
2
+ require 'fileutils'
3
+ module Waw
4
+ module Wawgen
5
+ # Implementation of 'waw create'
6
+ class Create
7
+
8
+ # Which layout
9
+ attr_reader :layout
10
+
11
+ # Force overrides?
12
+ attr_reader :force
13
+
14
+ # Creates a command instance
15
+ def initialize
16
+ @layout = 'empty'
17
+ @force = false
18
+ end
19
+
20
+ # Parses commandline options provided as an array of Strings.
21
+ def options
22
+ @options ||= OptionParser.new do |opt|
23
+ opt.program_name = File.basename $0
24
+ opt.version = WLang::VERSION
25
+ opt.release = nil
26
+ opt.summary_indent = ' ' * 4
27
+ banner = <<-EOF
28
+ # Usage waw create [options] ProjectName
29
+
30
+ # Creates an initial waw project structure
31
+ EOF
32
+ opt.banner = banner.gsub(/[ \t]+# /, "")
33
+
34
+ opt.separator nil
35
+ opt.separator "Options:"
36
+
37
+ opt.on("--force", "-f", "Erase any exsting file on conflict") do |value|
38
+ @force = true
39
+ end
40
+
41
+ opt.on("--layout=LAYOUT", "-l", "Use a specific waw layout (default empty)") do |value|
42
+ @layout = value
43
+ end
44
+
45
+ opt.on("--verbose", "-v", "Display extra progress as we parse.") do |value|
46
+ @verbosity = 2
47
+ end
48
+
49
+ # No argument, shows at tail. This will print an options summary.
50
+ # Try it and see!
51
+ opt.on_tail("-h", "--help", "Show this message") do
52
+ puts options
53
+ exit
54
+ end
55
+
56
+ # Another typical switch to print the version.
57
+ opt.on_tail("--version", "Show version") do
58
+ puts "waw version " << Waw::VERSION << " (c) University of Louvain, Bernard & Louis Lambeau"
59
+ exit
60
+ end
61
+
62
+ opt.separator nil
63
+ end
64
+ end
65
+
66
+ # Runs the command
67
+ def run(argv)
68
+ # parse options
69
+ rest = options.parse!(argv)
70
+ if rest.length != 1
71
+ puts options
72
+ exit(-1)
73
+ end
74
+
75
+ # check project name
76
+ project_name = rest[0]
77
+ exit("Invalid project name #{project_name}, must start with [a-zA-Z]") unless /^[a-zA-Z]/ =~ project_name
78
+ if project_name =~ /^[a-z]/
79
+ project_name = (project_name[0...1].upcase + project_name[1..-1])
80
+ puts "Warning: using #{project_name} as project name for ruby needs..."
81
+ end
82
+
83
+ # create project
84
+ project = ::Waw::Wawgen::Project.new(project_name)
85
+ begin
86
+ generate(project)
87
+ rescue WLang::Error => ex
88
+ puts ex.message
89
+ puts ex.wlang_backtrace.join("\n")
90
+ puts ex.backtrace.join("\n")
91
+ end
92
+ end
93
+
94
+ # Puts an error message and exits
95
+ def exit(msg)
96
+ puts msg
97
+ Kernel.exit(-1)
98
+ end
99
+
100
+ def generate_recursively(project, layout_folder, target_folder)
101
+ # Generate files now
102
+ Dir.new(layout_folder).each do |file|
103
+ next if ['.', '..', 'dontforgetme'].include?(file)
104
+ next if 'dependencies'==file
105
+ target = File.join(target_folder, file.gsub('project', project.lowname))
106
+ puts "Generating #{target} from #{layout}/#{file}"
107
+
108
+ if File.directory?(source=File.join(layout_folder, file))
109
+ FileUtils.mkdir_p target unless File.exists?(target)
110
+ generate_recursively(project, source, target)
111
+ else
112
+ File.open(target, 'w') do |io|
113
+ if /jquery/ =~ file
114
+ io << File.read(File.join(layout_folder, file))
115
+ else
116
+ context = {"project" => project}
117
+ io << WLang.file_instantiate(File.join(layout_folder, file), context, 'wlang/active-string', :parentheses).to_s
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ # Generates a given layout for a specific project
125
+ def generate_layout(project, layout)
126
+ # Locate the layout folder
127
+ layout_folder = File.join(File.dirname(__FILE__), '..', '..', '..', 'layouts', layout)
128
+ puts File.expand_path(layout_folder)
129
+ exit("Unknown layout #{layout}") unless File.exists?(layout_folder)
130
+
131
+ # Handle dependencies
132
+ dependencies_file = File.join(layout_folder, 'dependencies')
133
+ if File.exists?(dependencies_file)
134
+ File.readlines(dependencies_file).each do |line|
135
+ next if /^#/ =~ (line = line.strip)
136
+ line.split(/\s/).each do |word|
137
+ generate_layout(project, word)
138
+ end
139
+ end
140
+ end
141
+
142
+ # Let recursive generation occur
143
+ puts "Generating recursively project, #{layout_folder}, #{project.folder}"
144
+ generate_recursively(project, layout_folder, project.folder)
145
+ end
146
+
147
+ # Generates the project
148
+ def generate(project)
149
+ # A small debug message and we start
150
+ puts "Generating project with names #{project.upname} inside #{project.lowname} using layout #{layout}"
151
+
152
+ # 1) Create the output folder if it not exists
153
+ if File.exists?(project.folder) and not(force)
154
+ exit("The project folder #{project.lowname} already exists. Remove it first")
155
+ else
156
+ FileUtils.rm_rf project.folder if File.exists?(project.folder)
157
+ FileUtils.mkdir_p project.folder
158
+ end
159
+
160
+ generate_layout(project, layout)
161
+ FileUtils.chmod 0755, File.join(project.root, 'config.ru')
162
+ end
163
+
164
+ end # class Create
165
+ end # module Wawgen
166
+ end # module Waw
@@ -0,0 +1,25 @@
1
+ module Waw
2
+ module Wawgen
3
+ class Project
4
+
5
+ # Upper case name of the project
6
+ attr_reader :upname
7
+
8
+ # Lower case name of the project
9
+ attr_reader :lowname
10
+
11
+ # Creates a project instance
12
+ def initialize(name, folder=nil)
13
+ @upname, @lowname = name, WLang::encode(name, 'ruby/method-case')
14
+ @folder = folder
15
+ end
16
+
17
+ # Returns root folder
18
+ def root
19
+ @folder ||= lowname
20
+ end
21
+ alias :folder :root
22
+
23
+ end # class Project
24
+ end # module Wawgen
25
+ end # module Waw
@@ -0,0 +1,5 @@
1
+ require 'waw/wspec/html_analysis'
2
+ require 'waw/wspec/browser'
3
+ require 'waw/wspec/suite'
4
+ require 'waw/wspec/dsl'
5
+ require 'waw/wspec/scenario'
@@ -0,0 +1,240 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ module Waw
4
+ module WSpec
5
+ # A fake browser for waw application testing
6
+ class Browser
7
+ include Waw::WSpec::HTMLAnalysis
8
+
9
+ # Current browser location
10
+ attr_reader :location
11
+
12
+ # Current server response
13
+ attr_reader :response
14
+
15
+ # The last action result
16
+ attr_reader :last_action_result
17
+
18
+ # Creates a browser instance
19
+ def initialize(location = nil)
20
+ self.location = location if location
21
+ end
22
+
23
+ #################################################################### URI utilities
24
+
25
+ # Checks if something is an URI
26
+ def is_uri?(uri)
27
+ URI::Generic===uri
28
+ end
29
+
30
+ # Ensures that something is an uri or convert it.
31
+ def ensure_uri(uri)
32
+ raise ArgumentError, "ensure_uri: uri may not be nil" if uri.nil?
33
+ is_uri?(uri) ? uri : URI.parse(uri)
34
+ end
35
+
36
+ # Extracts the base of an URI
37
+ def extract_base(uri)
38
+ raise ArgumentError, "extract_base: uri may not be nil" if uri.nil?
39
+ ensure_uri("#{uri.scheme}://#{uri.host}#{uri.port ? (':' + uri.port.to_s) : ''}/")
40
+ end
41
+
42
+ # Computes the new location if a relative uri is followed
43
+ def relative_uri(uri)
44
+ raise ArgumentError, "relative_uri: uri may not be nil" if uri.nil?
45
+ uri = ensure_uri(uri)
46
+ if uri.path[0...1] == '/'
47
+ new_location = base.dup
48
+ new_location.path = uri.path
49
+ new_location.query = uri.query
50
+ new_location
51
+ else
52
+ new_location = base.dup
53
+ new_location.path = '/' + uri.path
54
+ new_location.query = uri.query
55
+ new_location
56
+ end
57
+ end
58
+
59
+ #################################################################### Query utilities
60
+
61
+ # Looks for the base of the website
62
+ def base
63
+ @base ||= find_base
64
+ end
65
+
66
+ # Finds the base of the current location
67
+ def find_base
68
+ if contents and (found = tag("base", {:href => /.*/}, contents))
69
+ ensure_uri(found[:href])
70
+ else
71
+ extract_base(location)
72
+ end
73
+ end
74
+
75
+ # Checks if the last request waw answered by a 404 not found
76
+ def is404
77
+ (Net::HTTPNotFound === response)
78
+ end
79
+ alias :is404? :is404
80
+
81
+ # Checks if the last request waw answered by a 200 OK
82
+ def is200
83
+ (Net::HTTPSuccess === response)
84
+ end
85
+ alias :is200? :is200
86
+
87
+ # Returns the current shown contents
88
+ def contents
89
+ response ? response.read_body : nil
90
+ end
91
+ alias :browser_contents :contents
92
+
93
+ #################################################################### Location set
94
+
95
+ # Go to a relative position
96
+ def go_relative(uri)
97
+ self.location = relative_uri(uri)
98
+ end
99
+
100
+ # Sets the current location
101
+ def location=(loc)
102
+ if (loc = ensure_uri(loc)).relative?
103
+ go_relative(loc)
104
+ else
105
+ @location, @response = fetch(loc)
106
+ @location
107
+ end
108
+ end
109
+
110
+ # Fetches the headers only and returns it without keeping the result in browser
111
+ # state
112
+ def headers_fetch(uri)
113
+ # Fetch the result at that location
114
+ if (loc = ensure_uri(uri)).relative?
115
+ headers_fetch(relative_uri(uri))
116
+ else
117
+ response = Net::HTTP.start(loc.host, loc.port) do |http|
118
+ headers = @cookie ? {'Cookie' => @cookie} : {}
119
+ http.head(loc.path, headers)
120
+ end
121
+ end
122
+ end
123
+
124
+ # Refreshes the browser
125
+ def refresh
126
+ self.location = location
127
+ end
128
+
129
+ # Simulates a click. Support relative as well as absolute paths.
130
+ # Raises a URI::InvalidURIError if the given href seems invalid.
131
+ def click_href(href)
132
+ uri = URI.parse(href)
133
+ if uri.relative?
134
+ go_relative(uri)
135
+ else
136
+ case uri.scheme
137
+ when "http"
138
+ self.location = href
139
+ else
140
+ raise ArgumentError, "This browser does not support #{href} location"
141
+ end
142
+ end
143
+ end
144
+
145
+ #################################################################### Server invocation utilities
146
+
147
+ # Applies the action routing for a given action
148
+ def apply_action_routing(action, result)
149
+ @last_action_result = result
150
+ action.routing.apply_on_browser(result, self) if action.routing
151
+ result.extend(Waw::Routing::Methods)
152
+ result
153
+ end
154
+
155
+ # Invokes an action server side, decodes json response an applies action routing.
156
+ # Returns the action result.
157
+ def invoke_action(action, args = {})
158
+ raise ArgumentError, "Browser.invoke_action expects an ActionController::Action as first parameter"\
159
+ unless ::Waw::ActionController::Action===action
160
+ location, response = fetch(relative_uri(action.uri), :post, args)
161
+ apply_action_routing(action, JSON.parse(response.body))
162
+ self.response
163
+ end
164
+
165
+ # Invokes a service server side and returns HTTP response
166
+ def invoke_service(service, args = {})
167
+ raise ArgumentError, "Browser.invoke_service expects a String as first parameter"\
168
+ unless String===service
169
+ location, response = fetch(relative_uri(service), :post, args)
170
+ self.response
171
+ end
172
+
173
+ # Invokes a server service with arguments and decoding method
174
+ def server_invoke(service, args, decode_method = nil)
175
+ location, response = fetch(relative_uri(service), :post, args)
176
+ response
177
+ end
178
+
179
+ #################################################################### Private section
180
+ # Clean cache after fetch
181
+ def clean_post_fetch
182
+ @base = nil
183
+ end
184
+
185
+ # Fetchs a given location
186
+ def fetch(uri, method = :get, data = {}, limit = 10)
187
+ # You should choose better exception.
188
+ raise 'HTTP redirect too deep' if limit == 0
189
+
190
+ # Fetch the result at that location
191
+ location = ensure_uri(uri)
192
+ response = Net::HTTP.start(location.host, location.port) do |http|
193
+ headers = @cookie ? {'Cookie' => @cookie} : {}
194
+ case method
195
+ when :get
196
+ path = location.path
197
+ path += '?' + location.query if location.query
198
+ http.request_get(path, headers)
199
+ when :post
200
+ req = Net::HTTP::Post.new(location.path, headers)
201
+ req.form_data = data.unsymbolize_keys
202
+ http.request(req)
203
+ else
204
+ raise ArgumentError, "Invalid fetch method #{method}"
205
+ end
206
+ end
207
+
208
+ # If a cookie is requested save it
209
+ @cookie = response['set-cookie']
210
+
211
+ # Catch the response, following redirections
212
+ result = case response
213
+ when Net::HTTPRedirection
214
+ fetch(response['location'] || response['Location'], :get, {}, limit - 1)
215
+ else
216
+ [location, response]
217
+ end
218
+
219
+ # Cleans cache and returns result
220
+ clean_post_fetch
221
+ result
222
+ end
223
+
224
+ #################################################################### Helpers to save context
225
+
226
+ # Installs the browser context
227
+ def install_context(location, response, cookie)
228
+ @location, @response, @cookie = location, response, cookie
229
+ self
230
+ end
231
+
232
+ # Duplicates this browser instance, with internal state
233
+ def dup
234
+ Browser.new.install_context(@location, @response, @cookie)
235
+ end
236
+
237
+ end # class Browser
238
+ class ServerError < StandardError; end
239
+ end # module WSpec
240
+ end # module Waw