upm 0.1.12 → 0.1.17
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/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
|

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