upm 0.1.16 → 0.1.17
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/TODO.md +13 -19
- data/VERSION +1 -1
- data/bin/upm +10 -7
- data/lib/upm/core_ext/file.rb +159 -0
- data/lib/upm/tool.rb +8 -0
- data/lib/upm/tool_dsl.rb +48 -6
- data/lib/upm/tools/pacman.rb +1 -0
- data/lib/upm/tools/pkg.rb +32 -10
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c5ec80be1569a58c23402685f57f02f46cebd10ae87b2684eb2aa8d132aa7cf
|
4
|
+
data.tar.gz: d2fee9bb7b69d36df657e749a109dd29cdc3bbabef9b83b7a1731f83afa504f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1c9ea83290760c4fc2c7ea7cab2580ea50ef5053751d49e7414f6d57b7b2e84b35064ec5ae4dbece2c5f00b5c7456ff80d72caf084d4c43a9668834937e25e8
|
7
|
+
data.tar.gz: 51dbea8086d5162b4f5b90b83028e924195f34a293c2481c05cc3e4e573752f1291f7bb73d2ecf0941ee6fbd351c6294f347c6f2ee0b4250065fc99cb7fdb6ce
|
data/TODO.md
CHANGED
@@ -1,19 +1,27 @@
|
|
1
1
|
# TODO
|
2
2
|
|
3
|
-
##
|
3
|
+
## UI
|
4
|
+
* fzf
|
5
|
+
* search
|
4
6
|
|
5
|
-
|
7
|
+
## Options
|
8
|
+
* Proper option/command parser
|
9
|
+
* Verbose mode (prints `run` commands)
|
6
10
|
|
7
|
-
##
|
11
|
+
## DSL
|
12
|
+
* Call commands from within other commands, or specify dependencies (eg: command "install", "pkg install", depends: "update" )
|
13
|
+
* DSL setting defaults (eg: cache_dir "~/.cache/upm")
|
14
|
+
|
15
|
+
## Performance
|
16
|
+
* RPi2 is very clunky
|
8
17
|
|
18
|
+
## Custom help for command
|
9
19
|
eg: command "something", help: "does stuff", root: true do ... end
|
10
20
|
|
11
21
|
## Pipes and filters
|
12
|
-
|
13
22
|
* `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.
|
14
23
|
|
15
24
|
## Streaming pipes with colours
|
16
|
-
|
17
25
|
* Make the `run` command able to grep the output while streaming the results to the screen.
|
18
26
|
* Make run pretend to be a tty, so I don't need `--color=always`.
|
19
27
|
* Use spawn, like so:
|
@@ -24,7 +32,6 @@ spawn(*%w[tr a-z A-Z], in: r)
|
|
24
32
|
```
|
25
33
|
|
26
34
|
## More package managers
|
27
|
-
|
28
35
|
Currently missing:
|
29
36
|
* RedHat/Fedora/CentOS
|
30
37
|
* OSX
|
@@ -33,31 +40,26 @@ Currently missing:
|
|
33
40
|
* SuSE
|
34
41
|
|
35
42
|
## Dependency-fetching features
|
36
|
-
|
37
43
|
* Use upm to fetch dependencies for any library or script, across any language.
|
38
44
|
* Some kind of manifest file (or personifest, to be politically correct)
|
39
45
|
* This is a little like bundler, npm, etc., but for any type of package.
|
40
46
|
|
41
47
|
## Ability to install any package from any OS to the user's home directory
|
42
|
-
|
43
48
|
Slurps up the packages and their dependencies, then unpacks them into ~/.upm/{bin,lib} or something.
|
44
49
|
(Like nix?)
|
45
50
|
|
46
51
|
Related tool: intoli/exodus
|
47
52
|
|
48
53
|
## fzf
|
49
|
-
|
50
54
|
Use fzf for "list" output (or other commands that require selecting, like "remove")
|
51
55
|
|
52
56
|
## Commandline argument parser
|
53
|
-
|
54
57
|
* Add options to commands:
|
55
58
|
* upm upgrade --download-only
|
56
59
|
* upm install --help
|
57
60
|
* upm help install
|
58
61
|
|
59
62
|
## Figure out how to integrate language package managers
|
60
|
-
|
61
63
|
* 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?
|
62
64
|
* 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)
|
63
65
|
* Possibilites:
|
@@ -72,36 +74,28 @@ Use fzf for "list" output (or other commands that require selecting, like "remov
|
|
72
74
|
* `upm help --ruby` should show available ruby commands
|
73
75
|
|
74
76
|
## Give identical output on every platform
|
75
|
-
|
76
77
|
* Requires parsing the output of every command into a canonical format, or reading the package databases directly.
|
77
78
|
|
78
79
|
## apt: Integrate 'acs' wrapper script
|
79
80
|
|
80
81
|
## Evaluate UPM::Tool.new block in an instance of an anonymous subclass of UPM::Tool
|
81
|
-
|
82
82
|
This will allow tools to create classes and modules inside the UPM::Tool block without namespace collisions
|
83
83
|
|
84
84
|
## Themes?
|
85
|
-
|
86
85
|
Why not!
|
87
86
|
|
88
87
|
## Mirror Selector
|
89
|
-
|
90
88
|
Do a ping test on available mirrors, and use fzf to select.
|
91
89
|
|
92
90
|
## Interrupt catcher
|
93
|
-
|
94
91
|
Don't print backtrace when ^C is pressed.
|
95
92
|
|
96
93
|
## Tests
|
97
|
-
|
98
94
|
Create fake OS environments that you can chroot into and run upm to test it out.
|
99
95
|
|
100
96
|
|
101
|
-
|
102
97
|
# DONE
|
103
98
|
|
104
99
|
## Abbrev cmds
|
105
|
-
|
106
100
|
* eg: upm install => upm i => u i
|
107
101
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.17
|
data/bin/upm
CHANGED
@@ -9,13 +9,16 @@ unless tool = UPM::Tool.for_os
|
|
9
9
|
exit 1
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
if command.nil?
|
12
|
+
if ARGV.any? { |arg| ["help", "version", "--help", "--version", "-h", "-v"].include? arg }
|
15
13
|
tool.help
|
16
14
|
else
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
command, *args = ARGV
|
16
|
+
if command.nil?
|
17
|
+
tool.help
|
18
|
+
else
|
19
|
+
begin
|
20
|
+
tool.call_command command, *args
|
21
|
+
rescue Interrupt
|
22
|
+
end
|
20
23
|
end
|
21
|
-
end
|
24
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
#
|
2
|
+
# Beginning of File reached! (Raised when reading a file backwards.)
|
3
|
+
#
|
4
|
+
class BOFError < Exception; end
|
5
|
+
|
6
|
+
class File
|
7
|
+
|
8
|
+
#
|
9
|
+
# A streaming `reverse_each` implementation. (For large files, it's faster and uses less memory.)
|
10
|
+
#
|
11
|
+
def reverse_each(&block)
|
12
|
+
return to_enum(:reverse_each) unless block_given?
|
13
|
+
|
14
|
+
seek_end
|
15
|
+
reverse_each_from_current_pos(&block)
|
16
|
+
end
|
17
|
+
alias_method :reverse_each_line, :reverse_each
|
18
|
+
|
19
|
+
#
|
20
|
+
# Read the previous `length` bytes. After the read, `pos` will be at the beginning of the region that you just read.
|
21
|
+
# Returns `nil` when the beginning of the file is reached.
|
22
|
+
#
|
23
|
+
# If the `block_aligned` argument is `true`, reads will always be aligned to file positions which are multiples of 512 bytes.
|
24
|
+
# (This should increase performance slightly.)
|
25
|
+
#
|
26
|
+
def reverse_read(length, block_aligned=false)
|
27
|
+
raise "length must be a multiple of 512" if block_aligned and length % 512 != 0
|
28
|
+
|
29
|
+
end_pos = pos
|
30
|
+
return nil if end_pos == 0
|
31
|
+
|
32
|
+
if block_aligned
|
33
|
+
misalignment = end_pos % length
|
34
|
+
length += misalignment
|
35
|
+
end
|
36
|
+
|
37
|
+
if length >= end_pos # this read will take us to the beginning of the file
|
38
|
+
seek(0)
|
39
|
+
else
|
40
|
+
seek(-length, IO::SEEK_CUR)
|
41
|
+
end
|
42
|
+
|
43
|
+
start_pos = pos
|
44
|
+
data = read(end_pos - start_pos)
|
45
|
+
seek(start_pos)
|
46
|
+
|
47
|
+
data
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Read each line of file backwards (from the current position.)
|
52
|
+
#
|
53
|
+
def reverse_each_from_current_pos
|
54
|
+
return to_enum(:reverse_each_from_current_pos) unless block_given?
|
55
|
+
|
56
|
+
# read the rest of the current line, in case we started in the middle of a line
|
57
|
+
start_pos = pos
|
58
|
+
fragment = readline rescue ""
|
59
|
+
seek(start_pos)
|
60
|
+
|
61
|
+
while data = reverse_read(4096)
|
62
|
+
lines = data.each_line.to_a
|
63
|
+
lines.last << fragment unless lines.last[-1] == "\n"
|
64
|
+
|
65
|
+
fragment = lines.first
|
66
|
+
|
67
|
+
lines[1..-1].reverse_each { |line| yield line }
|
68
|
+
end
|
69
|
+
|
70
|
+
yield fragment
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Seek to `EOF`
|
75
|
+
#
|
76
|
+
def seek_end
|
77
|
+
seek(0, IO::SEEK_END)
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Seek to `BOF`
|
82
|
+
#
|
83
|
+
def seek_start
|
84
|
+
seek(0)
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Read the previous line (leaving `pos` at the beginning of the string that was read.)
|
89
|
+
#
|
90
|
+
def reverse_readline
|
91
|
+
raise BOFError.new("beginning of file reached") if pos == 0
|
92
|
+
|
93
|
+
seek_backwards_to("\n", 512, -2)
|
94
|
+
new_pos = pos
|
95
|
+
data = readline
|
96
|
+
seek(new_pos)
|
97
|
+
data
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# Scan through the file until `string` is found, and set the IO's +pos+ to the first character of the matched string.
|
102
|
+
#
|
103
|
+
def seek_to(string, blocksize=512)
|
104
|
+
raise "Error: blocksize must be at least as large as the string" if blocksize < string.size
|
105
|
+
|
106
|
+
loop do
|
107
|
+
data = read(blocksize)
|
108
|
+
|
109
|
+
if index = data.index(string)
|
110
|
+
seek(-(data.size - index), IO::SEEK_CUR)
|
111
|
+
break
|
112
|
+
elsif eof?
|
113
|
+
return nil
|
114
|
+
else
|
115
|
+
seek(-(string.size - 1), IO::SEEK_CUR)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
pos
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# Scan backwards in the file until `string` is found, and set the IO's +pos+ to the first character after the matched string.
|
124
|
+
#
|
125
|
+
def seek_backwards_to(string, blocksize=512, rindex_end=-1)
|
126
|
+
raise "Error: blocksize must be at least as large as the string" if blocksize < string.size
|
127
|
+
|
128
|
+
loop do
|
129
|
+
data = reverse_read(blocksize)
|
130
|
+
|
131
|
+
if index = data.rindex(string, rindex_end)
|
132
|
+
seek(index+string.size, IO::SEEK_CUR)
|
133
|
+
break
|
134
|
+
elsif pos == 0
|
135
|
+
return nil
|
136
|
+
else
|
137
|
+
seek(string.size - 1, IO::SEEK_CUR)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
pos
|
142
|
+
end
|
143
|
+
alias_method :reverse_seek_to, :seek_backwards_to
|
144
|
+
|
145
|
+
#
|
146
|
+
# Iterate over each line of the file, yielding the line and the byte offset of the start of the line in the file
|
147
|
+
#
|
148
|
+
def each_line_with_offset
|
149
|
+
return to_enum(:each_line_with_offset) unless block_given?
|
150
|
+
|
151
|
+
offset = 0
|
152
|
+
|
153
|
+
each_line do |line|
|
154
|
+
yield line, offset
|
155
|
+
offset = tell
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
data/lib/upm/tool.rb
CHANGED
@@ -16,6 +16,8 @@ module UPM
|
|
16
16
|
|
17
17
|
include UPM::Tool::DSL
|
18
18
|
|
19
|
+
# TODO: Show unlisted commands
|
20
|
+
|
19
21
|
COMMAND_HELP = {
|
20
22
|
"install" => "install a package",
|
21
23
|
"remove/uninstall" => "remove a package",
|
@@ -23,6 +25,7 @@ module UPM
|
|
23
25
|
"search-sources" => "search package source (for use with 'build' command)",
|
24
26
|
"build" => "build a package from source and install it",
|
25
27
|
"list" => "list installed packages (or search their names if extra arguments are supplied)",
|
28
|
+
"files" => "list files in a package",
|
26
29
|
"info" => "show metadata about a package",
|
27
30
|
"update/sync" => "retrieve the latest package list or manifest",
|
28
31
|
"upgrade" => "update package list and install updates",
|
@@ -60,6 +63,11 @@ module UPM
|
|
60
63
|
|
61
64
|
def initialize(name, &block)
|
62
65
|
@name = name
|
66
|
+
|
67
|
+
set_default :cache_dir, "~/.cache/upm"
|
68
|
+
set_default :config_dir, "~/.cache/upm"
|
69
|
+
set_default :max_database_age, 15*60 # 15 minutes
|
70
|
+
|
63
71
|
instance_eval(&block)
|
64
72
|
|
65
73
|
@@tools[name] = self
|
data/lib/upm/tool_dsl.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'fileutils'
|
3
|
+
|
1
4
|
module UPM
|
2
5
|
class Tool
|
3
6
|
module DSL
|
@@ -13,6 +16,28 @@ module UPM
|
|
13
16
|
@prefix = name
|
14
17
|
end
|
15
18
|
|
19
|
+
def os(*args)
|
20
|
+
args.any? ? @os = args : @os
|
21
|
+
end
|
22
|
+
|
23
|
+
def max_database_age(age)
|
24
|
+
@max_database_age = age.to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
def cache_dir(dir)
|
28
|
+
@cache_dir = Pathname.new(dir).expand_path
|
29
|
+
@cache_dir.mkpath unless @cache_dir.exist?
|
30
|
+
end
|
31
|
+
|
32
|
+
def config_dir(dir)
|
33
|
+
@config_dir = Pathname.new(dir).expand_path
|
34
|
+
@config_dir.mkpath unless @config_dir.exist?
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_default(key, value)
|
38
|
+
send(key, value)
|
39
|
+
end
|
40
|
+
|
16
41
|
def command(name, shell_command=nil, root: false, paged: false, highlight: nil, &block)
|
17
42
|
@cmds ||= {}
|
18
43
|
|
@@ -38,16 +63,10 @@ module UPM
|
|
38
63
|
end
|
39
64
|
|
40
65
|
else
|
41
|
-
|
42
66
|
raise "Error: Must supply a block or shell command"
|
43
|
-
|
44
67
|
end
|
45
68
|
end
|
46
69
|
|
47
|
-
def os(*args)
|
48
|
-
args.any? ? @os = args : @os
|
49
|
-
end
|
50
|
-
|
51
70
|
## Helpers
|
52
71
|
|
53
72
|
def run(*args, root: false, paged: false, grep: nil, highlight: nil, sort: false)
|
@@ -139,7 +158,30 @@ module UPM
|
|
139
158
|
end
|
140
159
|
end
|
141
160
|
|
161
|
+
def database_lastupdate_file
|
162
|
+
raise "Error: Tool 'name' is not set" unless @name
|
163
|
+
raise "Error: 'cache_dir' is not set" unless @cache_dir
|
164
|
+
@cache_dir/"#{@name}-last-update"
|
165
|
+
end
|
166
|
+
|
167
|
+
def database_updated!
|
168
|
+
FileUtils.touch(database_lastupdate_file)
|
169
|
+
end
|
170
|
+
|
171
|
+
def database_lastupdate
|
172
|
+
database_lastupdate_file.exist? ? File.mtime(database_lastupdate_file) : 0
|
173
|
+
end
|
174
|
+
|
175
|
+
def database_age
|
176
|
+
Time.now.to_i - database_lastupdate.to_i
|
177
|
+
end
|
178
|
+
|
179
|
+
def database_needs_updating?
|
180
|
+
database_age > @max_database_age
|
181
|
+
end
|
182
|
+
|
142
183
|
def help
|
184
|
+
puts " UPM version: #{File.read("#{__dir__}/../../VERSION")}"
|
143
185
|
if osname = Tool.nice_os_name
|
144
186
|
puts " Detected OS: #{osname}"
|
145
187
|
end
|
data/lib/upm/tools/pacman.rb
CHANGED
data/lib/upm/tools/pkg.rb
CHANGED
@@ -1,19 +1,32 @@
|
|
1
|
-
require 'upm/freshports_search'
|
2
|
-
|
3
1
|
UPM::Tool.new "pkg" do
|
4
2
|
|
5
3
|
os "FreeBSD"
|
6
4
|
|
7
|
-
command "
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
command "update", root: true do
|
6
|
+
if database_needs_updating?
|
7
|
+
run "pkg", "update"
|
8
|
+
database_updated!
|
9
|
+
else
|
10
|
+
puts "Database has already been updated recently. Skipping."
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
command "install", root: true do |args|
|
15
|
+
call_command "update"
|
16
|
+
run "pkg", "install", "--no-repo-update", *args
|
17
|
+
end
|
18
|
+
|
19
|
+
command "upgrade", root: true do
|
20
|
+
call_command "update"
|
21
|
+
run "pkg", "upgrade", "--no-repo-update"
|
22
|
+
end
|
23
|
+
|
24
|
+
command "remove", "pkg remove", root: true
|
12
25
|
command "audit", "pkg audit", root: true
|
26
|
+
command "clean", "pkg clean -a",root: true
|
13
27
|
command "verify", "pkg check --checksums", root: true
|
14
28
|
command "which", "pkg which"
|
15
29
|
|
16
|
-
# command "files", "pkg list", paged: true
|
17
30
|
command "files" do |args|
|
18
31
|
if args.empty?
|
19
32
|
run "pkg", "info", "--list-files", "--all", paged: true
|
@@ -29,16 +42,19 @@ UPM::Tool.new "pkg" do
|
|
29
42
|
|
30
43
|
command "search", "pkg search", paged: true, highlight: true
|
31
44
|
command "search-sources" do |*args|
|
45
|
+
require 'upm/freshports_search'
|
32
46
|
query = args.join(" ")
|
33
47
|
FreshportsSearch.new.search!(query)
|
34
48
|
end
|
35
49
|
|
36
50
|
# command "log", "grep -E 'pkg.+installed' /var/log/messages", paged: true
|
37
51
|
command "log" do
|
52
|
+
require 'upm/core_ext/file'
|
38
53
|
lesspipe do |less|
|
39
|
-
open("/var/log/messages").
|
54
|
+
open("/var/log/messages").reverse_each_line do |line|
|
40
55
|
# Jan 19 18:25:21 freebsd pkg[815]: pcre-8.43_2 installed
|
41
|
-
|
56
|
+
# Apr 1 16:55:58 freebsd pkg[73957]: irssi-1.2.2,1 installed
|
57
|
+
if line =~ /^(\S+\s+\S+\s+\S+) (\S+) pkg(?:\[\d+\])?: (\S+)-(\S+) installed/
|
42
58
|
timestamp = DateTime.parse($1)
|
43
59
|
host = $2
|
44
60
|
pkgname = $3
|
@@ -77,4 +93,10 @@ UPM::Tool.new "pkg" do
|
|
77
93
|
print_files("/etc/pkg/FreeBSD.conf", exclude: /^(#|$)/)
|
78
94
|
end
|
79
95
|
|
96
|
+
# pkg clean # cleans /var/cache/pkg/
|
97
|
+
# rm -rf /var/cache/pkg/* # just remove it all
|
98
|
+
# pkg update -f # forces update of repository catalog
|
99
|
+
# rm /var/db/pkg/repo-*.sqlite # removes all remote repository catalogs
|
100
|
+
# pkg bootstrap -f # forces reinstall of pkg
|
101
|
+
|
80
102
|
end
|
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.17
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- epitron
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-14 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.
|
@@ -34,6 +34,7 @@ files:
|
|
34
34
|
- lib/upm.rb
|
35
35
|
- lib/upm/colored.rb
|
36
36
|
- lib/upm/core_ext.rb
|
37
|
+
- lib/upm/core_ext/file.rb
|
37
38
|
- lib/upm/freshports_search.rb
|
38
39
|
- lib/upm/lesspipe.rb
|
39
40
|
- lib/upm/log_parser.rb
|
@@ -55,7 +56,7 @@ homepage: http://github.com/epitron/upm/
|
|
55
56
|
licenses:
|
56
57
|
- WTFPL
|
57
58
|
metadata: {}
|
58
|
-
post_install_message:
|
59
|
+
post_install_message:
|
59
60
|
rdoc_options: []
|
60
61
|
require_paths:
|
61
62
|
- lib
|
@@ -70,8 +71,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
71
|
- !ruby/object:Gem::Version
|
71
72
|
version: '0'
|
72
73
|
requirements: []
|
73
|
-
rubygems_version: 3.1.
|
74
|
-
signing_key:
|
74
|
+
rubygems_version: 3.1.4
|
75
|
+
signing_key:
|
75
76
|
specification_version: 4
|
76
77
|
summary: Universal Package Manager
|
77
78
|
test_files: []
|