nandoc 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +124 -0
- data/Rakefile +53 -0
- data/bin/nandoc +6 -0
- data/doc/CREDITS.md +6 -0
- data/doc/FAQ/why-not-wiki.md +20 -0
- data/doc/FAQ.md +68 -0
- data/doc/TODOs-and-BUGs.md +15 -0
- data/doc/bar/baz.md +4 -0
- data/doc/bar/bliff.md +8 -0
- data/doc/foo.md +5 -0
- data/doc/getting-started.rb +13 -0
- data/doc/svg/less-fonts.svg +21 -0
- data/lib/nandoc/commands/create-nandoc-site.rb +225 -0
- data/lib/nandoc/commands/diff.rb +279 -0
- data/lib/nandoc/config.rb +58 -0
- data/lib/nandoc/cri-hacks.rb +13 -0
- data/lib/nandoc/data-source.rb +239 -0
- data/lib/nandoc/filters.rb +661 -0
- data/lib/nandoc/helpers/menu-bouncy.rb +109 -0
- data/lib/nandoc/helpers/site-map.rb +157 -0
- data/lib/nandoc/helpers/top-nav.rb +47 -0
- data/lib/nandoc/helpers.rb +42 -0
- data/lib/nandoc/item-class-hacks.rb +57 -0
- data/lib/nandoc/nandoc.persistent.json +3 -0
- data/lib/nandoc/parse-readme.rb +95 -0
- data/lib/nandoc/spec-doc/mini-test/spec-instance-methods.rb +0 -0
- data/lib/nandoc/spec-doc/mini-test.rb +105 -0
- data/lib/nandoc/spec-doc/mock-prompt.rb +121 -0
- data/lib/nandoc/spec-doc/support-modules.rb +158 -0
- data/lib/nandoc/spec-doc/test-case-agent.rb +57 -0
- data/lib/nandoc/spec-doc/test-framework-dispatcher.rb +15 -0
- data/lib/nandoc/spec-doc/test-framework-proxy.rb +78 -0
- data/lib/nandoc/spec-doc.rb +46 -0
- data/lib/nandoc/support/diff-proxy.rb +113 -0
- data/lib/nandoc/support/orphanage.rb +77 -0
- data/lib/nandoc/support/path-tardo.rb +85 -0
- data/lib/nandoc/support/regexp-enhance.rb +76 -0
- data/lib/nandoc/support/site-diff.rb +46 -0
- data/lib/nandoc/support/site-merge.rb +62 -0
- data/lib/nandoc/support/site-methods.rb +69 -0
- data/lib/nandoc/support/stream-colorizer.rb +203 -0
- data/lib/nandoc/support-modules.rb +270 -0
- data/lib/nandoc/test/diff-to-string.rb +251 -0
- data/lib/nandoc/test/minitest-extlib.rb +53 -0
- data/lib/nandoc/treebis/NOGIT-DOCS/NEWS.md +5 -0
- data/lib/nandoc/treebis/NOGIT-README.md +65 -0
- data/lib/nandoc/treebis/nandoc.persistent.json +3 -0
- data/lib/nandoc.rb +48 -0
- data/proto/README.md +31 -0
- data/proto/default/Rakefile +1 -0
- data/proto/default/Rules +46 -0
- data/proto/default/config.yaml +57 -0
- data/proto/default/content/css/nanoc-dist-altered.css +213 -0
- data/proto/default/content/css/trollop-subset.css +116 -0
- data/proto/default/content/js/menu-bouncy.js +126 -0
- data/proto/default/content/stylesheet.css.diff +20 -0
- data/proto/default/content/vendor/jquery-1.3.js +4241 -0
- data/proto/default/content/vendor/jquery.easing.1.3.js +205 -0
- data/proto/default/layouts/default.html +70 -0
- data/proto/default/lib/default.orig.rb +2 -0
- data/proto/default/lib/default.rb +5 -0
- data/proto/default/treebis-task.rb +28 -0
- data/proto/misc/orphan-surrogate.md +6 -0
- data/test/test.rb +102 -0
- 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
|