upm 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gemspec +9 -18
- data/.gitignore +1 -0
- data/README.md +77 -43
- data/Rakefile +1 -1
- data/TODO.md +38 -0
- data/VERSION +1 -1
- data/bin/upm +14 -0
- data/lib/upm/colored.rb +320 -0
- data/lib/upm/core_ext.rb +131 -0
- data/lib/upm/lesspipe.rb +57 -0
- data/lib/upm/log_parser.rb +56 -0
- data/lib/upm/tool.rb +180 -0
- data/lib/upm/tools/apt.rb +68 -0
- data/lib/upm/tools/pacman.rb +105 -0
- data/lib/upm/tools/xbps.rb +20 -0
- data/lib/upm.rb +12 -0
- metadata +17 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4da92ce63688e75043c91e8d40bf9a1829687b974a4bc21bc40e23c06ce108f9
|
4
|
+
data.tar.gz: 0eb4815df308f4dfaffdcc9fee5a877c65dda0d65f85ed3149fdb338bc34517b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8ca05eff0cd5037aa9a30b9206d79933fdd7fe6bc8a7d225185278c0696965416ba5d0315fec9cc25a1ecb1ed566ed80704b213c9fee1e37b515596a1d50dc0
|
7
|
+
data.tar.gz: 7a0341ee388207404b5a785d7d27fd5a563f886a8638165539f1a790520e9c097b8d33bbbd2f79d046183a6515e73d5d1cb08ab44f0e3a0d33fb7d4e58f6df94
|
data/.gemspec
CHANGED
@@ -2,26 +2,17 @@
|
|
2
2
|
# -*- coding: utf-8 -*-
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
|
-
s.name
|
6
|
-
|
7
|
-
s.
|
8
|
-
s.
|
9
|
-
|
10
|
-
s.summary = "Universal Package Manager"
|
5
|
+
s.name = "upm"
|
6
|
+
s.version = File.read "VERSION"
|
7
|
+
s.date = File.mtime("VERSION").strftime("%Y-%m-%d")
|
8
|
+
s.summary = "Universal Package Manager"
|
11
9
|
s.description = "Wrap all known command-line package tools with a consistent and pretty interface."
|
12
|
-
|
13
|
-
s.
|
14
|
-
s.
|
15
|
-
|
16
|
-
s.
|
17
|
-
s.authors = ["epitron"]
|
18
|
-
|
19
|
-
# s.executables = ["upm", "up", "u"]
|
10
|
+
s.homepage = "http://github.com/epitron/upm/"
|
11
|
+
s.licenses = ["WTFPL"]
|
12
|
+
s.email = "chris@ill-logic.com"
|
13
|
+
s.authors = ["epitron"]
|
14
|
+
s.executables = ["upm"]
|
20
15
|
|
21
16
|
s.files = `git ls`.lines.map(&:strip)
|
22
17
|
s.extra_rdoc_files = ["README.md", "LICENSE"]
|
23
|
-
|
24
|
-
# s.require_paths = %w[lib]
|
25
|
-
|
26
|
-
# s.add_dependency "slop", "~> 3.0"
|
27
18
|
end
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.gem
|
data/README.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# upm: Universal Package Manager
|
2
2
|
|
3
|
+
## Concept:
|
4
|
+
|
5
|
+
Wraps all known package managers to provide a consistent and pretty interface, along with advanced features not supported by all tools, such as rollback and pinning.
|
6
|
+
|
7
|
+
All tools will give you modern, pretty, colourful, piped-to-less output, and you'll only have to remember one consistent set of commands. It'll also prompt you with a text UI whenever faced with ambiguity.
|
8
|
+
|
3
9
|
## Usage:
|
4
10
|
|
5
11
|
```
|
@@ -8,35 +14,36 @@ up <command> <pkg>
|
|
8
14
|
u <command> <pkg>
|
9
15
|
```
|
10
16
|
|
11
|
-
## Commands
|
12
|
-
|
13
|
-
*
|
14
|
-
*
|
15
|
-
* install
|
16
|
-
*
|
17
|
-
*
|
18
|
-
*
|
19
|
-
*
|
20
|
-
*
|
21
|
-
*
|
22
|
-
*
|
23
|
-
*
|
24
|
-
*
|
25
|
-
*
|
26
|
-
*
|
27
|
-
*
|
28
|
-
*
|
29
|
-
*
|
30
|
-
|
31
|
-
|
17
|
+
## Commands:
|
18
|
+
|
19
|
+
* `install`
|
20
|
+
* `remove`
|
21
|
+
* `build` - compile a package from source and install it
|
22
|
+
* `search` - using the fastest known API or service
|
23
|
+
* `list` - show all packages, or the contents of a specific package
|
24
|
+
* `info` - show metadata about a package
|
25
|
+
* `sync`/`update` - retrieve the latest package list or manifest
|
26
|
+
* `upgrade` - install new versions of all packages
|
27
|
+
* `pin` - pinning a package means it won't be automatically upgraded
|
28
|
+
* `rollback` - revert to an earlier version of a package (including its dependencies)
|
29
|
+
* `log` - show history of package installs
|
30
|
+
* `packagers` - detect installed package managers, and pick which ones upm should wrap
|
31
|
+
* `sources`/`mirrors` - select remote repositories and mirrors
|
32
|
+
* `verfiy` - verifies the integrity of installed files
|
33
|
+
* `clean` - clear out the local package cache
|
34
|
+
* `monitor` - ad-hoc package manager for custom installations (like instmon)
|
35
|
+
* `keys` - keyrings and package authentication
|
36
|
+
* `default` - configure the action to take when no arguments are passed to "upm" (defaults to "os:update")
|
37
|
+
|
38
|
+
### Any command that takes a package name can be prefixed with the package tool's namespace:
|
32
39
|
|
33
40
|
```
|
34
|
-
|
35
|
-
deb:<pkg> (or d:)
|
36
|
-
rpm:<pkg>
|
37
|
-
|
38
|
-
|
39
|
-
|
41
|
+
os:<pkg> -- automatically select the package manager for the current unix distribution
|
42
|
+
deb:<pkg> (or d: u:)
|
43
|
+
rpm:<pkg> (or yum: y:)
|
44
|
+
bsd:<pkg> (or b:)
|
45
|
+
ruby:<pkg> (or r: gem:)
|
46
|
+
python:<pkg>,<pkg> (or py: p: pip:)
|
40
47
|
```
|
41
48
|
|
42
49
|
### ...or suffixed with its file extension:
|
@@ -48,25 +55,52 @@ pip:<pkg>
|
|
48
55
|
<pkg>.pip
|
49
56
|
```
|
50
57
|
|
51
|
-
##
|
58
|
+
## Package tools to wrap:
|
59
|
+
|
60
|
+
* Arch: `pacman`/`aur`/`abs` (svn mirror)
|
61
|
+
* Debian/Ubuntu: `apt-get`/`dpkg` (+ curated list of ppa's)
|
62
|
+
* RedHat/Fedora/Centos: `yum`/`rpm`
|
63
|
+
* Mac OSX: `brew`/`fink`/`ports`
|
64
|
+
* FreeBSD: `pkg`/`ports`
|
65
|
+
* OpenBSD: `pkg_add`/`ports`
|
66
|
+
* NetBSD: `pkgin`/`ports`
|
67
|
+
* SmartOS/Illumos: `pkgin`
|
68
|
+
* Windows: `apt-cyg`/`mingw-get`/`nuget`/`Windows Update`/(as-yet-not-created package manager, "winget")
|
69
|
+
* Wine: `winetricks`
|
70
|
+
* Ruby: `rubygems`
|
71
|
+
* Python: `pip`/`easy_install`
|
72
|
+
* Javascript: `npm`
|
73
|
+
* Clojure: `leiningen`
|
74
|
+
* Java: `gradle`
|
75
|
+
* Erlang: `rebar`
|
76
|
+
* Scala: `sbt`
|
77
|
+
* Rust: `cargo`
|
78
|
+
* R: `cran`
|
79
|
+
* Lua: `rocks`
|
80
|
+
* Julia: `Pkg`
|
81
|
+
* Haskell: `cabal`
|
82
|
+
* Perl: `cpan`
|
83
|
+
* go: `go-get`
|
84
|
+
|
85
|
+
...[and many more!](https://en.wikipedia.org/wiki/List_of_software_package_management_systems)
|
86
|
+
|
87
|
+
|
88
|
+
## What it might look like:
|
89
|
+
|
90
|
+
Info:
|
91
|
+
|
92
|
+
![acs](https://raw.githubusercontent.com/epitron/scripts/master/screenshots/acs.png)
|
93
|
+
|
94
|
+
Log:
|
95
|
+
|
96
|
+
![paclog](https://raw.githubusercontent.com/epitron/scripts/master/screenshots/paclog.png)
|
52
97
|
|
53
|
-
|
54
|
-
* FreeBSD: pkg
|
55
|
-
* RedHat/Fedora/Centos: yum/rpm
|
56
|
-
* Debian/Ubuntu: apt-get/dpkg (+ curated list of ppa's)
|
57
|
-
* Windows: apt-cyg/nuget/"winget" (new package manager)
|
58
|
-
* Arch: pacman/aur/abs (svn mirror)
|
59
|
-
* Mac OSX: brew/fink
|
60
|
-
* Python: pip/easy_install
|
61
|
-
* Ruby: rubygems
|
62
|
-
* Haskell: cabal
|
63
|
-
* Perl: cpan
|
64
|
-
* go: go-get
|
65
|
-
* Java: ?
|
98
|
+
Rollback:
|
66
99
|
|
100
|
+
![pacman-rollback](https://raw.githubusercontent.com/epitron/scripts/master/screenshots/pacman-rollback.png)
|
67
101
|
|
68
102
|
## TODOs:
|
69
103
|
|
70
|
-
*
|
104
|
+
* Use the pretty text-mode UI that passenger-install uses
|
71
105
|
* Context-dependent operation
|
72
|
-
* if you're in a ruby project,
|
106
|
+
* eg: if you're in a ruby project's directory, set the 'ruby' namespace to highest priority
|
data/Rakefile
CHANGED
data/TODO.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# TODO
|
2
|
+
|
3
|
+
## More package managers
|
4
|
+
|
5
|
+
Currently missing:
|
6
|
+
* RedHat/Fedora/CentOS
|
7
|
+
* OSX
|
8
|
+
* FreeBSD
|
9
|
+
* OpenBSD
|
10
|
+
* SuSE
|
11
|
+
|
12
|
+
## fzf
|
13
|
+
|
14
|
+
Use fzf for "list" output (or other commands that require selecting, like "remove")
|
15
|
+
|
16
|
+
## Proper ARGV parser
|
17
|
+
|
18
|
+
* Something better than "command, *args = ARGV" (with "--help" available, at the very least.)
|
19
|
+
|
20
|
+
## Streaming pipes
|
21
|
+
|
22
|
+
* Make the `run` command able to grep the output while streaming the results to the screen.
|
23
|
+
* Make run pretend to be a tty, so I don't need `--color=always`.
|
24
|
+
|
25
|
+
## Figure out how to integrate language package managers
|
26
|
+
|
27
|
+
* The packages that you can get through gem/pip/luarocks/etc. are often duplicated in the OS-level package managers. Should there be a preference?
|
28
|
+
* Should the search command show matches from all available package tools? (There could be a configure step where the user says which package managers should be included, and which have preference)
|
29
|
+
* Possibilites:
|
30
|
+
* upm install --ruby <pkg>
|
31
|
+
* upm install ruby:<pkg>,<pkg>
|
32
|
+
* Add detectors for language-specific package-managers
|
33
|
+
* Help screen needs to display language-specific package managers
|
34
|
+
* `upm help --ruby` should show available ruby commands
|
35
|
+
|
36
|
+
## Mirror Selector
|
37
|
+
|
38
|
+
* Do a ping test on available mirrors, and use fzf to select
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.1.0
|
data/bin/upm
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
bin_dir = File.dirname(File.realpath(__FILE__))
|
3
|
+
$LOAD_PATH.unshift(File.expand_path(File.join('..', 'lib'), bin_dir))
|
4
|
+
|
5
|
+
require 'upm'
|
6
|
+
|
7
|
+
tool = UPM::Tool.for_os
|
8
|
+
command, *args = ARGV
|
9
|
+
|
10
|
+
if command.nil?
|
11
|
+
tool.help
|
12
|
+
else
|
13
|
+
tool.call_command command, args
|
14
|
+
end
|
data/lib/upm/colored.rb
ADDED
@@ -0,0 +1,320 @@
|
|
1
|
+
#
|
2
|
+
# ANSI Colour-coding (for terminals that support it.)
|
3
|
+
#
|
4
|
+
# Originally by defunkt (Chris Wanstrath)
|
5
|
+
# Enhanced by epitron (Chris Gahan)
|
6
|
+
#
|
7
|
+
# It adds methods to String to allow easy coloring.
|
8
|
+
#
|
9
|
+
# (Note: Colors are automatically disabled if your program is piped to another program,
|
10
|
+
# ie: if STDOUT is not a TTY)
|
11
|
+
#
|
12
|
+
# Basic examples:
|
13
|
+
#
|
14
|
+
# >> "this is red".red
|
15
|
+
# >> "this is red with a blue background (read: ugly)".red_on_blue
|
16
|
+
# >> "this is light blue".light_blue
|
17
|
+
# >> "this is red with an underline".red.underline
|
18
|
+
# >> "this is really bold and really blue".bold.blue
|
19
|
+
#
|
20
|
+
# Color tags:
|
21
|
+
# (Note: You don't *need* to close color tags, but you can!)
|
22
|
+
#
|
23
|
+
# >> "<yellow>This is using <green>color tags</green> to colorize.".colorize
|
24
|
+
# >> "<1>N<9>u<11>m<15>eric tags!".colorize
|
25
|
+
# (For those who still remember the DOS color palette and want more terse tagged-colors.)
|
26
|
+
#
|
27
|
+
# Highlight search results:
|
28
|
+
#
|
29
|
+
# >> string.gsub(pattern) { |match| "<yellow>#{match}</yellow>" }.colorize
|
30
|
+
#
|
31
|
+
# Forcing colors:
|
32
|
+
#
|
33
|
+
# Since the presence of a terminal is detected automatically, the colors will be
|
34
|
+
# disabled when you pipe your program to another program. However, if you want to
|
35
|
+
# show colors when piped (eg: when you pipe to `less -R`), you can force it:
|
36
|
+
#
|
37
|
+
# >> Colored.enable!
|
38
|
+
# >> Colored.disable!
|
39
|
+
# >> Colored.enable_temporarily { puts "whee!".red }
|
40
|
+
#
|
41
|
+
|
42
|
+
require 'set'
|
43
|
+
require 'rbconfig'
|
44
|
+
require 'Win32/Console/ANSI' if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
|
45
|
+
#require 'Win32/Console/ANSI' if RUBY_PLATFORM =~ /win32/
|
46
|
+
|
47
|
+
module Colored
|
48
|
+
extend self
|
49
|
+
|
50
|
+
@@is_tty = STDOUT.isatty
|
51
|
+
|
52
|
+
COLORS = {
|
53
|
+
'black' => 30,
|
54
|
+
'red' => 31,
|
55
|
+
'green' => 32,
|
56
|
+
'yellow' => 33,
|
57
|
+
'blue' => 34,
|
58
|
+
'magenta' => 35,
|
59
|
+
'purple' => 35,
|
60
|
+
'cyan' => 36,
|
61
|
+
'white' => 37
|
62
|
+
}
|
63
|
+
|
64
|
+
EXTRAS = {
|
65
|
+
'clear' => 0,
|
66
|
+
'bold' => 1,
|
67
|
+
'light' => 1,
|
68
|
+
'underline' => 4,
|
69
|
+
'reversed' => 7
|
70
|
+
}
|
71
|
+
|
72
|
+
#
|
73
|
+
# BBS-style numeric color codes.
|
74
|
+
#
|
75
|
+
BBS_COLOR_TABLE = {
|
76
|
+
0 => :black,
|
77
|
+
1 => :blue,
|
78
|
+
2 => :green,
|
79
|
+
3 => :cyan,
|
80
|
+
4 => :red,
|
81
|
+
5 => :magenta,
|
82
|
+
6 => :yellow,
|
83
|
+
7 => :white,
|
84
|
+
8 => :light_black,
|
85
|
+
9 => :light_blue,
|
86
|
+
10 => :light_green,
|
87
|
+
11 => :light_cyan,
|
88
|
+
12 => :light_red,
|
89
|
+
13 => :light_magenta,
|
90
|
+
14 => :light_yellow,
|
91
|
+
15 => :light_white,
|
92
|
+
}
|
93
|
+
|
94
|
+
VALID_COLORS = begin
|
95
|
+
normal = COLORS.keys
|
96
|
+
lights = normal.map { |fore| "light_#{fore}" }
|
97
|
+
brights = normal.map { |fore| "bright_#{fore}" }
|
98
|
+
on_backgrounds = normal.map { |fore| normal.map { |back| "#{fore}_on_#{back}" } }.flatten
|
99
|
+
|
100
|
+
Set.new(normal + lights + brights + on_backgrounds + ["grey", "gray"])
|
101
|
+
end
|
102
|
+
|
103
|
+
COLORS.each do |color, value|
|
104
|
+
define_method(color) do
|
105
|
+
colorize(self, :foreground => color)
|
106
|
+
end
|
107
|
+
|
108
|
+
define_method("on_#{color}") do
|
109
|
+
colorize(self, :background => color)
|
110
|
+
end
|
111
|
+
|
112
|
+
define_method("light_#{color}") do
|
113
|
+
colorize(self, :foreground => color, :extra => 'bold')
|
114
|
+
end
|
115
|
+
|
116
|
+
define_method("bright_#{color}") do
|
117
|
+
colorize(self, :foreground => color, :extra => 'bold')
|
118
|
+
end
|
119
|
+
|
120
|
+
COLORS.each do |highlight, value|
|
121
|
+
next if color == highlight
|
122
|
+
|
123
|
+
define_method("#{color}_on_#{highlight}") do
|
124
|
+
colorize(self, :foreground => color, :background => highlight)
|
125
|
+
end
|
126
|
+
|
127
|
+
define_method("light_#{color}_on_#{highlight}") do
|
128
|
+
colorize(self, :foreground => color, :background => highlight, :extra => 'bold')
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
alias_method :gray, :light_black
|
135
|
+
alias_method :grey, :light_black
|
136
|
+
|
137
|
+
EXTRAS.each do |extra, value|
|
138
|
+
next if extra == 'clear'
|
139
|
+
define_method(extra) do
|
140
|
+
colorize(self, :extra => extra)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
define_method(:to_eol) do
|
145
|
+
tmp = sub(/^(\e\[[\[\e0-9;m]+m)/, "\\1\e[2K")
|
146
|
+
if tmp == self
|
147
|
+
return "\e[2K" << self
|
148
|
+
end
|
149
|
+
tmp
|
150
|
+
end
|
151
|
+
|
152
|
+
#
|
153
|
+
# Colorize a string (this method is called by #red, #blue, #red_on_green, etc.)
|
154
|
+
#
|
155
|
+
# Accepts options:
|
156
|
+
# :foreground
|
157
|
+
# The name of the foreground color as a string.
|
158
|
+
# :background
|
159
|
+
# The name of the background color as a string.
|
160
|
+
# :extra
|
161
|
+
# Extra styling, like 'bold', 'light', 'underline', 'reversed', or 'clear'.
|
162
|
+
#
|
163
|
+
#
|
164
|
+
# With no options, it uses tagged colors:
|
165
|
+
#
|
166
|
+
# puts "<light_green><magenta>*</magenta> Hey mom! I am <light_blue>SO</light_blue> colored right now.</light_green>".colorize
|
167
|
+
#
|
168
|
+
# Or numeric ANSI tagged colors (from the BBS days):
|
169
|
+
# puts "<10><5>*</5> Hey mom! I am <9>SO</9> colored right now.</10>".colorize
|
170
|
+
#
|
171
|
+
#
|
172
|
+
def colorize(string=nil, options = {})
|
173
|
+
if string == nil
|
174
|
+
return self.tagged_colors
|
175
|
+
end
|
176
|
+
|
177
|
+
if @@is_tty
|
178
|
+
colored = [color(options[:foreground]), color("on_#{options[:background]}"), extra(options[:extra])].compact * ''
|
179
|
+
colored << string
|
180
|
+
colored << extra(:clear)
|
181
|
+
else
|
182
|
+
string
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
#
|
187
|
+
# Find all occurrences of "pattern" in the string and highlight them
|
188
|
+
# with the specified color. (defaults to light_yellow)
|
189
|
+
#
|
190
|
+
# The pattern can be a string or a regular expression.
|
191
|
+
#
|
192
|
+
def highlight(pattern, color=:light_yellow, &block)
|
193
|
+
pattern = Regexp.new(Regexp.escape(pattern)) if pattern.is_a? String
|
194
|
+
|
195
|
+
if block_given?
|
196
|
+
gsub(pattern, &block)
|
197
|
+
else
|
198
|
+
gsub(pattern) { |match| match.send(color) }
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
#
|
203
|
+
# An array of all possible colors.
|
204
|
+
#
|
205
|
+
def colors
|
206
|
+
@@colors ||= COLORS.keys.sort
|
207
|
+
end
|
208
|
+
|
209
|
+
#
|
210
|
+
# Returns the terminal code for one of the extra styling options.
|
211
|
+
#
|
212
|
+
def extra(extra_name)
|
213
|
+
extra_name = extra_name.to_s
|
214
|
+
"\e[#{EXTRAS[extra_name]}m" if EXTRAS[extra_name]
|
215
|
+
end
|
216
|
+
|
217
|
+
#
|
218
|
+
# Returns the terminal code for a specified color.
|
219
|
+
#
|
220
|
+
def color(color_name)
|
221
|
+
background = color_name.to_s =~ /on_/
|
222
|
+
color_name = color_name.to_s.sub('on_', '')
|
223
|
+
return unless color_name && COLORS[color_name]
|
224
|
+
"\e[#{COLORS[color_name] + (background ? 10 : 0)}m"
|
225
|
+
end
|
226
|
+
|
227
|
+
#
|
228
|
+
# Will color commands actually modify the strings?
|
229
|
+
#
|
230
|
+
def enabled?
|
231
|
+
@@is_tty
|
232
|
+
end
|
233
|
+
|
234
|
+
alias_method :is_tty?, :enabled?
|
235
|
+
|
236
|
+
#
|
237
|
+
# Color commands will always produce colored strings, regardless
|
238
|
+
# of whether the script is being run in a terminal.
|
239
|
+
#
|
240
|
+
def enable!
|
241
|
+
@@is_tty = true
|
242
|
+
end
|
243
|
+
|
244
|
+
alias_method :force!, :enable!
|
245
|
+
|
246
|
+
#
|
247
|
+
# Enable Colored just for this block.
|
248
|
+
#
|
249
|
+
def enable_temporarily(&block)
|
250
|
+
last_state = @@is_tty
|
251
|
+
|
252
|
+
@@is_tty = true
|
253
|
+
block.call
|
254
|
+
@@is_tty = last_state
|
255
|
+
end
|
256
|
+
|
257
|
+
#
|
258
|
+
# Color commands will do nothing.
|
259
|
+
#
|
260
|
+
def disable!
|
261
|
+
@@is_tty = false
|
262
|
+
end
|
263
|
+
|
264
|
+
#
|
265
|
+
# Is this string legal?
|
266
|
+
#
|
267
|
+
def valid_tag?(tag)
|
268
|
+
VALID_COLORS.include?(tag) or
|
269
|
+
(tag =~ /^\d+$/ and BBS_COLOR_TABLE.include?(tag.to_i) )
|
270
|
+
end
|
271
|
+
|
272
|
+
#
|
273
|
+
# Colorize a string that has "color tags".
|
274
|
+
#
|
275
|
+
def tagged_colors
|
276
|
+
stack = []
|
277
|
+
|
278
|
+
# split the string into tags and literal strings
|
279
|
+
tokens = self.split(/(<\/?[\w\d_]+>)/)
|
280
|
+
tokens.delete_if { |token| token.size == 0 }
|
281
|
+
|
282
|
+
result = ""
|
283
|
+
|
284
|
+
tokens.each do |token|
|
285
|
+
|
286
|
+
# token is an opening tag!
|
287
|
+
|
288
|
+
if /<([\w\d_]+)>/ =~ token and valid_tag?($1)
|
289
|
+
stack.push $1
|
290
|
+
|
291
|
+
# token is a closing tag!
|
292
|
+
|
293
|
+
elsif /<\/([\w\d_]+)>/ =~ token and valid_tag?($1)
|
294
|
+
|
295
|
+
# if this color is on the stack somwehere...
|
296
|
+
if pos = stack.rindex($1)
|
297
|
+
# close the tag by removing it from the stack
|
298
|
+
stack.delete_at pos
|
299
|
+
else
|
300
|
+
raise "Error: tried to close an unopened color tag -- #{token}"
|
301
|
+
end
|
302
|
+
|
303
|
+
# token is a literal string!
|
304
|
+
|
305
|
+
else
|
306
|
+
|
307
|
+
color = (stack.last || "white")
|
308
|
+
color = BBS_COLOR_TABLE[color.to_i] if color =~ /^\d+$/
|
309
|
+
result << token.send(color)
|
310
|
+
|
311
|
+
end
|
312
|
+
|
313
|
+
end
|
314
|
+
|
315
|
+
result
|
316
|
+
end
|
317
|
+
|
318
|
+
end unless Object.const_defined? :Colored
|
319
|
+
|
320
|
+
String.send(:include, Colored)
|
data/lib/upm/core_ext.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
class DateTime
|
4
|
+
def to_i; to_time.to_i; end
|
5
|
+
end
|
6
|
+
|
7
|
+
module Enumerable
|
8
|
+
#
|
9
|
+
# Split this enumerable into chunks, given some boundary condition. (Returns an array of arrays.)
|
10
|
+
#
|
11
|
+
# Options:
|
12
|
+
# :include_boundary => true #=> include the element that you're splitting at in the results
|
13
|
+
# (default: false)
|
14
|
+
# :after => true #=> split after the matched element (only has an effect when used with :include_boundary)
|
15
|
+
# (default: false)
|
16
|
+
# :once => flase #=> only perform one split (default: false)
|
17
|
+
#
|
18
|
+
# Examples:
|
19
|
+
# [1,2,3,4,5].split{ |e| e == 3 }
|
20
|
+
# #=> [ [1,2], [4,5] ]
|
21
|
+
#
|
22
|
+
# "hello\n\nthere\n".each_line.split_at("\n").to_a
|
23
|
+
# #=> [ ["hello\n"], ["there\n"] ]
|
24
|
+
#
|
25
|
+
# [1,2,3,4,5].split(:include_boundary=>true) { |e| e == 3 }
|
26
|
+
# #=> [ [1,2], [3,4,5] ]
|
27
|
+
#
|
28
|
+
# chapters = File.read("ebook.txt").split(/Chapter \d+/, :include_boundary=>true)
|
29
|
+
# #=> [ ["Chapter 1", ...], ["Chapter 2", ...], etc. ]
|
30
|
+
#
|
31
|
+
def split_at(matcher=nil, options={}, &block)
|
32
|
+
include_boundary = options[:include_boundary] || false
|
33
|
+
|
34
|
+
if matcher.nil?
|
35
|
+
boundary_test_proc = block
|
36
|
+
else
|
37
|
+
if matcher.is_a? Regexp
|
38
|
+
boundary_test_proc = proc { |element| element =~ matcher }
|
39
|
+
else
|
40
|
+
boundary_test_proc = proc { |element| element == matcher }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
Enumerator.new do |yielder|
|
45
|
+
current_chunk = []
|
46
|
+
splits = 0
|
47
|
+
max_splits = options[:once] == true ? 1 : options[:max_splits]
|
48
|
+
|
49
|
+
each do |e|
|
50
|
+
|
51
|
+
if boundary_test_proc.call(e) and (max_splits == nil or splits < max_splits)
|
52
|
+
|
53
|
+
if current_chunk.empty? and not include_boundary
|
54
|
+
next # hit 2 boundaries in a row... just keep moving, people!
|
55
|
+
end
|
56
|
+
|
57
|
+
if options[:after]
|
58
|
+
# split after boundary
|
59
|
+
current_chunk << e if include_boundary # include the boundary, if necessary
|
60
|
+
yielder << current_chunk # shift everything after the boundary into the resultset
|
61
|
+
current_chunk = [] # start a new result
|
62
|
+
else
|
63
|
+
# split before boundary
|
64
|
+
yielder << current_chunk # shift before the boundary into the resultset
|
65
|
+
current_chunk = [] # start a new result
|
66
|
+
current_chunk << e if include_boundary # include the boundary, if necessary
|
67
|
+
end
|
68
|
+
|
69
|
+
splits += 1
|
70
|
+
|
71
|
+
else
|
72
|
+
current_chunk << e
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
yielder << current_chunk if current_chunk.any?
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Split the array into chunks, cutting between the matched element and the next element.
|
84
|
+
#
|
85
|
+
# Example:
|
86
|
+
# [1,2,3,4].split_after{|e| e == 3 } #=> [ [1,2,3], [4] ]
|
87
|
+
#
|
88
|
+
def split_after(matcher=nil, options={}, &block)
|
89
|
+
options[:after] ||= true
|
90
|
+
options[:include_boundary] ||= true
|
91
|
+
split_at(matcher, options, &block)
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Split the array into chunks, cutting before each matched element.
|
96
|
+
#
|
97
|
+
# Example:
|
98
|
+
# [1,2,3,4].split_before{|e| e == 3 } #=> [ [1,2], [3,4] ]
|
99
|
+
#
|
100
|
+
def split_before(matcher=nil, options={}, &block)
|
101
|
+
options[:include_boundary] ||= true
|
102
|
+
split_at(matcher, options, &block)
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Split the array into chunks, cutting between two elements.
|
107
|
+
#
|
108
|
+
# Example:
|
109
|
+
# [1,1,2,2].split_between{|a,b| a != b } #=> [ [1,1], [2,2] ]
|
110
|
+
#
|
111
|
+
def split_between(&block)
|
112
|
+
Enumerator.new do |yielder|
|
113
|
+
current = []
|
114
|
+
last = nil
|
115
|
+
|
116
|
+
each_cons(2) do |a,b|
|
117
|
+
current << a
|
118
|
+
if yield(a,b)
|
119
|
+
yielder << current
|
120
|
+
current = []
|
121
|
+
end
|
122
|
+
last = b
|
123
|
+
end
|
124
|
+
|
125
|
+
current << last unless last.nil?
|
126
|
+
yielder << current
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
alias_method :cut_between, :split_between
|
131
|
+
end
|
data/lib/upm/lesspipe.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#
|
2
|
+
# Create scrollable output via less!
|
3
|
+
#
|
4
|
+
# This command runs `less` in a subprocess, and gives you the IO to its STDIN pipe
|
5
|
+
# so that you can communicate with it.
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
#
|
9
|
+
# lesspipe do |less|
|
10
|
+
# 50.times { less.puts "Hi mom!" }
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# The default less parameters are:
|
14
|
+
# * Allow colour
|
15
|
+
# * Don't wrap lines longer than the screen
|
16
|
+
# * Quit immediately (without paging) if there's less than one screen of text.
|
17
|
+
#
|
18
|
+
# You can change these options by passing a hash to `lesspipe`, like so:
|
19
|
+
#
|
20
|
+
# lesspipe(:wrap=>false) { |less| less.puts essay.to_s }
|
21
|
+
#
|
22
|
+
# It accepts the following boolean options:
|
23
|
+
# :color => Allow ANSI colour codes?
|
24
|
+
# :wrap => Wrap long lines?
|
25
|
+
# :always => Always page, even if there's less than one page of text?
|
26
|
+
#
|
27
|
+
def lesspipe(*args)
|
28
|
+
if args.any? and args.last.is_a?(Hash)
|
29
|
+
options = args.pop
|
30
|
+
else
|
31
|
+
options = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
output = args.first if args.any?
|
35
|
+
|
36
|
+
params = []
|
37
|
+
params << "-R" unless options[:color] == false
|
38
|
+
params << "-S" unless options[:wrap] == true
|
39
|
+
params << "-F" unless options[:always] == true
|
40
|
+
if options[:tail] == true
|
41
|
+
params << "+\\>"
|
42
|
+
$stderr.puts "Seeking to end of stream..."
|
43
|
+
end
|
44
|
+
params << "-X"
|
45
|
+
|
46
|
+
IO.popen("less #{params * ' '}", "w") do |less|
|
47
|
+
if output
|
48
|
+
less.puts output
|
49
|
+
else
|
50
|
+
yield less
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
rescue Errno::EPIPE, Interrupt
|
55
|
+
# less just quit -- eat the exception.
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module UPM
|
2
|
+
class LogParser
|
3
|
+
|
4
|
+
def initialize(klass, log_glob)
|
5
|
+
@klass = klass
|
6
|
+
@log_glob = log_glob
|
7
|
+
end
|
8
|
+
|
9
|
+
def log_events
|
10
|
+
return to_enum(:log_events) unless block_given?
|
11
|
+
|
12
|
+
yielder = proc do |io|
|
13
|
+
io.each_line do |line|
|
14
|
+
if e = @klass.from_line(line.strip)
|
15
|
+
yield e
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
logs = Dir[@log_glob].sort_by { |path| File.mtime(path) }
|
21
|
+
|
22
|
+
logs.each do |log|
|
23
|
+
if log =~ /\.gz$/
|
24
|
+
IO.popen(["zcat", log], &yielder)
|
25
|
+
else
|
26
|
+
open(log, &yielder)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def display
|
32
|
+
lesspipe(tail: true) do |less|
|
33
|
+
groups = log_events.split_between { |a,b| (b.date.to_i - a.date.to_i) > 60 }
|
34
|
+
|
35
|
+
groups.each do |group|
|
36
|
+
first, last = group.first.date, group.last.date
|
37
|
+
elapsed = (last.to_i - first.to_i) / 60
|
38
|
+
|
39
|
+
empty_group = true
|
40
|
+
|
41
|
+
group.each do |ev|
|
42
|
+
# Print the header only if the query matched something in this group
|
43
|
+
if empty_group
|
44
|
+
empty_group = false
|
45
|
+
less.puts
|
46
|
+
less.puts "<8>== <11>#{first.strftime("<10>%Y-%m-%d <7>at <2>%l:%M %p")} <7>(<9>#{elapsed} <7>minute session) <8>========".colorize
|
47
|
+
end
|
48
|
+
|
49
|
+
less.puts ev
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end # lesspipe
|
53
|
+
end
|
54
|
+
|
55
|
+
end # LogParser
|
56
|
+
end
|
data/lib/upm/tool.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
|
2
|
+
# os:<pkg> -- automatically select the package manager for the current unix distribution
|
3
|
+
# deb:<pkg> (or d: u:)
|
4
|
+
# rpm:<pkg> (or yum: y:)
|
5
|
+
# bsd:<pkg> (or b:)
|
6
|
+
# ruby:<pkg> (or r: gem:)
|
7
|
+
# python:<pkg>,<pkg> (or py: p: pip:)
|
8
|
+
|
9
|
+
module UPM
|
10
|
+
|
11
|
+
class Tool
|
12
|
+
|
13
|
+
COMMAND_HELP = {
|
14
|
+
"install" => "install a package",
|
15
|
+
"remove/uninstall" => "remove a package",
|
16
|
+
"build" => "compile a package from source and install it",
|
17
|
+
"search" => "using the fastest known API or service",
|
18
|
+
"list" => "list installed packages (or search their names if extra arguments are supplied)",
|
19
|
+
"info" => "show metadata about a package",
|
20
|
+
"sync/update" => "retrieve the latest package list or manifest",
|
21
|
+
"upgrade" => "install new versions of all packages",
|
22
|
+
"pin" => "pinning a package means it won't be automatically upgraded",
|
23
|
+
"rollback" => "revert to an earlier version of a package (including its dependencies)",
|
24
|
+
"log" => "show history of package installs",
|
25
|
+
"packagers" => "detect installed package managers, and pick which ones upm should wrap",
|
26
|
+
"mirrors/sources" => "manage remote repositories and mirrors",
|
27
|
+
"verfiy" => "verify the integrity of installed files",
|
28
|
+
"clean" => "clear out the local package cache",
|
29
|
+
"monitor" => "ad-hoc package manager for custom installations (like instmon)",
|
30
|
+
"keys" => "keyrings and package authentication",
|
31
|
+
"default" => "configure the action to take when no arguments are passed to 'upm' (defaults to 'os:update')",
|
32
|
+
}
|
33
|
+
|
34
|
+
ALIASES = {
|
35
|
+
"file" => "files",
|
36
|
+
"sync" => "update",
|
37
|
+
"sources" => "mirrors",
|
38
|
+
"show" => "info",
|
39
|
+
}
|
40
|
+
|
41
|
+
@@tools = {}
|
42
|
+
|
43
|
+
def self.register_tools!
|
44
|
+
Dir["#{__dir__}/tools/*.rb"].each { |lib| require_relative(lib) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.os_release
|
48
|
+
@os_release ||= open("/etc/os-release") do |io|
|
49
|
+
io.read.scan(/^(\w+)="?(.+?)"?$/)
|
50
|
+
end.to_h
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.current_os_names
|
54
|
+
# ID=ubuntu
|
55
|
+
# ID_LIKE=debian
|
56
|
+
os_release.values_at("ID", "ID_LIKE")
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.nice_os_name
|
60
|
+
os_release.values_at("PRETTY_NAME", "NAME", "ID", "ID_LIKE").first
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.for_os(os_names=nil)
|
64
|
+
os_names = os_names ? [os_names].flatten : current_os_names
|
65
|
+
@@tools.find { |name, tool| os_names.any? { |name| tool.os.include? name } }.last
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.tools
|
69
|
+
@@tools
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(name, &block)
|
73
|
+
@name = name
|
74
|
+
instance_eval(&block)
|
75
|
+
|
76
|
+
@@tools[name] = self
|
77
|
+
end
|
78
|
+
|
79
|
+
def call_command(name, args)
|
80
|
+
if block = (@cmds[name] || @cmds[ALIASES[name]])
|
81
|
+
block.call args
|
82
|
+
else
|
83
|
+
puts "Command #{name} not supported in #{@name}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def help
|
88
|
+
puts " Detected OS: #{Tool.nice_os_name}"
|
89
|
+
puts "Package manager: #{@name}"
|
90
|
+
puts
|
91
|
+
puts "Available commands:"
|
92
|
+
available = COMMAND_HELP.select do |name, desc|
|
93
|
+
names = name.split("/")
|
94
|
+
names.any? { |name| @cmds[name] }
|
95
|
+
end
|
96
|
+
|
97
|
+
max_width = available.map(&:first).map(&:size).max
|
98
|
+
available.each do |name, desc|
|
99
|
+
puts " #{name.rjust(max_width)} | #{desc}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def print_files(*paths, include: nil, exclude: nil)
|
104
|
+
lesspipe do |less|
|
105
|
+
paths.each do |path|
|
106
|
+
less.puts "<8>=== <11>#{path} <8>========".colorize
|
107
|
+
open(path) do |io|
|
108
|
+
enum = io.each_line
|
109
|
+
enum = enum.grep(include) if include
|
110
|
+
enum = enum.reject { |line| line[exclude] } if exclude
|
111
|
+
enum.each { |line| less.puts line }
|
112
|
+
end
|
113
|
+
less.puts
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
## DSL methods
|
119
|
+
|
120
|
+
def prefix(name)
|
121
|
+
@prefix = name
|
122
|
+
end
|
123
|
+
|
124
|
+
def command(name, shell_command=nil, root: false, paged: false, &block)
|
125
|
+
@cmds ||= {}
|
126
|
+
|
127
|
+
if block_given?
|
128
|
+
@cmds[name] = block
|
129
|
+
elsif shell_command
|
130
|
+
if shell_command.is_a? String
|
131
|
+
shell_command = shell_command.split
|
132
|
+
elsif not shell_command.is_a? Array
|
133
|
+
raise "Error: command argument must be a String or an Array; it was a #{cmd.class}"
|
134
|
+
end
|
135
|
+
|
136
|
+
shell_command.unshift "sudo" if root
|
137
|
+
|
138
|
+
@cmds[name] = proc { |args| run(*shell_command, *args, paged: paged) }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def os(*names)
|
143
|
+
names.any? ? @os = names : @os
|
144
|
+
end
|
145
|
+
|
146
|
+
## Helpers
|
147
|
+
|
148
|
+
def run(*args, paged: false, grep: nil)
|
149
|
+
if !paged and !grep
|
150
|
+
system(*args)
|
151
|
+
else
|
152
|
+
|
153
|
+
IO.popen(args, err: [:child, :out]) do |command_io|
|
154
|
+
|
155
|
+
if grep
|
156
|
+
pattern = grep.is_a?(Regexp) ? grep.source : grep.to_s
|
157
|
+
grep_io = IO.popen(["grep", "--color=always", "-Ei", pattern], "w+")
|
158
|
+
IO.copy_stream(command_io, grep_io)
|
159
|
+
grep_io.close_write
|
160
|
+
command_io = grep_io
|
161
|
+
end
|
162
|
+
|
163
|
+
if paged
|
164
|
+
lesspipe do |less|
|
165
|
+
IO.copy_stream(command_io, less)
|
166
|
+
end
|
167
|
+
else
|
168
|
+
IO.copy_stream(command_io, STDOUT)
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
$?.to_i == 0
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
end # class Tool
|
178
|
+
|
179
|
+
end # module UPM
|
180
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
UPM::Tool.new "apt" do
|
2
|
+
|
3
|
+
os "debian", "ubuntu"
|
4
|
+
|
5
|
+
command "install", "apt install", root: true
|
6
|
+
command "update", "apt update", root: true
|
7
|
+
command "upgrade", "apt upgrade", root: true
|
8
|
+
command "remove", "apt remove", root: true
|
9
|
+
|
10
|
+
command "files", "dpkg-query -L", paged: true
|
11
|
+
command "search", "apt search", paged: true
|
12
|
+
command "info", "apt show", paged: true
|
13
|
+
|
14
|
+
command "list" do |args|
|
15
|
+
if args.any?
|
16
|
+
query = args.join
|
17
|
+
run("dpkg", "-l", grep: query, paged: true)
|
18
|
+
else
|
19
|
+
run("dpkg", "-l", paged: true)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
command "mirrors" do
|
24
|
+
print_files("/etc/apt/sources.list", *Dir["/etc/apt/sources.list.d/*"], exclude: /^(#|$)/)
|
25
|
+
end
|
26
|
+
|
27
|
+
command "log" do
|
28
|
+
UPM::LogParser.new(DpkgEvent, "/var/log/dpkg*").display
|
29
|
+
end
|
30
|
+
|
31
|
+
class DpkgEvent < Struct.new(:datestr, :date, :cmd, :name, :v1, :v2)
|
32
|
+
|
33
|
+
# 2010-12-03 01:36:56 remove gir1.0-mutter-2.31 2.31.5-0ubuntu9 2.31.5-0ubuntu9
|
34
|
+
# 2010-12-03 01:36:58 install gir1.0-mutter-2.91 <none> 2.91.2+git20101114.982a10ac-0ubuntu1~11.04~ricotz0
|
35
|
+
#LINE_RE = /^(.+ .+) (status \w+|\w+) (.+) (.+)$/
|
36
|
+
LINE_RE = /^(.+ .+) (remove|install|upgrade) (.+) (.+) (.+)$/
|
37
|
+
|
38
|
+
CMD_COLORS = {
|
39
|
+
'remove' => :light_red,
|
40
|
+
'install' => :light_yellow,
|
41
|
+
'upgrade' => :light_green,
|
42
|
+
nil => :white,
|
43
|
+
}
|
44
|
+
|
45
|
+
def self.parse_date(date)
|
46
|
+
DateTime.strptime(date, "%Y-%m-%d %H:%M:%S")
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.from_line(line)
|
50
|
+
if line =~ LINE_RE
|
51
|
+
new($1, parse_date($1), $2, $3, $4, $5)
|
52
|
+
else
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def cmd_color
|
58
|
+
CMD_COLORS[cmd]
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
date, time = datestr.split
|
63
|
+
"<grey>[<white>#{date} #{time}<grey>] <#{cmd_color}>#{cmd} <light_cyan>#{name} <light_white>#{v2} <white>(#{v1})".colorize
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
UPM::Tool.new "pacman" do
|
2
|
+
|
3
|
+
os "arch"
|
4
|
+
|
5
|
+
bin = ["pacman", "--color=always"]
|
6
|
+
|
7
|
+
command "install", [*bin, "-S"], root: true
|
8
|
+
command "update", [*bin, "-Sy"], root: true
|
9
|
+
command "upgrade", [*bin, "-Syu"], root: true
|
10
|
+
command "remove", [*bin, "-R"], root: true
|
11
|
+
|
12
|
+
command "files", [*bin, "-Ql"], paged: true
|
13
|
+
command "search", [*bin, "-Ss"], paged: true
|
14
|
+
|
15
|
+
command "info" do |args|
|
16
|
+
run(*bin, "-Qi", *args) || run(*bin, "-Si", *args)
|
17
|
+
end
|
18
|
+
|
19
|
+
command "list" do |args|
|
20
|
+
if args.any?
|
21
|
+
query = args.join
|
22
|
+
run(*bin, "-Q", grep: query, paged: true)
|
23
|
+
else
|
24
|
+
run(*bin, "-Q", paged: true)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
command "mirrors" do
|
29
|
+
print_files("/etc/pacman.d/mirrorlist", exclude: /^(#|$)/)
|
30
|
+
end
|
31
|
+
|
32
|
+
command "depends" do |args|
|
33
|
+
packages_that_depend_on = proc do |package|
|
34
|
+
result = []
|
35
|
+
|
36
|
+
[`pacman -Sii #{package}`, `pacman -Qi #{package}`].each do |output|
|
37
|
+
output.each_line do |l|
|
38
|
+
if l =~ /Required By\s+: (.+)/
|
39
|
+
result += $1.strip.split unless $1["None"]
|
40
|
+
break
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
result
|
46
|
+
end
|
47
|
+
|
48
|
+
args.each do |arg|
|
49
|
+
puts "=== Packages which depend on: #{arg} ============"
|
50
|
+
packages = packages_that_depend_on.call(arg)
|
51
|
+
puts
|
52
|
+
run *bin, "-Ss", "^(#{packages.join '|'})$" # upstream packages
|
53
|
+
run *bin, "-Qs", "^(#{packages.join '|'})$" # packages that are only installed locally
|
54
|
+
puts
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
command "log" do
|
59
|
+
UPM::LogParser.new(PacmanEvent, "/var/log/pacman.log*").display
|
60
|
+
end
|
61
|
+
|
62
|
+
class PacmanEvent < Struct.new(:datestr, :date, :cmd, :name, :v1, :v2)
|
63
|
+
|
64
|
+
# [2015-01-04 04:21] [PACMAN] installed lib32-libidn (1.29-1)
|
65
|
+
# [2015-01-04 04:21] [PACMAN] upgraded lib32-curl (7.38.0-1 -> 7.39.0-1)
|
66
|
+
# [2015-01-07 04:39] [ALPM] upgraded intel-tbb (4.3_20141023-1 -> 4.3_20141204-1)
|
67
|
+
# [2015-01-07 04:39] [ALPM] upgraded iso-codes (3.54-1 -> 3.57-1)
|
68
|
+
|
69
|
+
DATE_RE = /[\d:-]+/
|
70
|
+
LINE_RE = /^\[(#{DATE_RE} #{DATE_RE})\](?: \[(?:PACMAN|ALPM)\])? (removed|installed|upgraded) (.+) \((.+)(?: -> (.+))?\)$/
|
71
|
+
|
72
|
+
CMD_COLORS = {
|
73
|
+
'removed' => :light_red,
|
74
|
+
'installed' => :light_yellow,
|
75
|
+
'upgraded' => :light_green,
|
76
|
+
nil => :white,
|
77
|
+
}
|
78
|
+
|
79
|
+
def self.parse_date(date)
|
80
|
+
DateTime.strptime(date, "%Y-%m-%d %H:%M")
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.from_line(line)
|
84
|
+
if line =~ LINE_RE
|
85
|
+
new($1, parse_date($1), $2, $3, $4, $5)
|
86
|
+
else
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def cmd_color
|
92
|
+
CMD_COLORS[cmd]
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_s
|
96
|
+
date, time = datestr.split
|
97
|
+
"<grey>[<white>#{date} #{time}<grey>] <#{cmd_color}>#{cmd} <light_cyan>#{name} #{"<light_white>#{v2} " if v2}<white>(#{v1})".colorize
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
UPM::Tool.new "xbps" do
|
2
|
+
|
3
|
+
os "void"
|
4
|
+
|
5
|
+
command "install", "xbps-install", root: true
|
6
|
+
command "update", "xbps-install -S", root: true
|
7
|
+
command "upgrade", "xbps-install -Su", root: true
|
8
|
+
command "files", "xbps-query -f"
|
9
|
+
command "search", "xbps-query --regex -Rs"
|
10
|
+
# command "info", ""
|
11
|
+
|
12
|
+
command "list" do |args|
|
13
|
+
if args.any?
|
14
|
+
run "xbps-query", "-f", *args
|
15
|
+
else
|
16
|
+
run "xbps-query", "-l"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/lib/upm.rb
ADDED
metadata
CHANGED
@@ -1,29 +1,42 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: upm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- epitron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-05-04 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Wrap all known command-line package tools with a consistent and pretty
|
14
14
|
interface.
|
15
15
|
email: chris@ill-logic.com
|
16
|
-
executables:
|
16
|
+
executables:
|
17
|
+
- upm
|
17
18
|
extensions: []
|
18
19
|
extra_rdoc_files:
|
19
20
|
- README.md
|
20
21
|
- LICENSE
|
21
22
|
files:
|
22
23
|
- ".gemspec"
|
24
|
+
- ".gitignore"
|
23
25
|
- LICENSE
|
24
26
|
- README.md
|
25
27
|
- Rakefile
|
28
|
+
- TODO.md
|
26
29
|
- VERSION
|
30
|
+
- bin/upm
|
31
|
+
- lib/upm.rb
|
32
|
+
- lib/upm/colored.rb
|
33
|
+
- lib/upm/core_ext.rb
|
34
|
+
- lib/upm/lesspipe.rb
|
35
|
+
- lib/upm/log_parser.rb
|
36
|
+
- lib/upm/tool.rb
|
37
|
+
- lib/upm/tools/apt.rb
|
38
|
+
- lib/upm/tools/pacman.rb
|
39
|
+
- lib/upm/tools/xbps.rb
|
27
40
|
homepage: http://github.com/epitron/upm/
|
28
41
|
licenses:
|
29
42
|
- WTFPL
|
@@ -44,9 +57,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
44
57
|
version: '0'
|
45
58
|
requirements: []
|
46
59
|
rubyforge_project:
|
47
|
-
rubygems_version: 2.
|
60
|
+
rubygems_version: 2.7.6
|
48
61
|
signing_key:
|
49
62
|
specification_version: 4
|
50
63
|
summary: Universal Package Manager
|
51
64
|
test_files: []
|
52
|
-
has_rdoc:
|