cukedep 0.0.8 → 0.1.03

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 (36) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG.md +26 -0
  3. data/bin/cukedep +1 -1
  4. data/lib/cukedep/application.rb +7 -7
  5. data/lib/cukedep/config.rb +64 -7
  6. data/lib/cukedep/constants.rb +5 -1
  7. data/lib/cukedep/cuke-runner.rb +202 -0
  8. data/lib/cukedep/customization.rb +35 -0
  9. data/lib/cukedep/feature-rep.rb +2 -2
  10. data/lib/cukedep/file-action.rb +211 -0
  11. data/lib/cukedep/hook-dsl.rb +80 -0
  12. data/lib/cukedep/sandbox.rb +21 -0
  13. data/sample/cucumber.yml +4 -0
  14. data/sample/model/catalogue.yml +13 -0
  15. data/sample/model/members.yml +13 -0
  16. data/sample/model/model.rb +2 -2
  17. data/sample/model/rentals.yml +4 -0
  18. data/sample/model/users.yml +7 -0
  19. data/sample/result.html +472 -0
  20. data/spec/cukedep/application_spec.rb +8 -8
  21. data/spec/cukedep/cuke-runner_spec.rb +80 -0
  22. data/spec/cukedep/customization_spec.rb +36 -0
  23. data/spec/cukedep/file-action_spec.rb +374 -0
  24. data/spec/cukedep/gherkin-facade_spec.rb +54 -0
  25. data/spec/cukedep/hook-dsl_spec.rb +185 -0
  26. data/spec/cukedep/sample_features/cukedep.rake +204 -0
  27. data/spec/cukedep/sample_features/cukedep_hooks.rb +30 -0
  28. data/spec/cukedep/sample_features/dependencies.dot +38 -0
  29. data/spec/cukedep/sample_features/feature2id.csv +7 -0
  30. data/spec/cukedep/sample_features/files_to_copy/README.md +12 -0
  31. data/spec/cukedep/sample_features/files_to_copy/file1.txt +5 -0
  32. data/spec/cukedep/sample_features/files_to_copy/file2.txt +5 -0
  33. data/spec/cukedep/sample_features/files_to_copy/file3.txt +9 -0
  34. data/spec/spec_helper.rb +1 -0
  35. data/templates/rake.erb +184 -160
  36. metadata +26 -2
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YTdmNmY0Y2RkMTQxYjk5NmY2OGU2ZGIzMWVkNWQ0MmQ3MTFjNTk5OA==
4
+ NTMzODc5ODIyYzYyNzk4YmIxMzA0ODU3MzNkZWUxYTRkYTcwNDgxZA==
5
5
  data.tar.gz: !binary |-
6
- NjMxMWRiMmZiOWEwYjAwMmFiZWRlYTVlZDczMTU4OThkMjM1MTE2ZA==
6
+ YTE0OTg5NTlhNTI1NGFiODJlYTI0NzgyN2U1N2IyZGUzZjJmZDRhYQ==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- OTExMGMwMzliMTA0NGU2MGU2NTdhMjNlYWQxYjg1YjJlN2NlMDBmYWZiOWZh
10
- Yzc2MGU2M2I0ZjkwOWJjZGFlZGJiMmRhNTgzMWU5N2RiNWQwMmUwN2EzYzUy
11
- ODUwOGFlZjlmOWVjYjQ4YzZlMTkzMGUwZTE1OWFlNzY3NDAzMzQ=
9
+ YWIwY2NlODYwMDE3NzRiNWRmZTE4NjUyZTc0NzkwYmY2MjdhMTU5ZWFiMmQx
10
+ ZDY5ZjkwMGRiNGFlMTk0MjNmY2IyN2YyMzBiY2UwZTFkNmQ2NmRjYTU0N2Ni
11
+ NGY1MjZmZjZiYjQzYzBjMWY3YjViMTZkYmViNTBhZWFlOGEwZTc=
12
12
  data.tar.gz: !binary |-
