ascii_binder 0.1.9 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +16 -0
  3. data/ascii_binder.gemspec +5 -3
  4. data/bin/asciibinder +53 -44
  5. data/features/command_help.feature +8 -0
  6. data/features/command_version.feature +8 -0
  7. data/features/repo_build.feature +34 -0
  8. data/features/repo_clean.feature +20 -0
  9. data/features/repo_clone.feature +22 -0
  10. data/features/repo_create.feature +13 -0
  11. data/features/repo_package.feature +15 -0
  12. data/features/step_definitions/steps.rb +182 -0
  13. data/features/support/_clone_distro_map.yml +14 -0
  14. data/features/support/_invalid_distro_map.yml +14 -0
  15. data/features/support/env.rb +468 -0
  16. data/features/support/test_distro/.gitignore +9 -0
  17. data/features/support/test_distro/_distro_map.yml +29 -0
  18. data/features/support/test_distro/_images/asciibinder-logo-horizontal.png +0 -0
  19. data/features/support/test_distro/_images/asciibinder_web_logo.svg +125 -0
  20. data/features/support/test_distro/_images/book_pages_bg.jpg +0 -0
  21. data/features/support/test_distro/_images/favicon.ico +0 -0
  22. data/features/support/test_distro/_images/favicon32x32.png +0 -0
  23. data/features/support/test_distro/_javascripts/.gitkeep +0 -0
  24. data/features/support/test_distro/_stylesheets/asciibinder.css +568 -0
  25. data/features/support/test_distro/_templates/_css.html.erb +3 -0
  26. data/features/support/test_distro/_templates/_nav.html.erb +31 -0
  27. data/features/support/test_distro/_templates/page.html.erb +92 -0
  28. data/features/support/test_distro/_topic_map.yml +36 -0
  29. data/features/support/test_distro/index-main.html +10 -0
  30. data/features/support/test_distro/index-test.html +10 -0
  31. data/features/support/test_distro/main_only_topic_group/index.adoc +17 -0
  32. data/features/support/test_distro/test_only_topic_group/index.adoc +17 -0
  33. data/features/support/test_distro/welcome/index.adoc +17 -0
  34. data/features/support/test_distro/welcome/subtopics/index.adoc +17 -0
  35. data/features/support/test_distro/welcome/subtopics/main_only_topic.adoc +17 -0
  36. data/features/support/test_distro/welcome/subtopics/test_only_topic.adoc +17 -0
  37. data/features/support/test_distro/welcome/subtopics/wildcard_all.adoc +17 -0
  38. data/lib/ascii_binder.rb +4 -2
  39. data/lib/ascii_binder/distro.rb +111 -0
  40. data/lib/ascii_binder/distro_branch.rb +93 -0
  41. data/lib/ascii_binder/distro_map.rb +67 -0
  42. data/lib/ascii_binder/engine.rb +581 -0
  43. data/lib/ascii_binder/helpers.rb +42 -856
  44. data/lib/ascii_binder/site.rb +52 -0
  45. data/lib/ascii_binder/site_info.rb +22 -0
  46. data/lib/ascii_binder/site_map.rb +24 -0
  47. data/lib/ascii_binder/topic_entity.rb +255 -0
  48. data/lib/ascii_binder/topic_map.rb +61 -0
  49. data/lib/ascii_binder/version.rb +1 -1
  50. data/templates/_templates/page.html.erb +1 -1
  51. metadata +118 -14
