Skip to content

Instantly share code, notes, and snippets.

@vidarh
Created September 28, 2023 18:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vidarh/323204137de5293bfe216ec751646525 to your computer and use it in GitHub Desktop.
Save vidarh/323204137de5293bfe216ec751646525 to your computer and use it in GitHub Desktop.
Identify relevant commands for this directory, and optionally bring up rofi|dmenu to select, and optionally run them

dir-commands

Pick and optionally run relevant commands for a directory

Vidar Hokstad vidar@hokstad.com, 2023. Licensed under the MIT license.

With no options dir-commands looks for Makefile, Rakefile, Gemfile, package.json, .git etc. in the directory indicated by an argument or the current directory, and uses that to generate a list of names and commands, like this:

git gui|git gui
git push|git push
git status|git status
make clean|make clean
terminal|x-terminal-emulator
yarn build-css|yarn build-css
yarn build-js|yarn build-js
yarn build|yarn build

If you pass -n or --names, only the names (the part before '|') will be output. This makes the output suitable as input to dmenu or compatible scripts that expect a list of options on stdin.

If you pass -mSCRIPT or --menu=SCRIPT where SCRIPT can be e.g. dmenu or 'rofi -dmenu or similar compatible script, dir-commands will execute that menu script and feed its output to the menu selectors input. A future version will likely default to my own menu script if available, but that's not ready.

If you pass -r or --run, then dir-commands will either expect a command name on stdin or expect to read a command out from a --menu, which it will then match to one of the commands and execute.

-x is shortcut for --names --run --menu (rofi -dmenu || dmenu if rofi is not found)

-t or --term allows you to force dir-commands to open a new terminal even if executed from a tty (without -t, dir-commands will execute a terminal only if executed without a tty, and otherwise run the commands in the current terminal). The terminal is chosen from the first found in the path of x-terminal-emulator (usually a symlink in /etc/alternatives/x-terminal-emulator), term, st, mlterm, rxvt, xterm)

#!/usr/bin/env ruby
# Try
# alias .m='dir-commands -x
# in your shell, and "." will bring up a menu
require 'shellwords'
require 'optparse'
require 'json'
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: dir-commands [options] [dir]"
opts.on("-n", "--names", "List command names/labels only") { options[:names] = true }
opts.on("-r", "--run", "Run a command chosen by name *instead* of listing") do
options[:run] = true
end
opts.on("-mCOMMAND", "--menu=COMMAND", "Spawn COMMAND and write --names or full commands to its STDIN") do |menu|
options[:menu] = menu
end
opts.on("-t", "--term", "Spawn new terminal for terminal commands even if already in terminal") do
options[:term] = true
end
opts.on("-v", "--verbose", "Debug output") { options[:verbose] = true }
opts.on("-x", "Equivalent to roughly --nrm [rofi -dmenu||dmenu]") do
options[:run] = true
options[:names] = true
options[:menu] = `which rofi`.strip != "" ? "rofi -dmenu" : "dmenu"
end
opts.on("-h","-?", "--help") do
puts opts
exit(0)
end
end.parse!
dir = File.expand_path(ARGV.shift || ".")
$tty = STDIN.tty? && !options[:term]
$term = ["x-terminal-emulator","term","st","mlterm","rxvt","xterm"].find {|t| `which #{t}`.strip != "" }
def interm(dir, command)
if $tty
command
else
# The "cd ." is there in case of 'fun' shell startup scripts hooking into cd.
"#{$term} -e bash -c 'cd .; echo In `pwd`:; #{command} ; echo ENTER to exit; read x'"
end
end
def cmd(dir, command, label=nil)
$commands[label || command] = interm(dir,command)
end
$commands = {}
$commands["term"] = "term"
Dir.chdir(dir) do
if File.exist?("package.json")
pkg = JSON.load(File.read("package.json"))
pkg["scripts"].each {|k,_| cmd(dir, "yarn #{k}") }
end
if File.exist?("Rakefile")
%x{rake -T}.split("\n").map{|l| l.split("#")[0].strip }.each {|r|cmd(dir,r) }
end
if File.exist?("Makefile")
File.read("Makefile").
split("\n").
find_all {|line| line.match(/^[a-zA-Z]+\:/) }.
map do |line|
cmd(dir, "make #{line.split(":").first}")
end
end
if File.exist?("Gemfile")
cmd(dir, "bundle install")
cmd(dir, "bundle update")
end
if File.exist?("config.ru")
if `which rerun`.strip != "" || File.exist?("bin/rerun")
cmd(dir, "bundle exec rerun rackup")
else
cmd(dir, "bundle exec rackup")
end
# FIXME: Check config.ru for options.
$commands["open rackup URL"]="google-chrome --new-window http://localhost:9292/"
end
if File.exist?(".git")
cmd(dir, "git push")
cmd(dir, "git status")
if File.exist?("/usr/lib/git-core/git-gui")
$commands["git gui"] = "git gui"
end
end
end
out = options[:names] ?
$commands.keys.sort.join("\n") :
$commands.map{|name,command| "#{name}|#{command}"}.sort.join("\n")
if options[:menu]
io = IO.popen(options[:menu], "r+", :err => "/dev/null")
io.write(out)
io.close_write
out = c = io.gets
elsif options[:run]
c = gets
end
if options[:run]
e = $commands[c.to_s.strip]
if e
puts e if options[:verbose]
if $tty
Dir.chdir(dir) { exec(e) }
else
spawn(e, :chdir => dir, [:out, :err,:in]=>"/dev/null")
end
end
else
puts out
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment