reapack-index 1.0beta2
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.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.travis.yml +8 -0
- data/Gemfile +3 -0
- data/README.md +167 -0
- data/Rakefile +7 -0
- data/bin/reapack-index +8 -0
- data/lib/reapack/index.rb +351 -0
- data/lib/reapack/index/cli.rb +437 -0
- data/lib/reapack/index/gem_version.rb +5 -0
- data/lib/reapack/index/metadata.rb +185 -0
- data/lib/reapack/index/named_node.rb +68 -0
- data/lib/reapack/index/package.rb +65 -0
- data/lib/reapack/index/parsers.rb +35 -0
- data/lib/reapack/index/version.rb +178 -0
- data/reapack-index.gemspec +33 -0
- data/setup/.gitignore +1 -0
- data/setup/reapack-index.nsi +148 -0
- data/test/data/index.xml +11 -0
- data/test/data/noindex.lua +1 -0
- data/test/helper.rb +25 -0
- data/test/test_changelog.rb +76 -0
- data/test/test_cli.rb +846 -0
- data/test/test_index.rb +817 -0
- data/test/test_metadata.rb +403 -0
- data/test/test_named_node.rb +100 -0
- data/test/test_package.rb +95 -0
- data/test/test_parsers.rb +35 -0
- data/test/test_version.rb +215 -0
- metadata +238 -0
@@ -0,0 +1,437 @@
|
|
1
|
+
class ReaPack::Index::CLI
|
2
|
+
CONFIG_SEARCH = [
|
3
|
+
'~',
|
4
|
+
'.',
|
5
|
+
].freeze
|
6
|
+
|
7
|
+
PROGRAM_NAME = 'reapack-index'.freeze
|
8
|
+
|
9
|
+
DEFAULTS = {
|
10
|
+
verbose: false,
|
11
|
+
warnings: true,
|
12
|
+
progress: true,
|
13
|
+
quiet: false,
|
14
|
+
commit: nil,
|
15
|
+
output: './index.xml',
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def initialize(argv = [])
|
19
|
+
@opts = parse_options(argv)
|
20
|
+
path = argv.last || Dir.pwd
|
21
|
+
|
22
|
+
return unless @exit.nil?
|
23
|
+
|
24
|
+
@git = Rugged::Repository.discover path
|
25
|
+
@opts = parse_options(read_config).merge @opts unless @opts[:noconfig]
|
26
|
+
|
27
|
+
@opts = DEFAULTS.merge @opts
|
28
|
+
|
29
|
+
log Hash[@opts.sort].inspect if @exit.nil?
|
30
|
+
rescue Rugged::OSError, Rugged::RepositoryError => e
|
31
|
+
$stderr.puts e.message
|
32
|
+
@exit = false
|
33
|
+
end
|
34
|
+
|
35
|
+
def run
|
36
|
+
return @exit unless @exit.nil?
|
37
|
+
|
38
|
+
@db = ReaPack::Index.new File.expand_path(@opts[:output], @git.workdir)
|
39
|
+
@db.amend = @opts[:amend]
|
40
|
+
|
41
|
+
if @opts[:check]
|
42
|
+
return check
|
43
|
+
end
|
44
|
+
|
45
|
+
if @opts[:lslinks]
|
46
|
+
print_links
|
47
|
+
return true
|
48
|
+
end
|
49
|
+
|
50
|
+
if @opts[:dump_about]
|
51
|
+
print @db.description
|
52
|
+
return true
|
53
|
+
end
|
54
|
+
|
55
|
+
if remote = @git.remotes['origin']
|
56
|
+
@db.source_pattern = ReaPack::Index.source_for remote.url
|
57
|
+
end
|
58
|
+
|
59
|
+
set_about
|
60
|
+
eval_links
|
61
|
+
scan_commits
|
62
|
+
|
63
|
+
unless @db.modified?
|
64
|
+
$stderr.puts 'Nothing to do!' unless @opts[:quiet]
|
65
|
+
return true
|
66
|
+
end
|
67
|
+
|
68
|
+
# changelog will be cleared by Index#write!
|
69
|
+
changelog = @db.changelog
|
70
|
+
puts changelog unless @opts[:quiet]
|
71
|
+
|
72
|
+
@db.write!
|
73
|
+
commit changelog
|
74
|
+
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def prompt(question, &block)
|
80
|
+
$stderr.print "#{question} [y/N] "
|
81
|
+
answer = $stdin.getch
|
82
|
+
$stderr.puts answer
|
83
|
+
|
84
|
+
yes = answer.downcase == 'y'
|
85
|
+
block[] if block_given? && yes
|
86
|
+
|
87
|
+
yes
|
88
|
+
end
|
89
|
+
|
90
|
+
def scan_commits
|
91
|
+
if @git.empty?
|
92
|
+
warn 'The current branch does not contains any commit.'
|
93
|
+
return
|
94
|
+
end
|
95
|
+
|
96
|
+
walker = Rugged::Walker.new @git
|
97
|
+
walker.sorting Rugged::SORT_TOPO | Rugged::SORT_REVERSE
|
98
|
+
walker.push @git.head.target_id
|
99
|
+
|
100
|
+
last_commit = @db.commit.to_s
|
101
|
+
if Rugged.valid_full_oid?(last_commit) && last_commit.size <= 40
|
102
|
+
walker.hide last_commit if @git.include? last_commit
|
103
|
+
end
|
104
|
+
|
105
|
+
commits = walker.each.to_a
|
106
|
+
|
107
|
+
@done, @total = 0, commits.size
|
108
|
+
|
109
|
+
unless commits.empty?
|
110
|
+
print_progress
|
111
|
+
commits.each {|commit| process commit }
|
112
|
+
$stderr.print "\n" if @add_nl
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def process(commit)
|
117
|
+
if @opts[:verbose]
|
118
|
+
sha = commit.oid[0..6]
|
119
|
+
message = commit.message.lines.first.chomp
|
120
|
+
log "processing %s: %s" % [sha, message]
|
121
|
+
end
|
122
|
+
|
123
|
+
@db.commit = commit.oid
|
124
|
+
@db.time = commit.time
|
125
|
+
@db.files = lsfiles commit.tree
|
126
|
+
|
127
|
+
parent = commit.parents.first
|
128
|
+
|
129
|
+
if parent
|
130
|
+
diff = parent.diff commit.oid
|
131
|
+
else
|
132
|
+
diff = commit.diff
|
133
|
+
end
|
134
|
+
|
135
|
+
diff.each_delta {|delta| index delta, parent.nil? }
|
136
|
+
ensure
|
137
|
+
@done += 1
|
138
|
+
print_progress
|
139
|
+
end
|
140
|
+
|
141
|
+
def index(delta, is_initial)
|
142
|
+
if is_initial
|
143
|
+
status = 'new'
|
144
|
+
file = delta.old_file
|
145
|
+
else
|
146
|
+
status = delta.status
|
147
|
+
file = delta.new_file
|
148
|
+
end
|
149
|
+
|
150
|
+
return unless ReaPack::Index.type_of file[:path]
|
151
|
+
|
152
|
+
log "-> indexing #{status} file #{file[:path]}"
|
153
|
+
|
154
|
+
if status == :deleted
|
155
|
+
@db.remove file[:path]
|
156
|
+
else
|
157
|
+
blob = @git.lookup file[:oid]
|
158
|
+
|
159
|
+
begin
|
160
|
+
@db.scan file[:path], blob.content.force_encoding("UTF-8")
|
161
|
+
rescue ReaPack::Index::Error => e
|
162
|
+
warn "#{file[:path]}: #{e.message}"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def lsfiles(tree, base = String.new)
|
168
|
+
files = []
|
169
|
+
|
170
|
+
tree.each {|obj|
|
171
|
+
fullname = base.empty? ? obj[:name] : File.join(base, obj[:name])
|
172
|
+
case obj[:type]
|
173
|
+
when :blob
|
174
|
+
files << fullname
|
175
|
+
when :tree
|
176
|
+
files.concat lsfiles(@git.lookup(obj[:oid]), fullname)
|
177
|
+
end
|
178
|
+
}
|
179
|
+
|
180
|
+
files
|
181
|
+
end
|
182
|
+
|
183
|
+
def eval_links
|
184
|
+
Array(@opts[:links]).each {|link|
|
185
|
+
begin
|
186
|
+
@db.eval_link *link
|
187
|
+
rescue ReaPack::Index::Error => e
|
188
|
+
warn e.message
|
189
|
+
end
|
190
|
+
}
|
191
|
+
end
|
192
|
+
|
193
|
+
def print_links
|
194
|
+
ReaPack::Index::Link::VALID_TYPES.each {|type|
|
195
|
+
prefix = "[#{type}]".bold.light_black
|
196
|
+
@db.links(type).each {|link|
|
197
|
+
display = link.name == link.url ? link.url : '%s (%s)' % [link.name, link.url]
|
198
|
+
puts '%s %s' % [prefix, display]
|
199
|
+
}
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
def set_about
|
204
|
+
path = @opts[:about]
|
205
|
+
|
206
|
+
unless path
|
207
|
+
@db.description = String.new if @opts[:rmabout]
|
208
|
+
return
|
209
|
+
end
|
210
|
+
|
211
|
+
log "converting #{path} into RTF..."
|
212
|
+
|
213
|
+
# look for the file in the working directory, not on the repository root
|
214
|
+
@db.description = File.read(path)
|
215
|
+
rescue Errno::ENOENT => e
|
216
|
+
warn '--about: ' + e.message.sub(' @ rb_sysopen', '')
|
217
|
+
rescue ReaPack::Index::Error => e
|
218
|
+
warn e.message
|
219
|
+
end
|
220
|
+
|
221
|
+
def check
|
222
|
+
failures = []
|
223
|
+
root = @git.workdir
|
224
|
+
|
225
|
+
types = ReaPack::Index::FILE_TYPES.keys
|
226
|
+
files = Dir.glob "#{Regexp.quote(root)}**/*.{#{types.join ','}}"
|
227
|
+
|
228
|
+
files.sort.each {|file|
|
229
|
+
errors = ReaPack::Index.validate_file file
|
230
|
+
|
231
|
+
if errors
|
232
|
+
$stderr.print 'F' unless @opts[:quiet]
|
233
|
+
prefix = "\n - "
|
234
|
+
file[0..root.size-1] = ''
|
235
|
+
|
236
|
+
failures << "%s contains invalid metadata:#{prefix}%s" %
|
237
|
+
[file, errors.join(prefix)]
|
238
|
+
else
|
239
|
+
$stderr.print '.' unless @opts[:quiet]
|
240
|
+
end
|
241
|
+
}
|
242
|
+
|
243
|
+
$stderr.puts "\n" unless @opts[:quiet]
|
244
|
+
|
245
|
+
failures.each_with_index {|msg, index|
|
246
|
+
$stderr.puts unless @opts[:quiet] && index == 0
|
247
|
+
$stderr.puts msg.yellow
|
248
|
+
}
|
249
|
+
|
250
|
+
unless @opts[:quiet]
|
251
|
+
$stderr.puts "\n"
|
252
|
+
|
253
|
+
$stderr.puts "Finished checks for %d package%s with %d failure%s" % [
|
254
|
+
files.size, files.size == 1 ? '' : 's',
|
255
|
+
failures.size, failures.size == 1 ? '' : 's'
|
256
|
+
]
|
257
|
+
end
|
258
|
+
|
259
|
+
failures.empty?
|
260
|
+
end
|
261
|
+
|
262
|
+
def commit(changelog)
|
263
|
+
return unless case @opts[:commit]
|
264
|
+
when false, true
|
265
|
+
@opts[:commit]
|
266
|
+
else
|
267
|
+
prompt 'Commit the new index?'
|
268
|
+
end
|
269
|
+
|
270
|
+
target = @git.head.target
|
271
|
+
root = Pathname.new @git.workdir
|
272
|
+
file = Pathname.new @db.path
|
273
|
+
|
274
|
+
old_index = @git.index
|
275
|
+
old_index.read_tree target.tree
|
276
|
+
|
277
|
+
index = @git.index
|
278
|
+
index.add file.relative_path_from(root).to_s
|
279
|
+
|
280
|
+
Rugged::Commit.create @git, \
|
281
|
+
tree: index.write_tree(@git),
|
282
|
+
message: "index: #{changelog}",
|
283
|
+
parents: [target],
|
284
|
+
update_ref: 'HEAD'
|
285
|
+
|
286
|
+
old_index.write
|
287
|
+
|
288
|
+
$stderr.puts 'commit created'
|
289
|
+
end
|
290
|
+
|
291
|
+
def log(line)
|
292
|
+
$stderr.puts line if @opts[:verbose]
|
293
|
+
end
|
294
|
+
|
295
|
+
def warn(line)
|
296
|
+
return unless @opts[:warnings]
|
297
|
+
|
298
|
+
if @add_nl
|
299
|
+
$stderr.puts
|
300
|
+
@add_nl = false
|
301
|
+
end
|
302
|
+
|
303
|
+
$stderr.puts "Warning: #{line}".yellow
|
304
|
+
end
|
305
|
+
|
306
|
+
def print_progress
|
307
|
+
return if @opts[:verbose] || !@opts[:progress]
|
308
|
+
|
309
|
+
percent = (@done.to_f / @total) * 100
|
310
|
+
$stderr.print "\rIndexing commit %d of %d (%d%%)..." %
|
311
|
+
[[@done + 1, @total].min, @total, percent]
|
312
|
+
|
313
|
+
@add_nl = true
|
314
|
+
end
|
315
|
+
|
316
|
+
def parse_options(args)
|
317
|
+
opts = Hash.new
|
318
|
+
|
319
|
+
OptionParser.new do |op|
|
320
|
+
op.program_name = PROGRAM_NAME
|
321
|
+
op.version = ReaPack::Index::VERSION
|
322
|
+
op.banner = "Package indexer for ReaPack-based repositories\n" +
|
323
|
+
"Usage: #{PROGRAM_NAME} [options] [directory]"
|
324
|
+
|
325
|
+
op.separator 'Options:'
|
326
|
+
|
327
|
+
op.on '-a', '--[no-]amend', 'Reindex existing versions' do |bool|
|
328
|
+
opts[:amend] = bool
|
329
|
+
end
|
330
|
+
|
331
|
+
op.on '-c', '--check', 'Test every package including uncommited changes and exit' do
|
332
|
+
opts[:check] = true
|
333
|
+
end
|
334
|
+
|
335
|
+
op.on '-o', "--output FILE=#{DEFAULTS[:output]}",
|
336
|
+
'Set the output filename and path for the index' do |file|
|
337
|
+
opts[:output] = file.strip
|
338
|
+
end
|
339
|
+
|
340
|
+
op.on '-l', '--link LINK', 'Add or remove a website link' do |link|
|
341
|
+
opts[:links] ||= Array.new
|
342
|
+
opts[:links] << [:website, link.strip]
|
343
|
+
end
|
344
|
+
|
345
|
+
op.on '--donation-link LINK', 'Add or remove a donation link' do |link|
|
346
|
+
opts[:links] ||= Array.new
|
347
|
+
opts[:links] << [:donation, link]
|
348
|
+
end
|
349
|
+
|
350
|
+
op.on '--ls-links', 'Display the link list then exit' do |link|
|
351
|
+
opts[:lslinks] = true
|
352
|
+
end
|
353
|
+
|
354
|
+
op.on '-A', '--about=FILE', 'Set the about content from a file' do |file|
|
355
|
+
opts[:about] = file.strip
|
356
|
+
end
|
357
|
+
|
358
|
+
op.on '--remove-about', 'Remove the about content from the index' do
|
359
|
+
opts[:rmabout] = true
|
360
|
+
end
|
361
|
+
|
362
|
+
op.on '--dump-about', 'Dump the raw about content in RTF and exit' do
|
363
|
+
opts[:dump_about] = true
|
364
|
+
end
|
365
|
+
|
366
|
+
op.on '--[no-]progress', 'Enable or disable progress information' do |bool|
|
367
|
+
opts[:progress] = bool
|
368
|
+
end
|
369
|
+
|
370
|
+
op.on '-V', '--[no-]verbose', 'Activate diagnosis messages' do |bool|
|
371
|
+
opts[:verbose] = bool
|
372
|
+
end
|
373
|
+
|
374
|
+
op.on '-C', '--[no-]commit', 'Select whether to commit the modified index' do |bool|
|
375
|
+
opts[:commit] = bool
|
376
|
+
end
|
377
|
+
|
378
|
+
op.on '--prompt-commit', 'Ask at runtime whether to commit the index' do
|
379
|
+
opts[:commit] = nil
|
380
|
+
end
|
381
|
+
|
382
|
+
op.on '-W', '--warnings', 'Enable warnings' do
|
383
|
+
opts[:warnings] = true
|
384
|
+
end
|
385
|
+
|
386
|
+
op.on '-w', '--no-warnings', 'Turn off warnings' do
|
387
|
+
opts[:warnings] = false
|
388
|
+
end
|
389
|
+
|
390
|
+
op.on '-q', '--[no-]quiet', 'Disable almost all output' do
|
391
|
+
opts[:warnings] = false
|
392
|
+
opts[:progress] = false
|
393
|
+
opts[:verbose] = false
|
394
|
+
opts[:quiet] = true
|
395
|
+
end
|
396
|
+
|
397
|
+
op.on '--no-config', 'Bypass the configuration files' do
|
398
|
+
opts[:noconfig] = true
|
399
|
+
end
|
400
|
+
|
401
|
+
op.on_tail '-v', '--version', 'Display version information' do
|
402
|
+
puts op.ver
|
403
|
+
@exit = true
|
404
|
+
end
|
405
|
+
|
406
|
+
op.on_tail '-h', '--help', 'Prints this help' do
|
407
|
+
puts op
|
408
|
+
@exit = true
|
409
|
+
end
|
410
|
+
end.parse! args
|
411
|
+
|
412
|
+
opts
|
413
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
|
414
|
+
$stderr.puts "#{PROGRAM_NAME}: #{e.message}"
|
415
|
+
$stderr.puts "Try '#{PROGRAM_NAME} --help' for more information."
|
416
|
+
@exit = false
|
417
|
+
opts
|
418
|
+
end
|
419
|
+
|
420
|
+
def read_config
|
421
|
+
CONFIG_SEARCH.map {|dir|
|
422
|
+
dir = File.expand_path dir, @git.workdir
|
423
|
+
path = File.expand_path '.reapack-index.conf', dir
|
424
|
+
|
425
|
+
log 'reading configuration from %s' % path
|
426
|
+
|
427
|
+
unless File.readable? path
|
428
|
+
log 'configuration file is unreadable, skipping'
|
429
|
+
next
|
430
|
+
end
|
431
|
+
|
432
|
+
opts = Array.new
|
433
|
+
File.foreach(path) {|line| opts << Shellwords.split(line) }
|
434
|
+
opts
|
435
|
+
}.flatten.compact
|
436
|
+
end
|
437
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
class ReaPack::Index
|
2
|
+
class Metadata
|
3
|
+
TAG = 'metadata'.freeze
|
4
|
+
DESC = 'description'.freeze
|
5
|
+
|
6
|
+
def initialize(parent)
|
7
|
+
@parent = parent
|
8
|
+
|
9
|
+
@root = parent.element_children.find {|node| node.name == TAG }
|
10
|
+
end
|
11
|
+
|
12
|
+
def links(type)
|
13
|
+
Link.find_all(type, @root).map {|node| Link.from_node node }
|
14
|
+
.select {|link| link.url.index('http') == 0 }
|
15
|
+
end
|
16
|
+
|
17
|
+
def push_link(type, name = nil, url)
|
18
|
+
Link.check_type type
|
19
|
+
|
20
|
+
unless url =~ /\A#{URI::regexp(['http', 'https'])}\z/
|
21
|
+
raise Error, "invalid URL: #{url}"
|
22
|
+
end
|
23
|
+
|
24
|
+
make_root
|
25
|
+
|
26
|
+
link = Link.new name || url, url
|
27
|
+
node = Link.find type, link.name, @root
|
28
|
+
node ||= Link.find type, link.url, @root
|
29
|
+
|
30
|
+
if node
|
31
|
+
link.instance_variable_set :@is_new, false
|
32
|
+
link.instance_variable_set :@modified, link != Link.from_node(node)
|
33
|
+
|
34
|
+
node.remove_attribute Link::URL
|
35
|
+
else
|
36
|
+
link.instance_variable_set :@is_new, true
|
37
|
+
link.instance_variable_set :@modified, true
|
38
|
+
|
39
|
+
node = Nokogiri::XML::Node.new Link::TAG, @root.document
|
40
|
+
node.parent = @root
|
41
|
+
node[Link::REL] = type
|
42
|
+
end
|
43
|
+
|
44
|
+
if name
|
45
|
+
node[Link::URL] = url
|
46
|
+
node.content = name
|
47
|
+
else
|
48
|
+
node.content = url
|
49
|
+
end
|
50
|
+
|
51
|
+
link
|
52
|
+
end
|
53
|
+
|
54
|
+
def remove_link(type, search)
|
55
|
+
node = Link.find type, search, @root
|
56
|
+
|
57
|
+
raise Error, "no such #{type} link: #{search}" unless node
|
58
|
+
|
59
|
+
node.remove
|
60
|
+
auto_remove
|
61
|
+
end
|
62
|
+
|
63
|
+
def description
|
64
|
+
cdata = nil
|
65
|
+
|
66
|
+
if @root
|
67
|
+
desc = @root.element_children.find {|node| node.name == DESC }
|
68
|
+
cdata = desc.children.first if desc
|
69
|
+
end
|
70
|
+
|
71
|
+
cdata ? cdata.content : String.new
|
72
|
+
end
|
73
|
+
|
74
|
+
def description=(content)
|
75
|
+
return if content == description
|
76
|
+
|
77
|
+
make_root
|
78
|
+
desc = @root.element_children.find {|node| node.name == DESC }
|
79
|
+
|
80
|
+
if content.empty?
|
81
|
+
desc.remove
|
82
|
+
auto_remove
|
83
|
+
return
|
84
|
+
elsif content.index("{\\rtf") != 0
|
85
|
+
content = make_rtf content
|
86
|
+
end
|
87
|
+
|
88
|
+
if desc
|
89
|
+
desc.children.each {|n| n.remove }
|
90
|
+
else
|
91
|
+
desc = Nokogiri::XML::Node.new DESC, @root.document
|
92
|
+
desc.parent = @root
|
93
|
+
end
|
94
|
+
|
95
|
+
cdata = Nokogiri::XML::CDATA.new desc, content
|
96
|
+
cdata.parent = desc
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
def make_root
|
101
|
+
unless @root
|
102
|
+
@root = Nokogiri::XML::Node.new TAG, @parent.document
|
103
|
+
@root.parent = @parent
|
104
|
+
end
|
105
|
+
|
106
|
+
@root
|
107
|
+
end
|
108
|
+
|
109
|
+
def auto_remove
|
110
|
+
@root.remove if @root.children.empty?
|
111
|
+
end
|
112
|
+
|
113
|
+
def make_rtf(content)
|
114
|
+
PandocRuby.new(content).to_rtf :standalone
|
115
|
+
rescue Errno::ENOENT
|
116
|
+
raise Error, [
|
117
|
+
"RTF conversion failed because the pandoc executable " \
|
118
|
+
"cannot be found in your PATH.",
|
119
|
+
"Try again after installing pandoc from <http://pandoc.org/>."
|
120
|
+
].join("\n")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class Link
|
125
|
+
TAG = 'link'.freeze
|
126
|
+
REL = 'rel'.freeze
|
127
|
+
URL = 'href'.freeze
|
128
|
+
|
129
|
+
# the first type will be the default one
|
130
|
+
VALID_TYPES = [:website, :donation].freeze
|
131
|
+
|
132
|
+
def self.from_node(node)
|
133
|
+
name, url = node.text.to_s, node[URL].to_s
|
134
|
+
url, name = name, url if url.empty?
|
135
|
+
name = url if name.empty?
|
136
|
+
|
137
|
+
self.new name, url
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.check_type(type)
|
141
|
+
raise ArgumentError unless VALID_TYPES.include? type
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.same_type?(type, user)
|
145
|
+
# match invalid types by the first value of VALID_TYPES
|
146
|
+
# while the other values require an exact match
|
147
|
+
user == type || (type == VALID_TYPES[0] && VALID_TYPES.index(user).to_i < 1)
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.find_all(type, parent)
|
151
|
+
Link.check_type type
|
152
|
+
|
153
|
+
return [] unless parent
|
154
|
+
|
155
|
+
parent.element_children.select {|node|
|
156
|
+
node.name == TAG && Link.same_type?(type, node[REL].to_s.to_sym)
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.find(type, search, parent)
|
161
|
+
Link.find_all(type, parent).find {|node|
|
162
|
+
node.text == search || node[URL] == search
|
163
|
+
}
|
164
|
+
end
|
165
|
+
|
166
|
+
def initialize(name, url)
|
167
|
+
@name, @url = name, url
|
168
|
+
@is_new = @modified = false
|
169
|
+
end
|
170
|
+
|
171
|
+
attr_accessor :name, :url
|
172
|
+
|
173
|
+
def ==(other)
|
174
|
+
name == other.name && url == other.url
|
175
|
+
end
|
176
|
+
|
177
|
+
def is_new?
|
178
|
+
@is_new
|
179
|
+
end
|
180
|
+
|
181
|
+
def modified?
|
182
|
+
@modified
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|