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,270 @@
|
|
1
|
+
module NanDoc
|
2
|
+
module CliCommandHelpers
|
3
|
+
def command_name
|
4
|
+
(/::([_a-z0-9]+)\Z/i =~ self.class.to_s and base = $1) or fail('no')
|
5
|
+
base.gsub(/([a-z])([A-Z])/){ "#{$1}-#{$2}" }.downcase
|
6
|
+
end
|
7
|
+
def invite_to_more_command_help
|
8
|
+
"see `nandoc help #{command_name}` for more information."
|
9
|
+
end
|
10
|
+
def invocation_name
|
11
|
+
File.basename($PROGRAM_NAME)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
module StringFormatting; end
|
15
|
+
module OptsNormalizer
|
16
|
+
def normalize_opts opts
|
17
|
+
opts = opts.dup
|
18
|
+
opts.keys.select{|x| x.to_s.index('-') }.each do |k|
|
19
|
+
opts[k.to_s.gsub('-','_').to_sym] = opts.delete(k)
|
20
|
+
end
|
21
|
+
opts
|
22
|
+
end
|
23
|
+
def unnormalize_opt_keys keys
|
24
|
+
keys.map{|x| unnormalize_opt_key(x)}
|
25
|
+
end
|
26
|
+
def unnormalize_opt_key key
|
27
|
+
"--#{key.to_s.gsub('_','-')}"
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# only call this if you are like a ::Cri::Command object with
|
32
|
+
# all the nanDoc hacks. ick. This is a temprary hack. Trollop et al
|
33
|
+
# do this better.
|
34
|
+
#
|
35
|
+
def exclusive_opt_flags opts, &block
|
36
|
+
Exclusive.new(&block).parse(self, opts)
|
37
|
+
end
|
38
|
+
|
39
|
+
class Exclusive
|
40
|
+
include OptsNormalizer
|
41
|
+
def initialize &block
|
42
|
+
@exclusive_flag_keys = nil
|
43
|
+
@default_short = nil
|
44
|
+
@default_key = nil
|
45
|
+
@notice_stream = $stderr
|
46
|
+
instance_eval(&block)
|
47
|
+
fail('definition block needs at least flags()') unless
|
48
|
+
@exclusive_flag_keys
|
49
|
+
end
|
50
|
+
def flags * exclusive_flag_keys
|
51
|
+
@exclusive_flag_keys = exclusive_flag_keys
|
52
|
+
end
|
53
|
+
# params: [short_name_string] name_key
|
54
|
+
def default *a
|
55
|
+
if a.first.kind_of?(String)
|
56
|
+
@default_short = a.shift
|
57
|
+
end
|
58
|
+
if a.first.kind_of?(Symbol)
|
59
|
+
@default_key = a.shift
|
60
|
+
else
|
61
|
+
fail("bad args: #{a.first.inspect}")
|
62
|
+
end
|
63
|
+
fail("extra args: #{a.inspect}") if a.any?
|
64
|
+
end
|
65
|
+
def notice_stream mixed
|
66
|
+
@notice_stream = mixed
|
67
|
+
end
|
68
|
+
def parse cmd, opts
|
69
|
+
these = @exclusive_flag_keys & opts.keys
|
70
|
+
if these.empty? && @default_key
|
71
|
+
if @notice_stream
|
72
|
+
msg =
|
73
|
+
["using default: "+unnormalize_opt_key(@default_key),
|
74
|
+
@default_short ? "(#{@default_short})" : nil
|
75
|
+
].compact.join(' ')
|
76
|
+
@notice_stream.puts msg
|
77
|
+
end
|
78
|
+
these.push(@default_key)
|
79
|
+
end
|
80
|
+
if these.size > 1
|
81
|
+
flags = unnormalize_opt_keys(@exclusive_flag_keys)
|
82
|
+
cmd.task_abort <<-ABORT.gsub(/^ */,'')
|
83
|
+
#{flags.join(' and ')} are mutually exclusive.
|
84
|
+
usage: #{cmd.usage}
|
85
|
+
#{cmd.invite_to_more_command_help}
|
86
|
+
ABORT
|
87
|
+
end
|
88
|
+
these.first
|
89
|
+
end
|
90
|
+
end
|
91
|
+
class OptEnum
|
92
|
+
include OptsNormalizer, StringFormatting
|
93
|
+
def initialize(&block)
|
94
|
+
instance_eval(&block)
|
95
|
+
end
|
96
|
+
def command cmd
|
97
|
+
@command = cmd
|
98
|
+
end
|
99
|
+
def default str
|
100
|
+
@default = str
|
101
|
+
end
|
102
|
+
def name name
|
103
|
+
@name = name
|
104
|
+
end
|
105
|
+
def parse opts
|
106
|
+
found = nil
|
107
|
+
if opts.key?(@name)
|
108
|
+
v = opts[@name]
|
109
|
+
re = /\A#{Regexp.escape(v)}/
|
110
|
+
founds = @values.grep(re)
|
111
|
+
case founds.size
|
112
|
+
when 0; invalid(v)
|
113
|
+
when 1; found = founds.first
|
114
|
+
else found = founds.detect{|f| f==v} or too_many(founds)
|
115
|
+
end
|
116
|
+
elsif(@default)
|
117
|
+
found = @default
|
118
|
+
else
|
119
|
+
found = nil
|
120
|
+
end
|
121
|
+
opts[@name] = found if found # normalize short versions
|
122
|
+
found
|
123
|
+
end
|
124
|
+
def values *v
|
125
|
+
v = v.first if v.size==1 && Array === v
|
126
|
+
@values = v
|
127
|
+
end
|
128
|
+
private
|
129
|
+
def coda
|
130
|
+
"usage: #{@command.usage}\n#{@command.invite_to_more_command_help}"
|
131
|
+
end
|
132
|
+
def invalid val
|
133
|
+
@command.task_abort("invalid value #{val.inspect} for "<<
|
134
|
+
"#{long_name}. #{valid_values_are}\n#{coda}")
|
135
|
+
end
|
136
|
+
def long_name
|
137
|
+
unnormalize_opt_key(@name)
|
138
|
+
end
|
139
|
+
def too_many these
|
140
|
+
@command.task_abort("did you mean " <<
|
141
|
+
oxford_comma(these,' or ', "ed)<<" for #{long_name}?\n#{coda}")
|
142
|
+
end
|
143
|
+
def valid_values_are
|
144
|
+
"valid values are " << oxford_comma(@values,"ed)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
module PathHelper
|
149
|
+
def assert_path name, *paths
|
150
|
+
paths.each do |p|
|
151
|
+
unless File.exist?(p)
|
152
|
+
task_abort("#{name} does not exist: #{p}")
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
module StringFormatting
|
158
|
+
def basename_no_extension str
|
159
|
+
/([^\/\.]+)(?:\.[^\.\/]+)?\Z/ =~ str ? $1 : nil
|
160
|
+
end
|
161
|
+
def indent str, indent
|
162
|
+
str.gsub(/^/, indent)
|
163
|
+
end
|
164
|
+
def no_blank_lines str
|
165
|
+
str.gsub(/\n[[:space:]]*\n/, "\n")
|
166
|
+
end
|
167
|
+
def no_leading_ws str
|
168
|
+
str.sub(/\A[[:space:]]+/, '')
|
169
|
+
end
|
170
|
+
def no_trailing_ws str
|
171
|
+
str.sub(/[[:space:]]+\Z/, '')
|
172
|
+
end
|
173
|
+
def oxford_comma items, final = ' and ', "er
|
174
|
+
items = items.map("er) if quoter
|
175
|
+
these = []
|
176
|
+
these.push final if items.size > 1
|
177
|
+
these.concat(Array.new(items.size-2,', ')) if items.size > 2
|
178
|
+
these.reverse!
|
179
|
+
items.zip(these).flatten.compact.join
|
180
|
+
end
|
181
|
+
def quoted
|
182
|
+
proc{|x| "\"#{x}\"" }
|
183
|
+
end
|
184
|
+
|
185
|
+
#
|
186
|
+
# must respond to tab() and tabs()
|
187
|
+
# reindent a block by striping leading whitespace from lines evenly
|
188
|
+
# and then re-indenting each line according to our indent.
|
189
|
+
# this could be simpler, it has been more complicated
|
190
|
+
# we do it languidly because we can
|
191
|
+
#
|
192
|
+
def reindent h1, offset=0
|
193
|
+
indent_by = tab * (tabs+offset)
|
194
|
+
unindent_by = (/\A([[:space:]]+)/ =~ h1 && $1) or
|
195
|
+
fail('regex fail -- not sure if we need this to be so strict')
|
196
|
+
h2 = no_blank_lines(h1) # careful. will mess up with <pre> etc
|
197
|
+
return h2 if unindent_by == indent_by
|
198
|
+
h3 = unindent(h2, unindent_by)
|
199
|
+
h4 = indent(h3, indent_by)
|
200
|
+
h4
|
201
|
+
end
|
202
|
+
|
203
|
+
def unindent str, by
|
204
|
+
str.gsub(/^#{Regexp.escape(by)}/, '')
|
205
|
+
end
|
206
|
+
end
|
207
|
+
module SecretParent
|
208
|
+
#
|
209
|
+
# set parent attribute without it showing up in inspect() dumps
|
210
|
+
#
|
211
|
+
def parent= mixed
|
212
|
+
fail("no clear_parent() available yet.") unless mixed
|
213
|
+
@has_parent = !! mixed
|
214
|
+
class << self; self end.send(:define_method, :parent){mixed}
|
215
|
+
mixed # maybe chain assignmnet of 1 parent to several cx at once
|
216
|
+
end
|
217
|
+
def parent?
|
218
|
+
instance_variable_defined?('@has_parent') && @has_parent # no warnings
|
219
|
+
end
|
220
|
+
def parent
|
221
|
+
nil
|
222
|
+
end
|
223
|
+
end
|
224
|
+
module SharedAttrReader
|
225
|
+
#
|
226
|
+
# this is a specialized form of delegator pattern: let one object
|
227
|
+
# use the responses from another object for a set of accessors
|
228
|
+
#
|
229
|
+
def shared_attr_reader *list
|
230
|
+
fail('no inehiritance yet') if method_defined?(:shared=)
|
231
|
+
sm = Module.new
|
232
|
+
name = self.to_s+'::SharedAttrReaders'
|
233
|
+
sing = class << sm; self end
|
234
|
+
sing.send(:define_method, :name){name}
|
235
|
+
sing.send(:alias_method, :inspect, :name)
|
236
|
+
list.each do |attrib|
|
237
|
+
sm.send(:define_method, attrib){ shared.send(attrib) }
|
238
|
+
end
|
239
|
+
fail('no') if method_defined?(:shared)
|
240
|
+
define_method(:shared){ self }
|
241
|
+
define_method(:shared=) do |source|
|
242
|
+
sing = class << self; self end
|
243
|
+
sing.send(:define_method, :shared){ source }
|
244
|
+
sing.send(:include, sm) # wow cool that this works w/o having
|
245
|
+
# to Module#undef_method
|
246
|
+
source
|
247
|
+
end
|
248
|
+
nil
|
249
|
+
end
|
250
|
+
end
|
251
|
+
module TaskCommon
|
252
|
+
def task_abort msg
|
253
|
+
if msg.index("for more info") # not mr. right, mr. right now
|
254
|
+
tail = ''
|
255
|
+
else
|
256
|
+
last = msg[-1].chr
|
257
|
+
tail = ".?!".index(last) ? ' ' : ("\n"==last ? '' : '. ')
|
258
|
+
tail << 'Aborting.'
|
259
|
+
end
|
260
|
+
$stderr.puts "nanDoc: #{msg}#{tail}"
|
261
|
+
exit 1
|
262
|
+
end
|
263
|
+
end
|
264
|
+
module CliCommandHelpers
|
265
|
+
include OptsNormalizer, TaskCommon, PathHelper
|
266
|
+
end
|
267
|
+
module PathHelper
|
268
|
+
include TaskCommon
|
269
|
+
end
|
270
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
##
|
4
|
+
# turn the output of Diff::LCS.diff into a string similar
|
5
|
+
# to what would be retured by `diff`, optionally make it looks
|
6
|
+
# *sorta* like colorized output from git-diff
|
7
|
+
#
|
8
|
+
# @todo move this to minitest branch
|
9
|
+
# @todo this gives different results than diff for some stuff!!??
|
10
|
+
#
|
11
|
+
# poor man's diff:
|
12
|
+
# file_a, file_b = ARGV.shift(2)
|
13
|
+
# puts DiffToString.files_diff(file_a, file_b)
|
14
|
+
#
|
15
|
+
#
|
16
|
+
class DiffToString
|
17
|
+
module Style
|
18
|
+
Codes = {:red=>'31', :green=>'32', :bold=>'1', :red_bg=>'41',
|
19
|
+
:magenta => '35'
|
20
|
+
}
|
21
|
+
def stylize str, *codes
|
22
|
+
if 1 == codes.size
|
23
|
+
if codes.first.nil?
|
24
|
+
return str
|
25
|
+
elsif codes.first.kind_of?(Array)
|
26
|
+
codes = codes.first
|
27
|
+
end
|
28
|
+
end
|
29
|
+
codes = codes.map{|c| Codes[c]}
|
30
|
+
"\033[#{codes * ';'}m#{str}\033[0m";
|
31
|
+
end
|
32
|
+
end
|
33
|
+
include Style
|
34
|
+
|
35
|
+
class << self
|
36
|
+
# these are just convenience wrappers for instance methods
|
37
|
+
%w(diff files_diff strings_diff gitlike!).each do |meth|
|
38
|
+
define_method(meth){|*a| new.send(meth,*a) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
def initialize
|
42
|
+
@add_style = nil
|
43
|
+
@add_header = '%sa%s'
|
44
|
+
@change_header = '%sc%s'
|
45
|
+
@context = nil
|
46
|
+
@del_header = '%sd%s'
|
47
|
+
@del_style = nil
|
48
|
+
@last_range = nil
|
49
|
+
@left = '<'
|
50
|
+
@line_no_style = nil
|
51
|
+
@right = '>'
|
52
|
+
@separator_line = '---'
|
53
|
+
@trailing_whitespace_style = nil
|
54
|
+
end
|
55
|
+
attr_accessor :arr1, :arr2 # this is awful bleeding
|
56
|
+
def context= mixed
|
57
|
+
fail("no #{mixed.inspect}") unless mixed.kind_of?(Fixnum) && mixed >= 0
|
58
|
+
@context = mixed == 0 ? nil : mixed
|
59
|
+
end
|
60
|
+
def gitlike!
|
61
|
+
common_header = '@@ -%s, +%s @@'
|
62
|
+
@add_header = common_header
|
63
|
+
@add_style = [:bold, :green]
|
64
|
+
@change_header = common_header
|
65
|
+
@del_style = [:bold, :red]
|
66
|
+
@del_header = common_header
|
67
|
+
@header_style = [:bold, :magenta]
|
68
|
+
@left = '-'
|
69
|
+
@right = '+'
|
70
|
+
@separator_line = nil
|
71
|
+
@trailing_whitespace_style = [:red_bg]
|
72
|
+
self
|
73
|
+
end
|
74
|
+
def arrays_diff arr1, arr2, opts={}
|
75
|
+
diff = Diff::LCS.diff(arr1, arr2)
|
76
|
+
@arr1, @arr2 = arr1, arr2
|
77
|
+
consume_opts_for_diff(opts)
|
78
|
+
diff_to_str diff, opts
|
79
|
+
end
|
80
|
+
def diff mixed1, mixed2, opts={}
|
81
|
+
case (x=[mixed1.class, mixed2.class])
|
82
|
+
when [Array,Array]; arrays_diff(mixed1,mixed2,opts)
|
83
|
+
when [String,String]; strings_diff(mixed1,mixed2,opts)
|
84
|
+
else "no diff strategy for #{x.inspect}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
def files_diff a, b, opts={:sep=>"\n"}
|
88
|
+
str1 = File.read(a)
|
89
|
+
str2 = File.read(b)
|
90
|
+
strings_diff(str1, str2, opts)
|
91
|
+
end
|
92
|
+
def strings_diff a, b, opts={}
|
93
|
+
opts = opts.merge(:sep=>"\n")
|
94
|
+
arr1 = str_to_arr a, opts[:sep]
|
95
|
+
arr2 = str_to_arr b, opts[:sep]
|
96
|
+
arrays_diff(arr1, arr2, opts)
|
97
|
+
end
|
98
|
+
def str_to_arr str, sep
|
99
|
+
str.split(sep, -1)
|
100
|
+
end
|
101
|
+
def diff_to_str diff, opts
|
102
|
+
consume_opts_for_diff opts
|
103
|
+
@out = StringIO.new
|
104
|
+
@offset_offset = -1
|
105
|
+
diff.each do |chunk|
|
106
|
+
context_pre(chunk) if @context
|
107
|
+
dels = []
|
108
|
+
adds = []
|
109
|
+
start_add = last_add = start_del = last_del = nil
|
110
|
+
chunk.each do |change|
|
111
|
+
case change.action
|
112
|
+
when '+'
|
113
|
+
start_add ||= change.position + 1
|
114
|
+
last_add = change.position + 1
|
115
|
+
adds.push change.element
|
116
|
+
when '-'
|
117
|
+
start_del ||= change.position + 1
|
118
|
+
last_del = change.position + 1
|
119
|
+
dels.push change.element
|
120
|
+
else
|
121
|
+
fail("no: #{change.action}")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
if adds.any? && dels.any?
|
125
|
+
puts_change_header start_del, last_del, start_add, last_add
|
126
|
+
elsif adds.any?
|
127
|
+
puts_add_header start_add, last_add
|
128
|
+
else
|
129
|
+
puts_del_header start_del, last_del
|
130
|
+
end
|
131
|
+
@offset_offset -= ( dels.size - adds.size )
|
132
|
+
dels.each do |del|
|
133
|
+
puts_del "#{@left} #{del}"
|
134
|
+
end
|
135
|
+
if adds.any? && dels.any?
|
136
|
+
puts_sep
|
137
|
+
end
|
138
|
+
adds.each do |add|
|
139
|
+
puts_add "#{@right} #{add}"
|
140
|
+
end
|
141
|
+
context_post(chunk) if @context
|
142
|
+
end
|
143
|
+
@out.rewind
|
144
|
+
@out.read
|
145
|
+
end
|
146
|
+
private
|
147
|
+
def consume_opts_for_diff opts
|
148
|
+
if opts[:colors]
|
149
|
+
opts.delete[:colors]
|
150
|
+
gitlike!
|
151
|
+
end
|
152
|
+
if opts[:context]
|
153
|
+
self.context = opts.delete(:context)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
def context_pre chunk
|
157
|
+
pos = chunk.first.position - 1
|
158
|
+
puts_range_safe pos - @context, pos
|
159
|
+
end
|
160
|
+
def context_post chunk
|
161
|
+
pos = chunk.last.position + 1
|
162
|
+
puts_range_safe pos, pos + @context
|
163
|
+
end
|
164
|
+
def other_offset start
|
165
|
+
start + @offset_offset
|
166
|
+
end
|
167
|
+
def puts_del str
|
168
|
+
puts_change str, @del_style
|
169
|
+
end
|
170
|
+
def puts_add str
|
171
|
+
puts_change str, @add_style
|
172
|
+
end
|
173
|
+
def puts_add_header start_add, last_add
|
174
|
+
str = @add_header % [other_offset(start_add), range(start_add,last_add)]
|
175
|
+
@out.puts(stylize(str, @header_style))
|
176
|
+
end
|
177
|
+
def puts_change str, style
|
178
|
+
# separate string into three parts! main string,
|
179
|
+
# trailing non-newline whitespace, and trailing newlines
|
180
|
+
# we want to highlite the trailing whitespace, but if we are
|
181
|
+
# colorizing it we need to exclude the final trailing newlines
|
182
|
+
# for puts to work correctly
|
183
|
+
if /^(.*[^\s]|)([\t ]*)([\n]*)$/ =~ str
|
184
|
+
main_str, ws_str, nl_str = $1, $2, $3
|
185
|
+
@out.print(stylize(main_str, style))
|
186
|
+
@out.print(stylize(ws_str, @trailing_whitespace_style))
|
187
|
+
@out.puts(nl_str)
|
188
|
+
else
|
189
|
+
# hopefully regex never fails but it might
|
190
|
+
@out.puts(stylize(str, style))
|
191
|
+
end
|
192
|
+
end
|
193
|
+
def puts_change_header start_del, last_del, start_add, last_add
|
194
|
+
str = @change_header %
|
195
|
+
[range(start_del,last_del), range(start_add,last_add)]
|
196
|
+
@out.puts(stylize(str, @header_style))
|
197
|
+
end
|
198
|
+
def puts_del_header start_del, last_del
|
199
|
+
str = @del_header % [range(start_del,last_del), other_offset(start_del)]
|
200
|
+
@out.puts(stylize(str, @header_style))
|
201
|
+
end
|
202
|
+
def puts_range_safe start, final
|
203
|
+
start = [start, 0].max
|
204
|
+
final = [@arr1.size-1, final].min
|
205
|
+
if @last_range
|
206
|
+
start = [@last_range[1]+1, start].max
|
207
|
+
# assume sequential for now! no need to check about previous
|
208
|
+
# ones in front of us
|
209
|
+
end
|
210
|
+
return if start >= final
|
211
|
+
@last_range = [start, final]
|
212
|
+
@out.puts @arr1[start..final].map{|x| " #{x}"}
|
213
|
+
# @todo i don't know if i'm reading the chunks right
|
214
|
+
end
|
215
|
+
def puts_sep
|
216
|
+
if @separator_line
|
217
|
+
@out.puts(@separator_line)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
def range min, max
|
221
|
+
if min == max
|
222
|
+
min
|
223
|
+
else
|
224
|
+
"#{min},#{max}"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
if __FILE__ == $PROGRAM_NAME
|
230
|
+
require 'test/unit'
|
231
|
+
require 'test/unit/ui/console/testrunner'
|
232
|
+
class DiffToString::TestCase < Test::Unit::TestCase
|
233
|
+
def test_context
|
234
|
+
before = <<-B
|
235
|
+
alpha
|
236
|
+
beta
|
237
|
+
gamma
|
238
|
+
tau
|
239
|
+
B
|
240
|
+
after = <<-A
|
241
|
+
alpha
|
242
|
+
gamma
|
243
|
+
zeta
|
244
|
+
tau
|
245
|
+
A
|
246
|
+
puts DiffToString.diff(before.split("\n"), after.split("\n"),
|
247
|
+
:colors=>true, :context=>3)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
Test::Unit::UI::Console::TestRunner.run(DiffToString::TestCase)
|
251
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'diff/lcs'
|
2
|
+
require(File.dirname(__FILE__)+'/diff-to-string.rb')
|
3
|
+
|
4
|
+
module MiniTest
|
5
|
+
module Assertions
|
6
|
+
|
7
|
+
##
|
8
|
+
# Fails unless <tt>exp == act</tt>.
|
9
|
+
# On failure use diff to show the diff, if +exp+
|
10
|
+
# and +act+ are of the same class and
|
11
|
+
#
|
12
|
+
|
13
|
+
def assert_no_diff exp, act, msg=nil, opts={}
|
14
|
+
if opts.kind_of?(String)
|
15
|
+
opts = {:sep=>opts}
|
16
|
+
end
|
17
|
+
opts = {:sep=>"\n"}.merge(opts)
|
18
|
+
msg = message(msg) do
|
19
|
+
exp_kind, act_kind = [exp,act].map do |x|
|
20
|
+
[String, Array].detect{|c| x.kind_of?(c)}
|
21
|
+
end
|
22
|
+
if exp_kind != act_kind
|
23
|
+
"Expecting #{exp_kind.inspect} had #{act_kind.inspect}"
|
24
|
+
elsif exp_kind.nil?
|
25
|
+
"Will only do diff for strings and arrays, not #{exp.class}"
|
26
|
+
else
|
27
|
+
differ = DiffToString.gitlike!
|
28
|
+
if exp_kind == String
|
29
|
+
use_exp = exp.split(opts[:sep], -1)
|
30
|
+
use_act = act.split(opts[:sep], -1)
|
31
|
+
else
|
32
|
+
use_exp = exp
|
33
|
+
use_act = act
|
34
|
+
end
|
35
|
+
diff = Diff::LCS.diff(use_exp, use_act)
|
36
|
+
if diff.empty?
|
37
|
+
fail("test test fail -- never expecting empty diff here")
|
38
|
+
else
|
39
|
+
differ.arr1 = use_exp
|
40
|
+
differ.arr2 = use_act # awful
|
41
|
+
differ.diff_to_str(diff, :context=>3)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
if re = opts[:ignoring]
|
46
|
+
exp, act = [exp, act].map do |str|
|
47
|
+
str.kind_of?(String) ? str.gsub(re, re.source) : str
|
48
|
+
end
|
49
|
+
end
|
50
|
+
assert(exp == act, msg)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
## treebis
|
2
|
+
|
3
|
+
### in short,
|
4
|
+
Treebis is a minimal (small) task utility written in ruby designed expressly for wrapping common actions for moving, copying and altering filetrees. It's geared towards things like generators. Its look is comparable to a Rake task. Its effect is comparable to a shell script composed of mkdir, mv, cp commands etc.
|
5
|
+
|
6
|
+
This document is overkill. Treebis is underkill.
|
7
|
+
|
8
|
+
### it is:
|
9
|
+
- task wrapper for external commands that make filetrees, e.g.
|
10
|
+
- maybe the generators in things like rails, ramaze, nandoc
|
11
|
+
- copies filetrees to filetrees
|
12
|
+
- removes filetrees from filetrees
|
13
|
+
- applies diffs to filetrees
|
14
|
+
- different ways to represent trees and diffs - heredocs, diffs, filesystem.
|
15
|
+
- under 500 lines of code (?) (~300 SLOC ATTOTW)
|
16
|
+
- near 100% test coverage (?)
|
17
|
+
|
18
|
+
### it is not:
|
19
|
+
- a vcs or vcs wrapper (version control system)
|
20
|
+
- atomic
|
21
|
+
- a replacement for the heavy hitters used by web frameworks
|
22
|
+
- Safe. Little sanity checking and error checking is done. At present it
|
23
|
+
it is mostly a wrapper around FileUtils and patch. If you refer to files
|
24
|
+
that aren't there or try to read/move/remove files you don't have the
|
25
|
+
permissions to do so with, you will see the errors as you would from
|
26
|
+
FileUtils.
|
27
|
+
|
28
|
+
### faq
|
29
|
+
|
30
|
+
#### Q: why use it?
|
31
|
+
|
32
|
+
#### A:
|
33
|
+
<p class='ans'>Because you want a consistent way to wrap these common tasks that doesn't explicitly rely on shelling out to the underlying system, or other hodgepodges. (also see 'why did you make this?')
|
34
|
+
</p>
|
35
|
+
|
36
|
+
#### Q: why not use it?
|
37
|
+
|
38
|
+
#### A:
|
39
|
+
Because it doesn't do what you want or it does what you do not want.
|
40
|
+
|
41
|
+
#### Q: why is it named "Treebis?"
|
42
|
+
|
43
|
+
#### A:
|
44
|
+
because it rhymes with "Jeebus."
|
45
|
+
|
46
|
+
#### Q: why did you make this?
|
47
|
+
|
48
|
+
#### A:
|
49
|
+
by the third or fourth time i found myself re-writing this same kind of thing for different projects (or bleeding from its absence), i decided to abstract it. It's more readable than a bunch of FileUtils statements, it's more portable than a bunch of bash scripts (sorta), it's divorced from any heavy (or light) web frameworks, and it paves the way for possible future enhancements like atomicitiy and units of work; and wouldn't it be nice if every generator of every project used the same library?
|
50
|
+
|
51
|
+
### requirements
|
52
|
+
- ruby 1.8.7
|
53
|
+
- GNU patch 2.5.8 (if the diff-patching functionality is to be used)
|
54
|
+
(most versions of patch will likely work; it uses unified diffs.)
|
55
|
+
|
56
|
+
### installation
|
57
|
+
@todo
|
58
|
+
|
59
|
+
### usage
|
60
|
+
@todo
|
61
|
+
|
62
|
+
### future unfulfilled promises made today:
|
63
|
+
- dry run
|
64
|
+
- erb
|
65
|
+
- two-pass units of work !!??
|
data/lib/nandoc.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# this kind of sucks but for a wicked hack to work with symlinks
|
2
|
+
# we have to wrap this sucker.
|
3
|
+
|
4
|
+
unless Object.const_defined?('NanDoc')
|
5
|
+
|
6
|
+
require 'nanoc3'
|
7
|
+
require 'nanoc3/cli'
|
8
|
+
|
9
|
+
module NanDoc
|
10
|
+
#
|
11
|
+
# i make the D big so i can see it
|
12
|
+
# i move my head away from the microphone when i breathe
|
13
|
+
#
|
14
|
+
Root = File.expand_path('../..',__FILE__)
|
15
|
+
end
|
16
|
+
|
17
|
+
me = File.dirname(__FILE__)+'/nandoc'
|
18
|
+
|
19
|
+
# order is important:
|
20
|
+
require me + '/support-modules.rb'
|
21
|
+
require 'treebis' # gem
|
22
|
+
require me + '/config.rb'
|
23
|
+
|
24
|
+
module NanDoc
|
25
|
+
Treebis::PersistentDotfile.extend_to(self,
|
26
|
+
'./nandoc.persistent.json',
|
27
|
+
:file_utils => Config.file_utils
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
# order is not important: (alphabetical:)
|
32
|
+
require me + '/commands/create-nandoc-site.rb'
|
33
|
+
require me + '/commands/diff.rb'
|
34
|
+
require me + '/cri-hacks.rb'
|
35
|
+
require me + '/data-source.rb'
|
36
|
+
require me + '/filters.rb'
|
37
|
+
require me + '/helpers.rb'
|
38
|
+
require me + '/item-class-hacks.rb'
|
39
|
+
|
40
|
+
Nanoc3::DataSource.register ::NanDoc::DataSource, :nandoc
|
41
|
+
Nanoc3::Filter.register ::NanDoc::Filters::General, :nandoc
|
42
|
+
|
43
|
+
shared_base = Nanoc3::CLI::Base.shared_base
|
44
|
+
shared_base.remove_command Nanoc3::CLI::Commands::CreateSite
|
45
|
+
shared_base.add_command NanDoc::CreateNanDocSite.new
|
46
|
+
shared_base.add_command NanDoc::Commands::Diff.new
|
47
|
+
|
48
|
+
end
|