aur.rb 0.1.0 → 0.2.0

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.
@@ -0,0 +1,343 @@
1
+ require 'aur/helpers'
2
+ require 'aur/packages'
3
+ require 'time'
4
+
5
+ module Archlinux
6
+ # this hold a repo name
7
+ class Repo
8
+ extend CreateHelper
9
+
10
+ attr_accessor :repo, :config
11
+ def initialize(name, config: Archlinux.config)
12
+ @repo=name
13
+ @config=config
14
+ end
15
+
16
+ def list(mode: :pacsift)
17
+ command= case mode
18
+ when :pacman, :name
19
+ if @repo=="local"
20
+ "pacman -Qq"
21
+ else
22
+ "pacman -Slq #{@repo.shellescape}" #returns pkg
23
+ end
24
+ when :repo_name
25
+ #like pacsift, but using pacman
26
+ list(mode: :name).map {|i| @repo+"/"+i}
27
+ when :pacsift
28
+ #this mode is prefered, so that if the same pkg is in different
29
+ #repo, than pacman_info returns the correct info
30
+ #pacsift understand the 'local' repo
31
+ "pacsift --exact --repo=#{@repo.shellescape} <&-" #returns repo/pkg
32
+ when :paclist, :name_version
33
+ #cannot show the local repo; we could use `expac -Q '%n %v' but we
34
+ #don't use this mode anyway
35
+ "paclist #{@repo.shellescape}" #returns 'pkg version'
36
+ end
37
+ SH.run_simple(command, chomp: :lines) {return nil}
38
+ end
39
+
40
+ def packages(refresh=false)
41
+ @packages=nil if refresh
42
+ @packages ||= @config.to_packages(self.class.info(*list))
43
+ end
44
+
45
+ def self.pacman_info(*pkgs, local: false) #local=true refers to the local db info. Note that pacman does not understand local/pkg but pacinfo does
46
+ list=[]
47
+ to_list=lambda do |s|
48
+ return [] if s=="None"
49
+ s.split
50
+ end
51
+ # Note: core/pacman only works for -Sddp or -Si, not for -Qi
52
+ # Indeed local/pacman works for pacinfo, but not for pacman (needs
53
+ # the -Q options)
54
+ res=SH.run_simple({'COLUMNS' => '1000'}, "pacman -#{local ? 'Q': 'S'}i #{pkgs.shelljoin}", chomp: :lines)
55
+ key=nil; info={}
56
+ res.each do |l|
57
+ if key==:optional_deps and (m=l.match(/^\s+(\w*):\s+(.*)$/))
58
+ #here we cannot split(':') because we need to check for the leading space
59
+ info[key][m[1]]=m[2]
60
+ else
61
+ key, value=l.split(/\s*:\s*/,2)
62
+ if key.nil? #new package
63
+ list << info; key=nil; info={}
64
+ next
65
+ end
66
+ key=key.strip.downcase.gsub(' ', '_').to_sym
67
+ case key
68
+ when :optional_deps
69
+ dep, reason=value.split(/\s*:\s*/,2)
70
+ value={dep => reason}
71
+ when :groups, :provides, :depends_on, :required_by, :optional_for, :conflicts_with, :replaces
72
+ value=to_list.call(value)
73
+ when :install_script
74
+ value=false if value=="No"
75
+ value=true if value=="Yes"
76
+ when :install_date, :build_date
77
+ value=Time.parse(value)
78
+ end
79
+ info[key]=value
80
+ end
81
+ end
82
+ # no need to add the last info, pacman -Q/Si always end with a new line
83
+ list
84
+ end
85
+
86
+ def self.pacinfo(*pkgs) #this refers to the local db info
87
+ list=[]; info={}
88
+ res=SH.run_simple("pacinfo #{pkgs.shelljoin}", chomp: :lines)
89
+ res.each do |l|
90
+ key, value=l.split(/\s*:\s*/,2)
91
+ if key.nil? #next package
92
+ list << info; info={}
93
+ next
94
+ end
95
+ key=key.downcase.gsub(' ', '_').to_sym
96
+ case key
97
+ when :install_script
98
+ value=false if value=="No"
99
+ value=true if value=="Yes"
100
+ when :install_date, :build_date
101
+ value=Time.parse(value)
102
+ end
103
+ Archlinux.add_to_hash(info, key, value)
104
+ end
105
+ # no need to add at the end, pacinfo always end with a new line
106
+ list
107
+ end
108
+
109
+ def self.info(*packages)
110
+ if SH.find_executable("pacinfo")
111
+ pacinfo(*packages)
112
+ else
113
+ pacman_info(*packages)
114
+ end
115
+ end
116
+
117
+ # Exemple: Archlinux::Repo.packages(* %x/pacman -Qqm/.split)
118
+ # Warning: this does not use config, so this is just for a convenience
119
+ # helper. Use RepoPkgs for install/config stuff
120
+ def self.packages(*packages)
121
+ PackageList.new(info(*packages))
122
+ end
123
+
124
+ def self.foreign_list
125
+ %x(pacman -Qqm).split
126
+ end
127
+
128
+ def self.foreign_packages
129
+ packages(*foreign_list)
130
+ end
131
+ end
132
+
133
+ # can combined several repos
134
+ class RepoPkgs
135
+ extend CreateHelper
136
+ attr_accessor :list, :config
137
+
138
+ def initialize(list, config: Archlinux.config)
139
+ @list=list
140
+ @config=config
141
+ end
142
+
143
+ def infos
144
+ Repo.info(*@list)
145
+ end
146
+
147
+ def packages(refresh=false)
148
+ @packages=nil if refresh
149
+ @packages ||= @config.to_packages(infos)
150
+ end
151
+ end
152
+
153
+ class LocalRepo
154
+ extend CreateHelper
155
+ attr_accessor :dir, :config
156
+
157
+ def initialize(dir="/var/lib/pacman/local", config: Archlinux.config)
158
+ @dir=Pathname.new(dir)
159
+ @config=config
160
+ end
161
+
162
+ def infos
163
+ #todo: this is essentially the same code as for db repo; factorize this?
164
+ list=[]
165
+ @dir.glob("*/desc").each do |desc|
166
+ pkg={repo: :local}; mode=nil
167
+ desc.read.each_line do |l|
168
+ l.chomp!
169
+ next if l.empty?
170
+ if (m=l.match(/^%([A-Z0-9]*)%$/))
171
+ mode=m[1].downcase.to_sym
172
+ else
173
+ l=l.to_i if mode==:csize or mode==:isize
174
+ l=Time.at(l.to_i) if mode==:builddate
175
+ Archlinux.add_to_hash(pkg, mode, l)
176
+ end
177
+ end
178
+ list << pkg
179
+ end
180
+ list
181
+ end
182
+
183
+ def packages(refresh=false)
184
+ @packages=nil if refresh
185
+ @packages ||= @config.to_packages(infos)
186
+ end
187
+ end
188
+
189
+ # a list of packages archives
190
+ class PackageFiles
191
+ extend CreateHelper
192
+
193
+ attr_accessor :files
194
+ def initialize(*files, config: Archlinux.config)
195
+ files=files.flatten #in case we are used with create
196
+ @files=files.map {|file| Pathname.new(file)}
197
+ @config=config
198
+ end
199
+
200
+ def infos
201
+ # expac_infos
202
+ bsdtar_infos
203
+ end
204
+
205
+ def bsdtar_infos
206
+ list=[]
207
+ @files.each do |file|
208
+ info={repo: file}
209
+ SH.run_simple("bsdtar -xOqf #{file.shellescape} .PKGINFO", chomp: :lines) do |error|
210
+ SH.logger.info "Skipping #{file}: #{error}"
211
+ next
212
+ end.each do |l|
213
+ next if l=~/^#/
214
+ key, value=l.split(/\s*=\s*/,2)
215
+ key=key.to_sym
216
+ case key
217
+ when :builddate
218
+ value=Time.at(value.to_i)
219
+ when :size
220
+ key=:install_size; value=value.to_i
221
+ when :pkgver
222
+ key=:version
223
+ end
224
+ Archlinux.add_to_hash(info, key, value)
225
+ end
226
+ # no need to add at the end, pacinfo always end with a new line
227
+ list << info
228
+ end
229
+ list
230
+ end
231
+
232
+ def expac_infos(slice=200) #the command line should not be too long
233
+ format={
234
+ filename: "%f",
235
+ pkgname: "%n",
236
+ pkgbase: "%e",
237
+ version: "%v",
238
+ url: "%u",
239
+ description: "%d",
240
+ packager: "%p",
241
+ architecture: "%a",
242
+ build_date: "%b",
243
+ download_size: "%k",
244
+ install_size: "%m",
245
+ depends: "%D",
246
+ conflicts: "%H",
247
+ opt_depends: "%O",
248
+ provides: "%P",
249
+ replaces: "%T",
250
+ # format << "%r\n" #repo
251
+ }
252
+ total=format.keys.count
253
+ r=[]; delim=" , "
254
+ split=lambda do |l| l.split(delim) end
255
+ @files.each_slice(slice) do |files|
256
+ SH.run_simple("expac --timefmt=%s #{format.values.join("\n").shellescape} -l #{delim.shellescape} -p #{files.shelljoin}", chomp: :lines).each_slice(total).with_index do |l,i|
257
+ info={}
258
+ format.keys.each_with_index do |k, kk|
259
+ value=l[kk]
260
+ value=split[value] if %i(depends conflicts opt_depends provides replaces).include?(k)
261
+ value=nil if k==:pkgbase and value=="(null)"
262
+ value=Time.at(value.to_i) if k==:build_date
263
+ value=value.to_i if k==:download_size or k==:install_size
264
+ info[k]=value if value
265
+ end
266
+ info[:repo]=files[i]
267
+ r<<info
268
+ end
269
+ end
270
+ r
271
+ end
272
+
273
+ def packages(refresh=false)
274
+ @packages=nil if refresh
275
+ @packages ||= @config.to_packages(self.infos)
276
+ end
277
+
278
+ def sign(sign_name: :package, **opts)
279
+ @config&.sign(*@files, sign_name: sign_name, **opts)
280
+ end
281
+
282
+ def self.rm_files(*files, dir: nil)
283
+ deleted=[]
284
+ files.each do |file|
285
+ path=Pathname.new(file)
286
+ path=dir+path if path.relative? and dir
287
+ if path.exist?
288
+ path.rm
289
+ deleted << path
290
+ end
291
+ sig=Pathname.new("#{path}.sig")
292
+ if sig.exist?
293
+ sig.rm
294
+ deleted << sig
295
+ end
296
+ end
297
+ SH.logger.verbose2 "Deleted: #{deleted}"
298
+ deleted
299
+ end
300
+
301
+ # pass packages names to remove
302
+ def rm_pkgs(*pkgs)
303
+ files=[]
304
+ pkgs.each do |pkg_name|
305
+ pkg=packages.fetch(pkg_name, nil)
306
+ files << pkg.path if pkg
307
+ end
308
+ @packages=nil #we need to refresh the list.
309
+ self.class.rm_files(*files)
310
+ end
311
+
312
+ def clean(dry_run: true)
313
+ to_clean=[]
314
+ latest=packages.latest.values
315
+ packages.each do |_name, pkg|
316
+ to_clean << pkg unless latest.include?(pkg)
317
+ end
318
+ if block_given?
319
+ to_clean = yield to_clean
320
+ end
321
+ unless dry_run
322
+ to_clean.each do |f|
323
+ p=f.path
324
+ p.rm if p.exist?
325
+ sig=Pathname.new(p.to_s+".sig")
326
+ sig.rm if sig.exist?
327
+ end
328
+ end
329
+ return to_clean.map {|pkg| pkg.path}, to_clean
330
+ end
331
+
332
+ def self.from_dir(dir, config: Archlinux.config)
333
+ dir=Pathname.new(dir)
334
+ list=dir.glob('*.pkg.*').map do |g|
335
+ next if g.to_s.end_with?('~') or g.to_s.end_with?('.sig')
336
+ f=dir+g
337
+ next unless f.readable?
338
+ f
339
+ end.compact
340
+ self.new(*list, config: config)
341
+ end
342
+ end
343
+ end
@@ -1,4 +1,4 @@
1
1
  module Archlinux
