upm 0.1.12 → 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/README.md +75 -13
- data/TODO.md +13 -19
- data/VERSION +1 -1
- data/bin/upm +10 -7
- data/design/Favorite Packages.md +7 -0
- data/design/Fuzzy Installer.md +28 -0
- data/design/Updates Grouped Together for Readability.md +18 -0
- data/lib/upm/core_ext/file.rb +159 -0
- data/lib/upm/lesspipe.rb +34 -11
- data/lib/upm/tool.rb +8 -0
- data/lib/upm/tool_class_methods.rb +6 -5
- data/lib/upm/tool_dsl.rb +69 -28
- data/lib/upm/tools/apk.rb +27 -0
- data/lib/upm/tools/opkg.rb +26 -0
- data/lib/upm/tools/pacman.rb +2 -1
- data/lib/upm/tools/pkg.rb +59 -8
- data/lib/upm/tools/pkgin.rb +49 -0
- data/lib/upm/tools/yum.rb +27 -0
- metadata +14 -7
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/README.md
CHANGED
@@ -2,11 +2,25 @@
|
|
2
2
|
|
3
3
|
## Concept:
|
4
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
|
5
|
+
Wraps all known package managers to provide a consistent and pretty interface, along with advanced features not supported by all tools, such as:
|
6
|
+
- install log
|
7
|
+
- rollback
|
8
|
+
- pinning
|
9
|
+
- fuzzy search
|
10
|
+
- containerization/sandboxing
|
11
|
+
- learning (community statistics and user choices)
|
6
12
|
|
7
13
|
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
14
|
|
9
|
-
|
15
|
+
You can maintain lists of your favorite packages (and sync them to some remote server), so that you can automatically install them whenever you setup a new machine. (This can include git repos full of dotfiles/scripts, to give you a comfortable home environment, regardless of which OS you're using.)
|
16
|
+
|
17
|
+
## Installation:
|
18
|
+
|
19
|
+
First, install Ruby. Then:
|
20
|
+
|
21
|
+
```
|
22
|
+
gem install upm
|
23
|
+
```
|
10
24
|
|
11
25
|
## Usage:
|
12
26
|
|
@@ -18,19 +32,19 @@ u <command> <pkg>
|
|
18
32
|
|
19
33
|
## Commands:
|
20
34
|
|
21
|
-
* `install`
|
22
|
-
* `remove`
|
35
|
+
* `install`/`add` - download and install a package
|
36
|
+
* `remove`/`uninstall` - remove a previously installed package
|
23
37
|
* `build` - compile a package from source and install it
|
24
38
|
* `search` - using the fastest known API or service
|
25
39
|
* `list` - show all packages, or the contents of a specific package
|
26
40
|
* `info` - show metadata about a package
|
27
41
|
* `sync`/`update` - retrieve the latest package list or manifest
|
28
42
|
* `upgrade` - install new versions of all packages
|
29
|
-
* `
|
43
|
+
* `verify` - verify the integrity of installed files
|
30
44
|
* `audit` - show known vulnerabilities for installed packages
|
31
45
|
* `pin` - pinning a package means it won't be automatically upgraded
|
32
46
|
* `rollback` - revert to an earlier version of a package (including its dependencies)
|
33
|
-
* `log` - show history of package installs
|
47
|
+
* `log` - show history of package installs
|
34
48
|
* `packagers` - detect installed package managers, and pick which ones upm should wrap
|
35
49
|
* `sources`/`mirrors` - select remote repositories and mirrors
|
36
50
|
* `clean` - clear out the local package cache
|
@@ -70,21 +84,23 @@ go:<pkg>,<pkg>,<pkg>
|
|
70
84
|
* NetBSD: `pkgin`/`ports`
|
71
85
|
* SmartOS/Illumos: `pkgin`
|
72
86
|
* Windows: `apt-cyg`/`mingw-get`/`nuget`/`Windows Update`/(as-yet-not-created package manager, "winget")
|
73
|
-
* Wine: `winetricks`
|
87
|
+
* Wine/Proton/Steam: `winetricks`/`steam`
|
74
88
|
* Ruby: `rubygems`
|
75
89
|
* Python: `pip`/`easy_install`
|
76
|
-
* Javascript: `npm`
|
77
|
-
* Clojure: `leiningen`
|
78
|
-
* Java: `gradle`
|
79
|
-
* Erlang: `rebar`
|
80
|
-
* Scala: `sbt`
|
90
|
+
* Javascript/NodeJS: `npm`
|
81
91
|
* Rust: `cargo`
|
92
|
+
* Dart: `pub`
|
93
|
+
* go: `go-get`
|
82
94
|
* R: `cran`
|
95
|
+
* Qt: `qpm`
|
83
96
|
* Lua: `rocks`
|
84
97
|
* Julia: `Pkg`
|
85
98
|
* Haskell: `cabal`
|
99
|
+
* Clojure: `leiningen`
|
100
|
+
* Java: `gradle`
|
101
|
+
* Erlang: `rebar`
|
102
|
+
* Scala: `sbt`
|
86
103
|
* Perl: `cpan`
|
87
|
-
* go: `go-get`
|
88
104
|
|
89
105
|
...[and many more!](https://en.wikipedia.org/wiki/List_of_software_package_management_systems)
|
90
106
|
|
@@ -103,8 +119,54 @@ Rollback:
|
|
103
119
|
|
104
120
|
![pacman-rollback](https://raw.githubusercontent.com/epitron/scripts/master/screenshots/pacman-rollback.png)
|
105
121
|
|
122
|
+
# Future Directions
|
123
|
+
|
106
124
|
## TODOs:
|
107
125
|
|
108
126
|
* Use the pretty text-mode UI that passenger-install uses
|
109
127
|
* Context-dependent operation
|
110
128
|
* eg: if you're in a ruby project's directory, set the 'ruby' namespace to highest priority
|
129
|
+
|
130
|
+
## Containers, VMs, and Virtual Environments:
|
131
|
+
|
132
|
+
Containers, VMs, and Virtual Environments are another pile of tools which do roughly the same thing: they gather together the dependencies for a specific program, or small set of programs, into a bundle, and create an isolated environment in which it can run.
|
133
|
+
|
134
|
+
In the future, these could be wrapped by `ucm` (Universal Container Manager), if I get around to it.
|
135
|
+
|
136
|
+
### Container tools to wrap:
|
137
|
+
|
138
|
+
* Virtual Environments:
|
139
|
+
* Python: `virtualenv`
|
140
|
+
* Ruby: `bundler`
|
141
|
+
* Java: `gradle`
|
142
|
+
* NodeJS: `npm`
|
143
|
+
* Containerized Applications/Systems:
|
144
|
+
* AppImage
|
145
|
+
* docker
|
146
|
+
* rkt
|
147
|
+
* snapd
|
148
|
+
* systemd
|
149
|
+
* podman
|
150
|
+
* nanobox
|
151
|
+
* SmartOS zones
|
152
|
+
* BSD jails
|
153
|
+
* Wine environments:
|
154
|
+
* wine prefixes
|
155
|
+
* playonlinuxs
|
156
|
+
* proton
|
157
|
+
* Virtual Machines:
|
158
|
+
* qemu
|
159
|
+
* virtualbox
|
160
|
+
* VMware
|
161
|
+
* firecracker
|
162
|
+
* Hypervisors:
|
163
|
+
* ESXi
|
164
|
+
* Xen
|
165
|
+
* Nova
|
166
|
+
|
167
|
+
|
168
|
+
## Similar Projects
|
169
|
+
|
170
|
+
* [PackageKit](https://en.wikipedia.org/wiki/PackageKit)
|
171
|
+
* [libraries.io](https://libraries.io)
|
172
|
+
* [Repology](https://repology.org)
|
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,28 @@
|
|
1
|
+
# Fuzzy Installer
|
2
|
+
|
3
|
+
## Concept
|
4
|
+
|
5
|
+
'upm install <pkg query>' performs a search
|
6
|
+
|
7
|
+
If there's one match, install it; if there are multiple matches, show ranked results and let the user pick one (fzf-like picker, but with a hotkey that can expand descriptions).
|
8
|
+
|
9
|
+
Ranking of packages by source use statistics and heuristics
|
10
|
+
- Which version is newest?
|
11
|
+
- Which version is more stable?
|
12
|
+
- What has the user picked in the past? (eg: a distro's global ruby-<gem> package vs a globally installed gem vs a locally installed gem)
|
13
|
+
- What tools are installed? (docker? gem?)
|
14
|
+
- Which package is less likely to break stuff? (some package managers are better than others (*cough*pip))
|
15
|
+
- Is it a service that should be instaled in a container?
|
16
|
+
|
17
|
+
## Depends on
|
18
|
+
|
19
|
+
- A database of package aliases
|
20
|
+
- A ranking algorithm
|
21
|
+
- Package type prioritization (manually specified or machine learned, per-package rules based on all users who installed it)
|
22
|
+
-
|
23
|
+
- Distributed statistics
|
24
|
+
- Users can anonymously donate their stats
|
25
|
+
- Stats are all public
|
26
|
+
- Ranking algorithm can run locally or on a distributed system
|
27
|
+
|
28
|
+
#
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Updates Grouped Together for Readability
|
2
|
+
|
3
|
+
## Concept
|
4
|
+
|
5
|
+
The design of package update screens is pretty abysmal -- usually just a big blob of package names and versions. This can be improved.
|
6
|
+
|
7
|
+
## Groups
|
8
|
+
|
9
|
+
Packages can be grouped by any of the following:
|
10
|
+
|
11
|
+
- semantic version (MAJOR.MINOR.PATCH):
|
12
|
+
- MAJOR: incompatible API changes
|
13
|
+
- MINOR: backwards-compatible features added
|
14
|
+
- PATCH: bugfixes
|
15
|
+
- package's category/tags
|
16
|
+
- which repository the package came from
|
17
|
+
- who packaged it
|
18
|
+
- a tree-view of package dependencies (useful for seeing dependency relationships during package updates)
|
@@ -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/lesspipe.rb
CHANGED
@@ -46,19 +46,42 @@ def lesspipe(*args)
|
|
46
46
|
end
|
47
47
|
|
48
48
|
params = []
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
params << "
|
58
|
-
|
49
|
+
|
50
|
+
less_bin = File.which("less")
|
51
|
+
|
52
|
+
if File.symlink?(less_bin) and File.readlink(less_bin)[/busybox$/]
|
53
|
+
# busybox less only supports one option!
|
54
|
+
params << "-S" unless options[:wrap] == true
|
55
|
+
else
|
56
|
+
# authentic less
|
57
|
+
params << "-R" unless options[:color] == false
|
58
|
+
params << "-S" unless options[:wrap] == true
|
59
|
+
params << "-F" unless options[:always] == true
|
60
|
+
params << "-X"
|
61
|
+
params << "-I"
|
62
|
+
|
63
|
+
if regexp = options[:search]
|
64
|
+
params << "+/#{regexp}"
|
65
|
+
elsif options[:tail] == true
|
66
|
+
params << "+\\>"
|
67
|
+
$stderr.puts "Seeking to end of stream..."
|
68
|
+
end
|
59
69
|
end
|
60
70
|
|
61
|
-
|
71
|
+
env = {
|
72
|
+
"LESS_TERMCAP_mb" => "\e[01;31m",
|
73
|
+
"LESS_TERMCAP_md" => "\e[01;37m",
|
74
|
+
"LESS_TERMCAP_me" => "\e[0m",
|
75
|
+
"LESS_TERMCAP_se" => "\e[0m",
|
76
|
+
# "LESS_TERMCAP_so" => "\e[30;44m", # highlight: black on blue
|
77
|
+
"LESS_TERMCAP_so" => "\e[01;44;33m", # highlight: bright yellow on blue
|
78
|
+
# "LESS_TERMCAP_so" => "\e[30;43m", # highlight: black on yellow
|
79
|
+
"LESS_TERMCAP_ue" => "\e[0m",
|
80
|
+
"LESS_TERMCAP_us" => "\e[01;32m",
|
81
|
+
}
|
82
|
+
|
83
|
+
IO.popen(env, [less_bin, *params], "w") do |less|
|
84
|
+
# less.puts params.inspect
|
62
85
|
if output
|
63
86
|
less.puts output
|
64
87
|
else
|
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
|
@@ -10,9 +10,10 @@ module UPM
|
|
10
10
|
|
11
11
|
def os_release
|
12
12
|
@os_release ||= begin
|
13
|
-
open("/etc/os-release") do |io|
|
13
|
+
pairs = open("/etc/os-release") do |io|
|
14
14
|
io.read.scan(/^(\w+)="?(.+?)"?$/)
|
15
|
-
end
|
15
|
+
end
|
16
|
+
Hash[pairs]
|
16
17
|
rescue Errno::ENOENT
|
17
18
|
{}
|
18
19
|
end
|
@@ -44,14 +45,14 @@ module UPM
|
|
44
45
|
tool = nil
|
45
46
|
|
46
47
|
if os_names.any?
|
47
|
-
tool = @@tools.find { |name, tool| os_names.any? { |name| tool.os.include? name } }
|
48
|
+
tool = @@tools.find { |name, tool| os_names.any? { |name| tool.os.include? name } }
|
48
49
|
end
|
49
50
|
|
50
51
|
if tool.nil?
|
51
|
-
tool = @@tools.find { |name, tool| File.which(tool.identifying_binary) }
|
52
|
+
tool = @@tools.find { |name, tool| File.which(tool.identifying_binary) }
|
52
53
|
end
|
53
54
|
|
54
|
-
tool
|
55
|
+
tool.last
|
55
56
|
end
|
56
57
|
|
57
58
|
end
|
data/lib/upm/tool_dsl.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'fileutils'
|
3
|
+
|
1
4
|
module UPM
|
2
5
|
class Tool
|
3
6
|
module DSL
|
4
7
|
def identifying_binary(id_bin=nil)
|
5
|
-
if id_bin
|
8
|
+
if id_bin
|
6
9
|
@id_bin = id_bin
|
7
10
|
else
|
8
11
|
@id_bin || @name
|
@@ -13,19 +16,41 @@ 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
|
|
19
44
|
if block_given?
|
20
|
-
|
45
|
+
|
21
46
|
if root and Process.uid != 0
|
22
|
-
exec("sudo", $PROGRAM_NAME, *ARGV)
|
47
|
+
@cmds[name] = proc { exec("sudo", $PROGRAM_NAME, *ARGV) }
|
23
48
|
else
|
24
49
|
@cmds[name] = block
|
25
50
|
end
|
26
51
|
|
27
52
|
elsif shell_command
|
28
|
-
|
53
|
+
|
29
54
|
if shell_command.is_a? String
|
30
55
|
shell_command = shell_command.split
|
31
56
|
elsif not shell_command.is_a? Array
|
@@ -33,44 +58,38 @@ module UPM
|
|
33
58
|
end
|
34
59
|
|
35
60
|
@cmds[name] = proc do |args|
|
36
|
-
query = highlight ? args.join("
|
61
|
+
query = highlight ? args.join("\\s+") : nil
|
37
62
|
run(*shell_command, *args, paged: paged, root: root, highlight: query)
|
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(*names)
|
48
|
-
names.any? ? @os = names : @os
|
49
|
-
end
|
50
|
-
|
51
70
|
## Helpers
|
52
71
|
|
53
|
-
def run(*args, root: false, paged: false, grep: nil, highlight: nil)
|
72
|
+
def run(*args, root: false, paged: false, grep: nil, highlight: nil, sort: false)
|
54
73
|
if root
|
55
74
|
if Process.uid != 0
|
56
|
-
if File.which("sudo")
|
75
|
+
if File.which("sudo")
|
57
76
|
args.unshift "sudo"
|
58
77
|
elsif File.which("su")
|
59
78
|
args = ["su", "-c"] + args
|
60
79
|
else
|
61
80
|
raise "Error: You must be root to run this command. (And I couldn't find the 'sudo' *or* 'su' commands.)"
|
62
81
|
end
|
63
|
-
end
|
82
|
+
end
|
64
83
|
end
|
65
84
|
|
66
|
-
|
85
|
+
|
86
|
+
unless paged or grep or sort
|
67
87
|
system(*args)
|
68
88
|
else
|
69
|
-
|
70
89
|
IO.popen(args, err: [:child, :out]) do |command_io|
|
71
|
-
|
90
|
+
|
72
91
|
# if grep
|
73
|
-
# pattern = grep.is_a?(Regexp) ? grep.source : grep.to_s
|
92
|
+
# pattern = grep.is_a?(Regexp) ? grep.source : grep.to_s
|
74
93
|
# grep_io = IO.popen(["grep", "--color=always", "-Ei", pattern], "w+")
|
75
94
|
# IO.copy_stream(command_io, grep_io)
|
76
95
|
# grep_io.close_write
|
@@ -91,17 +110,16 @@ module UPM
|
|
91
110
|
# proc { |line| line }
|
92
111
|
# end
|
93
112
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
lesspipe(disabled: !paged, search: highlight, always: true) do |less|
|
101
|
-
command_io.each_line do |line|
|
102
|
-
# less.puts highlight_proc.(line) if grep_proc.(line)
|
103
|
-
less.puts line if grep_proc.(line)
|
113
|
+
lesspipe(disabled: !paged, search: highlight, always: false) do |less|
|
114
|
+
each_proc = if grep
|
115
|
+
proc { |line| less.puts line if line[grep] }
|
116
|
+
else
|
117
|
+
proc { |line| less.puts line }
|
104
118
|
end
|
119
|
+
|
120
|
+
lines = command_io.each_line
|
121
|
+
lines = lines.to_a.sort if sort
|
122
|
+
lines.each(&each_proc)
|
105
123
|
end
|
106
124
|
|
107
125
|
end
|
@@ -140,7 +158,30 @@ module UPM
|
|
140
158
|
end
|
141
159
|
end
|
142
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
|
+
|
143
183
|
def help
|
184
|
+
puts " UPM version: #{File.read("#{__dir__}/../../VERSION")}"
|
144
185
|
if osname = Tool.nice_os_name
|
145
186
|
puts " Detected OS: #{osname}"
|
146
187
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
UPM::Tool.new "apk" do
|
2
|
+
|
3
|
+
os "alpine"
|
4
|
+
|
5
|
+
command "install", "apk add", root: true
|
6
|
+
command "remove", "apk del", root: true
|
7
|
+
command "update", "apk update", root: true
|
8
|
+
command "upgrade", "apk upgrade", root: true
|
9
|
+
command "clean", "apk clean", root: true
|
10
|
+
|
11
|
+
command "files", "apk info -L", paged: true
|
12
|
+
command "search" do |args|
|
13
|
+
query = args.join(".+")
|
14
|
+
run "apk", "search", *args, sort: true, paged: true, highlight: query
|
15
|
+
end
|
16
|
+
|
17
|
+
command "list" do |args|
|
18
|
+
if args.any?
|
19
|
+
highlight_query = args.join(".+")
|
20
|
+
grep_query = /#{highlight_query}/
|
21
|
+
run "apk", "info", grep: grep_query, highlight: highlight_query, paged: true
|
22
|
+
else
|
23
|
+
run "apk", "info", paged: true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
UPM::Tool.new "opkg" do
|
2
|
+
|
3
|
+
os "openwrt", "lede"
|
4
|
+
|
5
|
+
command "install", "opkg install", root: true
|
6
|
+
command "update", "opkg update", root: true
|
7
|
+
command "upgrade", root: true do |args|
|
8
|
+
pkgs = `opkg list-upgradable`.each_line.map { |line| line.split.first }
|
9
|
+
run "opkg", "upgrade", *pkgs
|
10
|
+
end
|
11
|
+
|
12
|
+
command "search" do |args|
|
13
|
+
query = args.join
|
14
|
+
run "opkg", "list", grep: query, paged: true
|
15
|
+
end
|
16
|
+
|
17
|
+
command "list" do |args|
|
18
|
+
if args.any?
|
19
|
+
query = args.join
|
20
|
+
run "opkg", "list-installed", grep: query, paged: true
|
21
|
+
else
|
22
|
+
run "opkg", "list-installed", paged: true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/lib/upm/tools/pacman.rb
CHANGED
@@ -6,7 +6,7 @@ UPM::Tool.new "pacman" do
|
|
6
6
|
|
7
7
|
command "install", [*bin, "-S"], root: true
|
8
8
|
command "update", [*bin, "-Sy"], root: true
|
9
|
-
command "upgrade", [*bin, "-Syu"], root: true
|
9
|
+
command "upgrade", [*bin, "-Syu", "--noconfirm"], root: true
|
10
10
|
command "remove", [*bin, "-R"], root: true
|
11
11
|
|
12
12
|
command "verify", root: true do |args|
|
@@ -33,6 +33,7 @@ UPM::Tool.new "pacman" do
|
|
33
33
|
|
34
34
|
command "mirrors" do
|
35
35
|
print_files("/etc/pacman.d/mirrorlist", exclude: /^(#|$)/)
|
36
|
+
print_files("/etc/pacman.conf", include: /^Server\s*=/, exclude: /^(#|$)/)
|
36
37
|
end
|
37
38
|
|
38
39
|
command "depends" do |args|
|
data/lib/upm/tools/pkg.rb
CHANGED
@@ -1,24 +1,69 @@
|
|
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
|
-
|
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
|
11
25
|
command "audit", "pkg audit", root: true
|
26
|
+
command "clean", "pkg clean -a",root: true
|
12
27
|
command "verify", "pkg check --checksums", root: true
|
28
|
+
command "which", "pkg which"
|
29
|
+
|
30
|
+
command "files" do |args|
|
31
|
+
if args.empty?
|
32
|
+
run "pkg", "info", "--list-files", "--all", paged: true
|
33
|
+
else
|
34
|
+
run "pkg", "list", *args, paged: true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# the "pkg-provides" plugin is similar to arch's "pkgfile" (requires updates), and needs to be added to the plugins section of pkg's config ("pkg plugins" shows loaded plugins)
|
39
|
+
command "provides" do |args|
|
40
|
+
run "pkg", "info", "--list-files", "--all", grep: /#{args.join(".+")}/, highlight: true
|
41
|
+
end
|
13
42
|
|
14
|
-
command "files", "pkg list", paged: true
|
15
43
|
command "search", "pkg search", paged: true, highlight: true
|
16
44
|
command "search-sources" do |*args|
|
45
|
+
require 'upm/freshports_search'
|
17
46
|
query = args.join(" ")
|
18
47
|
FreshportsSearch.new.search!(query)
|
19
48
|
end
|
20
49
|
|
21
|
-
command "log", "grep pkg
|
50
|
+
# command "log", "grep -E 'pkg.+installed' /var/log/messages", paged: true
|
51
|
+
command "log" do
|
52
|
+
require 'upm/core_ext/file'
|
53
|
+
lesspipe do |less|
|
54
|
+
open("/var/log/messages").reverse_each_line do |line|
|
55
|
+
# Jan 19 18:25:21 freebsd pkg[815]: pcre-8.43_2 installed
|
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/
|
58
|
+
timestamp = DateTime.parse($1)
|
59
|
+
host = $2
|
60
|
+
pkgname = $3
|
61
|
+
pkgver = $4
|
62
|
+
less.puts "#{timestamp} | #{pkgname} #{pkgver}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
22
67
|
|
23
68
|
command "build" do |*args|
|
24
69
|
# svn checkout --depth empty svn://svn.freebsd.org/ports/head /usr/ports
|
@@ -48,4 +93,10 @@ UPM::Tool.new "pkg" do
|
|
48
93
|
print_files("/etc/pkg/FreeBSD.conf", exclude: /^(#|$)/)
|
49
94
|
end
|
50
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
|
+
|
51
102
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
UPM::Tool.new "pkgin" do
|
2
|
+
|
3
|
+
os "MINIX", "NetBSD"
|
4
|
+
|
5
|
+
command "install", "pkgin install", root: true
|
6
|
+
command "update", "pkgin update", root: true
|
7
|
+
command "upgrade", "pkgin upgrade", root: true
|
8
|
+
command "info", "pkgin clean", root: true
|
9
|
+
command "audit", "pkgin audit", root: true
|
10
|
+
command "verify", "pkgin check --checksums", root: true
|
11
|
+
|
12
|
+
command "files", "pkgin list", paged: true
|
13
|
+
command "search", "pkgin search", paged: true, highlight: true
|
14
|
+
command "search-sources" do |*args|
|
15
|
+
query = args.join(" ")
|
16
|
+
FreshportsSearch.new.search!(query)
|
17
|
+
end
|
18
|
+
|
19
|
+
command "log", "grep pkg: /var/log/messages", paged: true
|
20
|
+
|
21
|
+
command "build" do |*args|
|
22
|
+
# svn checkout --depth empty svn://svn.freebsd.org/ports/head /usr/ports
|
23
|
+
# cd /usr/ports
|
24
|
+
# svn update --set-depth files
|
25
|
+
# svn update Mk
|
26
|
+
# svn update Templates
|
27
|
+
# svn update Tools
|
28
|
+
# svn update --set-depth files $category
|
29
|
+
# cd $category
|
30
|
+
# svn update $port
|
31
|
+
puts "Not implemented"
|
32
|
+
end
|
33
|
+
|
34
|
+
command "info", "pkg info", paged: true
|
35
|
+
|
36
|
+
command "list" do |args|
|
37
|
+
if args.any?
|
38
|
+
query = args.join
|
39
|
+
run "pkg", "info", grep: query, highlight: query, paged: true
|
40
|
+
else
|
41
|
+
run "pkg", "info", paged: true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
command "mirrors" do
|
46
|
+
print_files("/etc/pkg/FreeBSD.conf", exclude: /^(#|$)/)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
UPM::Tool.new "yum" do
|
2
|
+
|
3
|
+
os "centos", "fedora", "rhel"
|
4
|
+
|
5
|
+
command "install", "yum install", root: true
|
6
|
+
command "remove", "yum remove", root: true
|
7
|
+
command "update", "yum updateinfo", root: true
|
8
|
+
command "upgrade", "yum update", root: true
|
9
|
+
command "clean", "yum clean", root: true
|
10
|
+
|
11
|
+
command "files", "rpm -ql", paged: true
|
12
|
+
command "search" do |args|
|
13
|
+
query = args.join(".+")
|
14
|
+
run "yum", "search", *args, sort: true, paged: true, highlight: query
|
15
|
+
end
|
16
|
+
|
17
|
+
command "list" do |args|
|
18
|
+
if args.any?
|
19
|
+
highlight_query = args.join(".+")
|
20
|
+
grep_query = /#{highlight_query}/
|
21
|
+
run "yum", "list", "installed", grep: grep_query, highlight: highlight_query, paged: true
|
22
|
+
else
|
23
|
+
run "yum", "list", "installed", paged: true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
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:
|
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.
|
@@ -28,9 +28,13 @@ files:
|
|
28
28
|
- TODO.md
|
29
29
|
- VERSION
|
30
30
|
- bin/upm
|
31
|
+
- design/Favorite Packages.md
|
32
|
+
- design/Fuzzy Installer.md
|
33
|
+
- design/Updates Grouped Together for Readability.md
|
31
34
|
- lib/upm.rb
|
32
35
|
- lib/upm/colored.rb
|
33
36
|
- lib/upm/core_ext.rb
|
37
|
+
- lib/upm/core_ext/file.rb
|
34
38
|
- lib/upm/freshports_search.rb
|
35
39
|
- lib/upm/lesspipe.rb
|
36
40
|
- lib/upm/log_parser.rb
|
@@ -38,17 +42,21 @@ files:
|
|
38
42
|
- lib/upm/tool.rb
|
39
43
|
- lib/upm/tool_class_methods.rb
|
40
44
|
- lib/upm/tool_dsl.rb
|
45
|
+
- lib/upm/tools/apk.rb
|
41
46
|
- lib/upm/tools/apt.rb
|
47
|
+
- lib/upm/tools/opkg.rb
|
42
48
|
- lib/upm/tools/pacman.rb
|
43
49
|
- lib/upm/tools/pkg.rb
|
50
|
+
- lib/upm/tools/pkgin.rb
|
44
51
|
- lib/upm/tools/xbps.rb
|
52
|
+
- lib/upm/tools/yum.rb
|
45
53
|
- spec/core_ext_spec.rb
|
46
54
|
- spec/spec_helper.rb
|
47
55
|
homepage: http://github.com/epitron/upm/
|
48
56
|
licenses:
|
49
57
|
- WTFPL
|
50
58
|
metadata: {}
|
51
|
-
post_install_message:
|
59
|
+
post_install_message:
|
52
60
|
rdoc_options: []
|
53
61
|
require_paths:
|
54
62
|
- lib
|
@@ -63,9 +71,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
71
|
- !ruby/object:Gem::Version
|
64
72
|
version: '0'
|
65
73
|
requirements: []
|
66
|
-
|
67
|
-
|
68
|
-
signing_key:
|
74
|
+
rubygems_version: 3.1.4
|
75
|
+
signing_key:
|
69
76
|
specification_version: 4
|
70
77
|
summary: Universal Package Manager
|
71
78
|
test_files: []
|