upm 0.1.6 → 0.1.7
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 +4 -4
- data/TODO.md +25 -16
- data/VERSION +1 -1
- data/bin/upm +9 -2
- data/lib/upm/core_ext.rb +29 -12
- data/lib/upm/lesspipe.rb +10 -0
- data/lib/upm/pacman_verifier.rb +124 -0
- data/lib/upm/tool.rb +20 -163
- data/lib/upm/tool_class_methods.rb +59 -0
- data/lib/upm/tool_dsl.rb +144 -0
- data/lib/upm/tools/pacman.rb +11 -2
- data/lib/upm/tools/pkg.rb +24 -0
- data/lib/upm/tools/xbps.rb +9 -8
- data/spec/core_ext_spec.rb +18 -0
- data/spec/spec_helper.rb +1 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8a190f9686ce55b16d74b0ff642047a17e19683187a40d40cebfc542262fcff
|
4
|
+
data.tar.gz: 470640b6500e070840f9f03131e0087d2b3fa3ab25d6450dee3d130463f082b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39453d5478a636f20c45580e91f5a7c9251a0258274e1da16a9736e5b09a74ba7cf4204c65205f44430a35b4a2c7a316a518cd3f5eead7e448723afe747b8e28
|
7
|
+
data.tar.gz: 2b761cbb9ea039c2257e263b46302df7e3c55b4c31849366a5f535141224aa9b1e52a400b75fec88a55486f9cc35a21f96e0d8990a124eb2f0c695e0d873e66c
|
data/TODO.md
CHANGED
@@ -1,18 +1,29 @@
|
|
1
1
|
# TODO
|
2
2
|
|
3
|
+
## Pipes and filters
|
4
|
+
|
5
|
+
* `Tool::DSL#run` is currently somewhat awkward; it would be simpler if returned an `Enumerator`, which could then be filtered (ie: highlight/grep), or concatenated to other `Enumerator`s.
|
6
|
+
|
7
|
+
## Streaming pipes with colours
|
8
|
+
|
9
|
+
* Make the `run` command able to grep the output while streaming the results to the screen.
|
10
|
+
* Make run pretend to be a tty, so I don't need `--color=always`.
|
11
|
+
* Use spawn, like so:
|
12
|
+
```
|
13
|
+
r,w = IO.pipe
|
14
|
+
spawn(*%w[echo hello world], out: w)
|
15
|
+
spawn(*%w[tr a-z A-Z], in: r)
|
16
|
+
```
|
17
|
+
|
3
18
|
## More package managers
|
4
19
|
|
5
20
|
Currently missing:
|
6
21
|
* RedHat/Fedora/CentOS
|
7
22
|
* OSX
|
8
|
-
* FreeBSD
|
23
|
+
* <s>FreeBSD</s>
|
9
24
|
* OpenBSD
|
10
25
|
* SuSE
|
11
26
|
|
12
|
-
## Abbrev cmds
|
13
|
-
|
14
|
-
* eg: upm install => upm i => u i
|
15
|
-
|
16
27
|
## Dependency-fetching features
|
17
28
|
|
18
29
|
* Use upm to fetch dependencies for any library or script, across any language.
|
@@ -37,17 +48,6 @@ Use fzf for "list" output (or other commands that require selecting, like "remov
|
|
37
48
|
* upm install --help
|
38
49
|
* upm help install
|
39
50
|
|
40
|
-
## Streaming pipes with colours
|
41
|
-
|
42
|
-
* Make the `run` command able to grep the output while streaming the results to the screen.
|
43
|
-
* Make run pretend to be a tty, so I don't need `--color=always`.
|
44
|
-
* Use spawn, like so:
|
45
|
-
```
|
46
|
-
r,w = IO.pipe
|
47
|
-
spawn(*%w[echo hello world], out: w)
|
48
|
-
spawn(*%w[tr a-z A-Z], in: r)
|
49
|
-
```
|
50
|
-
|
51
51
|
## Figure out how to integrate language package managers
|
52
52
|
|
53
53
|
* 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?
|
@@ -88,3 +88,12 @@ Don't print backtrace when ^C is pressed.
|
|
88
88
|
## Tests
|
89
89
|
|
90
90
|
Create fake OS environments that you can chroot into and run upm to test it out.
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
# DONE
|
95
|
+
|
96
|
+
## Abbrev cmds
|
97
|
+
|
98
|
+
* eg: upm install => upm i => u i
|
99
|
+
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.7
|
data/bin/upm
CHANGED
@@ -4,11 +4,18 @@ $LOAD_PATH.unshift(File.expand_path(File.join('..', 'lib'), bin_dir))
|
|
4
4
|
|
5
5
|
require 'upm'
|
6
6
|
|
7
|
-
tool = UPM::Tool.for_os
|
7
|
+
unless tool = UPM::Tool.for_os
|
8
|
+
$stderr.puts "Error: I don't recognize this OS, or its package manager."
|
9
|
+
exit 1
|
10
|
+
end
|
11
|
+
|
8
12
|
command, *args = ARGV
|
9
13
|
|
10
14
|
if command.nil?
|
11
15
|
tool.help
|
12
16
|
else
|
13
|
-
|
17
|
+
begin
|
18
|
+
tool.call_command command, *args
|
19
|
+
rescue Interrupt
|
20
|
+
end
|
14
21
|
end
|
data/lib/upm/core_ext.rb
CHANGED
@@ -5,21 +5,38 @@ class DateTime
|
|
5
5
|
end
|
6
6
|
|
7
7
|
class File
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
|
9
|
+
#
|
10
|
+
# Overly clever which(), which returns an array if more than one argument was supplied,
|
11
|
+
# or string/nil if only one argument was supplied.
|
12
|
+
#
|
13
|
+
def self.which(*bins)
|
14
|
+
results = []
|
15
|
+
bins = bins.flatten
|
16
|
+
paths = ENV["PATH"].split(":").map { |path| File.realpath(path) rescue nil }.compact.uniq
|
17
|
+
|
18
|
+
paths.each do |dir|
|
19
|
+
bins.each do |bin|
|
20
|
+
|
21
|
+
full_path = File.join(dir, bin)
|
22
|
+
|
23
|
+
if File.exists?(full_path)
|
24
|
+
if bins.size == 1
|
25
|
+
return full_path
|
26
|
+
else
|
27
|
+
results << full_path
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
12
32
|
end
|
13
|
-
|
33
|
+
|
34
|
+
bins.size == 1 ? nil : results
|
14
35
|
end
|
15
36
|
|
16
37
|
def self.which_is_best?(*bins)
|
17
|
-
bins.flatten
|
18
|
-
|
19
|
-
return location
|
20
|
-
end
|
21
|
-
end
|
22
|
-
nil
|
38
|
+
result = which(*bins.flatten)
|
39
|
+
result.is_a?(Array) ? result.first : result
|
23
40
|
end
|
24
41
|
end
|
25
42
|
|
@@ -147,4 +164,4 @@ module Enumerable
|
|
147
164
|
end
|
148
165
|
|
149
166
|
alias_method :cut_between, :split_between
|
150
|
-
end
|
167
|
+
end
|
data/lib/upm/lesspipe.rb
CHANGED
@@ -33,6 +33,16 @@ def lesspipe(*args)
|
|
33
33
|
|
34
34
|
output = args.first if args.any?
|
35
35
|
|
36
|
+
# Don't page, just output to STDOUT
|
37
|
+
if options[:disabled]
|
38
|
+
if output
|
39
|
+
puts output
|
40
|
+
else
|
41
|
+
yield STDOUT
|
42
|
+
end
|
43
|
+
return
|
44
|
+
end
|
45
|
+
|
36
46
|
params = []
|
37
47
|
params << "-R" unless options[:color] == false
|
38
48
|
params << "-S" unless options[:wrap] == true
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'digest/sha2'
|
3
|
+
require 'digest/md5'
|
4
|
+
require 'pp'
|
5
|
+
|
6
|
+
module UPM
|
7
|
+
class PacmanVerifier
|
8
|
+
|
9
|
+
SKIP_FILES = %w[
|
10
|
+
/.BUILDINFO
|
11
|
+
/.INSTALL
|
12
|
+
/.PKGINFO
|
13
|
+
/.CHANGELOG
|
14
|
+
]
|
15
|
+
|
16
|
+
PACKAGE_ROOT = "/var/lib/pacman/local/"
|
17
|
+
|
18
|
+
def compare(key, a, b)
|
19
|
+
a == b ? nil : [key, a, b]
|
20
|
+
end
|
21
|
+
|
22
|
+
def verify!(*included)
|
23
|
+
$stderr.puts "Checking integrity of #{included.any? ? included.size : "installed"} packages..."
|
24
|
+
|
25
|
+
report = []
|
26
|
+
|
27
|
+
Dir.entries(PACKAGE_ROOT).each do |package_dir|
|
28
|
+
mtree_path = File.join(PACKAGE_ROOT, package_dir, "mtree")
|
29
|
+
next unless File.exists?(mtree_path)
|
30
|
+
|
31
|
+
chunks = package_dir.split("-")
|
32
|
+
version = chunks[-2..-1].join("-")
|
33
|
+
package = chunks[0...-2].join("-")
|
34
|
+
|
35
|
+
next unless included.any? and included.include?(package)
|
36
|
+
|
37
|
+
puts "<8>[<7>+<8>] <10>#{package} <3>#{version}".colorize
|
38
|
+
|
39
|
+
result = []
|
40
|
+
defaults = {}
|
41
|
+
|
42
|
+
Zlib::GzipReader.open(mtree_path) do |io|
|
43
|
+
lines = io.each_line.drop(1)
|
44
|
+
|
45
|
+
lines.each do |line|
|
46
|
+
path, *expected = line.split
|
47
|
+
expected = expected.map { |opt| opt.split("=") }.to_h
|
48
|
+
|
49
|
+
if path == "/set"
|
50
|
+
defaults = expected
|
51
|
+
next
|
52
|
+
end
|
53
|
+
|
54
|
+
path = path[1..-1] if path[0] == "."
|
55
|
+
path = path.gsub(/\\(\d{3})/) { |m| $1.to_i(8).chr } # unescape \### codes
|
56
|
+
|
57
|
+
# next if expected["type"] == "dir"
|
58
|
+
next if SKIP_FILES.include?(path)
|
59
|
+
|
60
|
+
expected = defaults.merge(expected)
|
61
|
+
lstat = File.lstat(path)
|
62
|
+
|
63
|
+
errors = expected.map do |key, val|
|
64
|
+
case key
|
65
|
+
when "type"
|
66
|
+
compare("type", lstat.ftype[0...val.size], val)
|
67
|
+
when "link"
|
68
|
+
next if val == "/dev/null"
|
69
|
+
compare("link", File.readlink(path), val)
|
70
|
+
when "gid"
|
71
|
+
compare("gid", lstat.gid, val.to_i)
|
72
|
+
when "uid"
|
73
|
+
compare("uid", lstat.uid, val.to_i)
|
74
|
+
when "mode"
|
75
|
+
compare("mode", "%o" % (lstat.mode & 0xFFF), val)
|
76
|
+
when "size"
|
77
|
+
compare("size", lstat.size, val.to_i)
|
78
|
+
when "time"
|
79
|
+
next if expected["type"] == "dir"
|
80
|
+
next if expected["link"] == "/dev/null"
|
81
|
+
compare("time", lstat.mtime.to_i, val.to_i)
|
82
|
+
when "sha256digest"
|
83
|
+
compare("sha256digest", Digest::SHA256.file(path).hexdigest, val)
|
84
|
+
when "md5digest"
|
85
|
+
next if expected["sha256digest"]
|
86
|
+
compare("md5digest", Digest::MD5.file(path).hexdigest, val)
|
87
|
+
else
|
88
|
+
raise "Unknown key: #{key}=#{val}"
|
89
|
+
end
|
90
|
+
end.compact
|
91
|
+
|
92
|
+
if errors.any?
|
93
|
+
puts " <4>[<12>*<4>] <11>#{path}".colorize
|
94
|
+
errors.each do |key, a, e| # a=actual, e=expected
|
95
|
+
puts " <7>expected <14>#{key} <7>to be <2>#{e} <7>but was <4>#{a}".colorize
|
96
|
+
result << [path, "expected #{key.inspect} to be #{e.inspect} but was #{a.inspect}"]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
rescue Errno::EACCES
|
100
|
+
puts " <1>[<9>!<1>] <11>Can't read <7>#{path} <8>(<9>permission denied<8>)".colorize
|
101
|
+
result << [path, "permission denied"]
|
102
|
+
rescue Errno::ENOENT
|
103
|
+
puts " <4>[<12>?<4>] <12>Missing file <15>#{path}".colorize
|
104
|
+
result << [path, "missing"]
|
105
|
+
end
|
106
|
+
end # gzip
|
107
|
+
|
108
|
+
report << [package, result] if result.any?
|
109
|
+
end # mtree
|
110
|
+
|
111
|
+
puts
|
112
|
+
puts "#{report.size} packages with errors (#{report.map { |result| result.size }.sum} errors total)"
|
113
|
+
puts
|
114
|
+
|
115
|
+
if report.any?
|
116
|
+
puts "Packages with problems:"
|
117
|
+
report.each do |package, errors|
|
118
|
+
puts " #{package} (#{errors.size} errors)"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end # verify!
|
122
|
+
|
123
|
+
end # PacmanAuditor
|
124
|
+
end # UPM
|
data/lib/upm/tool.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'upm/tool_dsl'
|
2
|
+
require 'upm/tool_class_methods'
|
1
3
|
|
2
4
|
# os:<pkg> -- automatically select the package manager for the current unix distribution
|
3
5
|
# deb:<pkg> (or d: u:)
|
@@ -10,6 +12,10 @@ module UPM
|
|
10
12
|
|
11
13
|
class Tool
|
12
14
|
|
15
|
+
@@tools = {}
|
16
|
+
|
17
|
+
include UPM::Tool::DSL
|
18
|
+
|
13
19
|
COMMAND_HELP = {
|
14
20
|
"install" => "install a package",
|
15
21
|
"remove/uninstall" => "remove a package",
|
@@ -18,17 +24,20 @@ module UPM
|
|
18
24
|
"list" => "list installed packages (or search their names if extra arguments are supplied)",
|
19
25
|
"info" => "show metadata about a package",
|
20
26
|
"update/sync" => "retrieve the latest package list or manifest",
|
21
|
-
"upgrade" => "
|
27
|
+
"upgrade" => "update package list and install updates",
|
28
|
+
"download" => "download package list and updates, but don't insatall them",
|
22
29
|
"pin" => "pinning a package means it won't be automatically upgraded",
|
23
30
|
"rollback" => "revert to an earlier version of a package (including its dependencies)",
|
31
|
+
"verify/check" => "verify the integrity of packages' files on the filesystem",
|
32
|
+
"audit/vuln" => "show known vulnerabilities in installed packages",
|
24
33
|
"log" => "show history of package installs",
|
25
34
|
"packagers" => "detect installed package managers, and pick which ones upm should wrap",
|
26
35
|
"mirrors/sources" => "manage remote repositories and mirrors",
|
27
|
-
"verfiy" => "verify the integrity of installed files",
|
28
36
|
"clean" => "clear out the local package cache",
|
29
37
|
"monitor" => "ad-hoc package manager for custom installations (like instmon)",
|
30
38
|
"keys" => "keyrings and package authentication",
|
31
39
|
"default" => "configure the action to take when no arguments are passed to 'upm' (defaults to 'os:update')",
|
40
|
+
"stats" => "show statistics about package database(s)",
|
32
41
|
}
|
33
42
|
|
34
43
|
ALIASES = {
|
@@ -36,58 +45,17 @@ module UPM
|
|
36
45
|
"sync" => "update",
|
37
46
|
"sources" => "mirrors",
|
38
47
|
"show" => "info",
|
48
|
+
"vuln" => "audit",
|
49
|
+
"vulns" => "audit",
|
50
|
+
"check" => "verify",
|
51
|
+
"u" => "upgrade",
|
52
|
+
"i" => "install",
|
53
|
+
"d" => "download",
|
54
|
+
"s" => "search",
|
55
|
+
"f" => "files",
|
56
|
+
"r" => "remove",
|
39
57
|
}
|
40
58
|
|
41
|
-
@@tools = {}
|
42
|
-
def self.tools; @@tools; end
|
43
|
-
|
44
|
-
def self.register_tools!
|
45
|
-
Dir["#{__dir__}/tools/*.rb"].each { |lib| require_relative(lib) }
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.os_release
|
49
|
-
@os_release ||= begin
|
50
|
-
open("/etc/os-release") do |io|
|
51
|
-
io.read.scan(/^(\w+)="?(.+?)"?$/)
|
52
|
-
end.to_h
|
53
|
-
rescue Errno::ENOENT
|
54
|
-
{}
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def self.current_os_names
|
59
|
-
# ID=ubuntu
|
60
|
-
# ID_LIKE=debian
|
61
|
-
os_release.values_at("ID", "ID_LIKE").compact
|
62
|
-
end
|
63
|
-
|
64
|
-
def self.nice_os_name
|
65
|
-
os_release.values_at("PRETTY_NAME", "NAME", "ID", "ID_LIKE").first ||
|
66
|
-
(`uname -o`.chomp rescue nil)
|
67
|
-
end
|
68
|
-
|
69
|
-
def self.for_os(os_names=nil)
|
70
|
-
os_names = os_names ? [os_names].flatten : current_os_names
|
71
|
-
|
72
|
-
tool = nil
|
73
|
-
|
74
|
-
if os_names.any?
|
75
|
-
tool = @@tools.find { |name, tool| os_names.any? { |name| tool.os.include? name } }.last
|
76
|
-
end
|
77
|
-
|
78
|
-
if tool.nil?
|
79
|
-
tool = @@tools.find { |name, tool| File.which(tool.identifying_binary) }.last
|
80
|
-
end
|
81
|
-
|
82
|
-
if tool.nil?
|
83
|
-
puts "Error: couldn't find a package manager."
|
84
|
-
end
|
85
|
-
|
86
|
-
tool
|
87
|
-
end
|
88
|
-
|
89
|
-
###################################################################
|
90
|
-
|
91
59
|
def initialize(name, &block)
|
92
60
|
@name = name
|
93
61
|
instance_eval(&block)
|
@@ -95,117 +63,6 @@ module UPM
|
|
95
63
|
@@tools[name] = self
|
96
64
|
end
|
97
65
|
|
98
|
-
def call_command(name, *args)
|
99
|
-
if block = (@cmds[name] || @cmds[ALIASES[name]])
|
100
|
-
block.call args
|
101
|
-
else
|
102
|
-
puts "Command #{name} not supported in #{@name}"
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def help
|
107
|
-
if osname = Tool.nice_os_name
|
108
|
-
puts " Detected OS: #{osname}"
|
109
|
-
end
|
110
|
-
|
111
|
-
puts "Package manager: #{@name}"
|
112
|
-
puts
|
113
|
-
puts "Available commands:"
|
114
|
-
available = COMMAND_HELP.select do |name, desc|
|
115
|
-
names = name.split("/")
|
116
|
-
names.any? { |name| @cmds[name] }
|
117
|
-
end
|
118
|
-
|
119
|
-
max_width = available.map(&:first).map(&:size).max
|
120
|
-
available.each do |name, desc|
|
121
|
-
puts " #{name.rjust(max_width)} | #{desc}"
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
## DSL methods
|
126
|
-
|
127
|
-
def identifying_binary(id_bin=nil)
|
128
|
-
if id_bin
|
129
|
-
@id_bin = id_bin
|
130
|
-
else
|
131
|
-
@id_bin || @name
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
def prefix(name)
|
136
|
-
@prefix = name
|
137
|
-
end
|
138
|
-
|
139
|
-
def command(name, shell_command=nil, root: false, paged: false, &block)
|
140
|
-
@cmds ||= {}
|
141
|
-
|
142
|
-
if block_given?
|
143
|
-
@cmds[name] = block
|
144
|
-
elsif shell_command
|
145
|
-
if shell_command.is_a? String
|
146
|
-
shell_command = shell_command.split
|
147
|
-
elsif not shell_command.is_a? Array
|
148
|
-
raise "Error: command argument must be a String or an Array; it was a #{cmd.class}"
|
149
|
-
end
|
150
|
-
|
151
|
-
@cmds[name] = proc { |args| run(*shell_command, *args, paged: paged, root: root) }
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
def os(*names)
|
156
|
-
names.any? ? @os = names : @os
|
157
|
-
end
|
158
|
-
|
159
|
-
## Helpers
|
160
|
-
|
161
|
-
def run(*args, root: false, paged: false, grep: nil)
|
162
|
-
if root and File.which("sudo")
|
163
|
-
args.unshift "sudo"
|
164
|
-
end
|
165
|
-
|
166
|
-
if !paged and !grep
|
167
|
-
system(*args)
|
168
|
-
else
|
169
|
-
|
170
|
-
IO.popen(args, err: [:child, :out]) do |command_io|
|
171
|
-
|
172
|
-
if grep
|
173
|
-
pattern = grep.is_a?(Regexp) ? grep.source : grep.to_s
|
174
|
-
grep_io = IO.popen(["grep", "--color=always", "-Ei", pattern], "w+")
|
175
|
-
IO.copy_stream(command_io, grep_io)
|
176
|
-
grep_io.close_write
|
177
|
-
command_io = grep_io
|
178
|
-
end
|
179
|
-
|
180
|
-
if paged
|
181
|
-
lesspipe do |less|
|
182
|
-
IO.copy_stream(command_io, less)
|
183
|
-
end
|
184
|
-
else
|
185
|
-
IO.copy_stream(command_io, STDOUT)
|
186
|
-
end
|
187
|
-
|
188
|
-
end
|
189
|
-
|
190
|
-
$?.to_i == 0
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
def print_files(*paths, include: nil, exclude: nil)
|
195
|
-
lesspipe do |less|
|
196
|
-
paths.each do |path|
|
197
|
-
less.puts "<8>=== <11>#{path} <8>========".colorize
|
198
|
-
open(path) do |io|
|
199
|
-
enum = io.each_line
|
200
|
-
enum = enum.grep(include) if include
|
201
|
-
enum = enum.reject { |line| line[exclude] } if exclude
|
202
|
-
enum.each { |line| less.puts line }
|
203
|
-
end
|
204
|
-
less.puts
|
205
|
-
end
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
66
|
end # class Tool
|
210
67
|
|
211
68
|
end # module UPM
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module UPM
|
2
|
+
class Tool
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def tools; @@tools; end
|
6
|
+
|
7
|
+
def register_tools!
|
8
|
+
Dir["#{__dir__}/tools/*.rb"].each { |lib| require_relative(lib) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def os_release
|
12
|
+
@os_release ||= begin
|
13
|
+
open("/etc/os-release") do |io|
|
14
|
+
io.read.scan(/^(\w+)="?(.+?)"?$/)
|
15
|
+
end.to_h
|
16
|
+
rescue Errno::ENOENT
|
17
|
+
{}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def current_os_names
|
22
|
+
# eg: ID=ubuntu, ID_LIKE=debian
|
23
|
+
if os_release
|
24
|
+
os_release.values_at("ID", "ID_LIKE").compact
|
25
|
+
else
|
26
|
+
# `uname -s` => Darwin|FreeBSD|OpenBSD
|
27
|
+
# `uname -o` => Android|Cygwin
|
28
|
+
[`uname -s`, `uname -o`].map(&:chomp).uniq
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def nice_os_name
|
33
|
+
os_release.values_at("PRETTY_NAME", "NAME", "ID", "ID_LIKE").first ||
|
34
|
+
(`uname -o`.chomp rescue nil)
|
35
|
+
end
|
36
|
+
|
37
|
+
def installed
|
38
|
+
@@tools.select { |tool| File.which(tool.identifying_binary) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def for_os(os_names=nil)
|
42
|
+
os_names = os_names ? [os_names].flatten : current_os_names
|
43
|
+
|
44
|
+
tool = nil
|
45
|
+
|
46
|
+
if os_names.any?
|
47
|
+
tool = @@tools.find { |name, tool| os_names.any? { |name| tool.os.include? name } }&.last
|
48
|
+
end
|
49
|
+
|
50
|
+
if tool.nil?
|
51
|
+
tool = @@tools.find { |name, tool| File.which(tool.identifying_binary) }&.last
|
52
|
+
end
|
53
|
+
|
54
|
+
tool
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/upm/tool_dsl.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
module UPM
|
2
|
+
class Tool
|
3
|
+
module DSL
|
4
|
+
def identifying_binary(id_bin=nil)
|
5
|
+
if id_bin
|
6
|
+
@id_bin = id_bin
|
7
|
+
else
|
8
|
+
@id_bin || @name
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def prefix(name)
|
13
|
+
@prefix = name
|
14
|
+
end
|
15
|
+
|
16
|
+
def command(name, shell_command=nil, root: false, paged: false, highlight: nil, &block)
|
17
|
+
@cmds ||= {}
|
18
|
+
|
19
|
+
if block_given?
|
20
|
+
@cmds[name] = block
|
21
|
+
elsif shell_command
|
22
|
+
if shell_command.is_a? String
|
23
|
+
shell_command = shell_command.split
|
24
|
+
elsif not shell_command.is_a? Array
|
25
|
+
raise "Error: command argument must be a String or an Array; it was a #{cmd.class}"
|
26
|
+
end
|
27
|
+
|
28
|
+
@cmds[name] = proc do |args|
|
29
|
+
query = highlight && args.join(" ")
|
30
|
+
run(*shell_command, *args, paged: paged, root: root, highlight: query)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def os(*names)
|
36
|
+
names.any? ? @os = names : @os
|
37
|
+
end
|
38
|
+
|
39
|
+
## Helpers
|
40
|
+
|
41
|
+
def run(*args, root: false, paged: false, grep: nil, highlight: nil)
|
42
|
+
if root
|
43
|
+
if Process.uid != 0
|
44
|
+
if File.which("sudo")
|
45
|
+
args.unshift "sudo"
|
46
|
+
elsif File.which("su")
|
47
|
+
args = ["su", "-c"] + args
|
48
|
+
else
|
49
|
+
raise "Error: You must be root to run this command. (And I couldn't find the 'sudo' *or* 'su' commands.)"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
if !paged and !grep
|
55
|
+
system(*args)
|
56
|
+
else
|
57
|
+
|
58
|
+
IO.popen(args, err: [:child, :out]) do |command_io|
|
59
|
+
|
60
|
+
# if grep
|
61
|
+
# pattern = grep.is_a?(Regexp) ? grep.source : grep.to_s
|
62
|
+
# grep_io = IO.popen(["grep", "--color=always", "-Ei", pattern], "w+")
|
63
|
+
# IO.copy_stream(command_io, grep_io)
|
64
|
+
# grep_io.close_write
|
65
|
+
# command_io = grep_io
|
66
|
+
# end
|
67
|
+
|
68
|
+
# if paged
|
69
|
+
# lesspipe do |less|
|
70
|
+
# IO.copy_stream(command_io, less)
|
71
|
+
# end
|
72
|
+
# else
|
73
|
+
# IO.copy_stream(command_io, STDOUT)
|
74
|
+
# end
|
75
|
+
|
76
|
+
highlight_proc = if highlight
|
77
|
+
proc { |line| line.gsub(highlight) { |m| "\e[33;1m#{m}\e[0m" } }
|
78
|
+
else
|
79
|
+
proc { |line| line }
|
80
|
+
end
|
81
|
+
|
82
|
+
grep_proc = if grep
|
83
|
+
proc { |line| line[grep] }
|
84
|
+
else
|
85
|
+
proc { true }
|
86
|
+
end
|
87
|
+
|
88
|
+
lesspipe(disabled: !paged) do |less|
|
89
|
+
command_io.each_line do |line|
|
90
|
+
less.puts highlight_proc.(line) if grep_proc.(line)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
$?.to_i == 0
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def print_files(*paths, include: nil, exclude: nil)
|
101
|
+
lesspipe do |less|
|
102
|
+
paths.each do |path|
|
103
|
+
less.puts "<8>=== <11>#{path} <8>========".colorize
|
104
|
+
open(path) do |io|
|
105
|
+
enum = io.each_line
|
106
|
+
enum = enum.grep(include) if include
|
107
|
+
enum = enum.reject { |line| line[exclude] } if exclude
|
108
|
+
enum.each { |line| less.puts line }
|
109
|
+
end
|
110
|
+
less.puts
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def call_command(name, *args)
|
116
|
+
if block = (@cmds[name] || @cmds[ALIASES[name]])
|
117
|
+
block.call args
|
118
|
+
else
|
119
|
+
puts "Command #{name} not supported in #{@name}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def help
|
124
|
+
if osname = Tool.nice_os_name
|
125
|
+
puts " Detected OS: #{osname}"
|
126
|
+
end
|
127
|
+
|
128
|
+
puts "Package manager: #{@name}"
|
129
|
+
puts
|
130
|
+
puts "Available commands:"
|
131
|
+
available = COMMAND_HELP.select do |name, desc|
|
132
|
+
names = name.split("/")
|
133
|
+
names.any? { |name| @cmds[name] }
|
134
|
+
end
|
135
|
+
|
136
|
+
max_width = available.map(&:first).map(&:size).max
|
137
|
+
available.each do |name, desc|
|
138
|
+
puts " #{name.rjust(max_width)} | #{desc}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end # DSL
|
143
|
+
end # Tool
|
144
|
+
end # UPM
|
data/lib/upm/tools/pacman.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
|
1
2
|
UPM::Tool.new "pacman" do
|
2
3
|
|
3
4
|
os "arch"
|
@@ -9,17 +10,25 @@ UPM::Tool.new "pacman" do
|
|
9
10
|
command "upgrade", [*bin, "-Syu"], root: true
|
10
11
|
command "remove", [*bin, "-R"], root: true
|
11
12
|
|
13
|
+
# command "check", [*bin, "-Qkk"]
|
14
|
+
command "verify" do |args|
|
15
|
+
require 'upm/pacman_verifier'
|
16
|
+
UPM::PacmanVerifier.new.verify!(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
command "audit", "arch-audit", paged: true
|
12
20
|
command "files", [*bin, "-Ql"], paged: true
|
13
21
|
command "search", [*bin, "-Ss"], paged: true
|
14
22
|
|
23
|
+
|
15
24
|
command "info" do |args|
|
16
|
-
run(*bin, "-Qi", *args) || run(*bin, "-Si", *args)
|
25
|
+
run(*bin, "-Qi", *args, paged: true) || run(*bin, "-Si", *args, paged: true)
|
17
26
|
end
|
18
27
|
|
19
28
|
command "list" do |args|
|
20
29
|
if args.any?
|
21
30
|
query = args.join
|
22
|
-
run(*bin, "-Q", grep: query, paged: true)
|
31
|
+
run(*bin, "-Q", grep: query, highlight: query, paged: true)
|
23
32
|
else
|
24
33
|
run(*bin, "-Q", paged: true)
|
25
34
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
UPM::Tool.new "pkg" do
|
2
|
+
|
3
|
+
os "FreeBSD"
|
4
|
+
|
5
|
+
command "install", "pkg install", root: true
|
6
|
+
command "update", "pkg update", root: true
|
7
|
+
command "upgrade", "pkg upgrade", root: true
|
8
|
+
command "info", "pkg clean", root: true
|
9
|
+
command "check", "pkg check --checksums", root: true
|
10
|
+
|
11
|
+
command "files", "pkg list", paged: true
|
12
|
+
command "search", "pkg search", paged: true, highlight: true
|
13
|
+
command "info", "pkg info", paged: true
|
14
|
+
|
15
|
+
command "list" do |args|
|
16
|
+
if args.any?
|
17
|
+
query = args.join
|
18
|
+
run "pkg", "info", grep: query, highlight: query, paged: true
|
19
|
+
else
|
20
|
+
run "pkg", "info", paged: true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/upm/tools/xbps.rb
CHANGED
@@ -4,18 +4,19 @@ UPM::Tool.new "xbps" do
|
|
4
4
|
|
5
5
|
identifying_binary "xbps-install"
|
6
6
|
|
7
|
-
command "install", "xbps-install",
|
8
|
-
command "update", "xbps-install -S",
|
9
|
-
command "upgrade", "xbps-install -Su",
|
10
|
-
|
11
|
-
command "
|
12
|
-
|
7
|
+
command "install", "xbps-install", root: true
|
8
|
+
command "update", "xbps-install -S", root: true
|
9
|
+
command "upgrade", "xbps-install -Su", root: true
|
10
|
+
|
11
|
+
command "files", "xbps-query -f", paged: true
|
12
|
+
command "search", "xbps-query --regex -Rs", paged: true
|
13
13
|
|
14
14
|
command "list" do |args|
|
15
15
|
if args.any?
|
16
|
-
|
16
|
+
query = args.join
|
17
|
+
run "xbps-query", "-l", grep: query, paged: true
|
17
18
|
else
|
18
|
-
run "xbps-query", "-l"
|
19
|
+
run "xbps-query", "-l", paged: true
|
19
20
|
end
|
20
21
|
end
|
21
22
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
require "upm/core_ext"
|
3
|
+
|
4
|
+
describe File do
|
5
|
+
|
6
|
+
it "whiches" do
|
7
|
+
File.which("ls").should == "/usr/bin/ls"
|
8
|
+
File.which("ls", "rm").should == ["/usr/bin/ls", "/usr/bin/rm"]
|
9
|
+
File.which("zzzzzzzzzzzzzzzzzzzzzzzzzzzz").should == nil
|
10
|
+
end
|
11
|
+
|
12
|
+
it "which_is_best?s" do
|
13
|
+
File.which_is_best?("ls", "rm", "sudo").should == "/usr/bin/ls"
|
14
|
+
File.which_is_best?("sudo").should == "/usr/bin/sudo"
|
15
|
+
File.which_is_best?("zzzzzzzzzzzzzzzzzz").should == nil
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
$:.unshift File.join(__dir__, "../lib")
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: upm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- epitron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-05-
|
11
|
+
date: 2018-05-19 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.
|
@@ -33,10 +33,16 @@ files:
|
|
33
33
|
- lib/upm/core_ext.rb
|
34
34
|
- lib/upm/lesspipe.rb
|
35
35
|
- lib/upm/log_parser.rb
|
36
|
+
- lib/upm/pacman_verifier.rb
|
36
37
|
- lib/upm/tool.rb
|
38
|
+
- lib/upm/tool_class_methods.rb
|
39
|
+
- lib/upm/tool_dsl.rb
|
37
40
|
- lib/upm/tools/apt.rb
|
38
41
|
- lib/upm/tools/pacman.rb
|
42
|
+
- lib/upm/tools/pkg.rb
|
39
43
|
- lib/upm/tools/xbps.rb
|
44
|
+
- spec/core_ext_spec.rb
|
45
|
+
- spec/spec_helper.rb
|
40
46
|
homepage: http://github.com/epitron/upm/
|
41
47
|
licenses:
|
42
48
|
- WTFPL
|