upm 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+

|
93
|
+
|
94
|
+
Log:
|
95
|
+
|
96
|
+

|
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
|
+

|
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:
|