13
- YzU1MDgyZjk5ZGM3OGE4NTMxNDMwYTk5MWQyNGRmZDY1MGQwMWIyMTI0YmU0
14
- YTkxZWYzMTE0N2I0NDVkYjkzMDJjZjhiOTRlMGNmZDJkZmE2NjQxMDRmNWMw
15
- MjM4NGJlZDQ4YmYwZWJkMzhkZDkzOWE5OWY1Mjk0NjI2NDgxNmM=
13
+ YmU1Zjg1MDA2OTdmNzg3ZDZiN2RmMWRiM2YwZTgxNTNmZGViNDJlMWY2MDZh
14
+ NjE1ZTNjZmJlNjBhOGJhYmMyNGEyODNiMzdmYTY2MTlmODhiZmU2ZGM1ZTlm
15
+ NWQwMmI4YzJiMjAwMjc1YzFiMjljMDRhZDA0YmM4MzIzNWQ4NWE=
@@ -1,3 +1,29 @@
1
+ ### 0.1.03 / 2013-11-28
2
+ * [FIX] Failing execution of generated Rake file in Travis CI. Cause: Rake template always referenced class in gem while it should reference development code while self-testing.
3
+ * [FIX] Added three missing test data files in the committed version.
4
+
5
+ ### 0.1.02 / 2013-11-27
6
+ * [FIX] Failing test again in `file-action_spec.rb` file. Cause: Commit to GitHub doesn't copy empty dir!. Added directory creation code.
7
+
8
+ ### 0.1.01 / 2013-11-27
9
+ * [FIX] Failing test in `file-action_spec.rb` file. Cause: Commit to GitHub doesn't copy empty dir!. Added directory creation code.
10
+
11
+ ### 0.1.00 / 2013-11-27
12
+ * [FEATURE] Customized file actions (save, delete, copy) associated with invocation event
13
+ * [FEATURE] Hooks for further invocation event customization.
14
+ * [NEW] `Cukedep::CukeRunner` class. Responsibilities: -Invokes Cucumber, handles the invocation events.
15
+ * [NEW] `Cukedep::Customization` class. Responsibilities: -Loads custom hook code blocks (in `cukedep_hooks.rb`)
16
+ * [NEW] `Cukedep::FileAction` class hierarchy. Responsibilities: specify the actions to perform before/after Cucumber invocation(s).
17
+ * [NEW] `Cukedep::HookDSL` module. Used to define a DSL (Domain Specific Language)
18
+ * [NEW] `Cukedep::Sandbox`. Responsibilities: Gives the context in which hook code block are executed.
19
+ * [NEW] `Cukedep::Config` class: new methods: `load_cfg`, `write`, `file_action_attrs`
20
+ * [CHANGE] Method `Application#run!`: Using new interface of `Cukedep::Config` class.
21
+
22
+
23
+ ### 0.0.9 / 2013-11-04 [unreleased]
24
+ * [CHANGE] For uniformity reasons, method `Application#start!` renamed to `Application#run!`
25
+ * [FIX] Method `Application#run!`: incorrect string interpolation in error message.
26
+
1
27
  ### 0.0.8 / 2013-10-31
2
28
  * [Fix] After adding a non-ASCII in a sample feature, Gherkin raised an encoding incompatibility error. Fixed
3
29
  * [CHANGE] Added a new field 'feature_encoding' in Config object to store the encoding of feature files (default = 'UTF-8')
@@ -10,6 +10,6 @@ require 'cukedep' # Load the Cukedep::Application class
10
10
 
11
11
  # The application's entry point
12
12
  app = Cukedep::Application.new
13
- app.start!(ARGV)
13
+ app.run!(ARGV)
14
14
 
15
15
  # End of file
@@ -1,6 +1,5 @@
1
1
  # File: application.rb
2
2
 
3
- require 'yaml'
4
3
 
5
4
  require_relative 'cli/cmd-line'
6
5
  require_relative 'config'
@@ -16,17 +15,17 @@ class Application
16
15
  public
17
16
 
18
17
  # Entry point for the application object.
19
- def start!(theCmdLineArgs)
18
+ def run!(theCmdLineArgs)
20
19
  options = options_from(theCmdLineArgs)
21
20
  create_default_cfg if options[:setup]
22
- config = load_cfg
21
+ config = Config.load_cfg(Cukedep::YMLFilename)
23
22
 
24
23
  # Complain if no project dir is specified
25
24
  if config.proj_dir.nil? || config.proj_dir.empty?
26
25
  if options[:project]
27
26
  @proj_dir = options[:project]
28
27
  else
29
- msg_p1 = "No project dir specified via 'Cukedep::YMLFilename'"
28
+ msg_p1 = "No project dir specified in '#{Cukedep::YMLFilename}'"
30
29
  msg_p2 = ' nor via --project option.'