@@ -0,0 +1,14 @@
1
+ ---
2
+ ascii_binder:
3
+ name: AsciiBinder Doc Project
4
+ author: AsciiBinder Team <team@asciibinder.org>
5
+ site: main
6
+ site_name: Home
7
+ site_url: http://asciibinder.org/
8
+ branches:
9
+ master:
10
+ name: Latest
11
+ dir: latest
12
+ branch1:
13
+ name: Branch 1
14
+ dir: branch1
@@ -0,0 +1,14 @@
1
+ ---
2
+ ascii_binder:
3
+ name: " "
4
+ author: " "
5
+ site: " "
6
+ site_name: " "
7
+ site_url: " "
8
+ branches:
9
+ master:
10
+ name: " "
11
+ dir: " "
12
+ branch1:
13
+ name: " "
14
+ dir: " "
@@ -0,0 +1,468 @@
1
+ require 'fileutils'
2
+ require 'git'
3
+ require 'open3'
4
+ require 'tmpdir'
5
+ require 'yaml'
6
+
7
+ module Helpers
8
+ def gem_root
9
+ File.expand_path '../../..', __FILE__
10
+ end
11
+
12
+ def run_command(command,args=[],repo_dir=nil)
13
+ if repo_dir.nil?
14
+ repo_dir = working_dir
15
+ end
16
+ instructions = [File.join(gem_root,'bin','asciibinder'),command]
17
+ instructions.concat(args)
18
+ instructions << repo_dir
19
+ stdout_str, stderr_str, status = Open3.capture3(instructions.join(' '))
20
+ return { :stdout => stdout_str, :stderr => stderr_str, :status => status }
21
+ end
22
+
23
+ def print_output(command_output)
24
+ puts "STDOUT:\n#{command_output[:stdout]}\n\n"
25
+ puts "STDERR:\n#{command_output[:stderr]}\n\n"
26
+ puts "EXIT CODE: #{command_output[:status].exitstatus}\n\n"
27
+ end
28
+
29
+ def working_dir
30
+ @working_dir ||= begin
31
+ working_dir = Dir.mktmpdir('ascii_binder-cucumber')
32
+ track_tmp_dir(working_dir)
33
+ FileUtils.rm_rf(working_dir)
34
+ working_dir
35
+ end
36
+ end
37
+
38
+ def distro_map
39
+ @distro_map ||= YAML.load_file(File.join(docs_root,'_distro_map.yml'))
40
+ end
41
+
42
+ def topic_map
43
+ # Normally we want to read the topic map from each branch. In our test setup,
44
+ # each branch has an identical topic map, so we can get away with this for now.
45
+ @topic_map ||= YAML.load_stream(open(File.join(docs_root,'_topic_map.yml')))
46
+ end
47
+
48
+ def preview_dir
49
+ @preview_dir ||= File.join(docs_root,'_preview')
50
+ end
51
+
52
+ def package_dir
53
+ @package_dir ||= File.join(docs_root,'_package')
54
+ end
55
+
56
+ def repo_template_dir
57
+ @repo_template_dir ||= File.join(gem_root,'templates')
58
+ end
59
+
60
+ def test_distro_dir
61
+ @test_distro_dir ||= File.join(gem_root,'features','support','test_distro')
62
+ end
63
+
64
+ def track_tmp_dir(tmp_dir)
65
+ if @tracked_tmp_dirs.nil?
66
+ @tracked_tmp_dirs = []
67
+ end
68
+ @tracked_tmp_dirs << tmp_dir unless @tracked_tmp_dirs.include?(tmp_dir)
69
+ end
70
+
71
+ def clean_tracked_dirs
72
+ @tracked_tmp_dirs.each do |dir|
73
+ FileUtils.rm_rf(dir)
74
+ end
75
+ end
76
+
77
+ def find_html_files(dir)
78
+ `cd #{dir} && find .`.split("\n").select{ |item| item.end_with?('.html') }.map{ |item| item[2..-1] }
79
+ end
80
+
81
+ def actual_preview_info
82
+ all_preview_paths = find_html_files(preview_dir)
83
+
84
+ map = {}
85
+ dirmatch = {}
86
+ distro_map.each do |distro,distro_info|
87
+ map[distro] = {}
88
+ distro_info['branches'].each do |branch,branch_info|
89
+ map[distro][branch] = []
90
+ dirmatch["#{distro}/#{branch_info['dir']}"] = { :distro => distro, :branch => branch }
91
+ end
92
+ end
93
+
94
+ populated_distros = []
95
+ populated_branches = []
96
+ populated_pages = []
97
+ all_preview_paths.each do |preview_path|
98
+ found_dirmatch = false
99
+ dirmatch.each do |branch_path,db_keys|
100
+ next unless preview_path.start_with?(branch_path)
101
+ found_dirmatch = true
102
+ map[db_keys[:distro]][db_keys[:branch]] << preview_path
103
+ populated_distros << db_keys[:distro]
104
+ populated_branches << db_keys[:branch]
105
+ populated_pages << preview_path.split('/')[2..-1].join('/')
106
+ break
107
+ end
108
+ unless found_dirmatch
109
+ puts "ERROR: unexpected output file '#{preview_path}'"
110
+ exit 1
111
+ end
112
+ end
113
+
114
+ map.keys.each do |distro|
115
+ map[distro].keys.each do |branch|
116
+ map[distro][branch].sort!
117
+ end
118
+ end
119
+
120
+ return {
121
+ :map => map,
122
+ :distros => populated_distros.uniq,
123
+ :branches => populated_branches.uniq,
124
+ :pages => populated_pages.uniq,
125
+ }
126
+ end
127
+
128
+ def actual_site_map
129
+ all_site_paths = find_html_files(package_dir)
130
+
131
+ map = {}
132
+ dirmatch = {}
133
+ distro_map.each do |distro,distro_info|
134
+ site = distro_info['site']
135
+ unless map.has_key?(site)
136
+ map[site] = {}
137
+ end
138
+ map[site][distro] = {}
139
+ distro_info['branches'].each do |branch,branch_info|
140
+ map[site][distro][branch] = []
141
+ dirmatch["#{distro_info['site']}/#{branch_info['dir']}"] = {
142
+ :distro => distro,
143
+ :branch => branch,
144
+ :site => site,
145
+ }
146
+ end
147
+ end
148
+
149
+ all_site_paths.each do |site_path|
150
+ # skip the top-level index.html file in each site.
151
+ path_parts = site_path.split('/')
152
+ next if path_parts.length == 2 and path_parts[1] == 'index.html'
153
+
154
+ found_dirmatch = false
155
+ dirmatch.each do |branch_path,db_keys|
156
+ next unless site_path.start_with?(branch_path)
157
+ found_dirmatch = true
158
+ map[db_keys[:site]][db_keys[:distro]][db_keys[:branch]] << site_path
159
+ break
160
+ end
161
+ unless found_dirmatch
162
+ puts "ERROR: unexpected output file '#{site_path}'"
163
+ exit 1
164
+ end
165
+ end
166
+
167
+ map.keys.each do |site|
168
+ map[site].keys.each do |distro|
169
+ map[site][distro].keys.each do |branch|
170
+ map[site][distro][branch].sort!
171
+ end
172
+ end
173
+ end
174
+
175
+ return map
176
+ end
177
+
178
+ def distro_preview_path_map
179
+ map = {}
180
+ distro_map.each do |distro,distro_info|
181
+ map[distro] = {}
182
+ distro_info['branches'].each do |branch,branch_info|
183
+ map[distro][branch] = []
184
+ topic_map.each do |topic_node|
185
+ map[distro][branch].concat(topic_paths(distro,topic_node).map{ |subpath| "#{distro}/#{branch_info['dir']}/#{subpath}" })
186
+ end
187
+ map[distro][branch].sort!
188
+ end
189
+ end
190
+ return map
191
+ end
192
+
193
+ def distro_site_path_map
194
+ map = {}
195
+ distro_map.each do |distro,distro_info|
196
+ site = distro_info['site']
197
+ unless map.has_key?(site)
198
+ map[site] = {}
199
+ end
200
+ map[site][distro] = {}
201
+ distro_info['branches'].each do |branch,branch_info|
202
+ map[site][distro][branch] = []
203
+ topic_map.each do |topic_node|
204
+ map[site][distro][branch].concat(topic_paths(distro,topic_node).map{ |subpath| "#{site}/#{branch_info['dir']}/#{subpath}" })
205
+ end
206
+ map[site][distro][branch].sort!
207
+ end
208
+ end
209
+ return map
210
+ end
211
+
212
+ def topic_paths(distro,topic_node)
213
+ # First, determine if this topic node should be included for this distro.
214
+ if topic_node.has_key?('Distros')
215
+ found_distro = false
216
+ included_distros = topic_node['Distros'].split(',')
217
+ included_distros.each do |check_distro|
218
+ if check_distro.include?('*') and File.fnmatch(check_distro,distro)
219
+ found_distro = true
220
+ break
221
+ elsif check_distro == distro
222
+ found_distro = true
223
+ break
224
+ end
225
+ end
226
+ unless found_distro
227
+ return []
228
+ end
229
+ end
230
+
231
+ if topic_node.has_key?('File')
232
+ # This topic node is a topic "leaf"; return it with '.html' as the extension.
233
+ filename = topic_node['File'].split('.')[0]
234
+ return ["#{filename}.html"]
235
+ elsif topic_node.has_key?('Dir')
236
+ dirpath = topic_node['Dir']
237
+ subtopics = []
238
+ topic_node['Topics'].each do |subtopic_node|
239
+ subtopics.concat(topic_paths(distro,subtopic_node))
240
+ end
241
+ return subtopics.map{ |subpath| "#{dirpath}/#{subpath}" }
242
+ else
243
+ puts "ERROR: Malformed topic node. #{topic_node.inspect}"
244
+ exit 1
245
+ end
246
+ end
247
+
248
+ def set_initial_working_branch(branch)
249
+ @initial_working_branch = branch
250
+ end
251
+
252
+ def initial_working_branch
253
+ @initial_working_branch ||= nil
254
+ end
255
+
256
+ def using_offset_docs_root?
257
+ @using_offset_docs_root
258
+ end
259
+
260
+ def docs_root
261
+ using_offset_docs_root? ? File.join(working_dir,'docs') : working_dir
262
+ end
263
+
264
+ def initialize_test_repo(valid,multiple_distros=false,offset_docs_root=false)
265
+ unless valid
266
+ FileUtils.mkdir(working_dir)
267
+ else
268
+ status_check(run_command('create'),'Could not initialize test repo.')
269
+ if multiple_distros
270
+ FileUtils.cp_r(File.join(test_distro_dir,'.'),working_dir)
271
+ end
272
+ if offset_docs_root
273
+ @using_offset_docs_root = true
274
+ entries = Dir.entries(working_dir).select{ |item| not item.start_with?('.') }
275
+ system("cd #{working_dir} && mkdir docs")
276
+ entries.each do |entry|
277
+ system("cd #{working_dir} && mv #{entry} docs")
278
+ end
279
+ end
280
+ system("cd #{working_dir} && git add . > /dev/null && git commit -am 'test commit' > /dev/null")
281
+ if multiple_distros
282
+ system("cd #{working_dir} && git checkout -b branch1 > /dev/null 2>&1 && git checkout -b branch2 > /dev/null 2>&1 && git checkout master > /dev/null 2>&1")
283
+ end
284
+ set_initial_working_branch('master')
285
+ end
286
+ working_dir
287
+ end
288
+
289
+ def invalidate_distro_map
290
+ invalid_map = File.join(gem_root,'features','support','_invalid_distro_map.yml')
291
+ FileUtils.cp(invalid_map,File.join(docs_root,'_distro_map.yml'))
292
+ system("cd #{working_dir} && git add . > /dev/null && git commit -am 'Commit invalid distro map' > /dev/null")
293
+ end
294
+
295
+ def initialize_remote_repo
296
+ remote_dir = Dir.mktmpdir('ascii_binder-cucumber-remote')
297
+ FileUtils.rm_rf(remote_dir)
298
+ track_tmp_dir(remote_dir)
299
+ if run_command('create',[],remote_dir)[:status].exitstatus == 0
300
+ clone_map = File.join(gem_root,'features','support','_clone_distro_map.yml')
301
+ FileUtils.cp(clone_map,File.join(remote_dir,'_distro_map.yml'))
302
+ system("cd #{remote_dir} && git add . > /dev/null && git commit -am 'remote commit' > /dev/null && git checkout -b branch1 > /dev/null 2>&1 && git checkout master > /dev/null 2>&1")
303
+ else
304
+ puts "ERROR: Could not initialize remote repo"
305
+ exit 1
306
+ end
307
+ remote_dir
308
+ end
309
+
310
+ def status_check(step_output,error_message)
311
+ unless step_output[:status].exitstatus == 0
312
+ puts "ERROR: #{error_message}"
313
+ print_output(step_output)
314
+ exit 1
315
+ end
316
+ end
317
+
318
+ def build_check(scope,target='')
319
+ # Initial state of check_map matches ':default' scope
320
+ check_map = {
321
+ :current_branch_only => true,
322
+ :specified_distro_only => false,
323
+ :specified_page_only => false,
324
+ }
325
+ case scope
326
+ when :default
327
+ # Change nothing
328
+ when :distro
329
+ check_map[:specified_distro_only] = true
330
+ when :all_branches
331
+ check_map[:current_branch_only] = false
332
+ when :page
333
+ check_map[:specified_page_only] = true
334
+ else
335
+ puts "ERROR: Build scope '#{scope}' not recognized."
336
+ exit 1
337
+ end
338
+
339
+ # Make sure the build finished on the same branch where it started.
340
+ git = Git.open(working_dir)
341
+ current_working_branch = git.branch.name
342
+ unless current_working_branch == initial_working_branch
343
+ puts "ERROR: Build operation started on branch '#{initial_working_branch}' but ended on branch '#{current_working_branch}'"
344
+ exit 1
345
+ end
346
+
347
+ # generate the expected preview paths for each full distro + branch combo
348
+ all_paths_map = distro_preview_path_map
349
+
350
+ # get all of the paths in the actual preview directory
351
+ real_preview_info = actual_preview_info
352
+
353
+ gen_paths_map = real_preview_info[:map]
354
+ branch_count = real_preview_info[:branches].length
355
+ distro_count = real_preview_info[:distros].length
356
+ page_count = real_preview_info[:pages].length
357
+ target_distro = real_preview_info[:distros][0]
358
+ target_page = real_preview_info[:pages][0].split('/').join(':').split('.')[0]
359
+
360
+
361
+ if distro_count == 0 or branch_count == 0
362
+ puts "ERROR: A build operation should produce at least one distro / branch preview."
363
+ exit 1
364
+ end
365
+
366
+ # Compare branches by count
367
+ if branch_count > 1 and check_map[:current_branch_only]
368
+ puts "ERROR: Expected behavior for '#{scope}' scope is to build current working branch only."
369
+ exit 1
370
+ elsif branch_count == 1 and not check_map[:current_branch_only]
371
+ puts "ERROR: Expected behavior for '#{scope}' scope is to build all local branches."
372
+ exit 1
373
+ end
374
+
375
+ # Compare distros by count
376
+ if distro_count > 1 and check_map[:specified_distro_only]
377
+ puts "ERROR: Expected behavior for '#{scope}' scope is to build specified branch ('#{target}') only."
378
+ exit 1
379
+ elsif distro_count == 1
380
+ if not check_map[:specified_distro_only]
381
+ puts "ERROR: Expected behavior for '#{scope}' scope is to build all distros."
382
+ exit 1
383
+ elsif not target_distro == target
384
+ puts "ERROR: The build did not run for the expected target distro '#{target}' but instead for '#{target_distro}'"
385
+ exit 1
386
+ end
387
+ end
388
+
389
+ # Compare pages by count
390
+ if page_count > 1 and check_map[:specified_page_only]
391
+ puts "ERROR: Expected behavior for '#{scope}' is to build the specified page ('#{target}') only."
392
+ exit 1
393
+ elsif page_count == 1
394
+ if not check_map[:specified_page_only]
395
+ puts "ERROR: Expected behavior for '#{scope}' scope is to build all pages."
396
+ exit 1
397
+ elsif not target_page == target
398
+ puts "ERROR: The build did not run for the expected target page '#{target}' but instead for '#{target_page}'"
399
+ end
400
+ end
401
+
402
+ # Generated files vs expected files.
403
+ if not check_map[:specified_page_only]
404
+ all_paths_map.keys.each do |distro|
405
+ next if check_map[:specified_distro_only] and not distro == target
406
+ if not gen_paths_map.has_key?(distro)
407
+ puts "ERROR: Expected distro '#{distro}' was not generated for preview."
408
+ exit 1
409
+ end
410
+ all_paths_map[distro].keys.each do |branch|
411
+ next if check_map[:current_branch_only] and not branch == current_working_branch
412
+ if not gen_paths_map[distro].has_key?(branch)
413
+ puts "ERROR: Expected distro / branch combo '#{distro}' / '#{branch}' was not generated for preview."
414
+ exit 1
415
+ end
416
+ if not gen_paths_map[distro][branch] == all_paths_map[distro][branch]
417
+ puts "ERROR: Mismatch between expected and actual preview file paths for distro / branch combo '#{distro}' / '#{branch}'."
418
+ exit 1
419
+ end
420
+ end
421
+ end
422
+ end
423
+ end
424
+
425
+ def package_check(target_site='')
426
+ all_paths_map = distro_site_path_map
427
+ real_site_map = actual_site_map
428
+
429
+ real_site_map.keys.each do |site|
430
+ real_site_map[site].keys.each do |distro|
431
+ real_site_map[site][distro].keys.each do |branch|
432
+ # If a target site was specified and any content was generated for a different site, raise an error.
433
+ if not target_site == '' and not site == target_site and real_site_map[site][distro][branch].length > 0
434
+ puts "ERROR: Content was generated for site '#{site}' even though it was only expected for site '#{target_site}'"
435
+ exit 1
436
+ end
437
+
438
+ # Confirm that what was generated matches what was expected.
439
+ if (target_site == '' or site == target_site) and not real_site_map[site][distro][branch] == all_paths_map[site][distro][branch]
440
+ puts "ERROR: Mismatch between expected and actual site file paths for site / distro / branch combo '#{site}' / '#{distro}' / '#{branch}'."
441
+ exit 1
442
+ end
443
+ end
444
+ end
445
+
446
+ # Skip the next check for sites that aren't being packaged.
447
+ next unless target_site == '' or site == target_site
448
+
449
+ # Finally, confirm that the expected site index page was copied to the site home directory.
450
+ source_page = File.join(docs_root,"index-#{site}.html")
451
+ target_page = File.join(package_dir,site,'index.html')
452
+ unless FileUtils.compare_file(source_page,target_page)
453
+ puts "ERROR: Incorrect site index file contents at '#{target_page}'; expected contents of '#{source_page}'."
454
+ exit 1
455
+ end
456
+ end
457
+ end
458
+ end
459
+
460
+ World(Helpers)
461
+
462
+ Before do
463
+ working_dir
464
+ end
465
+
466
+ After do
467
+ clean_tracked_dirs
468
+ end