cuki 0.0.9 → 0.0.10

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/README.rdoc CHANGED
@@ -47,15 +47,20 @@ You can also pull a single feature:
47
47
 
48
48
  == Options
49
49
 
50
- - SKIP_AUTOFORMAT to avoid reformatting features (runs over the whole features directory)
51
- - PEM=/path/to/something.pem.cer (for client certificates)
52
- - CER=/path/to/something.pem (for client certificates)
50
+ - --skip--autoformat to avoid reformatting features (runs over the whole features directory)
51
+
52
+ == Configuration
53
+
54
+ If your Confluence installation requires a client certificate, you can supply the paths for these:
55
+
56
+ - CER=/path/to/ca.pem.cer
57
+ - PEM=/path/to/something.pem
53
58
 
54
59
  == Known Issues and Limitations
55
60
 
56
- - Will only work with Confluence setups which have no password, or use client certificates for authentcation
61
+ - Will only work with Confluence setups which have no password, or use client certificates for authentication
57
62
  - Only provides one-way sync, i.e. you can't edit a file locally and push it to Confluence
58
- - Fails if the AC block is the last H1 section of the page
63
+ - Fails if the AC block is the last h1. section of the page
59
64
  - Should fail if no features found in container block
60
65
 
61
66
  == TODO
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.9
1
+ 0.0.10
data/cucumber.yml CHANGED
@@ -1,2 +1,2 @@
1
1
  ---
2
- default: '--require features/'
2
+ default: '--require features/ -t ~@pending'
data/cuki.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "cuki"
8
- s.version = "0.0.9"
8
+ s.version = "0.0.10"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Andy Waite"]
12
- s.date = "2011-10-18"
12
+ s.date = "2011-10-22"
13
13
  s.description = ""
14
14
  s.email = "andy@andywaite.com"
15
15
  s.executables = ["cuki"]
@@ -31,6 +31,7 @@ Gem::Specification.new do |s|
31
31
  "cucumber.yml",
32
32
  "cuki.gemspec",
33
33
  "cuki.yaml.sample",
34
+ "features/pull/error_handling.feature",
34
35
  "features/pull/pull.feature",
35
36
  "features/pull/pull_single.feature",
36
37
  "features/pull/splitting.feature",
@@ -43,9 +44,14 @@ Gem::Specification.new do |s|
43
44
  "features/step_defs/pull_steps.rb",
44
45
  "features/step_defs/push_steps.rb",
45
46
  "features/support/env.rb",
47
+ "lib/cleaner.rb",
48
+ "lib/confluence_page.rb",
46
49
  "lib/cuki.rb",
50
+ "lib/feature_file.rb",
51
+ "lib/link_builder.rb",
47
52
  "lib/pusher.rb",
48
53
  "lib/string_utils.rb",
54
+ "lib/test_bits.rb",
49
55
  "spec/cuki_spec.rb",
50
56
  "spec/spec_helper.rb"
51
57
  ]
@@ -0,0 +1,16 @@
1
+ Feature: Error Handling
2
+
3
+ Scenario: Missing action
4
+ When I run `cuki`
5
+ Then it should fail with:
6
+ """
7
+ No action given
8
+ """
9
+
10
+ Scenario: Missing config
11
+ Given no file named "config/cuki.yaml" exists
12
+ When I run `cuki pull`
13
+ Then it should fail with:
14
+ """
15
+ No config file found at config/cuki.yaml
16
+ """
@@ -1,20 +1,5 @@
1
1
  Feature: Pull
2
2
 
3
- Scenario: Missing action
4
- When I run `cuki`
5
- Then it should fail with:
6
- """
7
- No action given
8
- """
9
-
10
- Scenario: Missing config
11
- Given no file named "config/cuki.yaml" exists
12
- When I run `cuki pull`
13
- Then it should fail with:
14
- """
15
- No config file found at config/cuki.yaml
16
- """
17
-
18
3
  Scenario: Pull all features
19
4
  Given a file named "config/cuki.yaml" with:
