nandoc 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/README +124 -0
  2. data/Rakefile +53 -0
  3. data/bin/nandoc +6 -0
  4. data/doc/CREDITS.md +6 -0
  5. data/doc/FAQ/why-not-wiki.md +20 -0
  6. data/doc/FAQ.md +68 -0
  7. data/doc/TODOs-and-BUGs.md +15 -0
  8. data/doc/bar/baz.md +4 -0
  9. data/doc/bar/bliff.md +8 -0
  10. data/doc/foo.md +5 -0
  11. data/doc/getting-started.rb +13 -0
  12. data/doc/svg/less-fonts.svg +21 -0
  13. data/lib/nandoc/commands/create-nandoc-site.rb +225 -0
  14. data/lib/nandoc/commands/diff.rb +279 -0
  15. data/lib/nandoc/config.rb +58 -0
  16. data/lib/nandoc/cri-hacks.rb +13 -0
  17. data/lib/nandoc/data-source.rb +239 -0
  18. data/lib/nandoc/filters.rb +661 -0
  19. data/lib/nandoc/helpers/menu-bouncy.rb +109 -0
  20. data/lib/nandoc/helpers/site-map.rb +157 -0
  21. data/lib/nandoc/helpers/top-nav.rb +47 -0
  22. data/lib/nandoc/helpers.rb +42 -0
  23. data/lib/nandoc/item-class-hacks.rb +57 -0
  24. data/lib/nandoc/nandoc.persistent.json +3 -0
  25. data/lib/nandoc/parse-readme.rb +95 -0
  26. data/lib/nandoc/spec-doc/mini-test/spec-instance-methods.rb +0 -0
  27. data/lib/nandoc/spec-doc/mini-test.rb +105 -0
  28. data/lib/nandoc/spec-doc/mock-prompt.rb +121 -0
  29. data/lib/nandoc/spec-doc/support-modules.rb +158 -0
  30. data/lib/nandoc/spec-doc/test-case-agent.rb +57 -0
  31. data/lib/nandoc/spec-doc/test-framework-dispatcher.rb +15 -0
  32. data/lib/nandoc/spec-doc/test-framework-proxy.rb +78 -0
  33. data/lib/nandoc/spec-doc.rb +46 -0
  34. data/lib/nandoc/support/diff-proxy.rb +113 -0
  35. data/lib/nandoc/support/orphanage.rb +77 -0
  36. data/lib/nandoc/support/path-tardo.rb +85 -0
  37. data/lib/nandoc/support/regexp-enhance.rb +76 -0
  38. data/lib/nandoc/support/site-diff.rb +46 -0
  39. data/lib/nandoc/support/site-merge.rb +62 -0
  40. data/lib/nandoc/support/site-methods.rb +69 -0
  41. data/lib/nandoc/support/stream-colorizer.rb +203 -0
  42. data/lib/nandoc/support-modules.rb +270 -0
  43. data/lib/nandoc/test/diff-to-string.rb +251 -0
  44. data/lib/nandoc/test/minitest-extlib.rb +53 -0
  45. data/lib/nandoc/treebis/NOGIT-DOCS/NEWS.md +5 -0
  46. data/lib/nandoc/treebis/NOGIT-README.md +65 -0
  47. data/lib/nandoc/treebis/nandoc.persistent.json +3 -0
  48. data/lib/nandoc.rb +48 -0
  49. data/proto/README.md +31 -0
  50. data/proto/default/Rakefile +1 -0
  51. data/proto/default/Rules +46 -0
  52. data/proto/default/config.yaml +57 -0
  53. data/proto/default/content/css/nanoc-dist-altered.css +213 -0
  54. data/proto/default/content/css/trollop-subset.css +116 -0
  55. data/proto/default/content/js/menu-bouncy.js +126 -0
  56. data/proto/default/content/stylesheet.css.diff +20 -0
  57. data/proto/default/content/vendor/jquery-1.3.js +4241 -0
  58. data/proto/default/content/vendor/jquery.easing.1.3.js +205 -0
  59. data/proto/default/layouts/default.html +70 -0
  60. data/proto/default/lib/default.orig.rb +2 -0
  61. data/proto/default/lib/default.rb +5 -0
  62. data/proto/default/treebis-task.rb +28 -0
  63. data/proto/misc/orphan-surrogate.md +6 -0
  64. data/test/test.rb +102 -0
  65. metadata +166 -0
