aur.rb 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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