31
30
  fail(StandardError, msg_p1 + msg_p2)
32
31
  end
@@ -62,10 +61,11 @@ protected
62
61
  answer = $stdin.gets
63
62
  exit if answer =~ /^\s*[Nn]\s*$/
64
63
  end
65
- File.open(Cukedep::YMLFilename, 'w') { |f| YAML.dump(Config.default, f) }
64
+ Config.default.write(Cukedep::YMLFilename)
65
+
66
66
  exit
67
67
  end
68
-
68
+ =begin
69
69
  # Read the .cukedep.yml file in the current working directory
70
70
  def load_cfg()
71
71
  if File.exist?(Cukedep::YMLFilename)
@@ -74,7 +74,7 @@ protected
74
74
  Config.default
75
75
  end
76
76
  end
77
-
77
+ =end
78
78
  # Parse the feature files (with the specified external encoding)
79
79
  def parse_features(external_encoding)
80
80
  # Create a Gherkin listener
@@ -1,34 +1,91 @@
1
1
  # File: config.rb
2
2
 
3
+ require 'yaml'
4
+ require_relative 'file-action'
5
+
3
6
  module Cukedep # Module used as a namespace
4
7
 
5
8
  FileMetaData = Struct.new(:name)
6
9
 
7
10
  Config = Struct.new(
8
- :feature_encoding, # The encoding of feature files
11
+ :feature_encoding, # The character encoding of feature files
9
12
  :proj_dir, # The directory of the cucumber project
10
13
  :feature2id, # Meta-data about the feature => feature id report
11
14
  :id2feature, # Meta-data about the feature id => feature report
12
15
  :graph_file, # Meta-data about the dependency graph file
13
16
  :rake_file, # Name of the output rake file
14
- :cucumber_args # Command-line syntax to use for the cucumber application
17
+ :cucumber_args, # Command-line syntax to use for the cucumber application
18
+ # File actions triggered at Cucumber invocation events
19
+ :before_all_f_actions,
20
+ :before_each_f_actions,
21
+ :after_each_f_actions,
22
+ :after_all_f_actions
15
23
  )
16
24
 
17
25
  # Re-open the class for further customisation
18
-
26
+
19
27
  # Configuration object for the Cukedep application.
20
28
  class Config
21
29
  # Factory method. Build a config object with default settings.
22
30
  def self.default()