20
5
  """
@@ -16,7 +16,7 @@ Feature: Splitting
16
16
  <input id="content-title" value="Product Management">
17
17
  <div id="markupTextarea">
18
18
  Pretext
19
-
19
+
20
20
  h1. Acceptance Criteria
21
21
 
22
22
  Something
@@ -41,8 +41,6 @@ Feature: Splitting
41
41
  """
42
42
  Feature: Add Product
43
43
 
44
-
45
-
46
44
  http://example.com/pages/viewpage.action?pageId=123#ProductManagement-AddProduct
47
45
 
48
46
  This feature describes adding a product
@@ -55,8 +53,6 @@ Feature: Splitting
55
53
  """
56
54
  Feature: Remove Product
57
55
 
58
-
59
-
60
56
  http://example.com/pages/viewpage.action?pageId=123#ProductManagement-RemoveProduct
61
57
 
62
58
  This feature describes removing a product
@@ -66,45 +62,83 @@ Feature: Splitting
66
62
 
67
63
  """
68
64
 
69
- @announce
70
- Scenario: Pull all features (specified container)
71
- Given a file named "config/cuki.yaml" with:
72
- """
73
- ---
74
- host: http://example.com
75
- container: !ruby/regexp '/h1\. \*Acceptance Criteria\*(.*)h1\./m'
76
- mappings:
77
- 123: features/products
78
- """
79
- And a Confluence page on "example.com" with id 123:
80
- """
81
- <input id="content-title" value="Product Management">
82
- <div id="markupTextarea">
83
- h1. *Acceptance Criteria*
65
+ @announce @focus
66
+ Scenario: Pull all features (specified container)
67
+ Given a file named "config/cuki.yaml" with:
68
+ """
69
+ ---
70
+ host: http://example.com
71
+ container: !ruby/regexp '/h1\. \*Acceptance Criteria\*/'
72
+ mappings:
73
+ 123: features/products
74
+ """
75
+ And a Confluence page on "example.com" with id 123:
76
+ """
77
+ <input id="content-title" value="Product Management">
78
+ <div id="markupTextarea">
79
+ h1. *Acceptance Criteria*
80
+
81
+ Something
84
82
 
85
- Something
83
+ h2. Add Product
86
84
 
87
- h2. Add Product
85
+ This feature describes adding a product
88
86
 
89
- This feature describes adding a product
87
+ h6. Scenario: Scenario A
90
88
 
91
- h6. Scenario: Scenario A
89
+ h1. Next Section
90
+ </div>
91
+ """
92
+ When I run `cuki pull --skip-autoformat --skip-header`
93
+ Then the file "features/products/add_product.feature" should contain exactly:
94
+ """
95
+ Feature: Add Product
92
96
 
93
- h1. Next Section
94
- </div>
95
- """
96
- When I run `cuki pull --skip-autoformat --skip-header`
97
- Then the file "features/products/add_product.feature" should contain exactly:
98
- """
99
- Feature: Add Product
97
+ http://example.com/pages/viewpage.action?pageId=123#ProductManagement-AddProduct
100
98
 
99
+ This feature describes adding a product
101
100
 
101
+ Scenario: Scenario A
102
102
 
103
- http://example.com/pages/viewpage.action?pageId=123#ProductManagement-AddProduct
104
103
 
105
- This feature describes adding a product
104
+ """
106
105
 
107
- Scenario: Scenario A
106
+ @announce
107
+ Scenario:Special Chars
108
+ Given a file named "config/cuki.yaml" with:
109
+ """
110
+ ---
111
+ host: http://example.com
112
+ mappings:
113
+ 123: features/products
114
+ """
115
+ And a Confluence page on "example.com" with id 123:
116
+ """
117
+ <input id="content-title" value="Product Management">
118
+ <div id="markupTextarea">
119
+ h1. Acceptance Criteria
120
+
121
+ Something
122
+
123
+ h2. Add/Remove Product
124
+
125
+ This feature describes adding a product
126
+
127
+ h6. Scenario: Scenario A
128
+
129
+ h1. Next Section
130
+ </div>
131
+ """
132
+ When I run `cuki pull --skip-autoformat --skip-header`
133
+ Then the file "features/products/add_remove_product.feature" should contain exactly:
134
+ """
135
+ Feature: Add/Remove Product
136
+
137
+ http://example.com/pages/viewpage.action?pageId=123#ProductManagement-Add%2FRemoveProduct
138
+
139
+ This feature describes adding a product
140
+
141
+ Scenario: Scenario A
108
142
 