2
2
  # aur.rb version
3
- VERSION = "0.1.0"
3
+ VERSION = "0.2.0"
4
4
  end
@@ -0,0 +1,186 @@
1
+ require 'aur/helpers'
2
+
3
+ module Archlinux
4
+ class Version
5
+ def self.create(v)
6
+ v.is_a?(self) ? v : self.new(v)
7
+ end
8
+
9
+ include Comparable
10
+ attr_reader :epoch, :version, :pkgrel
11
+ def initialize(*v)
12
+ if v.length==1
13
+ @v=v.first
14
+ parse(@v)
15
+ elsif v.empty?
16
+ @epoch=-1 #any version is better than empty version
17
+ @version=Gem::Version.new(0)
18
+ @pkgrel=nil
19
+ else
20
+ @v=v
21
+ epoch, version, pkgrel=v
22
+ @epoch=epoch || 0
23
+ @real_epoch= epoch ? true : false
24
+ @real_version=version
25
+ @version=set_version(version)
26
+ @pkgrel=pkgrel
27
+ end
28
+ end
29
+
30
+ # Gem::Version is super pickly :-(
31
+ def set_version(version)
32
+ version=version.tr('+_','.')
33
+ @version=Gem::Version.new(version) rescue Gem::Version.new("0.#{version}")
34
+ end
35
+
36
+ private def parse(v)
37
+ if v.nil? or v.empty?
38
+ @epoch=-1 #any version is better than empty version
39
+ @version=Gem::Version.new(0)
40
+ @pkgrel=nil
41
+ return
42
+ end
43
+ epoch, rest = v.split(':', 2)
44
+ if rest.nil?
45
+ rest=epoch; epoch=0
46
+ @real_epoch=false
47
+ else
48
+ @real_epoch=true
49
+ end
50
+ @epoch=epoch
51
+ version, pkgrel=Utils.rsplit(rest, '-', 2)
52
+ set_version(version)
53
+ @real_version=version
54
+ @pkgrel=pkgrel.to_i
55
+ end
56
+
57
+ def <=>(w)
58
+ w=self.class.new(w.to_s)
59
+ r= @epoch <=> w.epoch
60
+ if r==0
61
+ r= @version <=> w.version
62
+ if r==0 and @pkgrel and w.pkgrel
63
+ r= @pkgrel <=> w.pkgrel
64
+ end
65
+ end
66
+ r
67
+ end
68
+
69
+ def to_s
70
+ # @v.to_s
71
+ r=""
72
+ r << "#{@epoch}:" if @real_epoch
73
+ r << "#{@real_version}"
74
+ r << "-#{@pkgrel}" if @pkgrel
75
+ r
76
+ end
77
+
78
+ # strip version information from a package name
79
+ def self.strip(v)
80
+ v.sub(/[><=]+[\w.\-+:]*$/,'')
81
+ end
82
+ end
83
+
84
+ # ex: pacman>=2.0.0
85
+ QueryError=Class.new(ArchlinuxError)
86
+ class Query
87
+ def self.create(v)
88
+ v.is_a?(self) ? v : self.new(v)
89
+ end
90
+
91
+ def self.strip(query)
92
+ self.create(query).name
93
+ end
94
+
95
+ include Comparable
96
+ attr_accessor :name, :op, :version, :op2, :version2
97
+ def initialize(query)
98
+ @query=query
99
+ @name, @op, version, @op2, version2=parse(query)
100
+ @version=Version.new(version)
101
+ @version2=Version.new(version2)
102
+ end
103
+
104
+ def to_s
105
+ @query
106
+ end
107
+
108
+ def max
109
+ strict=false
110
+ if @op=='<=' or @op=='='
111
+ max=@version
112
+ elsif @op=="<"
113
+ max=@version; strict=true
114
+ elsif @op2=="<="
115
+ max=@version2
116
+ elsif @op2=="<"
117
+ max=@version2; strict=true
118
+ end
119
+ return max, strict #nil means Float::INFINITY, ie no restriction
120
+ end
121
+
122
+ def min
123
+ strict=false
124
+ if @op=='>=' or @op=='='
125
+ min=@version
126
+ elsif @op==">"
127
+ min=@version; strict=true
128
+ elsif @op2==">="
129
+ min=@version2
130
+ elsif @op2==">"
131
+ min=@version2; strict=true
132
+ end
133
+ return min, strict
134
+ end
135
+
136
+ def <=>(other)
137
+ other=self.class.create(other)
138
+ min, strict=self.min
139
+ omin, ostrict=other.min
140
+ return 1 if min==omin and strict
141
+ return -1 if min==omin and ostrict
142
+ min <=> omin
143
+ end
144
+
145
+ # here we check if a package can satisfy a query
146
+ # note that other can itself be a query, think of a package that
147
+ # requires foo>=2.0 and bar which provides foo>=3
148
+ # satisfy? is symmetric, it means that the intersection of the
149
+ # available version ranges is non empty
150
+ def satisfy?(other)
151
+ case other
152
+ when Version
153
+ omin=other; omax=other; ominstrict=false; omaxstrict=false
154
+ oname=@name #we assume the name comparison was already done
155
+ else
156
+ other=self.class.create(other)
157
+ omax, omaxstrict=other.max
158
+ omin, ominstrict=other.min
159
+ oname=other.name
160
+ end
161
+ return false unless @name==oname
162
+ min, strict=self.min
163
+ return false if omax and min and (omax < min or omax == min && (strict or omaxstrict))
164
+ max, strict=self.max
165
+ return false if max and omin and (omin > max or omin == max && (strict or ominstrict))
166
+ true
167
+ end
168
+
169
+ private def parse(query)
170
+ if (m=query.match(/^([^><=]*)([><=]*)([\w.\-+:]*)([><=]*)([\w.\-+:]*)$/))
171
+ name=m[1]
172
+ op=m[2]
173
+ version=m[3]
174
+ op2=m[4]
175
+ version2=m[5]
176
+ if op.nil?
177
+ name, version=Utils.rsplit(name, '-', 2)
178
+ op="="; op2=nil; version2=nil
179
+ end
180
+ return name, op, version, op2, version2
181
+ else
182
+ raise QueryError.new("Bad query #{query}")
183
+ end
184
+ end
185
+ end
186
+ end