@@ -0,0 +1,121 @@
1
+ # @todo figure out how to get the symlink hack to work and avoid
2
+ # this nonsense all the while letting this be a valid requireable file
3
+ require(File.expand_path('../../../nandoc.rb',__FILE__)) unless
4
+ Object.const_defined?('NanDoc')
5
+ require(File.expand_path('../../spec-doc.rb',__FILE__)) unless
6
+ NanDoc.const_defined?('SpecDoc')
7
+ require(File.dirname(__FILE__)+'/mini-test.rb') unless
8
+ NanDoc::SpecDoc.const_defined?('MiniTest')
9
+
10
+ module NanDoc
11
+ class MockPrompt
12
+ #
13
+ # This is a bit of a misnomer. it's actually just a wrapper
14
+ # around the real shell. It's for testing. SpecDoc.
15
+ # @todo - Move this class to under SpecDoc
16
+ #
17
+
18
+ include Treebis::Sopen2
19
+ include SpecDoc::AgentInstanceMethods
20
+
21
+
22
+ def initialize test_case=nil
23
+ @last_both = @last_out = @last_err = nil
24
+ @record = false
25
+ @test_case = test_case
26
+ end
27
+
28
+ # wrapper around FileUtils cd that takes block form
29
+ # the tail portion of the path if any that you want to go documented
30
+ # with SpecDoc is the second arg. The first arg if any is the part
31
+ # of the path that will go undocumented.
32
+ def cd actual_basedir, specdoc_subdir=nil, &block
33
+ actual_path = File.join( * [actual_basedir, specdoc_subdir].compact)
34
+ if specdoc_subdir && @record
35
+ @recordings.add(:cd, specdoc_subdir)
36
+ end
37
+ FileUtils.cd(actual_path) do
38
+ block.call(self)
39
+ end
40
+ if specdoc_subdir && @record
41
+ @recordings.add(:cd_end)
42
+ end
43
+ end
44
+
45
+ def cosmetic_ellipsis str
46
+ @record && @recordings.add(:cosmetic_ellipsis, str)
47
+ end
48
+
49
+ def enter2 cmd
50
+ @last_both = nil
51
+ @record && @recordings.add(:command, cmd)
52
+ @last_out, @last_err = sopen2(cmd)
53
+ end
54
+
55
+ def err string, opts={}
56
+ exp = reindent string
57
+ assert_equal_strings exp, @last_err, opts
58
+ end
59
+
60
+ def note(*a,&b)
61
+ @record && @recordings.note(*a, &b)
62
+ end
63
+
64
+ def out string, opts={}
65
+ exp = reindent(string)
66
+ @record && @recordings.add(:out, exp)
67
+ assert_equal_strings exp, @last_out, opts
68
+ end
69
+
70
+ def out_begin exp_begin
71
+ @exp_begin = reindent(exp_begin)
72
+ @record && @recordings.add(:out_begin, @exp_begin)
73
+ # we don't do the assert until we get to (any?) end
74
+ end
75
+
76
+ def out_end exp_end, opts={}
77
+ @exp_begin or fail("no begin found for end")
78
+ exp_end2 = reindent(exp_end)
79
+ @record && @recordings.add(:out_end, exp_end2)
80
+ act_begin = @last_out[0..@exp_begin.length-1]
81
+ assert_equal_strings @exp_begin, act_begin, opts
82
+ act_end = @last_out[(exp_end2.length * -1)..-1]
83
+ assert_equal_strings exp_end2, act_end, opts
84
+ end
85
+
86
+ def record story_name=nil
87
+ method = method_name_to_record(caller)
88
+ @record = true
89
+ recordings
90
+ @recordings.add(:method, method)
91
+ @recordings.add(:story, story_name) if story_name
92
+ end
93
+
94
+ def record_stop
95
+ @record = false
96
+ @recordings = nil
97
+ end
98
+
99
+ private
100
+
101
+ def assert_equal_strings exp, act, opts
102
+ # @test_case.assert_equal exp, @last_out
103
+ @test_case.assert_no_diff exp, act, nil, opts
104
+ end
105
+
106
+ # this is different than the dozens of similar ones
107
+ def reindent str
108
+ these = str.scan(/^[\t ]*/).each.with_index.map
109
+ string, idx = these.min_by{ |x| x[0].length }
110
+ if string.length == 0 && str.index("\n") # exp
111
+ s2, _ =
112
+ these.reject{ |x| x[0].length == 0 }.min_by{ |x| x[0].length }
113
+ string = s2 if s2
114
+ end
115
+ re = /^#{Regexp.escape(string)}/
116
+ str.gsub(re, '')
117
+ end
118
+
119
+ attr_reader :test_case
120
+ end
121
+ end
@@ -0,0 +1,158 @@
1
+ module NanDoc
2
+ module SpecDoc
3
+ module AgentInstanceMethods
4
+ # share things between MockPrompt and TestCaseAgent
5
+
6
+ # we used to use first method, now we use first test_ method
7
+ def method_name_to_record caller
8
+ line = caller.detect{ |x| x =~ /in `test_/ } or fail('hack fail')
9
+ method = line =~ /in `(.+)'\Z/ && $1 or fail("hack fail")
10
+ method
11
+ end
12
+
13
+ def recordings
14
+ @recordings ||= NanDoc::SpecDoc::Recordings.get(test_case)
15
+ end
16
+
17
+ def story story_name
18
+ method = method_name_to_record(caller)
19
+ rec = recordings
20
+ rec.add(:method, method)
21
+ rec.add(:story, story_name)
22
+ nil
23
+ end
24
+
25
+ end
26
+
27
+ class CodeSnippet
28
+ #
29
+ # internally this does deferred parsing of the thing
30
+ # a code snippet holds meta information (or maybe content)
31
+ # for record_ruby nandoc commands in tests.
32
+ #
33
+
34
+ def initialize matches_hash
35
+ @start_at = matches_hash
36
+ @stop_at = nil
37
+ @lines_proc = nil
38
+ end
39
+ attr_reader :start_at
40
+ %w(method line file).each do |meth|
41
+ sym = meth.to_sym
42
+ define_method(meth){ || @start_at[sym] }
43
+ end
44
+ def describe
45
+ last = method ? ":in `#{method}'" : ''
46
+ "#{file}:#{line}#{tail}"
47
+ end
48
+ # just hide all the lines from dumps to make irb debugging prettier
49
+ def file_lines
50
+ @lines_proc ||= begin
51
+ stop_at_assert # not really appropriate here
52
+ same_file_assert # not really appropriate here
53
+ all_lines = File.open(@start_at[:file],'r').lines.map # sure why not
54
+ proc{ all_lines }
55
+ end
56
+ @lines_proc.call
57
+ end
58
+ def line_start
59
+ @start_at[:line]
60
+ end
61
+ def line_stop
62
+ @stop_at[:line]
63
+ end
64
+ # @todo maybe get rid of this. we interpolate inspects elsewhere
65
+ def ruby_string_raw
66
+ @ruby_string_raw ||= begin
67
+ these = all_lines[line_start..(line_stop-2)]
68
+ these.join('') # they have newlines already
69
+ end
70
+ end
71
+ def stop_at data=nil
72
+ data ? (@stop_at = data) : @stop_at
73
+ end
74
+ private
75
+ def same_file_assert
76
+ @stop_at[:file] == @start_at[:file] or fail("I want life to be"<<
77
+ " simple. start and stop files must be the same: "<<
78
+ ([@stop_at, @start_at].map{ |x| File.basename(x)}*' and '))
79
+ end
80
+ def stop_at_assert
81
+ stop_at or fail("no record_ruby_stop() found in method "<<
82
+ "after #{describe}")
83
+ end
84
+ end
85
+
86
+ module ParseTrace
87
+ #
88
+ # @return [Regexp] enhanced regex that parses a stack trace line
89
+ #
90
+ def parse_trace
91
+ @parse_trace_re ||= begin
92
+ re = /\A(.*):(\d+)(?::in `([^']+)')?\Z/
93
+ RegexpEnhance.names(re, :file, :line, :method)
94
+ re
95
+ end
96
+ end
97
+ def parse_trace_assert line
98
+ md = parse_trace.match(line) or
99
+ fail("couldn't parse trace line: #{line}")
100
+ h = md.to_hash
101
+ /\A\d+\Z/ =~ h[:line] or fail("not line: #{h[:line]}.inspect")
102
+ h[:line] = h[:line].to_i
103
+ h
104
+ end
105
+ end
106
+
107
+
108
+ class Recordings < Array
109
+ #
110
+ # everything that nandoc does during test runs gets written
111
+ # to one of these. It's like a Sexp structure.
112
+ #
113
+
114
+
115
+ @for_test_case = {}
116
+
117
+ class << self
118
+ attr_accessor :for_test_case
119
+ def get test_case
120
+ @for_test_case[test_case.class] ||= new(test_case.class)
121
+ end
122
+ def report_test_case_not_found tc
123
+ msgs = ["no recordings found for #{tc}"]
124
+ msgs.join(' ')
125
+ end
126
+ end
127
+
128
+ def add name, *data
129
+ # this might change if we need to group by method name
130
+ push [name, *data]
131
+ end
132
+
133
+ def get_first_sexp_for_test_method meth
134
+ first = index([:method, meth]) or return nil
135
+ last = (first+1..length-1).detect do |i|
136
+ self[i].first == :method && self[i][1] != meth
137
+ end
138
+ last = last ? (last - 1) : (length - 1)
139
+ ret = self[first..last]
140
+ ret
141
+ end
142
+
143
+ def initialize test_case
144
+ @test_case = test_case
145
+ end
146
+
147
+ def note &block
148
+ push [:note, block]
149
+ end
150
+
151
+ def report_recording_not_found meth_name
152
+ "no recordings found for #{meth_name}"
153
+ end
154
+
155
+ attr_reader :test_case
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,57 @@
1
+ module NanDoc::SpecDoc
2
+ class TestCaseAgent
3
+ include ParseTrace, AgentInstanceMethods, ::Treebis::Capture3
4
+ def initialize test_case
5
+ @test_case = test_case
6
+ end
7
+
8
+ # @param [block] gets called, and whatever is written to $stdout
9
+ # will get asserted against whatever is in
10
+ # @param [String] exp.
11
+ # if it passes (or fails?) one of the strings is written to an
12
+ # :out node in the recording
13
+ # raises something if $stderr is written to.
14
+ #
15
+ # warning - blah blah @todo
16
+ #
17
+ #
18
+ def out exp, &block
19
+ trace = parse_trace_assert(caller.first)
20
+ out, err = capture3(&block)
21
+ fail("no: #{err.inspect}") unless err == ""
22
+ @test_case.assert_no_diff(exp, out)
23
+ if out == exp
24
+ recordings.add(:out, out, trace)
25
+ end
26
+ nil
27
+ end
28
+ def inspect mixed, exp_str = nil
29
+ act_str = mixed.inspect
30
+ line = caller.first
31
+ trace = parse_trace_assert(line)
32
+ if exp_str
33
+ @test_case.assert_no_diff(exp_str, act_str, "at #{line}")
34
+ end
35
+ recordings.add(:inspect, act_str, trace)
36
+ end
37
+ def record_ruby
38
+ md = parse_trace_assert(caller.first)
39
+ snip = CodeSnippet.new(md)
40
+ recordings.add(:method, snip.method)
41
+ recordings.add(:record_ruby, snip)
42
+ @last_snip = snip
43
+ nil
44
+ end
45
+ def record_ruby_stop
46
+ line = caller.first
47
+ md = parse_trace_assert(caller.first)
48
+ @last_snip or fail("no record_start in method before "<<
49
+ "record_stop at #{line}")
50
+ @last_snip.stop_at md
51
+ end
52
+ private
53
+ def recordings
54
+ Recordings.get(@test_case)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__)+'/test-framework-proxy.rb'
2
+
3
+ module NanDoc
4
+ module SpecDoc
5
+ class TestFrameworkDispatcher
6
+ def initialize gem_root
7
+ require File.dirname(__FILE__)+'/mini-test.rb'
8
+ @the_only_proxy = SpecDoc::MiniTest::Proxy.new(gem_root)
9
+ end
10
+ def get_sexp *a
11
+ @the_only_proxy.get_sexp(*a)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,78 @@
1
+ module NanDoc::SpecDoc
2
+ class TestFrameworkProxy
3
+ # abstract baseclass, an agent that runs tests
4
+
5
+ def initialize gem_root
6
+ @gem_root = gem_root
7
+ end
8
+
9
+ def get_sexp testfile, testname
10
+ TestFrameworkProxy.sexp_cache[testfile][testname] ||= begin
11
+ build_sexp testfile, testname
12
+ end
13
+ end
14
+
15
+ def build_sexp testfile, testname
16
+ load_file testfile
17
+ test_case, meth_name = find_test testname
18
+ run_test_case_method testfile, testname, test_case, meth_name
19
+ recs = ::NanDoc::SpecDoc::Recordings.for_test_case[test_case] or
20
+ fail(::NanDoc::SpecDoc::Recordings.
21
+ report_test_case_not_found(test_case))
22
+ sexp = recs.get_first_sexp_for_test_method(meth_name) or
23
+ fail recs.report_recording_not_found(meth_name)
24
+ sexp
25
+ end
26
+
27
+ @sexp_cache = Hash.new{ |h,k| h[k] = {} }
28
+ class << self
29
+ attr_reader :sexp_cache
30
+ end
31
+
32
+ protected
33
+
34
+ #
35
+ # the stream to write to when things like a specdoc test run fails
36
+ #
37
+ def err
38
+ $stderr
39
+ end
40
+
41
+ def handle_failed_tests runner, testfile, testname
42
+ err.print "can't SpecDoc. at least one test failed when trying to run"
43
+ err.puts " #{testfile.inspect} #{testname.inspect} - "
44
+ runner.report.each do |line|
45
+ err.puts line
46
+ end
47
+ err.puts "Please get your tests green and re-run."
48
+ exit(1);
49
+ end
50
+
51
+ def load_file testfile
52
+ path = testdir + '/' + testfile
53
+ fail("test file not found: #{path.inspect}") unless File.file?(path)
54
+ # if you need to, do a diff
55
+ require path
56
+ end
57
+
58
+ #
59
+ # default way to read what the test run wrote to stdout
60
+ #
61
+ def testout_str
62
+ @testout.rewind
63
+ @testout.read
64
+ end
65
+
66
+ #
67
+ # default way to deduce the root directory that holds the tests
68
+ #
69
+ def testdir
70
+ @testdir ||= begin
71
+ tries = [@gem_root+'/test', @gem_root+'/spec']
72
+ found = tries.detect{ |path| File.directory?(path) }
73
+ fail("Couldn't find test dir for gem at (#{tries*', '})") unless found
74
+ found
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,46 @@
1
+ # little hack to a) let clients just include this file for test runs
2
+ # and b) allow us to do the symlink hack for developing this.
3
+ unless Object.const_defined?('NanDoc')
4
+ require File.expand_path('../../nandoc.rb', __FILE__)
5
+ end
6
+
7
+ me = File.dirname(__FILE__)+'/spec-doc'
8
+ require me + '/support-modules.rb'
9
+ require me + '/test-case-agent.rb'
10
+ require me + '/test-framework-dispatcher.rb'
11
+
12
+ module NanDoc
13
+ module SpecDoc
14
+ class << self
15
+
16
+ #
17
+ # enhance a test framework spec or test case module
18
+ # for e.g. a minitest spec class.
19
+ #
20
+ def include_to mod
21
+ if Object.const_defined?('MiniTest') &&
22
+ mod.ancestors.include?(::MiniTest::Spec)
23
+ require File.dirname(__FILE__)+'/spec-doc/mini-test.rb'
24
+ ::NanDoc::SpecDoc::MiniTest::SpecInstanceMethods.include_to mod
25
+ else
26
+ fail("don't know how to enhance test module: #{mod}")
27
+ end
28
+ end
29
+ end
30
+
31
+ def initialize gem_root
32
+ @sexp_cache = Hash.new{|h,k| h[k] = {}}
33
+ @test_framework_dispatcher = TestFrameworkDispatcher.new(gem_root)
34
+ end
35
+
36
+ #
37
+ # only run any test method at most once, just to keep recordings clean
38
+ #
39
+ def get_sexp testfile, testname
40
+ sexp = @sexp_cache[testfile][testname] ||= begin
41
+ @test_framework_dispatcher.get_sexp testfile, testname
42
+ end
43
+ sexp
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,113 @@
1
+ module NanDoc
2
+ module DiffProxy
3
+ include Treebis::Sopen2
4
+ extend self
5
+ def diff path_a, path_b, opts={}
6
+ fail('no') unless File.exist?(path_a) && File.exist?(path_b)
7
+ rel_to = opts.delete(:relative_to)
8
+ path_a, path_b = relativize(rel_to, path_a, path_b) if rel_to
9
+ opts = {'--unified=3'=>nil, '--recursive'=>nil}.merge(opts)
10
+ args = ['diff'] + opts.each.map.flatten.compact + [path_a, path_b]
11
+ out = err = nil
12
+ block = proc do
13
+ out, err = sopen2(*args)
14
+ end
15
+ if rel_to
16
+ FileUtils.cd(rel_to, :verbose=>true, &block)
17
+ else
18
+ block.call
19
+ end
20
+ diff = Diff.new(out, err, args)
21
+ if diff.error?
22
+ return fail(diff.full_error_message){|f| f.diff = diff }
23
+ end
24
+ diff
25
+ end
26
+ private
27
+ def fail(*a, &b)
28
+ raise Fail.new(*a, &b)
29
+ end
30
+ def relativize base, path_a, path_b
31
+ fail("KISS") unless [0,0]==[path_a, path_b].map{|x| x.index(base)}
32
+ tail_a, tail_b = [path_a, path_b].map{|x| '.'+x[base.length..-1]}
33
+ [tail_a, tail_b]
34
+ end
35
+ class Diff
36
+ class << self
37
+ def default_diff_stylesheet
38
+ @default_diff_stylesheet ||= {
39
+ :header => [:bold, :yellow],
40
+ :add => [:bold, :green],
41
+ :remove => [:bold, :red],
42
+ :range => [:bold, :magenta],
43
+ :trailing_whitespace => [:background, :red]
44
+ }
45
+ end
46
+ def stream_colorizer_prototype
47
+ @stream_colorizer_prototype ||= begin
48
+ require File.dirname(__FILE__)+'/stream-colorizer.rb'
49
+ NanDoc::StreamColorizer.new do |sc|
50
+ sc.stylesheet_merge(default_diff_stylesheet)
51
+ sc.when %r(\Adiff ), :state=>:header
52
+ sc.when(:header) do |o|
53
+ o.style :header
54
+ o.when %r(\A@@), :state=>:range
55
+ end
56
+ sc.when(:range) do |o|
57
+ o.style :range
58
+ o.when_not %r(\A@@), :state=>:plain
59
+ end
60
+ sc.when(:plain) do |o|
61
+ o.style nil
62
+ o.when %r(\Adiff ), :state=>:header
63
+ o.when %r(\A\+), :state=>:add
64
+ o.when %r(\A\-), :state=>:remove
65
+ end
66
+ sc.when(:add) do |o|
67
+ o.style :add
68
+ o.trailing_whitespace_style :trailing_whitespace
69
+ o.when_not %r(\A\+), :state=>:plain
70
+ end
71
+ sc.when(:remove) do |o|
72
+ o.style :remove
73
+ o.trailing_whitespace_style :trailing_whitespace
74
+ o.when_not %r(\A\-), :state=>:plain
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ def initialize out, err, args
81
+ @out = out
82
+ @error = err
83
+ @args = args
84
+ end
85
+ attr_reader :error
86
+ def command
87
+ Shellwords.join(@args)
88
+ end
89
+ def error?; ! @error.empty? end
90
+ def full_error_message
91
+ "diff failed: #{command}\ngot error: #{error}"
92
+ end
93
+ def ok?; ! error? end
94
+ def to_s
95
+ @out
96
+ end
97
+ def colorize out, opts={}
98
+ colorizer = self.class.stream_colorizer_prototype.spawn do |c|
99
+ c.stylesheet_merge(opts[:styles] || {})
100
+ end
101
+ colorizer.filter(to_s, out)
102
+ nil
103
+ end
104
+ end
105
+ class Fail < RuntimeError;
106
+ def initialize(*a,&b)
107
+ super(*a)
108
+ yield self if block_given?
109
+ end
110
+ attr_accessor :diff
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,77 @@
1
+ module NanDoc
2
+ class DataSource # reopen, not really necessary
3
+ class Orphanage
4
+ #
5
+ # @api private implementation for experimental orphan_rescue()
6
+ #
7
+ include ItemMethods
8
+ class << self
9
+ def rescue_orphans config, items
10
+ new(config, items).rescue_orphans
11
+ end
12
+ private :new
13
+ end
14
+
15
+ def rescue_orphans
16
+ fail("must have site root to continue hacking") unless site_root
17
+ items = @items.map # we are going to add to it in loop below
18
+ items.each do |item|
19
+ next unless is_orphan? item
20
+ id = item.identifier
21
+ parent_id = parent_identifier(id)
22
+ bare_root = identifier_bare_rootname_assert(id)
23
+ bare_parent = slash_strip_assert(parent_id)
24
+ new_id = nil
25
+ @renamer = nil
26
+ if @basenames.include?(bare_root)
27
+ # then we need to hack a rename to the identifier
28
+ @renamer = /\A\/#{Regexp.escape(bare_root)}\/(.+)\Z/
29
+ new_id = rename(id)
30
+ end
31
+ if (!new_id || bare_parent!=bare_root) && is_orphan?(item, new_id)
32
+ make_surrogate_parent parent_id
33
+ end
34
+ item.identifier = new_id if new_id
35
+ end
36
+ end
37
+ private
38
+ def initialize config, items
39
+ @basenames = config[:source_file_basenames] || []
40
+ @items = items
41
+ end
42
+
43
+ def is_orphan? item, using_identifier=nil
44
+ item.nandoc_content_leaf? or return false
45
+ using_identifier ||= item.identifier
46
+ parent = find_parent using_identifier
47
+ parent.nil? && using_identifier != '/'
48
+ end
49
+
50
+ def make_surrogate_parent parent_id
51
+ use_id = @renamer ? rename(parent_id) : parent_id
52
+ use_path = @renamer ? "../#{parent_id}" : parent_id
53
+ content = surrogate_content
54
+ fake_parent = Nanoc3::Item.new(
55
+ content,
56
+ {:filename => use_path, :content_filename => use_path },
57
+ use_id
58
+ )
59
+ @items.unshift(fake_parent)
60
+ end
61
+
62
+ # very private
63
+ def rename str
64
+ @renamer =~ str or
65
+ fail("rename fail: #{str.inspect} against #{@renamer}")
66
+ renamed = "/#{$1}"
67
+ renamed
68
+ end
69
+
70
+ def surrogate_content
71
+ @surrogate_content ||= begin
72
+ File.read(NanDoc::Config.orphan_surrogate_filename)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end