23
- Config.new(
31
+ instance = Config.new(
24
32
  'UTF-8',
25
- nil,
26
- FileMetaData.new('feature2id.csv'),
27
- FileMetaData.new('feature2id.csv'),
33
+ nil,
34
+ FileMetaData.new('feature2id.csv'),
35
+ FileMetaData.new('feature2id.csv'),
28
36
  FileMetaData.new('dependencies.dot'),
29
37
  'cukedep.rake',
30
38
  []
31
39
  )
40
+
41
+ file_action_attrs.each do |attr|
42
+ instance[attr] = empty_action_triplet
43
+ end
44
+
45
+ return instance
46
+ end
47
+
48
+
49
+ # Read the YAML file with specified name from the current working directory.
50
+ # If that file does not exist, then return an instance with default values.
51
+ def self.load_cfg(filename)
52
+ # TODO: validation
53
+ instance = File.exist?(filename) ? YAML.load_file(filename) : self.default
54
+
55
+ return instance
56
+ end
57
+
58
+
59
+ # Save the Config object to a YAML file.
60
+ def write(filename)
61
+ File.open(filename, 'w') { |f| YAML.dump(self, f) }
62
+ end
63
+
64
+
65
+ private
66
+
67
+ # Purpose: get the list of attributes referencing
68
+ # a file action triplet.
69
+ def self.file_action_attrs()
70
+ return [
71
+ :before_all_f_actions,
72
+ :before_each_f_actions,
73
+ :after_each_f_actions,
74
+ :after_all_f_actions
75
+ ]
76
+ end
77
+
78
+
79
+ # Return Hash config for a no-op action triplet.
80
+ def self.empty_action_triplet()
81
+ {
82
+ save_patterns: [],
83
+ save_subdir: '',
84
+ delete_patterns: [],
85
+ delete_subdir: '',
86
+ copy_patterns: [],
87
+ copy_subdir: ''
88
+ }
32
89
  end
33
90
 
34
91
  end # class
@@ -3,7 +3,7 @@
3
3
 
4
4
  module Cukedep # Module used as a namespace
5
5
  # The version number of the gem.
6
- Version = '0.0.8'
6
+ Version = '0.1.03'
7
7
 
8
8
  # Brief description of the gem.
9
9
  Description = 'Manage dependencies between Cucumber feature files'
@@ -23,6 +23,10 @@ module Cukedep # Module used as a namespace
23
23
 
24
24
  # The file name for the user's settings
25
25
  YMLFilename = '.cukedep.yml'
26
+
27
+ # The file name for the custom block codes associated
28
+ # with before/after events.
29
+ HookFilename = 'cukedep_hooks.rb'
26
30
  end
27
31
  end # module
28
32
 
@@ -0,0 +1,202 @@
1
+ # File: cuke-runner.rb
2
+
3
+ require 'pathname'
4
+ require 'rake'
5
+
6
+ # Run Cucumber via specialized Rake task
7
+ require 'cucumber/rake/task'
8
+
9
+ require_relative 'file-action'
10
+ require_relative 'customization'
11
+
12
+ # UGLY workaround for bug in Cucumber's rake task
13
+ if Gem::VERSION[0].to_i >= 2 && Cucumber::VERSION <= '1.3.2'
14
+ # Monkey-patch a buggy method
15
+ class Cucumber::Rake::Task::ForkedCucumberRunner
16
+ def gem_available?(gemname)
17
+ if Gem::VERSION[0].to_i >= 2
18
+ gem_available_new_rubygems?(gemname)
19
+ else
20
+ gem_available_old_rubygems?(gemname)
21
+ end
22
+ end
23
+ end # class
24
+ end
25
+
26
+
27
+
28
+ module Cukedep # This module is used as a namespace
29
+
30
+ # Purpose: to launch Cucumber in the appropriate directory
31
+ # and pass it command-line arguments.
32
+ # Responsibilities:
33
+ # Know how to invoke Cucumber
34
+ # Know the base directory
35
+ # Know the project's root dir
36
+ class CukeRunner
37
+ # The current state of the runner.
38
+ attr_reader(:state)
39
+
40
+ # The absolute path of the root's project directory
41
+ attr_reader(:proj_dir)
42
+ attr_reader(:base_dir)
43
+ attr_reader(:config)
44
+ attr_reader(:handlers)
45
+
46
+ attr(:cucumber_opts, true)
47
+
48
+ # Constructor
49
+ def initialize(baseDir, projectDir, aConfig)
50
+ @base_dir = baseDir
51
+ @proj_dir = validated_proj_dir(projectDir)
52
+ @config = aConfig
53
+ @handlers = Customization.new.build_handlers(baseDir)
54
+
55
+ @state = :Initialized
56
+ end
57
+
58
+ # Launch Cucumber in the project directory.
59
+ def invoke()
60
+ options = [] # TODO: retrieve Cucumber options
61
+ orig_dir = Dir.getwd()
62
+ Dir.chdir(proj_dir)
63
+
64
+ begin
65
+ cuke_task = Cucumber::Rake::Task.new do |t|
66
+ t.cucumber_opts = options
67
+ end
68
+
69
+ cuke_task.runner.run()
70
+ rescue SystemExit => exc # Cucumber reports a failure.
71
+ raise StandardError, "Cucumber exited with status #{exc.status}"
72
+ ensure
73
+ Dir.chdir(orig_dir)
74
+ end
75
+ end
76
+
77
+ # Event handler that is triggered
78
+ # before any other event.
79
+ # It executes the before all hook first.
80
+ # Then it executes in the following order:
81
+ # Built-in save action, Custom save action
82
+ # Built-in delete action, Custom delete action
83
+ # Built-in copy action, Custom copy action
84
+ def before_all()
85
+ expected_state(:Initialized)
86
+
87
+ # Execute before all hook code
88
+ run_code_block
89
+
90
+ # Execute file actions
91
+ builtin_actions = ActionTriplet.builtin(:before_all)
92
+ custom_actions = ActionTriplet.new(config.before_all_f_actions)
93
+ run_triplets([builtin_actions, custom_actions])
94
+ @state = :ReadyToRun
95
+ end
96
+
97
+
98
+ # Event handler that is triggered
99
+ # after any other event.
100
+ # It executes first actions in the following order:
101
+ # Built-in save action, Custom save action
102
+ # Built-in delete action, Custom delete action
103
+ # Built-in copy action, Custom copy action
104
+ # Then it executes the after all hook last.
105
+ def after_all()
106
+ expected_state(:ReadyToRun)
107
+
108
+ builtin_actions = ActionTriplet.builtin(:after_all)
109
+ custom_actions = ActionTriplet.new(config.after_all_f_actions)
110
+ run_triplets([builtin_actions, custom_actions])
111
+
112
+ # Execute before all hook code
113
+ run_code_block
114
+ @state = :Complete
115
+ end
116
+
117
+
118
+ def run!(fileNames)
119
+ expected_state(:ReadyToRun)
120
+ before_each(fileNames)
121
+ invoke
122
+ after_each
123
+ end
124
+
125
+
126
+ private
127
+ def validated_proj_dir(projectDir)
128
+ path = Pathname.new(projectDir)
129
+ path = path.expand_path if path.relative?
130
+ unless path.exist?
131
+ raise StandardError, "No such project path: '#{path}'"
132
+ end
133
+
134
+ return path.to_s
135
+ end
136
+
137
+ def expected_state(aState)
138
+ unless state == aState
139
+ msg = "expected state was '#{aState}' instead of '#{state}'."
140
+ raise StandardError, msg
141
+ end
142
+ end
143
+
144
+
145
+ def before_each(fileNames)
146
+ # Execute before each hook code
147
+ run_code_block(fileNames)
148
+
149
+ builtin_actions = ActionTriplet.builtin(:before_each).dup
150
+ unless builtin_actions.nil?
151
+ builtin_actions.copy_action.patterns = fileNames
152
+ end
153
+
154
+ custom_actions = ActionTriplet.new(config.before_each_f_actions)
155
+ run_triplets([builtin_actions, custom_actions])
156
+ end
157
+
158
+
159
+ def after_each()
160
+ builtin_actions = ActionTriplet.builtin(:after_each)
161
+
162
+ custom_actions = ActionTriplet.new(config.after_each_f_actions)
163
+ run_triplets([builtin_actions, custom_actions])
164
+
165
+ # Execute after each hook code
166
+ run_code_block
167
+ end
168
+
169
+
170
+ def run_triplets(theTriplets)
171
+ all_triplets = theTriplets.compact # Remove nil elements
172
+
173
+ # Do all save actions...
174
+ all_triplets.each { |t| t.save_action.run!(proj_dir, base_dir) }
175
+
176
+ # Do all delete actions...
177
+ all_triplets.each { |t| t.delete_action.run!(proj_dir) }
178
+
179
+ # Do all copy actions...
180
+ all_triplets.each { |t| t.copy_action.run!(base_dir, proj_dir) }
181
+ end
182
+
183
+
184
+ def run_code_block(*args)
185
+ # Retrieve the name of the parent method.
186
+ parent_mth = (caller[0].sub(/^(.+):in (.+)$/, "\\2"))[1..-2]
187
+ kind, scope = parent_mth.split('_')
188
+ hook_kind = (kind + '_hooks')
189
+
190
+ kode = handlers[hook_kind.to_sym][scope.to_sym]
191
+ unless kode.nil?
192
+ safe_args = args.map { |one_arg| one_arg.dup.freeze }
193
+ kode.call(*safe_args)
194
+ end
195
+ end
196
+
197
+
198
+ end # class
199
+
200
+ end # module
201
+
202
+ # End of file
@@ -0,0 +1,35 @@
1
+ # File: customization.rb
2
+
3
+ require_relative 'constants'
4
+ require_relative 'hook-dsl'
5
+
6
+ module Cukedep # This module is used as a namespace
7
+
8
+
9
+ class Customization
10
+
11
+ # Retrieve before/after handlers from file
12
+ # Handlers are put in a Hash with keys :before_hooks, :after_hooks.
13
+ def build_handlers(directory)
14
+ handlers = nil
15
+
16
+ filepath = directory + '/' + Cukedep::HookFilename
17
+ if File.exist? filepath
18
+ obj = Object.new
19
+ obj.extend(HookDSL)
20
+ hook_source = File.read(filepath)
21
+ obj.instance_eval(hook_source)
22
+ handlers = {
23
+ before_hooks: obj.before_hooks,
24
+ after_hooks: obj.after_hooks
25
+ }
26
+ end
27
+
28
+ return handlers
29
+ end
30
+
31
+ end # class
32
+
33
+ end # module
34
+
35
+ # End of file