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.
- checksums.yaml +4 -4
- data/ChangeLog.md +136 -2
- data/LICENSE.txt +1 -1
- data/README.md +21 -1
- data/Rakefile +7 -10
- data/TODO.md +69 -0
- data/aur.rb.gemspec +1 -0
- data/bin/aur.rb +6 -0
- data/lib/aur.rb +43 -1853
- data/lib/aur/aur_rpc.rb +178 -0
- data/lib/aur/cli.rb +334 -0
- data/lib/aur/config.rb +330 -0
- data/lib/aur/db.rb +328 -0
- data/lib/aur/devtools.rb +258 -0
- data/lib/aur/helpers.rb +56 -0
- data/lib/aur/install_packages.rb +158 -0
- data/lib/aur/load_config.rb +5 -0
- data/lib/aur/makepkg.rb +445 -0
- data/lib/aur/no_load_config.rb +5 -0
- data/lib/aur/packages.rb +599 -0
- data/lib/aur/repos.rb +343 -0
- data/lib/aur/version.rb +1 -1
- data/lib/aur/versions.rb +186 -0
- metadata +22 -5
data/lib/aur/devtools.rb
ADDED
@@ -0,0 +1,258 @@
|
|
1
|
+
require 'aur/config'
|
2
|
+
|
3
|
+
module Archlinux
|
4
|
+
class PacmanConf
|
5
|
+
def self.create(v, config: Archlinux.config)
|
6
|
+
v.is_a?(self) ? v : self.new(v, config: config) #pass empty keywords so that a Hash is seen as an argument and not a list of keywords
|
7
|
+
end
|
8
|
+
|
9
|
+
Archlinux.delegate_h(self, :@pacman_conf)
|
10
|
+
attr_accessor :pacman_conf, :config
|
11
|
+
|
12
|
+
def initialize(conf="/etc/pacman.conf", config: Archlinux.config, **keys)
|
13
|
+
@config=config
|
14
|
+
if conf.is_a?(String) or conf.is_a?(Pathname)
|
15
|
+
conf=parse(conf, **keys)
|
16
|
+
end
|
17
|
+
@pacman_conf=conf
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.parse(content)
|
21
|
+
list=%i(HoldPkg IgnorePkg IgnoreGroup NoUpgrade NoExtract SigLevel LocalFileSigLevel RemoteFileSigLevel Usage Server)
|
22
|
+
mode=:options
|
23
|
+
config={options: {}, repos: {}}
|
24
|
+
content=content.each_line if content.is_a?(String)
|
25
|
+
content.each do |l|
|
26
|
+
if (m=l.match(/^\[([\w-]+)\]$/))
|
27
|
+
mode=m[1]
|
28
|
+
if mode == "options"
|
29
|
+
mode=:options
|
30
|
+
else
|
31
|
+
config[:repos][mode]||={}
|
32
|
+
end
|
33
|
+
else
|
34
|
+
key, value=l.split(' = ', 2)
|
35
|
+
key=key.to_sym
|
36
|
+
h = mode==:options ? config[:options] : config[:repos][mode]
|
37
|
+
if list.include?(key)
|
38
|
+
h[key]||=[]
|
39
|
+
h[key] << value
|
40
|
+
else
|
41
|
+
h[key]=value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
config
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse(file, raw: false, args: nil)
|
49
|
+
unless args
|
50
|
+
if raw
|
51
|
+
args=[:'pacconf', "--raw", "--config=#{file}"]
|
52
|
+
else
|
53
|
+
args=[:'pacman-conf', "--config=#{file}"]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
output=@config.launch(*args) do |*args|
|
57
|
+
SH.run_simple(*args, chomp: :lines)
|
58
|
+
end
|
59
|
+
self.class.parse(output)
|
60
|
+
end
|
61
|
+
|
62
|
+
def non_official_repos
|
63
|
+
repos=@pacman_conf[:repos]
|
64
|
+
repos.slice(*(repos.keys - %w(core extra community multilib testing community-testing multilib-testing)))
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
r=[]
|
69
|
+
print_values=lambda do |h, section|
|
70
|
+
r<<"[#{section}]" if section
|
71
|
+
h.each do |k,v|
|
72
|
+
case v
|
73
|
+
when nil
|
74
|
+
r << k
|
75
|
+
when Array
|
76
|
+
v.each { |vv| r << "#{k} = #{vv}" }
|
77
|
+
else
|
78
|
+
r << "#{k} = #{v}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
print_values.call(@pacman_conf[:options], "options")
|
83
|
+
@pacman_conf[:repos].each do |section, props|
|
84
|
+
print_values.call(props, section)
|
85
|
+
end
|
86
|
+
r.join("\n")+"\n"
|
87
|
+
end
|
88
|
+
|
89
|
+
def tempfile
|
90
|
+
SH::VirtualFile.new("pacman.conf", to_s)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Devtools
|
95
|
+
def self.create(v)
|
96
|
+
v.is_a?(self) ? v : self.new(v)
|
97
|
+
end
|
98
|
+
Archlinux.delegate_h(self, :@opts)
|
99
|
+
|
100
|
+
attr_accessor :config, :opts
|
101
|
+
def initialize(config: Archlinux.config, **opts)
|
102
|
+
@config=config
|
103
|
+
@opts=@config.opts.merge(opts)
|
104
|
+
# %i(pacman_conf makepkg_conf).each do |key|
|
105
|
+
# @opts[key]=@opts[key].tempfile.file if @opts[key].respond_to?(:tempfile)
|
106
|
+
# end
|
107
|
+
root=@opts.dig(:chroot, :root) and @opts[:chroot][:root]=Pathname.new(root)
|
108
|
+
add_binds
|
109
|
+
end
|
110
|
+
|
111
|
+
def add_binds
|
112
|
+
require 'uri'
|
113
|
+
if (conf=@opts[:pacman_conf]).is_a?(PacmanConf)
|
114
|
+
conf[:repos].each do |_name, opts|
|
115
|
+
opts[:Server].each do |server|
|
116
|
+
server.match(%r!file://(.*)!) do |m|
|
117
|
+
@opts[:bind_ro]||=[]
|
118
|
+
@opts[:bind_ro] << URI.decode_www_form_component(m[1])
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def files
|
126
|
+
%i(pacman_conf makepkg_conf).each do |key|
|
127
|
+
if @opts[key]
|
128
|
+
file=@opts[key]
|
129
|
+
file=file.tempfile.file if file.respond_to?(:tempfile)
|
130
|
+
yield key, file
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def pacman_config
|
136
|
+
Pacman.create(@opts[:pacman_conf])
|
137
|
+
end
|
138
|
+
|
139
|
+
def pacman(*args, default_opts: [], **opts, &b)
|
140
|
+
files do |key, file|
|
141
|
+
default_opts += ["--config", file] if key==:pacman_conf
|
142
|
+
end
|
143
|
+
opts[:method]||=:sh
|
144
|
+
@config.launch(:pacman, *args, default_opts: default_opts, **opts, &b)
|
145
|
+
end
|
146
|
+
|
147
|
+
def makepkg(*args, run: :sh, default_opts: [], **opts, &b)
|
148
|
+
files do |key, file|
|
149
|
+
# trick to pass options to pacman
|
150
|
+
args << "PACMAN_OPTS+=--config=#{file.shellescape}"
|
151
|
+
default_opts += ["--config", file] if key==:makepkg_conf
|
152
|
+
end
|
153
|
+
opts[:method]||=run
|
154
|
+
@config.launch(:makepkg, *args, default_opts: default_opts, **opts, &b)
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
def nspawn(*args, root: @opts.dig(:chroot,:root)+'root', default_opts: [], **opts, &b)
|
159
|
+
files do |key, file|
|
160
|
+
default_opts += ["-C", file] if key==:pacman_conf
|
161
|
+
default_opts += ["-M", file] if key==:makepkg_conf
|
162
|
+
end
|
163
|
+
if (binds_ro=@opts[:bind_ro])
|
164
|
+
binds_ro.each do |b|
|
165
|
+
args.unshift("--bind-ro=#{b}")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
if (binds_rw=@opts[:bind_rw])
|
169
|
+
binds_rw.each do |b|
|
170
|
+
args.unshift("--bind=#{b}")
|
171
|
+
end
|
172
|
+
end
|
173
|
+
args.unshift root
|
174
|
+
|
175
|
+
opts[:method]||=:sh
|
176
|
+
@config.launch(:'arch-nspawn', *args, default_opts: default_opts, **opts, &b)
|
177
|
+
end
|
178
|
+
|
179
|
+
# this takes the same options as nspawn
|
180
|
+
# => this creates or update a chroot
|
181
|
+
def mkarchroot(*args, nspawn: @opts.dig(:chroot, :update), default_opts: [], root: @opts.dig(:chroot, :root), **opts, &b)
|
182
|
+
files do |key, file|
|
183
|
+
default_opts += ["-C", file] if key==:pacman_conf
|
184
|
+
default_opts += ["-M", file] if key==:makepkg_conf
|
185
|
+
end
|
186
|
+
root.sudo_mkpath unless root.directory?
|
187
|
+
root=root+'root'
|
188
|
+
opts[:method]||=:sh
|
189
|
+
if (root+'.arch-chroot').file?
|
190
|
+
# Note that if nspawn is not called (and the chroot does not
|
191
|
+
# exist), then the passed pacman.conf will not be replace the one
|
192
|
+
# in the chroot. And when makechrootpkg calls nspawn, it does not
|
193
|
+
# transmit the -C/-M options. So even if we don't want to update,
|
194
|
+
# we should call a dummy bin like 'true'
|
195
|
+
if nspawn
|
196
|
+
return nspawn.call(root) if nspawn.is_a?(Proc)
|
197
|
+
nspawn=nspawn.shellsplit if nspawn.is_a?(String)
|
198
|
+
self.nspawn(*nspawn, root: root, **opts, &b)
|
199
|
+
end
|
200
|
+
else
|
201
|
+
@config.launch(:mkarchroot, root, *args, default_opts: default_opts, sudo: @config.sudo, **opts,&b)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def makechrootpkg(*args, default_opts: [], **opts, &b)
|
206
|
+
default_opts+=['-r', @opts.dig(:chroot, :root)]
|
207
|
+
if (binds_ro=@opts[:bind_ro])
|
208
|
+
binds_ro.each do |b|
|
209
|
+
default_opts += ["-D", b]
|
210
|
+
end
|
211
|
+
end
|
212
|
+
if (binds_rw=@opts[:bind_rw])
|
213
|
+
binds_rw.each do |b|
|
214
|
+
default_opts += ["-d", b]
|
215
|
+
end
|
216
|
+
end
|
217
|
+
opts[:method]||=:sh
|
218
|
+
|
219
|
+
#makechrootpkg calls itself with sudo --preserve-env=SOURCE_DATE_EPOCH,GNUPGHOME so it does not keep PKGDEST..., work around this by providing our own sudo
|
220
|
+
#@config.launch(:makechrootpkg, *args, default_opts: default_opts, sudo: @config.sudo('sudo --preserve-env=GNUPGHOME,PKGDEST,SOURCE_DATE_EPOCH'), **opts, &b)
|
221
|
+
## Update: this has been fixed in devtools-20190329
|
222
|
+
@config.launch(:makechrootpkg, *args, default_opts: default_opts, **opts, &b)
|
223
|
+
end
|
224
|
+
|
225
|
+
def tmp_pacman(conf, **opts)
|
226
|
+
PacmanConf.create(conf).tempfile.create(true) do |file|
|
227
|
+
pacman=lambda do |*args, **pac_opts, &b|
|
228
|
+
pac_opts[:method]||=:sh
|
229
|
+
@config.launch(:pacman, *args, default_opts: ["--config", file], **opts.merge(pac_opts), &b)
|
230
|
+
end
|
231
|
+
yield pacman, file
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def sync_db(*names, install: [], **pacman_opts)
|
236
|
+
conf=PacmanConf.create(@opts[:pacman_conf])
|
237
|
+
new_conf={options: conf[:options], repos: {}}
|
238
|
+
repos=conf[:repos]
|
239
|
+
names.each do |name|
|
240
|
+
if repos[name]
|
241
|
+
new_conf[:repos][name]=repos[name]
|
242
|
+
else
|
243
|
+
SH.logger.warn "sync_db: unknown repo #{name}"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
tmp_pacman(new_conf) do |pacman, file|
|
247
|
+
if block_given?
|
248
|
+
return yield(pacman, file)
|
249
|
+
else
|
250
|
+
args=['-Syu']
|
251
|
+
args+=install
|
252
|
+
return pacman[*args, sudo: @config.sudo, **pacman_opts]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|
258
|
+
end
|
data/lib/aur/helpers.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'dr/base/utils'
|
3
|
+
require 'dr/base/uri' # for DR::URIEscape.escape
|
4
|
+
require 'shell_helpers'
|
5
|
+
require 'dr/ruby_ext/core_ext'
|
6
|
+
|
7
|
+
module Archlinux
|
8
|
+
ArchlinuxError=Class.new(StandardError)
|
9
|
+
Utils=::DR::Utils
|
10
|
+
Pathname=::SH::Pathname
|
11
|
+
|
12
|
+
def self.delegate_h(klass, var)
|
13
|
+
# put in a Module so that they are easier to distinguish from the
|
14
|
+
# 'real' functions
|
15
|
+
m=Module.new do
|
16
|
+
extend(Forwardable)
|
17
|
+
methods=[:[], :[]=, :any?, :assoc, :clear, :compact, :compact!, :delete, :delete_if, :dig, :each, :each_key, :each_pair, :each_value, :empty?, :fetch, :fetch_values, :has_key?, :has_value?, :include?, :index, :invert, :keep_if, :key, :key?, :keys, :length, :member?, :merge, :merge!, :rassoc, :reject, :reject!, :select, :select!, :shift, :size, :slice, :store, :to_a, :to_h, :to_s, :transform_keys, :transform_keys!, :transform_values, :transform_values!, :update, :value?, :values, :values_at]
|
18
|
+
include(Enumerable)
|
19
|
+
def_delegators var, *methods
|
20
|
+
end
|
21
|
+
klass.include(m)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.add_to_hash(h, key, value)
|
25
|
+
case h[key]
|
26
|
+
when nil
|
27
|
+
h[key] = value
|
28
|
+
when Array
|
29
|
+
h[key] << value
|
30
|
+
else
|
31
|
+
h[key]=[h[key], value]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.create_class(klass, *parameters, **kw, &b)
|
36
|
+
klass=Archlinux.const_get(klass) if klass.is_a?(Symbol)
|
37
|
+
if klass.is_a?(Proc)
|
38
|
+
klass.call(*parameters, **kw, &b)
|
39
|
+
else
|
40
|
+
klass.new(*parameters, **kw, &b)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module CreateHelper
|
45
|
+
def create(v, config: Archlinux.config)
|
46
|
+
v.is_a?(self) ? v : self.new(v, config: config)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
## Not used: we modify Config#pretty_print directly
|
51
|
+
# module PPHelper
|
52
|
+
# def pretty_print_instance_variables
|
53
|
+
# instance_variables.reject {|n| n==:@config}.sort
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'aur/packages'
|
2
|
+
require 'aur/makepkg'
|
3
|
+
|
4
|
+
module Archlinux
|
5
|
+
# class that support installation (ie define install_method)
|
6
|
+
class InstallPackageList < PackageList
|
7
|
+
def initialize(*args, **opts)
|
8
|
+
super
|
9
|
+
@install_method=method(:install_method)
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_makepkg_list(l)
|
13
|
+
MakepkgList.new(l.map {|p| Query.strip(p)}, config: @config)
|
14
|
+
end
|
15
|
+
|
16
|
+
def install_method(l, **opts, &b)
|
17
|
+
# if we are used as a source, fall back to the upstream method
|
18
|
+
if @install_list&.respond_to?(:install_method)
|
19
|
+
@install_list.install_method(l, **opts, &b)
|
20
|
+
else
|
21
|
+
m=get_makepkg_list(l)
|
22
|
+
info=opts.delete(:pkgs_info)
|
23
|
+
if info
|
24
|
+
tops=info[:top_pkgs].keys
|
25
|
+
deps=info[:all_pkgs].keys-tops
|
26
|
+
#if we cache the makepkg, we need to update both deps and tops
|
27
|
+
#in case we did a previous install
|
28
|
+
# require 'pry'; binding.pry
|
29
|
+
deps.each { |dep| m[Query.strip(dep)]&.asdeps=true }
|
30
|
+
tops.each { |dep| m[Query.strip(dep)]&.asdeps=false }
|
31
|
+
end
|
32
|
+
m=b.call(m) if b #return false to prevent install
|
33
|
+
m.install(**opts) if m
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# cache aur queries
|
39
|
+
class AurCache < InstallPackageList
|
40
|
+
|
41
|
+
def initialize(*args, **kw)
|
42
|
+
super
|
43
|
+
@ext_query=method(:ext_query)
|
44
|
+
#@query_ignore=AurPackageList.official
|
45
|
+
if @config[:aur_url]==GlobalAurCache.config[:aur_url]
|
46
|
+
@klass=GlobalAurCache
|
47
|
+
else
|
48
|
+
@klass=AurQueryCustom.new(config: @config)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def ext_query(*queries, **_opts)
|
53
|
+
pkgs=queries.map {|p| Query.strip(p)}
|
54
|
+
# in a query like foo>1000, even if foo exist and was queried,
|
55
|
+
# the query fails so it gets called in ext_query
|
56
|
+
# remove these packages
|
57
|
+
# TODO: do the same for a provides query
|
58
|
+
pkgs-=self.names
|
59
|
+
if pkgs.empty?
|
60
|
+
l=self.class.new([])
|
61
|
+
else
|
62
|
+
SH.logger.debug "! #{self.class}: Calling aur for infos on: #{pkgs.join(', ')}"
|
63
|
+
l=@klass.packages(*pkgs)
|
64
|
+
@query_ignore += pkgs - l.names #these don't exist in aur
|
65
|
+
end
|
66
|
+
r=l.resolve(*queries, ext_query: false, fallback: false)
|
67
|
+
return r, l
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# cache MakepkgList and download PKGBUILD dynamically
|
72
|
+
class MakepkgCache < InstallPackageList
|
73
|
+
attr_accessor :select_existing, :get_mode, :makepkg_list
|
74
|
+
def initialize(*args, get_mode: {}, **opts)
|
75
|
+
super(*args, **opts)
|
76
|
+
# puts "MakepkgCache called with options: #{[args, get_mode]}"
|
77
|
+
@select_existing=get_mode.delete(:existing)
|
78
|
+
@get_mode=get_mode
|
79
|
+
@ext_query=method(:ext_query)
|
80
|
+
@makepkg_list=MakepkgList.new([], config: @config)
|
81
|
+
#@query_ignore=AurPackageList.official
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_makepkg_list(l)
|
85
|
+
pkgs=l.map {|p| Query.strip(p)}
|
86
|
+
# use the cache
|
87
|
+
m=MakepkgList.new(pkgs.map {|pkg| @makepkg_list.key?(pkg) ? @makepkg_list[pkg] : pkg}, config: @config)
|
88
|
+
@makepkg_list.merge(m.l.values)
|
89
|
+
m
|
90
|
+
end
|
91
|
+
|
92
|
+
def ext_query(*queries, **opts)
|
93
|
+
m=get_makepkg_list(queries)
|
94
|
+
m.keep_if {|_k,v| v.exist?} if @select_existing
|
95
|
+
m.packages(get: @get_mode).as_ext_query(*queries, full_pkgs: true, **opts)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# combine Aur and Makepkg caches
|
100
|
+
class AurMakepkgCache < InstallPackageList
|
101
|
+
attr_accessor :aur_cache, :makepkg_cache
|
102
|
+
def initialize(*args, **opts)
|
103
|
+
super
|
104
|
+
@aur_cache = AurCache.new(**opts)
|
105
|
+
@makepkg_cache = MakepkgCache.new(get_mode: {update: true, clone: true, pkgver: true, view: true}, **opts)
|
106
|
+
@ext_query=method(:ext_query)
|
107
|
+
#@query_ignore=AurPackageList.official
|
108
|
+
end
|
109
|
+
|
110
|
+
def ext_query(*queries, **opts)
|
111
|
+
devel=queries.select do |query|
|
112
|
+
Query.strip(query)=~/(-git|-hg|-svn)$/
|
113
|
+
end
|
114
|
+
if @install_list_of
|
115
|
+
# we only want to check the pkgver of packages we already have; for
|
116
|
+
# the others the aur version is enough
|
117
|
+
devel=devel & @install_list_of.names
|
118
|
+
end
|
119
|
+
aur=queries-devel
|
120
|
+
r1, l1=@makepkg_cache.as_ext_query(*devel, **opts)
|
121
|
+
missing=devel-r1.keys
|
122
|
+
r2, l2=@aur_cache.as_ext_query(*(missing+aur), **opts)
|
123
|
+
return r1.merge(r2), l1.merge(l2)
|
124
|
+
end
|
125
|
+
|
126
|
+
def get_makepkg_list(l)
|
127
|
+
got=l.select {|pkg| @makepkg_cache.key?(pkg)}
|
128
|
+
got_m=@makepkg_cache.get_makepkg_list(got)
|
129
|
+
rest=@aur_cache.get_makepkg_list(l-got)
|
130
|
+
MakepkgList.new(l.map do |name|
|
131
|
+
strip=Query.strip(name)
|
132
|
+
got_m.key?(strip) ? got_m[strip] : rest[strip]
|
133
|
+
end, config: @config)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class AurPackageList < InstallPackageList
|
138
|
+
def self.official
|
139
|
+
@official||=%w(core extra community).map {|repo| Repo.new(repo).list(mode: :pacman)}.flatten.compact
|
140
|
+
end
|
141
|
+
|
142
|
+
def initialize(*args, **opts)
|
143
|
+
super
|
144
|
+
# @install_list=self.class.cache
|
145
|
+
@install_list=@config.install_list #AurMakepkgCache.new(**opts)
|
146
|
+
# TODO this won't work if we use several PackageList with the same
|
147
|
+
# cache at the same time
|
148
|
+
@install_list.install_list_of=self
|
149
|
+
@children_mode=%i(depends make_depends check_depends)
|
150
|
+
@install_method=method(:install_method)
|
151
|
+
@query_ignore=official
|
152
|
+
end
|
153
|
+
|
154
|
+
def official
|
155
|
+
self.class.official
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|