apti 0.6

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.
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # Description
2
+
3
+ Apti is a frontend of aptitude (Debian's package manager) with improved presentation of packages.
4
+
5
+ It uses the same commands as aptitude, and allow you to use it without superuser rights (the sudo or root password is asked if needed).
6
+
7
+ The improved commands are: `install`, `remove`, `purge`, `safe-upgrade`, `full-upgrade` and `search`.
8
+
9
+ # Installation
10
+
11
+ Dependencies:
12
+
13
+ * aptitude (of course)
14
+ * ruby >= 1.9
15
+ * ruby-i18n
16
+
17
+ Put apti in /usr/local/, and create a link to main.rb: `ln -s /usr/local/apti/main.rb /usr/local/bin/apti`
18
+
19
+ Or with rubygems `gem build apti.gemspec` and then `gem install apti-0.5.1.gem`.
20
+
21
+ # Configuration
22
+
23
+ Configuration file is in ~/.config/apti (by default).
24
+
25
+ * colors:
26
+ * available colors are: BLACK, RED, GREEN, ORANGE, BLUE, MAGENTA, CYAN and WHITE.
27
+ * available effects are: NORMAL, BOLD, UNDERLINE, BLINK and HIGHLIGHT.
28
+ * display_size: displaying size of packages or not (default true).
29
+ * spaces:
30
+ * columns: between columns in `install`, `remove`, `upgrade`.
31
+ * unit: juste before size's unit.
32
+ * search: between package name and description.
33
+ * no_confirm: if true, don't ask for the aptitude's confirmation.
34
+
35
+ # Screenshots
36
+
37
+ ![command apti install](http://gnux.legtux.org/src/images/scripts/apti_install.png "command apti install")
38
+
39
+ ![command apti safe-upgrade](http://gnux.legtux.org/src/images/scripts/apti_safe_upgrade.png "command apti safe-upgrade")
40
+
41
+ ![command apti search](http://gnux.legtux.org/src/images/scripts/apti_search.png "command apti search")
42
+
data/TODO.md ADDED
@@ -0,0 +1,7 @@
1
+ # bugs
2
+
3
+ # next versions
4
+
5
+ ## next
6
+
7
+ * respect this style of coding: [ruby-style-guide](https://github.com/bbatsov/ruby-style-guide)
data/bin/apti ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require_relative '../src/apti/Apti'
5
+
6
+ # Enable warnings.
7
+ $VERBOSE = true
8
+
9
+ apti = Apti::Apti.new
10
+
11
+ if ARGV[0].nil?
12
+ apti.help
13
+ exit(1)
14
+ end
15
+
16
+ packages = ARGV[1..(ARGV.length - 1)].join(' ')
17
+
18
+ if ['search', 'remove', 'purge', 'install'].include?(ARGV[0]) && packages.empty?
19
+ apti.help
20
+ exit(1)
21
+ end
22
+
23
+ case ARGV[0]
24
+
25
+ when '--help'
26
+ apti.help
27
+ when '-h'
28
+ apti.help
29
+
30
+ when '--version'
31
+ apti.version
32
+
33
+ when 'safe-upgrade'
34
+ apti.upgrade(packages)
35
+
36
+ when 'upgrade'
37
+ puts I18n.t(:'warning.upgrade')
38
+ apti.upgrade(packages)
39
+
40
+ when 'full-upgrade'
41
+ apti.upgrade(packages, true)
42
+
43
+ when 'search'
44
+ apti.search(packages)
45
+
46
+ when 'update'
47
+ apti.execute_command('aptitude update')
48
+
49
+ when 'remove'
50
+ apti.remove(packages)
51
+
52
+ when 'purge'
53
+ apti.remove(packages, true)
54
+
55
+ when 'install'
56
+ apti.install(packages)
57
+
58
+ when 'stats'
59
+ apti.stats
60
+
61
+ # other aptitude command
62
+ else
63
+ if ARGV[0].eql?(nil)
64
+ help
65
+
66
+ else
67
+ apti.execute_command "aptitude #{ARGV[0]} #{packages}"
68
+ end
69
+
70
+ end
data/changelog.xml ADDED
@@ -0,0 +1,53 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <?xml-stylesheet type="text/xsl" href="changelog.xslt"?>
3
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
4
+ <channel>
5
+ <title>Apti's update log</title>
6
+ <link>https://gitorious.org/apti/apti</link>
7
+ <description>Log about all "stable" updates on Apti</description>
8
+
9
+ <atom:link href="https://gitorious.org/apti/apti/source/master:changelog.xml" rel="self" type="application/rss+xml" />
10
+
11
+ <lastBuildDate>Sun Mar 16 14:30:00 2014 +0100</lastBuildDate>
12
+
13
+ <item>
14
+ <title>Version 0.6</title>
15
+ <link>https://gitorious.org/apti/apti/source/v0.6:</link>
16
+ <guid isPermaLink="false">v0_6</guid>
17
+ <description><![CDATA[
18
+ <ul>
19
+ <li>Separate new revisions and new versions in upgrade.</li>
20
+ </ul>
21
+ ]]></description>
22
+ <pubDate>Sun Mar 16 14:30:00 2014 +0100</pubDate>
23
+ </item>
24
+
25
+ <item>
26
+ <title>Version 0.5.1</title>
27
+ <link>https://gitorious.org/apti/apti/source/v0.5.1:</link>
28
+ <guid isPermaLink="false">v0_5_1</guid>
29
+ <description><![CDATA[
30
+ <ul>
31
+ <li>Fix bug when try to create the configuration directory of Apti ;</li>
32
+ <li>Fix problem in search when description is too long.</li>
33
+ </ul>
34
+ ]]></description>
35
+ <pubDate>Tue Jan 28 13:32:47 2014 +0100</pubDate>
36
+ </item>
37
+
38
+ <item>
39
+ <title>Version 0.5</title>
40
+ <link>https://gitorious.org/apti/apti/source/v0.5:</link>
41
+ <guid isPermaLink="false">v0_5</guid>
42
+ <description><![CDATA[
43
+ <ul>
44
+ <li>Add internationalisation (English, French) ;</li>
45
+ <li>Better separations of packages (explicitly installed, dependencies, ...) ;</li>
46
+ <li>New configuration file (in yaml) with better personalisation (color, background, effect) ;</li>
47
+ <li>Some bug fixes.</li>
48
+ </ul>
49
+ ]]></description>
50
+ <pubDate>Wed Jan 22 11:00:12 2014 +0100</pubDate>
51
+ </item>
52
+ </channel>
53
+ </rss>
@@ -0,0 +1,83 @@
1
+ # Generic configuration file for Apti
2
+ # Last modification : mar 13 2014
3
+
4
+ ---
5
+ # Colors configuration
6
+ colors:
7
+ # Used when new package to install, Green / Bold if not present
8
+ install:
9
+ text: GREEN # Color of text
10
+ background: # Color of background
11
+ effect: BOLD # Some effect on text
12
+
13
+ # Used for upgrades
14
+ upgrade:
15
+ # Upgrades with revisions
16
+ revision:
17
+ # Color for common part of version, Orange / Bold if not present
18
+ static:
19
+ text: ORANGE
20
+ background:
21
+ effect: BOLD
22
+
23
+ # Color for old revision, Red / Bold if not present
24
+ old:
25
+ text: RED
26
+ background:
27
+ effect: BOLD
28
+
29
+ # Color for new revision, Green / Bold if not present
30
+ new:
31
+ text: GREEN
32
+ background:
33
+ effect: BOLD
34
+
35
+ # Upgrades with completely new version (no revision)
36
+ version:
37
+ # Color for old version, Red / Bold if not present
38
+ old:
39
+ text: RED
40
+ background:
41
+ effect: BOLD
42
+
43
+ # Color for new version, Green / Bold if not present
44
+ new:
45
+ text: GREEN
46
+ background:
47
+ effect: BOLD
48
+
49
+ # Color for removing / purging packages, Red / Bold if not present
50
+ remove:
51
+ text: RED
52
+ background:
53
+ effect: BOLD
54
+
55
+ # Color for descriptions (in search)
56
+ description:
57
+ text: BLACK
58
+ background:
59
+ effect: BOLD
60
+
61
+ # Color for packages size
62
+ size:
63
+ text: BLACK
64
+ background:
65
+ effect: BOLD
66
+
67
+ # Color for text, like "Upgrading, new revision:"
68
+ text:
69
+ text: WHITE
70
+ background:
71
+ effect: BOLD
72
+
73
+ # Apti must display size information?
74
+ display_size: true
75
+
76
+ # Spaces size
77
+ spaces:
78
+ columns: 3 # Spaces between two columns
79
+ unit: 1 # Spaces before size unit
80
+ search: 40 # Spaces in search alignment
81
+
82
+ # Must Apti ask to confirm operation (besides "aptitude" question)?
83
+ no_confirm: false
data/locales/en.yml ADDED
@@ -0,0 +1,44 @@
1
+ en:
2
+ system_is_up_to_date: "System is up to date."
3
+ package_not_installed: "Package(s) not installed."
4
+ installing_for_dependencies: "Installing for dependencies:"
5
+ removing_unused_dependencies: "Removing unused dependencies:"
6
+ using: "Using:"
7
+
8
+ operation:
9
+ installing: "Installing:"
10
+ removing: "Removing:"
11
+ purging: "Purging:"
12
+ upgrading: "Upgrading,"
13
+ new_revisions: "new revisions:"
14
+ new_versions: "new versions:"
15
+ nothing_to_do: "Nothing to do."
16
+ question:
17
+ installation: "Continue the installation?"
18
+ remove: "Remove these package?"
19
+ purge: "Purge these packages?"
20
+ upgrade: "Continue the upgrade?"
21
+
22
+ error:
23
+ package:
24
+ not_found: "Couldn't find package(s): %{packages}"
25
+ installed: "Packages(s) already installed."
26
+ not_installed: "Packages(s) not installed."
27
+
28
+ header:
29
+ package: "Package"
30
+ version: "Version"
31
+ size: "Size"
32
+
33
+ stat:
34
+ total_installed: "Total installed packages: %{number}"
35
+ explicitly_installed : "Explicitly installed packages: %{number}"
36
+ space_used_in_cache: "Space used by packages in cache: %{size}"
37
+
38
+ warning:
39
+ upgrade: "Warning: upgrade is deprecated, use safe-upgrade."
40
+
41
+ number:
42
+ separator:
43
+ thousands: ","
44
+ decimal: "."
data/locales/fr.yml ADDED
@@ -0,0 +1,44 @@
1
+ fr:
2
+ system_is_up_to_date: "Le système est à jour."
3
+ package_not_installed: "Paquet(s) non installé(s)."
4
+ installing_for_dependencies: "Installation pour dépendances :"
5
+ removing_unused_dependencies: "Suppression des dépendances non utilisées :"
6
+ using: "Utilisant :"
7
+
8
+ operation:
9
+ installing: "Installation :"
10
+ removing: "Suppression :"
11
+ purging: "Purge :"
12
+ upgrading: "Mise à jour,"
13
+ new_revisions: "nouvelles révisions :"
14
+ new_versions: "nouvelles versions :"
15
+ nothing_to_do: "Rien à faire."
16
+ question:
17
+ installation: "Continuer l'installation ?"
18
+ remove: "Supprimer ces paquets ?"
19
+ purge: "Purger ces paquets ?"
20
+ upgrade: "Continuer la mise à jour ?"
21
+
22
+ error:
23
+ package:
24
+ not_found: "Impossible de trouver le(s) paquet(s) : %{packages}"
25
+ installed: "Paquet(s) déjà installé(s)."
26
+ not_installed: "Paquet(s) non installé(s)."
27
+
28
+ header:
29
+ package: "Paquet"
30
+ version: "Version"
31
+ size: "Taille"
32
+
33
+ stat:
34
+ total_installed: "Nombre de paquets installés : %{number}"
35
+ explicitly_installed: "Nombre de paquets installés explicitement : %{number}"
36
+ space_used_in_cache: "Espace utilisé par les paquets dans le cache : %{size}"
37
+
38
+ warning:
39
+ upgrade: "Avertissement: upgrade est déprécié, utilisez safe-upgrade."
40
+
41
+ number:
42
+ separator:
43
+ thousands: " "
44
+ decimal: ","
data/src/apti/Apti.rb ADDED
@@ -0,0 +1,791 @@
1
+ # encoding: utf-8
2
+ #===============================================================================
3
+ #
4
+ # This file is part of Apti.
5
+ #
6
+ # Copyright (C) 2012-2014 by Florent Lévigne <florent.levigne at mailoo dot com>
7
+ # Copyright (C) 2013-2014 by Julien Rosset <jul.rosset at gmail dot com>
8
+ #
9
+ #
10
+ # Apti is free software: you can redistribute it and/or modify
11
+ # it under the terms of the GNU General Public License as published by
12
+ # the Free Software Foundation, either version 3 of the License, or
13
+ # (at your option) any later version.
14
+ #
15
+ # Apti is distributed in the hope that it will be useful,
16
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
+ # GNU General Public License for more details.
19
+ #
20
+ # You should have received a copy of the GNU General Public License
21
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
22
+ #
23
+ #===============================================================================
24
+
25
+ module Apti
26
+
27
+ require 'i18n'
28
+ require_relative 'config/Config'
29
+ require_relative 'version'
30
+
31
+ class Apti
32
+ NEED_SUPERUSER_RIGHTS = [
33
+ 'install', 'remove', 'purge', 'hold', 'unhold', 'keep', 'reinstall',
34
+ 'markauto', 'unmarkauto', 'build-depends', 'build-dep', 'forbid-version',
35
+ 'update', 'safe-upgrade', 'full-upgrade', 'keep-all', 'forget-new',
36
+ 'clean', 'autoclean'
37
+ ]
38
+
39
+ #
40
+ # @!attribute config [r]
41
+ # @return [Apti::Config::Config] Config.
42
+ #
43
+ # @!attribute VERSION [r]
44
+ # @return [String] Apti's version.
45
+ attr_reader :config, :VERSION
46
+
47
+ # Reads the configuration file, and define locale.
48
+ #
49
+ # @return [void]
50
+ def initialize
51
+ @config = Config::Config.new
52
+
53
+ locales_path = File.dirname("#{__FILE__}") + '/../../locales'
54
+ lang = `echo $LANG`.split('_').first
55
+
56
+ if defined? I18n.enforce_available_locales
57
+ I18n.enforce_available_locales = true
58
+ end
59
+
60
+ I18n.load_path = Dir[File.join(locales_path, '*.yml')]
61
+ I18n.default_locale = :en
62
+
63
+ if defined? I18n.locale_available?
64
+ I18n.locale = lang if I18n.locale_available?(lang)
65
+ else
66
+ I18n.locale = lang
67
+ end
68
+ end
69
+
70
+ # Display help.
71
+ #
72
+ # @return [void]
73
+ def help
74
+ puts "usage: #{File.basename $0} commande"
75
+ puts 'Commandes:'
76
+ puts ' update'
77
+ puts ' safe-upgrade'
78
+ puts ' search package'
79
+ puts ' install package'
80
+ puts ' remove package'
81
+ puts ' others aptitude commande...'
82
+ puts ' stats'
83
+ end
84
+
85
+ # Display version.
86
+ #
87
+ # @return [void]
88
+ def version
89
+ puts "apti #{VERSION}"
90
+ puts I18n.t(:using)
91
+ puts " aptitude #{`aptitude --version | head -n 1 | cut -d ' ' -f 2`}"
92
+ puts " ruby #{`ruby --version | cut -d ' ' -f 2`}"
93
+ end
94
+
95
+ # Install packages.
96
+ #
97
+ # @param package [String] List of packages to install.
98
+ #
99
+ # @return [void]
100
+ def install(package)
101
+
102
+ if package.eql? nil
103
+ usage
104
+ end
105
+ # Check if some packages does not exist.
106
+ packages_not_found = get_packages_not_found(package.split)
107
+
108
+ if !packages_not_found.empty?
109
+ puts I18n.t(:'error.package.not_found', packages: packages_not_found.join(' '))
110
+ exit 1
111
+ end
112
+
113
+ # Check if all packages are not already installed.
114
+ packages_already_installed = all_installed?(package.split)
115
+
116
+ if packages_already_installed
117
+ puts I18n.t(:'error.package.installed')
118
+ exit 1
119
+ end
120
+
121
+ aptitude_string = `aptitude install -VZs --allow-untrusted --assume-yes #{package}`
122
+ command = "aptitude install #{package}"
123
+
124
+ # If problem with dependencies: display aptitude's message.
125
+ if aptitude_string.include?('1)')
126
+ puts aptitude_string
127
+ exit 1
128
+ end
129
+
130
+ packages = aptitude_string.split(/ {2}/)
131
+ operation = I18n.t(:'operation.installing')
132
+ question = I18n.t(:'operation.question.installation')
133
+
134
+ if display_packages(packages, operation, 'install', question, aptitude_string.split(/\n/)[-2])
135
+ execute_command(command, true)
136
+ end
137
+ end
138
+
139
+ # Remove / Purge packages.
140
+ #
141
+ # @param package [String] List of packages to remove / purge.
142
+ # @param purge [Boolean] True if purging packages, else removing.
143
+ #
144
+ # @return [void]
145
+ def remove(package, purge = false)
146
+ require_relative 'Package'
147
+
148
+ # Check if some packages does not exist.
149
+ packages_not_found = get_packages_not_found(package.split)
150
+
151
+ if !packages_not_found.empty?
152
+ puts I18n.t(:'error.package.not_found', packages: packages_not_found.join(' '))
153
+ exit 1
154
+ end
155
+
156
+ # Check if all packages are not uninstalled (only for remove).
157
+ if !purge
158
+ packages_not_installed = all_not_installed?(package.split)
159
+
160
+ if packages_not_installed
161
+ puts I18n.t(:'error.package.not_installed')
162
+ exit 1
163
+ end
164
+ end
165
+
166
+ if purge
167
+ aptitude_string = `aptitude purge -VZs --assume-yes #{package}`
168
+ command = "aptitude purge #{package}"
169
+ operation = I18n.t(:'operation.purging')
170
+ question = I18n.t(:'operation.question.purge')
171
+
172
+ else
173
+ aptitude_string = `aptitude remove -VZs --assume-yes #{package}`
174
+ command = "aptitude remove #{package}"
175
+ operation = I18n.t(:'operation.removing')
176
+ question = I18n.t(:'operation.question.remove')
177
+ end
178
+
179
+ # If problem with dependencies, wrong name given,
180
+ # or trying to remove a virtual package : display aptitude's message.
181
+ if aptitude_string.include?('1)') || aptitude_string.include?('«')
182
+ puts aptitude_string
183
+ exit 0
184
+
185
+ # If the package is not installed.
186
+ elsif !aptitude_string.include?(':')
187
+ puts I18n.t(:package_not_installed)
188
+ exit 0
189
+ end
190
+
191
+ # Remove the "p" parameter on packages to purge.
192
+ aptitude_string.sub!(/\{p\}/, '')
193
+
194
+ # Split packages.
195
+ packages = aptitude_string.split(/ {2}/)
196
+
197
+ if display_packages(packages, operation, 'remove', question, aptitude_string.split(/\n/)[-2])
198
+ execute_command(command, true)
199
+ end
200
+ end
201
+
202
+ # Do upgrade (safe-upgrade or full-upgrade).
203
+ #
204
+ # @param packages [String] List of packages to upgrade.
205
+ # @param full_upgrade [Boolean] True if full-upgrade, else safe-upgrade.
206
+ #
207
+ # @return [void]
208
+ def upgrade(packages, full_upgrade = false)
209
+ if full_upgrade
210
+ aptitude_string = `aptitude full-upgrade -VZs --allow-untrusted --assume-yes #{packages}`
211
+ command = "aptitude full-upgrade #{packages}"
212
+
213
+ else
214
+ aptitude_string = `aptitude safe-upgrade -VZs --allow-untrusted --assume-yes #{packages}`
215
+ command = "aptitude safe-upgrade #{packages}"
216
+ end
217
+
218
+ # If problem with dependencies, use aptitude.
219
+ if aptitude_string.include?('1)')
220
+ execute_command(command)
221
+ exit 0
222
+
223
+ # If there is no package to upgrade.
224
+ elsif !aptitude_string.include?(':')
225
+ puts I18n.t(:system_is_up_to_date)
226
+ exit 0
227
+ end
228
+
229
+ # Split packages.
230
+ packages = aptitude_string.split(/ {2}/)
231
+ operation = I18n.t(:'operation.upgrading')
232
+ question = I18n.t(:'operation.question.upgrade')
233
+
234
+ if display_packages(packages, operation, 'upgrade', question, aptitude_string.split(/\n/)[-2])
235
+ execute_command(command, true)
236
+ end
237
+ end
238
+
239
+ # Search packages.
240
+ #
241
+ # @param package_name [String] Package(s) to search.
242
+ #
243
+ # @return [void]
244
+ def search(package_name)
245
+ require_relative 'Package'
246
+
247
+ aptitude_string = `aptitude search --disable-columns #{package_name}`
248
+ terminal_width = `tput cols`.to_i
249
+
250
+ # Information size (i, p, A, ...) : 6 seems to be good.
251
+ package_parameter_length_alignment = 6
252
+
253
+ get_search_packages(aptitude_string).each do |package|
254
+ print package.parameter
255
+
256
+ print ''.rjust(package_parameter_length_alignment - package.parameter.length)
257
+
258
+ # Display package name: if the package is installed, we display it in color.
259
+ if package.parameter.include?('i')
260
+ print "#{@config.colors.install.to_shell_color}#{package.name}#{get_color_for()}"
261
+ else
262
+ print package.name
263
+ end
264
+
265
+ print ''.rjust(@config.spaces.search - package.name.length)
266
+
267
+ size_of_line = package_parameter_length_alignment + @config.spaces.search + package.description.length
268
+
269
+ # If description is too long, we shorten it.
270
+ if size_of_line > terminal_width
271
+ package.description = package.description[0..(terminal_width - package_parameter_length_alignment - @config.spaces.search - 1)]
272
+ end
273
+
274
+ puts "#{@config.colors.description.to_shell_color}#{package.description.chomp}#{get_color_for()}"
275
+ end
276
+ end
277
+
278
+ # Print stats about packages.
279
+ #
280
+ # @return [void]
281
+ def stats
282
+ packages_installed = `dpkg --get-selections | grep -v deinstall | wc -l`
283
+ packages_installed_explicitly = `aptitude search '~i !~M' | wc -l`
284
+ cache_size = `du -sh /var/cache/apt/archives/ | cut -f 1`
285
+
286
+ puts "#{`lsb_release -ds`}\n"
287
+
288
+ puts I18n.t(:'stat.total_installed', number: packages_installed)
289
+ puts I18n.t(:'stat.explicitly_installed', number: packages_installed_explicitly)
290
+ puts I18n.t(:'stat.space_used_in_cache', size: cache_size)
291
+ end
292
+
293
+ # Execute the command with superuser rights if needed.
294
+ #
295
+ # @param command [String] Command to execute.
296
+ # @param no_confirm [Boolean] If true execute the command without asking confirmation (--assume-yes).
297
+ #
298
+ # @return [void]
299
+ def execute_command(command, no_confirm = false)
300
+ if not NEED_SUPERUSER_RIGHTS.include?(command.split[1])
301
+ system(command)
302
+
303
+ elsif `groups`.split.include?('sudo')
304
+ if no_confirm && @config.no_confirm
305
+ system "sudo #{command} --assume-yes"
306
+ else
307
+ system "sudo #{command}"
308
+ end
309
+
310
+ else
311
+ if no_confirm && @config.no_confirm
312
+ system "su -c '#{command} --assume-yes'"
313
+ else
314
+ system "su -c '#{command}'"
315
+ end
316
+ end
317
+ end
318
+
319
+ private
320
+
321
+ # Return an array of packages that does not exist.
322
+ #
323
+ # @param packages [Array<String>] Packages to check.
324
+ #
325
+ # @return [Array<String>] Packages not found.
326
+ def get_packages_not_found(packages)
327
+ require_relative 'Package'
328
+
329
+ not_found = []
330
+
331
+ packages.each do |package_name|
332
+ # If we use option (ex: -t sid), we don't check if packages exist.
333
+ if package_name =~ /^-.*$/
334
+ return []
335
+ end
336
+
337
+ pkg = Package.new
338
+ pkg.name = package_name
339
+ if !pkg.exist?
340
+ not_found.push(package_name)
341
+ end
342
+ end
343
+
344
+ not_found
345
+ end
346
+
347
+ # Check if all packages are already installed.
348
+ #
349
+ # @param packages [Array<String>] Packages to check.
350
+ #
351
+ # @return [Boolean]
352
+ def all_installed?(packages)
353
+ require_relative 'Package'
354
+
355
+ all_installed = true
356
+
357
+ packages.each do |package_name|
358
+ pkg = Package.new
359
+ pkg.name = package_name
360
+ if !pkg.is_installed?
361
+ all_installed = false
362
+ end
363
+ end
364
+
365
+ all_installed
366
+ end
367
+
368
+ # Check if all packages are not installed.
369
+ #
370
+ # @param packages [Array<String>] Packages to check.
371
+ #
372
+ # @return [Boolean]
373
+ def all_not_installed?(packages)
374
+ require_relative 'Package'
375
+
376
+ all_not_installed = true
377
+
378
+ packages.each do |package_name|
379
+ pkg = Package.new
380
+ pkg.name = package_name
381
+ if pkg.is_installed?
382
+ all_not_installed = false
383
+ end
384
+ end
385
+
386
+ all_not_installed
387
+ end
388
+
389
+ # Separate packages in analysis parts (only for install, remove and upgrade).
390
+ #
391
+ # Return a Hash like +Hash{max, packages}+ with:
392
+ #
393
+ # * max: Apti::Package, Fake package with max lengths of all attributs.
394
+ # * packages: Array<Apti::Package>, Array of packages.
395
+ # @param packages_line [Array<String>] List of packages, as outputted by aptitude.
396
+ #
397
+ # @return [Hash]
398
+ def analysis_packages(packages_line)
399
+ require_relative 'Package'
400
+
401
+ max = Package.new
402
+ max.name = I18n.t(:'header.package')
403
+ max.version_static = ''
404
+ max.version_old = I18n.t(:'header.version')
405
+ max.version_new = ''
406
+ max.size_before_decimal = ''
407
+ max.size_after_decimal = ''
408
+ max.size_unit = ''
409
+
410
+ # Longest text for packages with no-revision (see below for explanations).
411
+ max_old_static = ''
412
+
413
+ thousands_separator = I18n.t(:'number.separator.thousands')
414
+ decimal_separator = I18n.t(:'number.separator.decimal')
415
+
416
+ packages = []
417
+
418
+ packages_line.delete_if { |package| package == '' || package == "\n" }
419
+
420
+ packages_line.each do |package_line|
421
+ # ex: brasero-common{a} [3.8.0-2 -> 3.8.0-5] <+11,2 MB>
422
+ # name : brasero-common
423
+ # parameter : a
424
+ # old version : 3.8.0
425
+ # old revision : 2
426
+ # new version : 3.8.0
427
+ # new revision : 5
428
+ # size before : +11
429
+ # size after : 2
430
+ # size_unit : MB
431
+ if package_line =~ /
432
+ ^([[:alnum:]+.:-]*) # Package name.
433
+ (?:\{([[:alpha:]])\})? # Package parameter (a,u,...) if any.
434
+ \p{Space}
435
+ \[
436
+ ([[:alnum:][:space:]+.:~]*) # Old version (without revision).
437
+ (?:-([[:alnum:][:space:]+.:~-]+))? # Old revision (if any).
438
+ (?:
439
+ \p{Space}->\p{Space} # Separator : ->
440
+ ([[:alnum:]+.:~]*) # New version (without revision).
441
+ (?:-([[:alnum:]+.:~-]+))? # New revision (if any).
442
+ )?
443
+ \]
444
+ (?:\p{Space}<
445
+ ([+-]? # Size symbol.
446
+ [[:digit:]]{1,3}(?:[#{thousands_separator}]?[[:digit:]]{3})*) # Size before decimal : integer part.
447
+ ([#{decimal_separator}][[:digit:]]+)? # Size after decimal : decimal part.
448
+ \p{Space}
449
+ ([[:alpha:]]+) # Size unit.
450
+ >)?$
451
+ /x
452
+ package = Package.new
453
+
454
+ package.name = Regexp.last_match[1]
455
+ package.parameter = Regexp.last_match[2]
456
+
457
+ if Regexp.last_match[3] == Regexp.last_match[5] # If old version (without revision) and new version (without revision) are identical => only "revision" upgrade.
458
+ package.version_static = Regexp.last_match(3); # Version (without revision).
459
+ package.version_old = Regexp.last_match(4); # Old revision.
460
+ package.version_new = Regexp.last_match(6); # New revision.
461
+
462
+ if package.version_static.length > max.version_static.length
463
+ max.version_static = package.version_static
464
+ end
465
+
466
+ if package.version_old.length > max.version_old.length
467
+ max.version_old = package.version_old
468
+ end
469
+ else # Else => "version" upgrade or not an upgrade.
470
+ package.version_static = nil # No "root" version : used to know if "revision" upgrade or not.
471
+ package.version_old = Regexp.last_match(3) # "Old" version => always present.
472
+ if !Regexp.last_match(4).nil?
473
+ package.version_old = package.version_old + "-" + Regexp.last_match(4) # Add revision only if exists
474
+ end
475
+
476
+ if !Regexp.last_match(5).nil? # And only if new version exist.
477
+ package.version_new = Regexp.last_match(5) # Remember it.
478
+
479
+ if !Regexp.last_match(6).nil?
480
+ package.version_new = package.version_new + "-" + Regexp.last_match(6)
481
+ end
482
+ end
483
+
484
+ if package.version_old.length > max_old_static.length
485
+ max_old_static = package.version_old
486
+ end
487
+ end
488
+
489
+ package.size_before_decimal = Regexp.last_match[7]
490
+ package.size_after_decimal = Regexp.last_match[8]
491
+ package.size_unit = Regexp.last_match[9]
492
+
493
+ if package.name.length > max.name.length
494
+ max.name = package.name
495
+ end
496
+
497
+ if !package.version_new.nil? && package.version_new.length > max.version_new.length
498
+ max.version_new = package.version_new
499
+ end
500
+
501
+ if !package.size_before_decimal.nil? && package.size_before_decimal.length > max.size_before_decimal.length
502
+ max.size_before_decimal = package.size_before_decimal
503
+ end
504
+ if !package.size_after_decimal.nil? && package.size_after_decimal.length > max.size_after_decimal.length
505
+ max.size_after_decimal = package.size_after_decimal
506
+ end
507
+ if !package.size_unit.nil? && package.size_unit.length > max.size_unit.length
508
+ max.size_unit = package.size_unit
509
+ end
510
+
511
+ packages.push(package)
512
+ end
513
+ end
514
+
515
+ # Check if longest text of no-revision upgrades (including install or remove) are greater than bloc of longest version and longest old revision of revision upgrades.
516
+ # Must be done AFTER treating all packages to ensure to use REALLY longest parts!
517
+ if max_old_static.length > (max.version_old.length + max.version_static.length)
518
+ # If so, this is first part ("root" version part) must be increased.
519
+ max.version_static = max_old_static.slice(0, max_old_static.length - max.version_old.length)
520
+ end
521
+
522
+ out = {}
523
+ out['max'] = max
524
+ out['packages'] = packages
525
+
526
+ out
527
+ end
528
+
529
+ # Return an Array of the package(s) searched.
530
+ #
531
+ # @param aptitude_string [String] Output of aptitude search's command.
532
+ #
533
+ # @return [Array<Apti::Package>] Array of packages.
534
+ def get_search_packages(aptitude_string)
535
+ require_relative 'Package'
536
+
537
+ packages = []
538
+
539
+ aptitude_string.each_line do |package_line|
540
+ package = Package.new
541
+
542
+ package_str = package_line.split '- '
543
+
544
+ # Parameter and name, ex: i A aptitude-common.
545
+ package_parameter_and_name = package_str.first
546
+
547
+ package.description = ''
548
+
549
+ # Construct the description (all after the first '-').
550
+ name_passed = false
551
+ package_str.each do |str|
552
+ if not name_passed
553
+ name_passed = true
554
+ else
555
+ package.description.concat "- #{str }"
556
+ end
557
+ end
558
+
559
+ # The package name (without informations).
560
+ package.name = package_parameter_and_name.split.last
561
+
562
+ # Informations of the package: i, p, A, ...
563
+ package_info = package_parameter_and_name.split
564
+ package_info.pop
565
+ package.parameter = package_info.join(' ')
566
+
567
+ packages.push(package)
568
+ end
569
+
570
+ packages
571
+ end
572
+
573
+ # Display all packages of an operation (install, remove or upgrade).
574
+ #
575
+ # @param packages [Array<String>] List of packages as outputted by aptitude.
576
+ # @param operation [String] Operation requested : "Installing", "Upgrading" or "Removing".
577
+ # @param color [String] Color (Linux bash color notation) to use for old / current package version.
578
+ # @param question [String] Question to ask for continuing operation after displaying packages list.
579
+ # @param download_size [String] Aptitude's text about download sizes.
580
+ #
581
+ # @return [void]
582
+ def display_packages(packages, operation, color, question, download_size)
583
+ analysis = analysis_packages(packages)
584
+ max = analysis['max']
585
+ packages = analysis['packages']
586
+
587
+ explicit = []
588
+ dep_install = []
589
+ dep_remove = []
590
+
591
+ packages.each do |package|
592
+ case package.parameter
593
+ when 'a'
594
+ dep_install.push(package)
595
+
596
+ when 'u'
597
+ dep_remove.push(package)
598
+
599
+ else
600
+ explicit.push(package)
601
+ end
602
+ end
603
+
604
+ # If we do an upgrade, we separate new revisions and new versions.
605
+ if operation.eql?(I18n.t(:'operation.upgrading'))
606
+ upgrade = true
607
+
608
+ upgrade_revisions = []
609
+ upgrade_versions = []
610
+
611
+ explicit.each do |package|
612
+ if package.version_static.nil?
613
+ upgrade_versions.push(package)
614
+ else
615
+ upgrade_revisions.push(package)
616
+ end
617
+
618
+ end
619
+ end
620
+
621
+ if explicit.empty?
622
+ puts I18n.t(:'operation.nothing_to_do')
623
+ exit 1
624
+ end
625
+
626
+
627
+ print_header(max.name.length, max.version_all.length)
628
+
629
+ # If we have packages to upgrade, we display them at the end (after news to install, and those to remove).
630
+ if !upgrade
631
+ puts "#{@config.colors.text.to_shell_color}#{operation}#{get_color_for()}"
632
+ explicit.each { |package| display_package_line(package, max, color) }
633
+ puts ''
634
+ end
635
+
636
+ if !dep_install.empty?
637
+ puts "#{@config.colors.text.to_shell_color}#{I18n.t(:installing_for_dependencies)}#{get_color_for()}"
638
+ dep_install.each { |package| display_package_line(package, max, 'install') }
639
+ puts ''
640
+ end
641
+
642
+ if !dep_remove.empty?
643
+ puts "#{@config.colors.text.to_shell_color}#{I18n.t(:removing_unused_dependencies)}#{get_color_for()}"
644
+ dep_remove.each { |package| display_package_line(package, max, 'remove') }
645
+ puts ''
646
+ end
647
+
648
+ if upgrade
649
+ if !upgrade_revisions.empty?
650
+ puts "#{@config.colors.text.to_shell_color}#{I18n.t(:'operation.upgrading')} #{I18n.t(:'operation.new_revisions')}#{get_color_for()}"
651
+ upgrade_revisions.each { |package| display_package_line(package, max, color) }
652
+ puts ''
653
+ end
654
+
655
+ if !upgrade_versions.empty?
656
+ puts "#{@config.colors.text.to_shell_color}#{I18n.t(:'operation.upgrading')} #{I18n.t(:'operation.new_versions')}#{get_color_for()}"
657
+ upgrade_versions.each { |package| display_package_line(package, max, color) }
658
+ puts ''
659
+ end
660
+ end
661
+
662
+ # Size to download and install.
663
+ puts "#{download_size}"
664
+
665
+ answer = ''
666
+ while !answer.downcase.eql?('y') && !answer.downcase.eql?('n')
667
+ print "\n#{@config.colors.text.to_shell_color}#{question} (Y/n)#{get_color_for()} "
668
+ answer = STDIN.gets.chomp
669
+ if answer.empty?
670
+ answer = 'y'
671
+ end
672
+ end
673
+
674
+ answer.downcase.eql?('y')
675
+ end
676
+
677
+ # Displaying the line of ONE package (for install, remove and upgrade).
678
+ #
679
+ # @param package [Apti::Package] The package to display.
680
+ # @param max [Apti::Package] Fake package with max lengths of all attributs.
681
+ # @param operation [String] Name of sub-variable of Apti::config.colors according to current operation (install, upgrade or remove).
682
+ #
683
+ # @return [void]
684
+ def display_package_line(package, max, operation)
685
+ # Name.
686
+ print " #{package.name}"
687
+ # Spaces.
688
+ print ''.rjust((max.name.length - package.name.length) + @config.spaces.columns)
689
+
690
+ # Package version (with revision if new version, without if new revision).
691
+ if package.version_static.nil?
692
+ print "#{get_color_for(operation, 'version.old')}#{package.version_old}"
693
+ else
694
+ print "#{get_color_for(operation, 'revision.static')}#{package.version_static}"
695
+ end
696
+ print "#{get_color_for()}"
697
+
698
+ if !package.version_new.nil?
699
+ print "#{''.rjust((max.version_old.length + max.version_static.length) - package.version_old.length - (package.version_static.nil? ? 0 : package.version_static.length))}"
700
+ if !package.version_static.nil?
701
+ print "#{get_color_for(operation, 'revision.old')}#{package.version_old}#{get_color_for()}"
702
+ end
703
+
704
+ print " -> #{get_color_for(operation, (package.version_static.nil? ? 'version' : 'revision') + '.new')}#{package.version_new}#{get_color_for}"
705
+ rjust_size = max.version_new.length - package.version_new.length
706
+ else
707
+ rjust_size = max.version_all.length - package.version_old.length
708
+ end
709
+
710
+ if @config.display_size && !package.size_before_decimal.nil?
711
+ line_size_after_length = (package.size_after_decimal.nil? ? 0 : package.size_after_decimal.length)
712
+
713
+ # Spaces.
714
+ print ''.rjust(rjust_size + @config.spaces.columns + max.size_before_decimal.length - package.size_before_decimal.length)
715
+ # Start color.
716
+ print @config.colors.size.to_shell_color
717
+ # Size.
718
+ print "#{package.size_before_decimal}#{package.size_after_decimal}"
719
+ # Spaces and unit.
720
+ print package.size_unit.rjust((max.size_after_decimal.length - line_size_after_length) + (max.size_unit.length) + @config.spaces.unit)
721
+ # End color.
722
+ print get_color_for()
723
+ end
724
+
725
+ print "\n"
726
+ end
727
+
728
+ # Get color of an "operation" (install, upgrade or remove).
729
+ # In case of "upgrade" operation, a sub-operation can be provide to access the desired color (examples in Apti::display_package_line code).
730
+ #
731
+ # If no "operation" is get, return the special Config::Color::STYLE_END color.
732
+ #
733
+ # @param operation [String] The "operation".
734
+ # @param sub_op [String] The sub-operation, separate levels with dot.
735
+ #
736
+ # @return [String] The shell notation of the color.
737
+ def get_color_for(operation = nil, sub_op = nil)
738
+ if operation.nil?
739
+ color = Config::Color.new(Config::Color::STYLE_END)
740
+ else
741
+ # Get the config field according to current operation.
742
+ color = @config.colors.send(operation)
743
+
744
+ if operation == 'upgrade' && !sub_op.nil?
745
+ # Must be do level by level because Object::send doesn't support dots (not like expected).
746
+ sub_op.split('.').each { |sub_var| color = color.send(sub_var) }
747
+ end
748
+ end
749
+
750
+ # Directly change color to shell notation.
751
+ color.to_shell_color
752
+ end
753
+
754
+ # Print header for install, remove and upgrade.
755
+ #
756
+ # @param largest_name [Fixnum] Largest size of package name.
757
+ # @param largest_version [Fixnum] Largest size of complete version (old / current AND new).
758
+ #
759
+ # @return [void]
760
+ def print_header(largest_name, largest_version)
761
+ terminal_width = `tput cols`.to_i
762
+
763
+ # Top line.
764
+ terminal_width.times do
765
+ print '='
766
+ end
767
+ print "\n"
768
+
769
+ # Column's names.
770
+ header_package = I18n.t(:'header.package')
771
+ header_version = I18n.t(:'header.version')
772
+ header_size = I18n.t(:'header.size')
773
+
774
+ print " #{header_package}"
775
+ print "#{''.rjust(largest_name - header_package.length + @config.spaces.columns)}"
776
+ print "#{header_version}"
777
+ if @config.display_size
778
+ print "#{''.rjust(largest_version - header_version.length + @config.spaces.columns + 1)}"
779
+ print "#{header_size}"
780
+ end
781
+ print "\n"
782
+
783
+ # Bottom line.
784
+ terminal_width.times do
785
+ print '='
786
+ end
787
+ print "\n"
788
+ end
789
+
790
+ end
791
+ end