reapack-index 1.0beta3 → 1.0beta4

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,6 +7,8 @@ class ReaPack::Index::CLI
7
7
  ].freeze
8
8
 
9
9
  DEFAULTS = {
10
+ check: false,
11
+ scan: [],
10
12
  verbose: false,
11
13
  warnings: true,
12
14
  progress: true,
@@ -41,28 +43,37 @@ class ReaPack::Index::CLI
41
43
  OptionParser.new do |op|
42
44
  op.program_name = PROGRAM_NAME
43
45
  op.version = ReaPack::Index::VERSION
44
- op.banner = "Package indexer for ReaPack-based repositories\n" +
46
+ op.banner = "Package indexer for git-based ReaPack repositories\n" +
45
47
  "Usage: #{PROGRAM_NAME} [options] [directory]"
46
48
 
47
- op.separator 'Options:'
48
-
49
- op.on '-a', '--[no-]amend', 'Reindex existing versions' do |bool|
50
- opts[:amend] = bool
51
- end
49
+ op.separator 'Modes:'
52
50
 
53
51
  op.on '-c', '--check', 'Test every package including uncommited changes and exit' do
54
52
  opts[:check] = true
55
53
  end
56
54
 
55
+ op.on '-s', '--scan [COMMIT]', 'Scan new commits (default) or specific commits' do |commit|
56
+ opts[:check] = false
57
+ opts[:scan] ||= []
58
+
59
+ if commit
60
+ opts[:scan] << commit.strip
61
+ else
62
+ opts[:scan].clear
63
+ end
64
+ end
65
+
66
+ op.separator 'Indexer options:'
67
+
68
+ op.on '-a', '--[no-]amend', 'Reindex existing versions' do |bool|
69
+ opts[:amend] = bool
70
+ end
71
+
57
72
  op.on '-i', '--ignore PATH', "Don't check or index any file starting with PATH" do |path|
58
73
  opts[:ignore] ||= []
59
74
  opts[:ignore] << expand_path(path)
60
75
  end
61
76
 
62
- op.on '-n', '--name NAME', 'Set the name shown in ReaPack for this repository' do |name|
63
- opts[:name] = name.strip
64
- end
65
-
66
77
  op.on '-U', "--url-template TEMPLATE=#{DEFAULTS[:url_template]}",
67
78
  'Set the template for implicit download links' do |tpl|
68
79
  opts[:url_template] = tpl.strip
@@ -73,6 +84,12 @@ class ReaPack::Index::CLI
73
84
  opts[:output] = file.strip
74
85
  end
75
86
 
87
+ op.separator 'Repository metadata:'
88
+
89
+ op.on '-n', '--name NAME', 'Set the name shown in ReaPack for this repository' do |name|
90
+ opts[:name] = name.strip
91
+ end
92
+
76
93
  op.on '-l', '--link LINK', 'Add or remove a website link' do |link|
77
94
  opts[:links] ||= Array.new
78
95
  opts[:links] << [:website, link.strip]
@@ -99,6 +116,8 @@ class ReaPack::Index::CLI
99
116
  opts[:dump_about] = true
100
117
  end
101
118
 
119
+ op.separator 'Misc options:'
120
+
102
121
  op.on '--[no-]progress', 'Enable or disable progress information' do |bool|
103
122
  opts[:progress] = bool
104
123
  end
@@ -1,5 +1,5 @@
1
1
  module ReaPack
2
2
  class Index
3
- VERSION = '1.0beta3'
3
+ VERSION = '1.0beta4'
4
4
  end
5
5
  end
@@ -0,0 +1,189 @@
1
+ class ReaPack::Index
2
+ class Git
3
+ def initialize(path)
4
+ @repo = Rugged::Repository.discover path
5
+ end
6
+
7
+ def empty?
8
+ @repo.empty?
9
+ end
10
+
11
+ def path
12
+ File.expand_path @repo.workdir
13
+ end
14
+
15
+ def commits_since(sha)
16
+ return [] if empty?
17
+
18
+ walker = Rugged::Walker.new @repo
19
+ walker.sorting Rugged::SORT_TOPO | Rugged::SORT_REVERSE
20
+ walker.push @repo.head.target_id
21
+
22
+ walker.hide sha if fetch_commit sha
23
+
24
+ walker.map {|c| Commit.new c, @repo }
25
+ end
26
+
27
+ def get_commit(sha)
28
+ c = fetch_commit sha
29
+ Commit.new c, @repo if c
30
+ end
31
+
32
+ def last_commit
33
+ c = @repo.last_commit
34
+ Commit.new c, @repo if c
35
+ end
36
+
37
+ def guess_url_template
38
+ remote = @repo.remotes['origin']
39
+ return unless remote
40
+
41
+ uri = Gitable::URI.parse remote.url
42
+ return unless uri.path =~ /\A\/?(?<user>[^\/]+)\/(?<repo>[^\/]+)\.git\Z/
43
+
44
+ tpl = uri.to_web_uri
45
+ tpl.path += '/raw/$commit/$path'
46
+
47
+ tpl.to_s
48
+ end
49
+
50
+ def relative_path(path)
51
+ root = Pathname.new @repo.workdir
52
+ file = Pathname.new path
53
+
54
+ file.relative_path_from(root).to_s
55
+ end
56
+
57
+ def create_commit(message, files)
58
+ old_index = @repo.index
59
+ target = empty? ? nil : @repo.head.target
60
+
61
+ if target
62
+ old_index.read_tree target.tree
63
+ else
64
+ old_index.clear
65
+ end
66
+
67
+ new_index = @repo.index
68
+ files.each {|f|
69
+ if File.exist? f
70
+ new_index.add relative_path(f)
71
+ else
72
+ new_index.remove relative_path(f)
73
+ end
74
+ }
75
+
76
+ c = Rugged::Commit.create @repo, \
77
+ tree: new_index.write_tree(@repo),
78
+ message: message,
79
+ parents: [target].compact,
80
+ update_ref: 'HEAD'
81
+
82
+ old_index.write
83
+
84
+ # force-reload the repository
85
+ @repo = Rugged::Repository.discover path
86
+
87
+ get_commit c
88
+ end
89
+
90
+ private
91
+ def fetch_commit(sha)
92
+ if sha && sha.size.between?(7, 40) && @repo.include?(sha)
93
+ object = @repo.lookup sha
94
+ object if object.is_a? Rugged::Commit
95
+ end
96
+ rescue Rugged::InvalidError
97
+ nil
98
+ end
99
+ end
100
+
101
+ class Git::Commit
102
+ def initialize(commit, repo)
103
+ @commit, @repo = commit, repo
104
+ @parent = commit.parents.first
105
+ end
106
+
107
+ def each_diff
108
+ return enum_for :each_diff unless block_given?
109
+
110
+ if @parent
111
+ diff = @parent.diff id
112
+ else
113
+ diff = @commit.diff
114
+ end
115
+
116
+ diff.each_delta {|delta| yield Git::Diff.new(delta, @parent.nil?, @repo) }
117
+ end
118
+
119
+ def id
120
+ @commit.oid
121
+ end
122
+
123
+ def short_id
124
+ id[0...7]
125
+ end
126
+
127
+ def message
128
+ @commit.message
129
+ end
130
+
131
+ def summary
132
+ message.lines.first.chomp
133
+ end
134
+
135
+ def time
136
+ @commit.time
137
+ end
138
+
139
+ def filelist
140
+ lsfiles @commit.tree
141
+ end
142
+
143
+ def ==(o)
144
+ id == o.id
145
+ end
146
+
147
+ private
148
+ def lsfiles(tree, base = String.new)
149
+ files = []
150
+
151
+ tree.each {|obj|
152
+ fullname = base.empty? ? obj[:name] : File.join(base, obj[:name])
153
+ case obj[:type]
154
+ when :blob
155
+ files << fullname
156
+ when :tree
157
+ files.concat lsfiles(@repo.lookup(obj[:oid]), fullname)
158
+ end
159
+ }
160
+
161
+ files
162
+ end
163
+ end
164
+
165
+ class Git::Diff
166
+ def initialize(delta, is_initial, repo)
167
+ @delta, @repo = delta, repo
168
+
169
+ if is_initial
170
+ @status = :added
171
+ @file = delta.old_file
172
+ else
173
+ @status = delta.status.to_sym
174
+ @file = delta.new_file
175
+ end
176
+ end
177
+
178
+ attr_reader :status
179
+
180
+ def file
181
+ @file[:path].force_encoding(Encoding::UTF_8)
182
+ end
183
+
184
+ def new_content
185
+ return if status == :deleted
186
+ @repo.lookup(@file[:oid]).content.force_encoding(Encoding::UTF_8)
187
+ end
188
+ end
189
+ end
@@ -24,7 +24,7 @@ class ReaPack::Index
24
24
  raise Addressable::URI::InvalidURIError
25
25
  end
26
26
  rescue Addressable::URI::InvalidURIError
27
- raise Error, "invalid link: #{url}"
27
+ raise Error, "invalid link '#{url}'"
28
28
  end
29
29
 
30
30
  make_root
@@ -60,7 +60,7 @@ class ReaPack::Index
60
60
  def remove_link(type, search)
61
61
  node = Link.find type, search, @root
62
62
 
63
- raise Error, "no such #{type} link: #{search}" unless node
63
+ raise Error, "no such #{type} link '#{search}'" unless node
64
64
 
65
65
  node.remove
66
66
  auto_remove
@@ -7,7 +7,7 @@ class ReaPack::Index
7
7
  @tag
8
8
  end
9
9
 
10
- def self.find_in(parent, name)
10
+ def self.find_one(name, parent)
11
11
  node = parent.element_children.find {|node|
12
12
  node.name == tag && node[NAME_ATTR] == name
13
13
  }
@@ -21,27 +21,32 @@ class ReaPack::Index
21
21
  .map {|node| self.new node }
22
22
  end
23
23
 
24
- def self.get(name, parent, create)
24
+ def self.fetch(name, parent, create)
25
25
  return unless parent
26
26
 
27
- node = self.find_in parent, name
27
+ instance = find_one name, parent
28
28
 
29
29
  if create
30
- node ||= self.new name, parent
30
+ instance ||= self.create name, parent
31
31
  end
32
32
 
33
- node
33
+ instance
34
34
  end
35
35
 
36
- def initialize(node, parent = nil)
37
- return @node = node if parent.nil?
36
+ def self.create(name, parent)
37
+ node = Nokogiri::XML::Node.new tag, parent.document
38
+ node[NAME_ATTR] = name
39
+ node.parent = parent
38
40
 
39
- @is_new = true
40
- @dirty = true
41
+ instance = new node
42
+ instance.instance_variable_set :@is_new, true
43
+ instance.instance_variable_set :@dirty, true
41
44
 
42
- @node = Nokogiri::XML::Node.new self.class.tag, parent.document
43
- @node[NAME_ATTR] = node
44
- @node.parent = parent
45
+ instance
46
+ end
47
+
48
+ def initialize(node)
49
+ @node = node
45
50
  end
46
51
 
47
52
  attr_reader :node
@@ -6,13 +6,10 @@ class ReaPack::Index
6
6
  class Package < NamedNode
7
7
  @tag = 'reapack'.freeze
8
8
 
9
- TYPE = 'type'.freeze
9
+ TYPE_ATTR = 'type'.freeze
10
10
 
11
- def initialize(node, parent = nil)
11
+ def initialize(node)
12
12
  super
13
-
14
- @versions = {}
15
-
16
13
  read_versions
17
14
  end
18
15
 
@@ -20,16 +17,24 @@ class ReaPack::Index
20
17
  super || @versions.values.any? {|ver| ver.modified? }
21
18
  end
22
19
 
20
+ def category
21
+ @node.parent[NAME_ATTR]
22
+ end
23
+
24
+ def path
25
+ @path ||= File.join category, name if category && name
26
+ end
27
+
23
28
  def type
24
- @node[TYPE].to_s
29
+ @node[TYPE_ATTR]&.to_sym
25
30
  end
26
31
 
27
32
  def type=(new_type)
28
- new_type ||= String.new
33
+ new_type = new_type.to_sym
29
34
 
30
35
  return if type == new_type
31
36
 
32
- @node[TYPE] = new_type
37
+ @node[TYPE_ATTR] = new_type
33
38
  @dirty = true
34
39
  end
35
40
 
@@ -45,7 +50,7 @@ class ReaPack::Index
45
50
  if has_version? name
46
51
  ver = @versions[name]
47
52
  else
48
- ver = @versions[name] = Version.new name, @node
53
+ ver = @versions[name] = Version.create name, @node
49
54
  end
50
55
 
51
56
  if block_given?
@@ -57,6 +62,8 @@ class ReaPack::Index
57
62
 
58
63
  private
59
64
  def read_versions
65
+ @versions ||= {}
66
+
60
67
  Version.find_all(@node).each {|ver|
61
68
  @versions[ver.name] = ver
62
69
  }
@@ -0,0 +1,46 @@
1
+ class ReaPack::Index
2
+ Provides = Struct.new :file_pattern, :url_template, :platform, :type do
3
+ PROVIDES_REGEX = /
4
+ \A
5
+ ( \[ \s* (?<options> .+? ) \s* \] )?
6
+ \s*
7
+ (?<file> .+?)
8
+ ( \s+ (?<url> (?:file|https?):\/\/.+ ) )?
9
+ \z
10
+ /x.freeze
11
+
12
+ class << self
13
+ def parse_each(input)
14
+ if block_given?
15
+ input.to_s.lines.map {|line| yield parse(line) }
16
+ else
17
+ enum_for :parse_each, input
18
+ end
19
+ end
20
+
21
+ def parse(line)
22
+ m = line.strip.match PROVIDES_REGEX
23
+ options, pattern, url_tpl = m[:options], m[:file], m[:url]
24
+
25
+ instance = self.new pattern, url_tpl
26
+
27
+ options and options.split(',').each {|user_opt|
28
+ user_opt.strip!
29
+ next if user_opt.empty?
30
+
31
+ opt = user_opt.downcase.to_sym
32
+
33
+ if Source.is_platform? opt
34
+ instance.platform = opt
35
+ elsif type = ReaPack::Index.resolve_type(opt)
36
+ instance.type = type
37
+ else
38
+ raise Error, "unknown option (platform or type) '#{user_opt}'"
39
+ end
40
+ }
41
+
42
+ instance
43
+ end
44
+ end
45
+ end
46
+ end