├── Rakefile ├── README.md └── bundle_gem_bins /Rakefile: -------------------------------------------------------------------------------- 1 | desc 'Bump incremental version number' 2 | task :bump, :type do |_, args| 3 | args.with_defaults(type: 'inc') 4 | version_file = 'bundle_gem_bins' 5 | content = IO.read(version_file) 6 | content.sub!(/VERSION = '(?\d+)\.(?\d+)\.(?\d+)(?
\S+)?'/) do
 7 |     m = Regexp.last_match
 8 |     major = m['major'].to_i
 9 |     minor = m['minor'].to_i
10 |     inc = m['inc'].to_i
11 |     pre = m['pre']
12 | 
13 |     case args[:type]
14 |     when /^maj/
15 |       major += 1
16 |       minor = 0
17 |       inc = 0
18 |     when /^min/
19 |       minor += 1
20 |       inc = 0
21 |     else
22 |       inc += 1
23 |     end
24 | 
25 |     $stdout.puts "At version #{major}.#{minor}.#{inc}#{pre}"
26 |     "VERSION = '#{major}.#{minor}.#{inc}#{pre}'"
27 |   end
28 |   File.open(version_file, 'w+') { |f| f.puts content }
29 | end
30 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | ### Deprecated
 2 | 
 3 | **This script has been superceded by [Binbundle](https://github.com/ttscoff/binbundle/), a more complete version that's easier to install and offers more features.**
 4 | 
 5 | ### Description
 6 | 
 7 | Creates a "bundle" file of all installed gems with executables. The resulting file is an executable script that can be run standalone, or in combination with this script to add options like `sudo` or `--user-install` to the `gem install` commands. These options can be specified when creating the file as well. A file created with `sudo` or `--user-install` commands can still be overridden when running via this script and `--install`.
 8 | 
 9 | Created file is called `Binfile` in the current directory unless another path is specified with `--file`.
10 | 
11 | ### Installation
12 | 
13 | Save the script to a directory in your $PATH and make it executable with `chmod a+x bundle_gem_bins`.
14 | 
15 | ### Usage
16 | 
17 | Run `bundle_gem_bins` to create a Binfile in the current directory. That file can optionally be made executable (you'll be prompted). In the future when doing a clean install or using a new system, you can just run that file to reinstall all of your gem binaries.
18 | 
19 | Using this script with the `--install` flag will read the Binfile and execute it line by line, adding options like version numbers, sudo, or the `--user-install` flag.
20 | 
21 | You can also run with subcommands `bundle` or `install`, e.g. `bundle_gem_bins install`.
22 | 
23 | ```
24 | Usage: bundle_gem_bins [options]
25 |         --[no-]versions              Include version info in output (default true)
26 |         --dry-run                    Output to STDOUT instead of file
27 |     -s, --sudo                       Install gems with sudo
28 |     -u, --user-install               Use --user-install to install gems
29 |     -f, --file FILE                  Output to alternative filename (default Binfile)
30 |     -v, --version                    Display version
31 |         --install                    Run bundle script
32 |     -h, --help                       Display this screen
33 | ```
34 | 
35 | 


--------------------------------------------------------------------------------
/bundle_gem_bins:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env ruby
  2 | # frozen_string_literal: true
  3 | 
  4 | # Creates a "bundle" file of all installed gems with executables. The resulting
  5 | # file is an executable script that can be run standalone, or in combination
  6 | # with this script to add options like `sudo` or `--user-install` to the `gem
  7 | # install` commands. These options can be specified when creating the file as
  8 | # well. A file created with `sudo` or `--user-install` commands can still be
  9 | # overridden when running via this script and `--install`.
 10 | 
 11 | # Created file is called `Binfile` in the current directory unless another path
 12 | # is specified with `--file`.
 13 | 
 14 | require 'optparse'
 15 | require 'fileutils'
 16 | 
 17 | VERSION = '1.0.5'
 18 | 
 19 | module GemBins
 20 |   # Command line prompts
 21 |   module Prompt
 22 |     ##
 23 |     ## Ask a yes or no question in the terminal
 24 |     ##
 25 |     ## @param      question          [String] The question
 26 |     ##                               to ask
 27 |     ## @param      default_response  [Boolean]   default
 28 |     ##                               response if no input
 29 |     ##
 30 |     ## @return     [Boolean] yes or no
 31 |     ##
 32 |     def self.yn(question, default_response: nil)
 33 |       $stdin.reopen('/dev/tty')
 34 | 
 35 |       default = if default_response.is_a?(String)
 36 |                   default_response =~ /y/i ? true : false
 37 |                 else
 38 |                   default_response
 39 |                 end
 40 | 
 41 |       # if this isn't an interactive shell, answer default
 42 |       return default unless $stdout.isatty
 43 | 
 44 |       # clear the buffer
 45 |       if ARGV&.length
 46 |         ARGV.length.times do
 47 |           ARGV.shift
 48 |         end
 49 |       end
 50 |       system 'stty cbreak'
 51 | 
 52 |       options = if default.nil?
 53 |                   '[y/n]'
 54 |                 else
 55 |                   "[#{default ? 'Y/n' : 'y/N'}]"
 56 |                 end
 57 |       $stdout.syswrite "#{question.sub(/\?$/, '')} #{options}? "
 58 |       res = $stdin.sysread 1
 59 |       puts
 60 |       system 'stty cooked'
 61 | 
 62 |       res.chomp!
 63 |       res.downcase!
 64 | 
 65 |       return default if res.empty?
 66 | 
 67 |       res =~ /y/i ? true : false
 68 |     end
 69 |   end
 70 | 
 71 |   # String helpers
 72 |   class ::String
 73 |     def gem_list
 74 |       split("\n").delete_if { |line| line.strip.empty? || line =~ /^#/ }.each_with_object([]) do |l, arr|
 75 |         m = l.match(/^(?:sudo )?gem install (?:--user-install )?(?\S+)(?: (?:-v|--version) '(?[0-9.]+)')?/)
 76 |         arr << { gem: m['gem'], version: m['version'] }
 77 |       end
 78 |     end
 79 | 
 80 |     def sudo(include_version: true)
 81 |       gem_list.map do |gem|
 82 |         version = include_version && gem[:version] ? " -v '#{gem[:version]}'" : ''
 83 |         "sudo gem install #{gem[:gem]}#{version}"
 84 |       end
 85 |     end
 86 | 
 87 |     def user_install(include_version: true)
 88 |       gem_list.map do |gem|
 89 |         version = include_version && gem[:version] ? " -v '#{gem[:version]}'" : ''
 90 |         "gem install --user-install #{gem[:gem]}#{version}"
 91 |       end
 92 |     end
 93 | 
 94 |     def normal_install(include_version: true)
 95 |       gem_list.map do |gem|
 96 |         version = include_version && gem[:version] ? " -v '#{gem[:version]}'" : ''
 97 |         "gem install #{gem[:gem]}#{version}"
 98 |       end
 99 |     end
100 |   end
101 | 
102 |   # Main class
103 |   class GemBins
104 |     def local_gems
105 |       Gem::Specification.sort_by { |g| [g.name.downcase, g.version] }.group_by(&:name)
106 |     end
107 | 
108 |     def initialize(options = {})
109 |       @include_version = options[:include_version] || false
110 |       @user_install = options[:user_install]
111 |       @sudo = options[:sudo]
112 |       @dry_run = options[:dry_run]
113 |       @file = File.expand_path(options[:file])
114 | 
115 |       @local_gems = local_gems.delete_if { |_, specs| specs.delete_if { |spec| spec.executables.empty? }.empty? }
116 |     end
117 | 
118 |     def install
119 |       unless File.exist?(@file)
120 |         puts "File #{@file} not found"
121 |         Process.exit 1
122 |       end
123 | 
124 |       res = Prompt.yn("Install gems from #{File.basename(@file)}", default_response: true)
125 | 
126 |       Process.exit 0 unless res
127 | 
128 |       puts "Installing gems from #{@file}"
129 | 
130 |       contents = IO.read(@file)
131 |       lines = if @sudo
132 |                 contents.sudo(include_version: @include_version)
133 |               elsif @user_install
134 |                 contents.user_install(include_version: @include_version)
135 |               else
136 |                 contents.normal_install(include_version: @include_version)
137 |               end
138 | 
139 |       if @dry_run
140 |         puts lines.join("\n")
141 |         Process.exit 0
142 |       end
143 | 
144 |       `sudo echo -n ''` if @sudo
145 | 
146 |       lines.each do |cmd|
147 |         print cmd
148 | 
149 |         output = `/bin/bash -c '#{cmd}' 2>&1`
150 |         result = $?.success?
151 | 
152 |         if result
153 |           puts ' ✅'
154 |         else
155 |           puts ' 😥'
156 |           puts output
157 |         end
158 |       end
159 |     end
160 | 
161 |     def generate
162 |       gems_with_bins = {}
163 | 
164 |       @local_gems.map do |g, specs|
165 |         versions = specs.map { |spec| spec.version.to_s }
166 |         bins = specs.map(&:executables)
167 |         gems_with_bins[g] = { version: versions.max, bins: bins.sort.uniq }
168 |       end
169 | 
170 |       lines = []
171 | 
172 |       gems_with_bins.each do |k, v|
173 |         ver = @include_version ? " -v '#{v[:version]}'" : ''
174 |         ui = @user_install ? '--user-install ' : ''
175 |         sudo = @sudo ? 'sudo ' : ''
176 |         lines << "# Executables: #{v[:bins].join(', ')}\n#{sudo}gem install #{ui}#{k}#{ver}"
177 |       end
178 | 
179 |       output = lines.join("\n\n")
180 | 
181 |       if @dry_run
182 |         puts output
183 |       else
184 |         if File.exist?(@file)
185 |           res = Prompt.yn("#{@file} already exists, overwrite", default_response: false)
186 |           Process.exit 1 unless res
187 |         end
188 | 
189 |         File.open(@file, 'w') do |f|
190 |           f.puts '#!/bin/bash'
191 |           f.puts
192 |           f.puts output
193 |         end
194 | 
195 |         puts "Wrote list to #{@file}"
196 | 
197 |         res = Prompt.yn('Make file executable', default_response: true)
198 |         if res
199 |           FileUtils.chmod 0o777, @file
200 |           puts 'Made file executable'
201 |         end
202 |       end
203 |     end
204 |   end
205 | end
206 | 
207 | options = {
208 |   dry_run: false,
209 |   file: 'Binfile',
210 |   include_version: true,
211 |   sudo: false,
212 |   user_install: false
213 | }
214 | 
215 | optparse = OptionParser.new do |opts|
216 |   opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
217 | 
218 |   opts.on('--[no-]versions', 'Include version info in output (default true)') do |opt|
219 |     options[:include_version] = opt
220 |   end
221 | 
222 |   opts.on('--dry-run', 'Output to STDOUT instead of file') do
223 |     options[:dry_run] = true
224 |   end
225 | 
226 |   opts.on('-s', '--sudo', 'Install gems with sudo') do
227 |     options[:sudo] = true
228 |   end
229 | 
230 |   opts.on('-u', '--user-install', 'Use --user-install to install gems') do
231 |     options[:user_install] = true
232 |   end
233 | 
234 |   opts.on('-f', '--file FILE', 'Output to alternative filename (default Binfile)') do |opt|
235 |     options[:file] = opt
236 |   end
237 | 
238 |   opts.on('-v', '--version', 'Display version') do
239 |     puts "#{File.basename(__FILE__)} v#{VERSION}"
240 |     Process.exit 0
241 |   end
242 | 
243 |   opts.on('--install', 'Run bundle script') do
244 |     options[:install] = true
245 |   end
246 | 
247 |   opts.on('-h', '--help', 'Display this screen') do
248 |     puts opts
249 |     exit
250 |   end
251 | end
252 | 
253 | optparse.parse!
254 | 
255 | gb = GemBins::GemBins.new(options)
256 | 
257 | options[:install] = true if ARGV.count.positive? && ARGV[0] =~ /^inst/
258 | 
259 | if options[:install]
260 |   gb.install
261 | else
262 |   gb.generate
263 | end
264 | 


--------------------------------------------------------------------------------