upm 0.1.6 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|