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