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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3bca79b16263ae9e7ac025695b61ec7bd88f81334b15564611e3f6977861ddcc
4
- data.tar.gz: cbba1be68aa1f5deb00d59a0ec72ef912a790f04f2cccb7f185f6931eb8d76ad
3
+ metadata.gz: 9c5ec80be1569a58c23402685f57f02f46cebd10ae87b2684eb2aa8d132aa7cf
4
+ data.tar.gz: d2fee9bb7b69d36df657e749a109dd29cdc3bbabef9b83b7a1731f83afa504f4
5
5
  SHA512:
6
- metadata.gz: 9642d123de6aaf546cc328c18ef942e0883bc526d53fe6eefd28cc10573220dccc9a1e6a308e68559459b3d2e22b2161cbda623b147095b3114ba97f711b6126
7
- data.tar.gz: 9439866b904f66b394f9680c03bf8e77646bd493012696de2fd64335b38fed05263dda5ca04ed482dfd26a2d0e6a5628fbe57eb65da1c3409d54678b9bb552ef
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 rollback and pinning.
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
- It will also allow users to maintain lists of their favorite packages (and sync them to some remote server), so that they can automatically install them whenever they setup a new machine. (This can include git repos full of dotfiles/scripts, to give the user a comfortable home environment regardless of which OS they're using.)
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
- * `verfiy` - verify the integrity of installed files
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
- ## Performance
3
+ ## UI
4
+ * fzf
5
+ * search
4
6
 
5
- It's currently very clunky on the rpi2.
7
+ ## Options
8
+ * Proper option/command parser
9
+ * Verbose mode (prints `run` commands)
6
10
 
7
- ## Custom help for command
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.12
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
- command, *args = ARGV
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
- begin
18
- tool.call_command command, *args
19
- rescue Interrupt
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,7 @@
1
+ # Favorite Packages
2
+
3
+ ## Concept
4
+
5
+ The user can create their own package groups manually, or dump of their current desktop's package selecitons and organize them.
6
+
7
+
@@ -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
@@ -46,19 +46,42 @@ def lesspipe(*args)
46
46
  end
47
47
 
48
48
  params = []
49
- params << "-R" unless options[:color] == false
50
- params << "-S" unless options[:wrap] == true
51
- params << "-F" unless options[:always] == true
52
- params << "-X"
53
-
54
- if regexp = options[:search]
55
- params << "+/#{regexp}"
56
- elsif options[:tail] == true
57
- params << "+\\>"
58
- $stderr.puts "Seeking to end of stream..."
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
- IO.popen("less #{params * ' '}", "w") do |less|
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
@@ -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.to_h
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 } }&.last
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) }&.last
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
@@ -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("\\\\s+") : nil
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
- if !paged and !grep
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
- grep_proc = if grep
95
- proc { |line| line[grep] }
96
- else
97
- proc { true }
98
- end
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
@@ -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|
@@ -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 "install", "pkg install", root: true
8
- command "update", "pkg update", root: true
9
- command "upgrade", "pkg upgrade", root: true
10
- command "info", "pkg clean", root: true
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: /var/log/messages", paged: true
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.12
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: 2018-05-30 00:00:00.000000000 Z
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
- rubyforge_project:
67
- rubygems_version: 2.7.7
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: []