109
143
 
110
- """
144
+ """
@@ -1,6 +1,6 @@
1
1
  Feature: Push
2
2
 
3
- @focus @pending
3
+ @pending
4
4
  Scenario: Push a feature with no scenarios
5
5
  Given a file named "config/cuki.yaml" with:
6
6
  """
data/lib/cleaner.rb ADDED
@@ -0,0 +1,32 @@
1
+ class Cleaner
2
+
3
+ def self.clean(content)
4
+
5
+ content.gsub!('&nbsp;', '')
6
+
7
+ # remove the double pipes used for table headers in Confluence
8
+ content.gsub!('||', '|')
9
+
10
+ # remove other noise
11
+ content.gsub!("\r\n", "\n")
12
+ content.gsub!("\\\\\n", '')
13
+ content.gsub!('\\', '')
14
+
15
+ # remove any unwanted headers
16
+ content.gsub!(/h\d\. (Scenario: .*)/, '\1')
17
+ content.gsub!(/h\d\. (Scenario Outline: .*)/, '\1')
18
+ content.gsub!(/h\d\. (Background: .*)/, '\1')
19
+
20
+ #Remove fancy quotes
21
+ content.gsub!('’', "'")
22
+ content.gsub!('‘', "'")
23
+ content.gsub!('“', '"')
24
+ content.gsub!('”', '"')
25
+
26
+ content.gsub!(/^#(.*)/, '-' + '\1')
27
+
28
+ content
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,17 @@
1
+ require 'nokogiri'
2
+
3
+ class ConfluencePage
4
+
5
+ def initialize(response)
6
+ @doc = Nokogiri(response)
7
+ end
8
+
9
+ def title
10
+ @doc.at('#content-title')[:value]
11
+ end
12
+
13
+ def content
14
+ CGI.unescapeHTML @doc.css('#markupTextarea').text
15
+ end
16
+
17
+ end
data/lib/cuki.rb CHANGED
@@ -1,17 +1,15 @@
1
1
  require 'rubygems'
2
2
  require 'httpclient'
3
- require 'nokogiri'
4
3
  require 'yaml'
5
4
  require 'CGI'
6
5
  require 'json'
7
6
  require 'parallel'
8
- require File.dirname(__FILE__) + '/string_utils.rb'
9
-
10
- # terrible hack
11
- if File.exist?('stubs.json') || File.exist?('stubs.json')
12
- require 'webmock'
13
- include WebMock::API
14
- end
7
+ require File.dirname(__FILE__) + '/string_utils'
8
+ require File.dirname(__FILE__) + '/link_builder'
9
+ require File.dirname(__FILE__) + '/cleaner'
10
+ require File.dirname(__FILE__) + '/confluence_page'
11
+ require File.dirname(__FILE__) + '/feature_file'
12
+ require File.dirname(__FILE__) + '/test_bits'
15
13
 
16
14
  class Cuki
17
15
 
@@ -49,9 +47,6 @@ class Cuki
49
47
  end
50
48
  end
51
49
  autoformat
52
- elsif 'push' == action
53
- configure_push_stubs
54
- Pusher.push file, @config
55
50
  else
56
51
  puts "Unknown action '#{action}"
57
52
  exit(1)
@@ -72,8 +67,14 @@ class Cuki
72
67
  exit(1)
73
68
  end
74
69
  @config = YAML::load( File.open( CONFIG_PATH ) )
75
- raise "Host not found in #{CONFIG_PATH}" unless @config["host"]
76
- raise "Mappings not found in #{CONFIG_PATH}" unless @config["mappings"]
70
+ unless @config["host"]
71
+ puts "Host not found in #{CONFIG_PATH}"
72
+ exit(1)
73
+ end
74
+ unless @config["mappings"]
75
+ puts "Mappings not found in #{CONFIG_PATH}"
76
+ exit(1)
77
+ end
77
78
  end
78
79
 
79
80
  def configure_http_client
@@ -84,37 +85,42 @@ class Cuki
84
85
 
85
86
  def pull_feature(id, filepath)
86
87
  @content = ''
87
- wiki_edit_link = @config['host'] + '/pages/editpage.action?pageId=' + id.to_s
88
- wiki_view_link = @config['host'] + '/pages/viewpage.action?pageId=' + id.to_s
88
+
89
+ link_builder = LinkBuilder.new(@config['host'])
90
+
91
+ wiki_edit_link = link_builder.edit(id)
92
+ wiki_view_link = link_builder.view(id)
93
+
89
94
  puts "Downloading #{wiki_edit_link}"
90
95
  response = @client.get wiki_edit_link
91
- doc = Nokogiri(response.body)
96
+
97
+ confluence_page = ConfluencePage.new(response.body)
92
98
 
93
- unless doc.at('#content-title')
99
+ unless confluence_page.content
94
100
  puts "Not a valid confluence page:"
95
- puts doc.to_s
101
+ puts response.body
96
102
  exit(1)
97
103
  end
98
104
 
99
-
100
105
  unless filepath.include?('.feature')
101
106
 
102
- @config['container'] ||= /h1\. Acceptance Criteria(.*)h1\./m
107
+ @config['container'] ||= /h1\. Acceptance Criteria/
103
108
 
104
- handle_multi doc, id
109
+ handle_multi response.body, id
105
110
  else
106
111
 
107
- @content += "Feature: " + doc.at('#content-title')[:value] + "\n\n"
108
- @content += "#{wiki_view_link}\n\n"
109
- @content += get_markup(doc)
112
+ feature_file = FeatureFile.new
113
+ feature_file.title = confluence_page.title
114
+ feature_file.link = wiki_view_link
115
+ feature_file.content = confluence_page.content
110
116
 
111
- clean
117
+ content = Cleaner.clean(feature_file.to_s)
112
118
 
113
- process_tags
119
+ content = process_tags content
114
120
 
115
- @content = generated_by + @content unless @skip_header
121
+ content = generated_by + content unless @skip_header
116
122
 
117
- save_file filepath
123
+ save_file content, filepath
118
124
  end
119
125
  end
120
126
 
@@ -126,28 +132,57 @@ class Cuki
126
132
  `cucumber -a . --dry-run -P` unless @skip_autoformat
127
133
  end
128
134
 
129
- def handle_multi doc, id
130
- feature_title_compressed = doc.at('#content-title')[:value].anchorize
135
+ def handle_multi response_body, id
136
+ confluence_page = ConfluencePage.new(response_body)
137
+
138
+ feature_title_compressed = confluence_page.title.anchorize
131
139
 
132
- @content += get_markup(doc)
140
+ @content += confluence_page.content
133
141
 
134
- clean
142
+ @content = Cleaner.clean(@content)
135
143
 
136
- acceptance_criteria_matches = @content.match(@config['container'])
137
- unless acceptance_criteria_matches
144
+ unless @content.match(@config['container'])
145
+ puts "Could not find acceptance criteria container"
146
+ exit(1)
147
+ end
148
+ acceptance_criteria_block = @content.split(@config['container']).last
149
+ if acceptance_criteria_block.match(/h1\./)
150
+ acceptance_criteria_block = acceptance_criteria_block.split(/h1\./).first
151
+ end
152
+ unless acceptance_criteria_block
138
153
  puts "Could not match #{@config['container']} in #{id}"
139
154
  exit(1)
140
155
  end
141
- acceptance_criteria = acceptance_criteria_matches[1]
142
- scenario_titles = acceptance_criteria.scan(/h2. (.*)/).flatten
143
- scenario_blocks = acceptance_criteria.split(/h2. .*/)
156
+ acceptance_criteria = acceptance_criteria_block
157
+ scenario_titles = acceptance_criteria.scan(/h2\. (.*)/).flatten
158
+ scenario_blocks = acceptance_criteria.split(/h2\. .*/)
144
159
  scenario_blocks.shift
145
160
 
146
161
  combined = {}
162
+ found = 0
147
163
  scenario_titles.each_with_index do |title, index|
148
164
  combined[title] = scenario_blocks[index].gsub(/h6. (.*)/, '\1')
165
+ found += 1
166
+ end
167
+ if 0 == found
168
+ puts "No scenarios found in doc #{id}"
169
+ exit(1)
149
170
  end
150
171
  combined.each do |title, content|
172
+
173
+ tags = []
174
+ if @config['tags']
175
+ @config['tags'].each_pair do |tag, snippet|
176
+ tags << "@#{tag}" if @content.include?(snippet)
177
+ end
178
+ end
179
+ unless tags.empty?
180
+ content = tags.join(' ') + "\n" + content
181
+ # tags.each do |tag|
182
+ # content.gsub!(@config['tags'][tag.gsub('@', '')], '')
183
+ # end
184
+ end
185
+
151
186
  scenario_title_compressed = title.anchorize
152
187
  feature_filename = title.parameterize
153
188
 
@@ -155,94 +190,45 @@ class Cuki
155
190
 
156
191
  FileUtils.mkdir_p(dirpath)
157
192
 
158
- fname = "#{dirpath}/#{feature_filename.gsub("\r", '')}.feature"
193
+ fname = "#{dirpath}/#{feature_filename.gsub("\r", '').parameterize}.feature"
159
194
  File.open(fname, 'w') do |f|
160
195
  puts "Writing #{fname}"
161
196
  f.write generated_by unless @skip_header
162
197
  f.write "Feature: #{title}\n\n"
163
198
  link = @config['host'] + "/pages/viewpage.action?pageId=#{id}##{feature_title_compressed}-#{scenario_title_compressed}"
164
- f.write "\n\n" + link
199
+ f.write link
165
200
  f.write content
166
201
  end
167
202
  end
168
203
  end
169
204
 
170
- def clean
171
-
172
- @content.gsub!('&nbsp;', '')
173
-
174
- # remove the double pipes used for table headers in Confluence
175
- @content.gsub!('||', '|')
176
-
177
- # remove other noise
178
- @content.gsub!("\r\n", "\n")
179
- @content.gsub!("\\\\\n", '')
180
- @content.gsub!('\\', '')
181
-
182
- # remove any unwanted headers
183
- @content.gsub!(/h\d\. (Scenario: .*)/, '\1')
184
- @content.gsub!(/h\d\. (Scenario Outline: .*)/, '\1')
185
- @content.gsub!(/h\d\. (Background: .*)/, '\1')
186
-
187
- #Remove fancy quotes
188
- @content.gsub!('’', "'")
189
- @content.gsub!('‘', "'")
190
- @content.gsub!('“', '"')
191
- @content.gsub!('”', '"')
192
-
193
- @content.gsub!(/^#(.*)/, '-' + '\1')
194
-
195
- end
196
-
197
- def process_tags
205
+ def process_tags(content)
198
206
  tags = []
199
207
  if @config['tags']
200
208
  @config['tags'].each_pair do |tag, snippet|
201
- tags << "@#{tag}" if @content.include?(snippet)
209
+ tags << "@#{tag}" if content.include?(snippet)
202
210
  end
203
211
  end
204
212
  unless tags.empty?
205
- @content = tags.join(' ') + "\n" + @content
213
+ content = tags.join(' ') + "\n" + content
206
214
  tags.each do |tag|
207
- @content.gsub!(@config['tags'][tag.gsub('@', '')], '')
215
+ content.gsub!(@config['tags'][tag.gsub('@', '')], '')
208
216
  end
209
217
  end
218
+ content
210
219
  end
211
220
 
212
- def save_file(filepath)
221
+ def save_file(content, filepath)
213
222
  dir_path = File.dirname(filepath)
214
223
 
215
224
  FileUtils.mkdir_p(dir_path) unless File.exists?(dir_path)
216
225
 
217
226
  File.open(filepath, 'w') do |f|
218
227
  puts "Writing #{filepath}"
219
- f.puts @content
228
+ f.puts content
220
229
  end
221
230
  end
222
-
223
- def configure_pull_stubs
224
- if File.exist?('stubs.json')
225
- stubs = JSON.parse(File.open('stubs.json').read)
226
- stubs.each_pair do |url, body|
227
- stub_request(:get, url).to_return(:status => 200, :body => body, :headers => {})
228
- end
229
- FileUtils.rm 'stubs.json'
230
- end
231
- end
232
-
233
- def configure_push_stubs
234
- if File.exist?('push_stubs.json')
235
- stubs = JSON.parse(File.open('push_stubs.json').read)
236
- stubs.each do |a|
237
- stub_request(:post, "http://mywiki/").
238
- with(:body => {"title" => a['title'], "content"=> "\n\n" + a['content']},
239
- :headers => {'Content-Type'=>'application/x-www-form-urlencoded'}).
240
- to_return(:status => 200, :body => "", :headers => {})
241
- end
242
- FileUtils.rm 'push_stubs.json'
243
- end
244
- end
245
-
231
+
246
232
  def validate_args args
247
233
  if args.empty?
248
234
  puts "No action given"
@@ -250,8 +236,4 @@ class Cuki
250
236
  end
251
237
  end
252
238
 
253
- def get_markup(doc)
254
- CGI.unescapeHTML(doc.css('#markupTextarea').text)
255
- end
256
-
257
239
  end
@@ -0,0 +1,18 @@
1
+ class FeatureFile
2
+
3
+ attr_accessor :title, :link, :content
4
+
5
+ def initialize
6
+ @title = nil
7
+ @link = nil
8
+ @content = nil
9
+ end
10
+
11
+ def to_s
12
+ result = "Feature: " + title + "\n\n"
13
+ result += "#{link}\n\n"
14
+ result += content
15
+ result
16
+ end
17
+
18
+ end
@@ -0,0 +1,15 @@
1
+ class LinkBuilder
2
+
3
+ def initialize(host)
4
+ @host = host
5
+ end
6
+
7
+ def edit(id)
8
+ @host + '/pages/editpage.action?pageId=' + id.to_s
9
+ end
10
+
11
+ def view(id)
12
+ @host + '/pages/viewpage.action?pageId=' + id.to_s
13
+ end
14
+
15
+ end
data/lib/string_utils.rb CHANGED
@@ -4,6 +4,6 @@ class String
4
4
  end
5
5
 
6
6
  def parameterize
7
- self.downcase.gsub(' ', '_')
7
+ self.downcase.gsub(/[^a-z0-9\-]/, '_')
8
8
  end
9
9
  end
data/lib/test_bits.rb ADDED
@@ -0,0 +1,15 @@
1
+ # terrible hack
2
+ if File.exist?('stubs.json') || File.exist?('stubs.json')
3
+ require 'webmock'
4
+ include WebMock::API
5
+ end
6
+
7
+ def configure_pull_stubs
8
+ if File.exist?('stubs.json')
9
+ stubs = JSON.parse(File.open('stubs.json').read)
10
+ stubs.each_pair do |url, body|
11
+ stub_request(:get, url).to_return(:status => 200, :body => body, :headers => {})
12
+ end
13
+ FileUtils.rm 'stubs.json'
14
+ end
15
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cuki
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 9
10
- version: 0.0.9
9
+ - 10
10
+ version: 0.0.10
11
11
  platform: ruby
12
12
  authors:
13
13
  - Andy Waite
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-10-18 00:00:00 Z
18
+ date: 2011-10-22 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  requirement: &id001 !ruby/object:Gem::Requirement
@@ -156,6 +156,7 @@ files:
156
156
  - cucumber.yml
157
157
  - cuki.gemspec
158
158
  - cuki.yaml.sample
159
+ - features/pull/error_handling.feature
159
160
  - features/pull/pull.feature
160
161
  - features/pull/pull_single.feature
161
162
  - features/pull/splitting.feature
@@ -168,9 +169,14 @@ files:
168
169
  - features/step_defs/pull_steps.rb
169
170
  - features/step_defs/push_steps.rb
170
171
  - features/support/env.rb
172
+ - lib/cleaner.rb
173
+ - lib/confluence_page.rb
171
174
  - lib/cuki.rb
175
+ - lib/feature_file.rb
176
+ - lib/link_builder.rb
172
177
  - lib/pusher.rb
173
178
  - lib/string_utils.rb
179
+ - lib/test_bits.rb
174
180
  - spec/cuki_spec.rb
175
181
  - spec/spec_helper.rb
176
182
  homepage: http://github.com/andyw8/cuki