Add CLAUDE.md and Readme.md, remove lots of cruft
This commit is contained in:
parent
16137efd33
commit
9e2234daa0
30 changed files with 280 additions and 41226 deletions
55
CLAUDE.md
Normal file
55
CLAUDE.md
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Repository Overview
|
||||||
|
|
||||||
|
This is a personal utility bin directory containing command-line tools and scripts for development, git operations, and system automation. The scripts are primarily written in bash, Ruby, and Perl, with some Python utilities.
|
||||||
|
|
||||||
|
## Script Categories and Architecture
|
||||||
|
|
||||||
|
### Git Utilities
|
||||||
|
- Git workflow enhancement scripts (git-update, git-remove-merged-branches, git-ai-message, etc.)
|
||||||
|
- Follow the pattern of accepting remote/branch parameters with sensible defaults
|
||||||
|
- Include error handling with `set -e` in bash scripts
|
||||||
|
- Support dry-run modes where applicable (use `-n` flag)
|
||||||
|
|
||||||
|
### Development Tools
|
||||||
|
- Web development utilities (serve, make-bookmarklet, sri-integrity)
|
||||||
|
- Image processing tools (scale-app-icons, generate-xcode-imageset, retina-scale)
|
||||||
|
- Data processing scripts (colours.rb for color conversion, progress for piped data)
|
||||||
|
|
||||||
|
### System Scripts
|
||||||
|
- Shell enhancement utilities (hist.rb for command analysis, enable-sudo-touch-id)
|
||||||
|
- File management tools (find-unused-images, count-chars)
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Script Structure
|
||||||
|
- All executable scripts start with appropriate shebang (`#!/bin/bash`, `#!/usr/bin/env ruby`, etc.)
|
||||||
|
- Bash scripts use `set -e` for error handling
|
||||||
|
- Ruby scripts often process STDIN/command line arguments with explicit argument parsing
|
||||||
|
- Support both piped input and file arguments where relevant
|
||||||
|
|
||||||
|
### Argument Handling
|
||||||
|
- Use `"${1:-default}"` pattern for optional arguments with defaults in bash
|
||||||
|
- Ruby scripts use ARGV.shift pattern for argument processing
|
||||||
|
- Include help/usage when arguments are malformed
|
||||||
|
|
||||||
|
### Git Script Conventions
|
||||||
|
- Accept remote name as first argument (default to origin or auto-detect)
|
||||||
|
- Accept branch name as second argument (default to current branch or master/main)
|
||||||
|
- Preserve original branch state (stash/unstash, checkout back to original branch)
|
||||||
|
- Use `git rev-parse --abbrev-ref HEAD` to get current branch
|
||||||
|
|
||||||
|
### Data Processing
|
||||||
|
- Ruby scripts that process data streams use proper buffering and error handling
|
||||||
|
- Support both byte and line counting modes where applicable
|
||||||
|
- Use STDERR for progress/status output, STDOUT for data
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
This repository has no build system, package.json, or formal testing framework. Scripts are standalone utilities meant to be:
|
||||||
|
- Executable from anywhere in the system PATH
|
||||||
|
- Self-contained with minimal dependencies
|
||||||
|
- Robust with proper error handling
|
||||||
47
Readme.md
Normal file
47
Readme.md
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# bin
|
||||||
|
|
||||||
|
My personal collection of command-line utilities. Mostly git workflow stuff, but there's a bunch of other random tools that have accumulated over the years.
|
||||||
|
|
||||||
|
## What's in here
|
||||||
|
|
||||||
|
These are shell scripts, Ruby utilities, and various other command-line tools that live in my PATH. I've been collecting them for ages - some date back to 2006 - and they handle the mundane tasks I got tired of doing manually.
|
||||||
|
|
||||||
|
The usual suspects:
|
||||||
|
- Git workflow shortcuts (because who remembers those flag combinations?)
|
||||||
|
- Image processing for app icons and retina displays
|
||||||
|
- JSON manipulation and data analysis tools
|
||||||
|
- System automation for macOS
|
||||||
|
- Media conversion utilities
|
||||||
|
|
||||||
|
Most follow the Unix philosophy of doing one thing well, though some have grown a bit over time. The ones I still use regularly work reliably, but there are definitely some in here that haven't been touched in years and may or may not still function properly.
|
||||||
|
|
||||||
|
## The collection
|
||||||
|
|
||||||
|
- [**colours.rb**](https://github.com/samsonjs/config/blob/main/bin/colours.rb) - Convert between color formats (hex, RGB, UIColor)
|
||||||
|
- [**convert-all-songs**](https://github.com/samsonjs/config/blob/main/bin/convert-all-songs) - Batch convert audio files to different formats
|
||||||
|
- [**convert-song**](https://github.com/samsonjs/config/blob/main/bin/convert-song) - Convert single audio file to different format
|
||||||
|
- [**diff-so-fancy**](https://github.com/samsonjs/config/blob/main/bin/diff-so-fancy) - Enhanced git diff output with better formatting ([source](https://github.com/so-fancy/diff-so-fancy))
|
||||||
|
- [**enable-sudo-touch-id**](https://github.com/samsonjs/config/blob/main/bin/enable-sudo-touch-id) - Enable Touch ID authentication for sudo commands
|
||||||
|
- [**finder-show-hidden-files**](https://github.com/samsonjs/config/blob/main/bin/finder-show-hidden-files) - Toggle visibility of hidden files in Finder
|
||||||
|
- [**generate-xcode-imageset**](https://github.com/samsonjs/config/blob/main/bin/generate-xcode-imageset) - Generate Xcode imageset from @2x and @3x images
|
||||||
|
- [**git-conflicts**](https://github.com/samsonjs/config/blob/main/bin/git-conflicts) - List files in merge conflict state
|
||||||
|
- [**git-diff-merge-conflict-resolution**](https://github.com/samsonjs/config/blob/main/bin/git-diff-merge-conflict-resolution) - Show diff for merge conflict resolution
|
||||||
|
- [**git-edit-conflicted-files**](https://github.com/samsonjs/config/blob/main/bin/git-edit-conflicted-files) - Open all conflicted files in editor
|
||||||
|
- [**git-large-files**](https://github.com/samsonjs/config/blob/main/bin/git-large-files) - Find largest objects in git repository pack files
|
||||||
|
- [**git-open-in-github**](https://github.com/samsonjs/config/blob/main/bin/git-open-in-github) - Open current repo/branch in GitHub web interface
|
||||||
|
- [**git-remove-merged-branches**](https://github.com/samsonjs/config/blob/main/bin/git-remove-merged-branches) - Delete merged branches from remote repository
|
||||||
|
- [**git-uncommit**](https://github.com/samsonjs/config/blob/main/bin/git-uncommit) - Undo the last commit (soft reset)
|
||||||
|
- [**git-update**](https://github.com/samsonjs/config/blob/main/bin/git-update) - Update and rebase current branch from remote with stash management
|
||||||
|
- [**jsonugly**](https://github.com/samsonjs/config/blob/main/bin/jsonugly) - Minify JSON by removing whitespace
|
||||||
|
- [**make-bookmarklet**](https://github.com/samsonjs/config/blob/main/bin/make-bookmarklet) - Convert JavaScript code to bookmarklet format
|
||||||
|
- [**progress**](https://github.com/samsonjs/config/blob/main/bin/progress) - Add progress indicators to piped data streams
|
||||||
|
- [**retina-scale**](https://github.com/samsonjs/config/blob/main/bin/retina-scale) - Scale images for 1x, 2x, and 3x display densities
|
||||||
|
- [**roll**](https://github.com/samsonjs/config/blob/main/bin/roll) - Random choice selector from command line arguments
|
||||||
|
- [**save-keyboard-shortcuts.sh**](https://github.com/samsonjs/config/blob/main/bin/save-keyboard-shortcuts.sh) - Export macOS keyboard shortcuts to file
|
||||||
|
- [**scale-app-icons**](https://github.com/samsonjs/config/blob/main/bin/scale-app-icons) - Generate iOS and macOS app icons at all required sizes
|
||||||
|
- [**screen-shell**](https://github.com/samsonjs/config/blob/main/bin/screen-shell) - Screen session management utility
|
||||||
|
- [**sd-before-copy**](https://github.com/samsonjs/config/blob/main/bin/sd-before-copy) - Pre-backup script for SuperDuper! to run Vortex backup system
|
||||||
|
- [**serve**](https://github.com/samsonjs/config/blob/main/bin/serve) - Start simple HTTP server (default port 8080)
|
||||||
|
- [**sri-integrity**](https://github.com/samsonjs/config/blob/main/bin/sri-integrity) - Generate Sub-Resource Integrity hashes for external resources
|
||||||
|
- [**youtube-snarf-audio**](https://github.com/samsonjs/config/blob/main/bin/youtube-snarf-audio) - Extract audio from YouTube videos
|
||||||
|
|
||||||
28
colours.rb
28
colours.rb
|
|
@ -1,4 +1,32 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
#
|
||||||
|
# colours.rb - Convert between color formats (hex, RGB, UIColor)
|
||||||
|
#
|
||||||
|
# Converts between hex colors and RGB values, with output in multiple formats
|
||||||
|
# including CSS, UIColor (iOS/macOS), and normalized float values.
|
||||||
|
#
|
||||||
|
# Usage: colours.rb <hex-color>
|
||||||
|
# colours.rb <red> <green> <blue>
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# colours.rb "#ff0000"
|
||||||
|
# colours.rb ff0000
|
||||||
|
# colours.rb 255 0 0
|
||||||
|
# colours.rb 1.0 0.0 0.0
|
||||||
|
|
||||||
|
if ARGV.empty? || ARGV.include?('-h') || ARGV.include?('--help')
|
||||||
|
puts "Usage: #{File.basename(__FILE__)} <hex-color>"
|
||||||
|
puts " #{File.basename(__FILE__)} <red> <green> <blue>"
|
||||||
|
puts ""
|
||||||
|
puts "Convert between color formats (hex, RGB, UIColor)"
|
||||||
|
puts ""
|
||||||
|
puts "Examples:"
|
||||||
|
puts " #{File.basename(__FILE__)} \"#ff0000\""
|
||||||
|
puts " #{File.basename(__FILE__)} ff0000"
|
||||||
|
puts " #{File.basename(__FILE__)} 255 0 0"
|
||||||
|
puts " #{File.basename(__FILE__)} 1.0 0.0 0.0"
|
||||||
|
exit 0
|
||||||
|
end
|
||||||
|
|
||||||
hex = ''
|
hex = ''
|
||||||
rgb = []
|
rgb = []
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,13 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# convert-all-songs - Batch convert all mp3 files in current directory to m4a format
|
||||||
|
#
|
||||||
|
# Converts all .mp3 files in the current directory to .m4a (AAC) format at 128 kbps.
|
||||||
|
# Removes any existing .m4a files before conversion to avoid conflicts.
|
||||||
|
#
|
||||||
|
# Usage: convert-all-songs
|
||||||
|
#
|
||||||
|
# Requirements: ffmpeg, convert-song script in same directory
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,13 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# convert-song - Convert an mp3 file to m4a format with reduced bitrate
|
||||||
|
#
|
||||||
|
# Converts an mp3 to an m4a (AAC) and reduces the bitrate to 128 kbps.
|
||||||
|
# Drops video streams (useful for mp3s with embedded artwork).
|
||||||
|
#
|
||||||
|
# Usage: convert-song <input.mp3>
|
||||||
|
#
|
||||||
|
# Requirements: ffmpeg
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
pbpaste | ruby -e 'puts ARGF.read.length'
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
#!/usr/bin/env ruby -w
|
|
||||||
|
|
||||||
require 'csv'
|
|
||||||
|
|
||||||
HEADERS = %w[device_name sessions].freeze
|
|
||||||
|
|
||||||
DEVICES_PATH = File.join(__dir__, 'supported_devices.csv')
|
|
||||||
|
|
||||||
# Maps device model to name using Google's giant CSV that lives alongside this file.
|
|
||||||
DEVICE_MAP = CSV.foreach(DEVICES_PATH).each_with_object({}) do |row, map|
|
|
||||||
# skip the first header row
|
|
||||||
next if row[0] == 'Retail Branding'
|
|
||||||
|
|
||||||
# columns: Retail Branding (maker), Marketing Name, Device (unused), Model
|
|
||||||
maker = row[0]
|
|
||||||
name = row[1]
|
|
||||||
model = row[3]
|
|
||||||
map[model] = "#{maker} #{name}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def main
|
|
||||||
in_csv = CSV.new(ARGF)
|
|
||||||
sessions_by_device = count_devices(in_csv)
|
|
||||||
render_csv(sessions_by_device)
|
|
||||||
end
|
|
||||||
|
|
||||||
def zero_hash
|
|
||||||
Hash.new { |_k, _v| 0 }
|
|
||||||
end
|
|
||||||
|
|
||||||
def count_devices(in_csv)
|
|
||||||
# skip the first header row
|
|
||||||
in_csv.drop(1).each_with_object(zero_hash) do |row, h|
|
|
||||||
# devices come in a raw model and we have to look up the marketing name for each one
|
|
||||||
# e.g. SM-S908N and SM-S908U are 2 of 9 models of the Galaxy S22 Ultra line
|
|
||||||
device_model = row[0]
|
|
||||||
device_name = DEVICE_MAP[device_model] || device_model
|
|
||||||
|
|
||||||
# Skip things that are obviously not Android
|
|
||||||
next if device_name =~ /iphone|ipad/i
|
|
||||||
|
|
||||||
h[device_name] += 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_csv(sessions_by_device)
|
|
||||||
puts CSV.generate_line(HEADERS)
|
|
||||||
sessions_by_device
|
|
||||||
.sort_by { |_device, sessions| sessions }
|
|
||||||
.reverse
|
|
||||||
.each do |device_name, sessions|
|
|
||||||
out_row = [device_name, sessions]
|
|
||||||
puts CSV.generate_line(out_row)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
main if $PROGRAM_NAME == __FILE__
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
#!/usr/bin/env ruby -w
|
|
||||||
|
|
||||||
require 'csv'
|
|
||||||
|
|
||||||
HEADERS = %w[version sessions].freeze
|
|
||||||
|
|
||||||
in_csv = CSV.new(ARGF)
|
|
||||||
is_header = true
|
|
||||||
|
|
||||||
zero_hash = Hash.new { |k, v| 0 }
|
|
||||||
sessions_by_version = in_csv.inject(zero_hash) do |h, row|
|
|
||||||
if is_header
|
|
||||||
is_header = false
|
|
||||||
next h
|
|
||||||
end
|
|
||||||
|
|
||||||
version = row[1]
|
|
||||||
sessions = row[2].to_i
|
|
||||||
major_version = version.split('.').first
|
|
||||||
h[major_version] += sessions
|
|
||||||
h
|
|
||||||
end
|
|
||||||
|
|
||||||
puts CSV.generate_line(HEADERS)
|
|
||||||
sessions_by_version.keys.map(&:to_i).sort.each do |version|
|
|
||||||
out_row = [version, sessions_by_version[version.to_s]]
|
|
||||||
puts CSV.generate_line(out_row)
|
|
||||||
end
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
docker run --rm --privileged alpine hwclock -s
|
|
||||||
|
|
@ -1,4 +1,18 @@
|
||||||
#!/bin/zsh
|
#!/bin/zsh
|
||||||
|
#
|
||||||
|
# enable-sudo-touch-id - Enable Touch ID authentication for sudo commands
|
||||||
|
#
|
||||||
|
# Configures PAM to allow Touch ID for sudo authentication on macOS.
|
||||||
|
# Creates /etc/pam.d/sudo_local from template and enables the auth module.
|
||||||
|
#
|
||||||
|
# Usage: enable-sudo-touch-id
|
||||||
|
|
||||||
|
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
|
||||||
|
echo "Usage: $(basename "$0")"
|
||||||
|
echo "Enable Touch ID authentication for sudo commands"
|
||||||
|
echo "Requires macOS with Touch ID support"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -e /etc/pam.d/sudo_local ]]; then
|
if [[ -e /etc/pam.d/sudo_local ]]; then
|
||||||
echo "TouchID unlock already in place"
|
echo "TouchID unlock already in place"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
for i in `find . -name "*.png" -o -name "*.jpg"`; do
|
|
||||||
file=`basename -s .jpg "$i" | xargs basename -s .png | xargs basename -s @2x | xargs basename -s @3x`
|
|
||||||
result=`ack -i "$file"`
|
|
||||||
if [ -z "$result" ]; then
|
|
||||||
echo "$i"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
@ -1,4 +1,22 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# finder-show-hidden-files - Toggle visibility of hidden files in macOS Finder
|
||||||
|
#
|
||||||
|
# Sets the AppleShowAllFiles preference and restarts Finder to show/hide hidden files.
|
||||||
|
# Defaults to showing hidden files if no argument is provided.
|
||||||
|
#
|
||||||
|
# Usage: finder-show-hidden-files [true|false]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# finder-show-hidden-files # Show hidden files
|
||||||
|
# finder-show-hidden-files false # Hide hidden files
|
||||||
|
|
||||||
|
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
|
||||||
|
echo "Usage: $(basename "$0") [true|false]"
|
||||||
|
echo "Toggle visibility of hidden files in Finder"
|
||||||
|
echo "Defaults to 'true' (show hidden files) if no argument provided"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
status=${1:-true}
|
status=${1:-true}
|
||||||
defaults write com.apple.finder AppleShowAllFiles $status
|
defaults write com.apple.finder AppleShowAllFiles $status
|
||||||
|
|
|
||||||
69
flux
69
flux
|
|
@ -1,69 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
|
|
||||||
require 'date'
|
|
||||||
require 'json'
|
|
||||||
require 'optparse'
|
|
||||||
|
|
||||||
TIMES = JSON.parse(File.read(File.expand_path('../sun-yyj.json', __FILE__)))['times']
|
|
||||||
USAGE_TEXT = "Usage: flux [options]"
|
|
||||||
|
|
||||||
def main
|
|
||||||
options = parse_options
|
|
||||||
flux(options)
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_options
|
|
||||||
options = {
|
|
||||||
dry_run: false,
|
|
||||||
set_current: false,
|
|
||||||
}
|
|
||||||
OptionParser.new do |opts|
|
|
||||||
opts.banner = USAGE_TEXT
|
|
||||||
|
|
||||||
opts.on("-n", "--dry-run", "Don't actually change the lights") do |dry_run|
|
|
||||||
options[:dry_run] = dry_run
|
|
||||||
end
|
|
||||||
opts.on("-c", "--set-current", "Change the lights to the setting that should currently be active") do |current|
|
|
||||||
options[:set_current] = current
|
|
||||||
end
|
|
||||||
end.parse!
|
|
||||||
options
|
|
||||||
end
|
|
||||||
|
|
||||||
def flux(options)
|
|
||||||
date = Date.today
|
|
||||||
month = date.month.to_s
|
|
||||||
day = date.day.to_s
|
|
||||||
if times = TIMES[month][day]
|
|
||||||
times['midnight'] = '22:30'
|
|
||||||
puts "sunrise: #{times['sunrise']}"
|
|
||||||
puts "morning: #{times['morning']}"
|
|
||||||
puts "sunset: #{times['sunset']}"
|
|
||||||
puts "night: #{times['night']}"
|
|
||||||
puts "midnight: #{times['midnight']}"
|
|
||||||
|
|
||||||
time = Time.now
|
|
||||||
hour, min = time.hour, time.min
|
|
||||||
padded_min = min < 10 ? "0#{min}" : "#{min}"
|
|
||||||
now = "#{hour}:#{padded_min}"
|
|
||||||
puts "current time: #{now}"
|
|
||||||
found =
|
|
||||||
if options[:set_current]
|
|
||||||
now = now.sub(':', '').to_i
|
|
||||||
if k = times.keys.select { |k| now >= times[k].sub(':', '').to_i }.last
|
|
||||||
[k, times[k]]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
times.detect { |k, v| now == v }
|
|
||||||
end
|
|
||||||
if found
|
|
||||||
setting = found[0]
|
|
||||||
puts "> exec lights #{setting} - 300"
|
|
||||||
exec "lights #{setting} - 300" unless options[:dry_run]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
raise "Cannot find today's date (#{date}) in times: #{TIMES.inspect}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
main if $0 == __FILE__
|
|
||||||
24
flux-clouds
24
flux-clouds
|
|
@ -1,24 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
|
|
||||||
require 'forecast_io'
|
|
||||||
require 'json'
|
|
||||||
|
|
||||||
ForecastIO.api_key = JSON.parse(File.read(File.expand_path('~/Dropbox/Personal/forecastio.json', __FILE__)))['apikey']
|
|
||||||
|
|
||||||
LATITUDE = 48.456642
|
|
||||||
LONGITUDE = -123.370325
|
|
||||||
|
|
||||||
def main
|
|
||||||
if forecast = ForecastIO.forecast(LATITUDE, LONGITUDE)
|
|
||||||
cloud_cover = forecast.currently.cloudCover
|
|
||||||
puts "Cloud cover: #{cloud_cover}"
|
|
||||||
setting = cloud_cover > 0.6 ? 'cloudy' : 'sunny'
|
|
||||||
# File.open('/Users/sjs/flux-clouds.log', 'a') { |f| f.puts "Cloud cover: #{cloud_cover}"; f.puts "> lights #{setting} - 100" }
|
|
||||||
puts "> lights #{setting} - 100"
|
|
||||||
exec "lights #{setting} - 100"
|
|
||||||
else
|
|
||||||
raise "Unable to check forecast"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
main if $0 == __FILE__
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
#!/bin/zsh
|
|
||||||
|
|
||||||
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
||||||
DIFF=$(git diff --staged)
|
|
||||||
if [[ -n "$DIFF" ]]; then
|
|
||||||
llm "Write a code commit message for this diff on the branch $BRANCH and only output the message itself so it can be used directly to commit. Include a one-line summary, and optionally a description below that if there are lots of changes. Be concise and avoid adding fluff or filler. Wrap the description at 70 characters per line.\n\n\`\`\`\n$DIFF\n\`\`\`"
|
|
||||||
fi
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
|
|
||||||
def git_dir?
|
|
||||||
File.exists?('.git') || `git rev-parse --git-dir >/dev/null`
|
|
||||||
end
|
|
||||||
|
|
||||||
def authors
|
|
||||||
`git log | grep '^Author:' | sed -e 's/Author: //g' | sort -f | uniq`.split(/\n/)
|
|
||||||
end
|
|
||||||
|
|
||||||
def authors_by_field n
|
|
||||||
raise "invalid field #{n}, expected 0 or 1" unless n == 0 || n == 1
|
|
||||||
other = (n-1).abs
|
|
||||||
authors.inject({}) do |as, a|
|
|
||||||
parts = a.split(' <')
|
|
||||||
parts[1].sub!(/>$/, '')
|
|
||||||
(as[parts[n]] ||= []) << parts[other]
|
|
||||||
as
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def authors_by_name; authors_by_field(0) end
|
|
||||||
def authors_by_email; authors_by_field(1) end
|
|
||||||
|
|
||||||
def main
|
|
||||||
exit 1 unless git_dir?
|
|
||||||
|
|
||||||
sort = :name
|
|
||||||
sort = ARGV.first.to_sym if ARGV.length > 0
|
|
||||||
if sort == :name
|
|
||||||
authors_by_name.each do |name, emails|
|
|
||||||
puts "#{name} <#{emails.join(', ')}>"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
authors_by_email.each do |email, names|
|
|
||||||
puts "#{email}: #{names.first} (aka #{names[1..-1].join(' aka ')})"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
main if $0 == __FILE__
|
|
||||||
|
|
@ -1,3 +1,21 @@
|
||||||
#!/bin/zsh
|
#!/bin/zsh
|
||||||
|
#
|
||||||
|
# git-diff-merge-conflict-resolution - Show combined diff for merge conflict resolution
|
||||||
|
#
|
||||||
|
# Displays a combined diff showing how conflicts were resolved in a merge commit.
|
||||||
|
# Uses git diff-tree with --cc to show the changes from all parents.
|
||||||
|
#
|
||||||
|
# Usage: git-diff-merge-conflict-resolution [commit]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# git-diff-merge-conflict-resolution # Show resolution for HEAD
|
||||||
|
# git-diff-merge-conflict-resolution abc123 # Show resolution for specific commit
|
||||||
|
|
||||||
|
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
|
||||||
|
echo "Usage: $(basename "$0") [commit]"
|
||||||
|
echo "Show combined diff for merge conflict resolution"
|
||||||
|
echo "Defaults to HEAD if no commit specified"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
git diff-tree --color --cc "${1:-HEAD}"
|
git diff-tree --color --cc "${1:-HEAD}"
|
||||||
|
|
@ -1,4 +1,22 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# git-edit-conflicted-files - Open all conflicted files in your editor
|
||||||
|
#
|
||||||
|
# Opens all files with merge conflicts in your preferred editor.
|
||||||
|
# Uses git-conflicts to find conflicted files and opens them all at once.
|
||||||
|
#
|
||||||
|
# Usage: git-edit-conflicted-files [editor]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# git-edit-conflicted-files # Use $VISUAL or $EDITOR
|
||||||
|
# git-edit-conflicted-files vim # Use vim specifically
|
||||||
|
|
||||||
|
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
|
||||||
|
echo "Usage: $(basename "$0") [editor]"
|
||||||
|
echo "Open all conflicted files in your editor"
|
||||||
|
echo "Uses \$VISUAL or \$EDITOR if no editor specified"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
EDIT="${1:-${VISUAL:-$EDITOR}}"
|
EDIT="${1:-${VISUAL:-$EDITOR}}"
|
||||||
eval $EDIT $(git conflicts | ruby -e "puts ARGF.each_line.to_a.map{|l| \"'\"+l.strip+\"'\"}.join(' ')")
|
eval $EDIT $(git conflicts | ruby -e "puts ARGF.each_line.to_a.map{|l| \"'\"+l.strip+\"'\"}.join(' ')")
|
||||||
|
|
|
||||||
43
hist
43
hist
|
|
@ -1,43 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
# Project Name: None
|
|
||||||
# File / Folder: hist.rb
|
|
||||||
# File Language: ruby
|
|
||||||
# Copyright (C): 2006 heptadecagram
|
|
||||||
# First Author: heptadecagram
|
|
||||||
# First Created: 2006.03.13 20:14:58
|
|
||||||
# Last Modifier: sjs
|
|
||||||
# Last Modified: 2010.02.11
|
|
||||||
#
|
|
||||||
# now works w/ zsh and reports commands that account for at least 1% of the total
|
|
||||||
|
|
||||||
command = {}
|
|
||||||
execution = {}
|
|
||||||
$total = 0
|
|
||||||
|
|
||||||
IO.foreach('/Users/sjs/config/zsh/zhistory') do |line|
|
|
||||||
line.chomp! =~ /^:\s\d+:\d+;((\S+).*)$/
|
|
||||||
next if $1.nil? || $2.nil?
|
|
||||||
execution[$1] = 1 + execution[$1].to_i
|
|
||||||
command[$2] = 1 + command[$2].to_i
|
|
||||||
$total += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
puts $total
|
|
||||||
|
|
||||||
execution = execution.select {|a,b| b.to_f / $total > 0.01}
|
|
||||||
command = command.select {|a,b| b.to_f / $total > 0.01}
|
|
||||||
|
|
||||||
Max_length = execution.sort_by {|a| a[0].length }.reverse[0][0].length
|
|
||||||
|
|
||||||
def print_hash(hash)
|
|
||||||
sorted = hash.sort {|a,b| b[1] <=> a[1] }
|
|
||||||
sorted.each do |cmd,value|
|
|
||||||
printf " %#{Max_length}s: %3d(%.2f%%)\n", cmd, value, value.to_f / $total
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
puts "Executions:\n"
|
|
||||||
print_hash(execution)
|
|
||||||
puts "Commands:\n"
|
|
||||||
print_hash(command)
|
|
||||||
41
hist.rb
41
hist.rb
|
|
@ -1,41 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
# Project Name: None
|
|
||||||
# File / Folder: hist.rb
|
|
||||||
# File Language: ruby
|
|
||||||
# Copyright (C): 2006 heptadecagram
|
|
||||||
# First Author: heptadecagram
|
|
||||||
# First Created: 2006.03.13 20:14:58
|
|
||||||
# Last Modifier: heptadecagram
|
|
||||||
# Last Modified: 2008.05.02
|
|
||||||
|
|
||||||
command = {}
|
|
||||||
execution = {}
|
|
||||||
$total = 0
|
|
||||||
|
|
||||||
IO.foreach('/Users/sjs/config/zsh/zhistory') do |line|
|
|
||||||
line.chomp! =~ /^:\s\d+:\d+;((\S+).*)$/
|
|
||||||
next if $1.nil? || $2.nil?
|
|
||||||
execution[$1] = 1 + execution[$1].to_i
|
|
||||||
command[$2] = 1 + command[$2].to_i
|
|
||||||
$total += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
puts $total
|
|
||||||
|
|
||||||
execution = execution.select {|a,b| b.to_f / $total > 0.01}
|
|
||||||
command = command.select {|a,b| b.to_f / $total > 0.01}
|
|
||||||
|
|
||||||
Max_length = execution.sort_by {|a| a[0].length }.reverse[0][0].length
|
|
||||||
|
|
||||||
def print_hash(hash)
|
|
||||||
sorted = hash.sort {|a,b| b[1] <=> a[1] }
|
|
||||||
sorted.each do |cmd,value|
|
|
||||||
printf " %#{Max_length}s: %3d(%.2f%%)\n", cmd, value, value.to_f / $total
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
puts "Executions:\n"
|
|
||||||
print_hash(execution)
|
|
||||||
puts "Commands:\n"
|
|
||||||
print_hash(command)
|
|
||||||
294
jqed
294
jqed
|
|
@ -1,294 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import urwid
|
|
||||||
import urwid_readline
|
|
||||||
import os
|
|
||||||
import subprocess as sp
|
|
||||||
import shutil
|
|
||||||
import shlex
|
|
||||||
import select
|
|
||||||
import platform
|
|
||||||
import re
|
|
||||||
|
|
||||||
VERSION = 'v0.1.3 (2020-09-25)'
|
|
||||||
|
|
||||||
PROMPT = 'jq> '
|
|
||||||
PAUSED_PROMPT_A = '||'
|
|
||||||
PAUSED_PROMPT_B = '> '
|
|
||||||
|
|
||||||
IS_WSL = "Microsoft" in platform.platform()
|
|
||||||
|
|
||||||
palette = [
|
|
||||||
('prompt_ok', 'light green,bold', 'default'),
|
|
||||||
('prompt_paused', 'yellow,bold', 'default'),
|
|
||||||
('prompt_err', 'light red,bold', 'default'),
|
|
||||||
('inp_plain', 'bold', 'default'),
|
|
||||||
('body_plain', '', 'default'),
|
|
||||||
('err_bar', 'light red,bold', 'default'),
|
|
||||||
]
|
|
||||||
|
|
||||||
class JqManager:
|
|
||||||
def __init__(self, inp_file, loop):
|
|
||||||
self.inp_file = inp_file
|
|
||||||
self.loop = loop
|
|
||||||
|
|
||||||
self.loop.event_loop.watch_file(self.inp_file.fileno(), self._file_avail_cb)
|
|
||||||
self.inp_data = ''
|
|
||||||
self.last_out_data = ''
|
|
||||||
self.out_data = ''
|
|
||||||
self.out_err = ''
|
|
||||||
self.scroll_line = 0
|
|
||||||
|
|
||||||
self.paused = False
|
|
||||||
self.prompt_ok = True
|
|
||||||
self.is_inp_data_done = False
|
|
||||||
self._jq_path = shutil.which('jq')
|
|
||||||
if not self._jq_path:
|
|
||||||
try:
|
|
||||||
orig_stdout.write('jq does not seem to be installed\nPerhaps you want: sudo apt install jq\n'.encode())
|
|
||||||
except BrokenPipeError:
|
|
||||||
sys.stderr.write('jq does not seem to be installed\nPerhaps you want: sudo apt install jq\n')
|
|
||||||
exit(1)
|
|
||||||
self.jq_proc = None
|
|
||||||
self.respawn_jq(None, inp.get_edit_text())
|
|
||||||
|
|
||||||
urwid.connect_signal(inp, 'change', self.respawn_jq)
|
|
||||||
|
|
||||||
def _file_avail_cb(self):
|
|
||||||
chunk = os.read(orig_stdin.fileno(), 1024).decode()
|
|
||||||
if len(chunk) != 0:
|
|
||||||
self.inp_data += chunk
|
|
||||||
try:
|
|
||||||
self.jq_proc.stdin.write(chunk.encode())
|
|
||||||
except ValueError:
|
|
||||||
# if `self.jq_proc.stdin` was closed
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.loop.event_loop.remove_watch_file(orig_stdin.fileno())
|
|
||||||
self.is_inp_data_done = True
|
|
||||||
self.jq_proc.stdin.close()
|
|
||||||
|
|
||||||
def toggle_pause(self):
|
|
||||||
self.paused = not self.paused
|
|
||||||
if self.out_data != '':
|
|
||||||
self.last_out_data = self.out_data
|
|
||||||
if self.prompt_ok:
|
|
||||||
self.update_body()
|
|
||||||
if self.paused:
|
|
||||||
if self.prompt_ok:
|
|
||||||
inp.set_caption([('prompt_paused', PAUSED_PROMPT_A), ('prompt_ok', PAUSED_PROMPT_B)])
|
|
||||||
else:
|
|
||||||
inp.set_caption([('prompt_paused', PAUSED_PROMPT_A), ('prompt_err', PAUSED_PROMPT_B)])
|
|
||||||
self.loop.event_loop.remove_watch_file(self.inp_file.fileno())
|
|
||||||
else:
|
|
||||||
if self.prompt_ok:
|
|
||||||
inp.set_caption(('prompt_ok', PROMPT))
|
|
||||||
else:
|
|
||||||
inp.set_caption(('prompt_err', PROMPT))
|
|
||||||
self.loop.event_loop.watch_file(self.inp_file.fileno(), self._file_avail_cb)
|
|
||||||
|
|
||||||
def _jq_out_avail_cb(self):
|
|
||||||
if self.jq_proc.stdout not in select.select([self.jq_proc.stdout], [], [], 0)[0]:
|
|
||||||
# Ignore spurius calls
|
|
||||||
return
|
|
||||||
|
|
||||||
chunk = ''
|
|
||||||
while self.jq_proc.stdout in select.select([self.jq_proc.stdout], [], [], 0)[0]:
|
|
||||||
new_chunk = os.read(self.jq_proc.stdout.fileno(), 1024).decode()
|
|
||||||
if len(new_chunk) == 0:
|
|
||||||
break
|
|
||||||
chunk += new_chunk
|
|
||||||
|
|
||||||
if len(chunk) != 0:
|
|
||||||
self.out_data += chunk
|
|
||||||
if not self.paused:
|
|
||||||
self.update_body()
|
|
||||||
else:
|
|
||||||
if self.out_err == '':
|
|
||||||
if loop.screen_size is not None:
|
|
||||||
new_scroll_line = min(max(len(self.out_data.split('\n')) - int(loop.screen_size[1] / 2), 0), self.scroll_line)
|
|
||||||
if new_scroll_line != self.scroll_line:
|
|
||||||
self.scroll_line = new_scroll_line
|
|
||||||
self.update_body()
|
|
||||||
self.loop.event_loop.remove_watch_file(self.jq_proc.stdout.fileno())
|
|
||||||
self.jq_proc.stdout.close()
|
|
||||||
self.jq_proc.stdin.close()
|
|
||||||
self.jq_proc.wait()
|
|
||||||
|
|
||||||
def _jq_err_avail_cb(self):
|
|
||||||
if self.jq_proc.stderr not in select.select([self.jq_proc.stderr], [], [], 0)[0]:
|
|
||||||
# Ignore spurius calls
|
|
||||||
return
|
|
||||||
|
|
||||||
chunk = ''
|
|
||||||
while self.jq_proc.stderr in select.select([self.jq_proc.stderr], [], [], 0)[0]:
|
|
||||||
new_chunk = os.read(self.jq_proc.stderr.fileno(), 1024).decode()
|
|
||||||
if len(new_chunk) == 0:
|
|
||||||
break
|
|
||||||
chunk += new_chunk
|
|
||||||
|
|
||||||
if len(chunk) != 0:
|
|
||||||
self.out_err += chunk
|
|
||||||
err_bar.set_text(self.out_err.replace(' (Unix shell quoting issues?)', '').strip())
|
|
||||||
else:
|
|
||||||
self.loop.event_loop.remove_watch_file(self.jq_proc.stderr.fileno())
|
|
||||||
self.jq_proc.stderr.close()
|
|
||||||
|
|
||||||
if self.out_err != '':
|
|
||||||
self.prompt_ok = False
|
|
||||||
|
|
||||||
if self.paused:
|
|
||||||
inp.set_caption([('prompt_paused', PAUSED_PROMPT_A), ('prompt_err', PAUSED_PROMPT_B)])
|
|
||||||
else:
|
|
||||||
inp.set_caption(('prompt_err', PROMPT))
|
|
||||||
elif self.out_data == '':
|
|
||||||
self.last_out_data = ''
|
|
||||||
self.update_body()
|
|
||||||
|
|
||||||
def respawn_jq(self, _, query):
|
|
||||||
if self.jq_proc is not None:
|
|
||||||
if not self.jq_proc.stdout.closed:
|
|
||||||
self.loop.event_loop.remove_watch_file(self.jq_proc.stdout.fileno())
|
|
||||||
if not self.jq_proc.stderr.closed:
|
|
||||||
self.loop.event_loop.remove_watch_file(self.jq_proc.stderr.fileno())
|
|
||||||
self.jq_proc.stdin.close()
|
|
||||||
self.jq_proc.stdout.close()
|
|
||||||
self.jq_proc.stderr.close()
|
|
||||||
self.jq_proc.terminate()
|
|
||||||
self.jq_proc.wait()
|
|
||||||
err_bar.set_text('')
|
|
||||||
if self.out_data != '' and not self.paused:
|
|
||||||
self.last_out_data = self.out_data
|
|
||||||
self.out_data = ''
|
|
||||||
self.out_err = ''
|
|
||||||
self.prompt_ok = True
|
|
||||||
if self.paused:
|
|
||||||
inp.set_caption([('prompt_paused', PAUSED_PROMPT_A), ('prompt_ok', PAUSED_PROMPT_B)])
|
|
||||||
else:
|
|
||||||
inp.set_caption(('prompt_ok', PROMPT))
|
|
||||||
|
|
||||||
self.jq_proc = sp.Popen([self._jq_path, query], stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE, bufsize=0)
|
|
||||||
try:
|
|
||||||
encoded_inp_data = self.inp_data.encode()
|
|
||||||
try:
|
|
||||||
for offset in range(0, len(encoded_inp_data), 1024):
|
|
||||||
self.jq_proc.stdin.write(encoded_inp_data[offset:offset+1024])
|
|
||||||
self._jq_out_avail_cb()
|
|
||||||
self._jq_err_avail_cb()
|
|
||||||
if self.is_inp_data_done:
|
|
||||||
self.jq_proc.stdin.close()
|
|
||||||
self.loop.event_loop.watch_file(self.jq_proc.stdout.fileno(), self._jq_out_avail_cb)
|
|
||||||
self.loop.event_loop.watch_file(self.jq_proc.stderr.fileno(), self._jq_err_avail_cb)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
except BrokenPipeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def update_body(self):
|
|
||||||
height = 256
|
|
||||||
if self.loop.screen_size is not None:
|
|
||||||
height = self.loop.screen_size[1]
|
|
||||||
|
|
||||||
if self.out_data and not self.paused:
|
|
||||||
body.set_text('\n'.join(self.out_data.split('\n')[self.scroll_line:][:height]))
|
|
||||||
else:
|
|
||||||
body.set_text('\n'.join(self.last_out_data.split('\n')[self.scroll_line:][:height]))
|
|
||||||
|
|
||||||
class BetterEdit(urwid_readline.ReadlineEdit):
|
|
||||||
def keypress(self, size, key):
|
|
||||||
if key == 'ctrl left':
|
|
||||||
try:
|
|
||||||
self.edit_pos = self.edit_text[:self.edit_pos].rindex(' ')
|
|
||||||
except ValueError:
|
|
||||||
self.edit_pos = 0
|
|
||||||
elif key == 'ctrl right':
|
|
||||||
try:
|
|
||||||
self.edit_pos += self.edit_text[self.edit_pos:].index(' ') + 1
|
|
||||||
except ValueError:
|
|
||||||
self.edit_pos = len(self.edit_text)
|
|
||||||
elif key == 'ctrl p':
|
|
||||||
jq_man.toggle_pause()
|
|
||||||
elif key in ('up', 'down', 'page up', 'page down'):
|
|
||||||
if key == 'up':
|
|
||||||
jq_man.scroll_line = max(0, jq_man.scroll_line - 1)
|
|
||||||
elif key == 'down':
|
|
||||||
jq_man.scroll_line = min(max(len(jq_man.out_data.split('\n')) - int(loop.screen_size[1] / 2), 0), jq_man.scroll_line + 1)
|
|
||||||
elif key == 'page up':
|
|
||||||
jq_man.scroll_line = max(0, jq_man.scroll_line - int(loop.screen_size[1] / 2))
|
|
||||||
elif key == 'page down':
|
|
||||||
jq_man.scroll_line = min(max(len(jq_man.out_data.split('\n')) - int(loop.screen_size[1] / 2), 0), jq_man.scroll_line + int(loop.screen_size[1] / 2))
|
|
||||||
jq_man.update_body()
|
|
||||||
else:
|
|
||||||
return super().keypress(size, key)
|
|
||||||
|
|
||||||
|
|
||||||
class WSLScreen(urwid.raw_display.Screen):
|
|
||||||
"""
|
|
||||||
This class is used to fix issue #6, where urwid has artifacts under WSL
|
|
||||||
"""
|
|
||||||
def write(self, data):
|
|
||||||
# replace urwid's SI/SO, which produce artifacts under WSL.
|
|
||||||
# at some point we may figure out what they actually do.
|
|
||||||
data = re.sub("[\x0e\x0f]", "", data)
|
|
||||||
super().write(data)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
if sys.stdin.isatty():
|
|
||||||
sys.stderr.write('error: jqed requires some data piped on standard input, for example try: `ip --json link | jqed`\n')
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if len(sys.argv) > 2:
|
|
||||||
sys.stderr.write('usage: jqed [initial expression]\n')
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
# Preserve original stdio, and replace stdio with /dev/tty
|
|
||||||
orig_stdin = os.fdopen(os.dup(sys.stdin.fileno()))
|
|
||||||
orig_stdout = os.fdopen(os.dup(sys.stdout.fileno()), mode='wb', buffering=0)
|
|
||||||
|
|
||||||
os.close(0)
|
|
||||||
os.close(1)
|
|
||||||
sys.stdin = open('/dev/tty', 'rb')
|
|
||||||
sys.stdout = open('/dev/tty', 'wb')
|
|
||||||
|
|
||||||
# Apparently urwid has some artifacts with WSL, see issue #6
|
|
||||||
# Hopefully this won't break WSL2
|
|
||||||
if IS_WSL:
|
|
||||||
urwid_screen = WSLScreen()
|
|
||||||
else:
|
|
||||||
urwid_screen = urwid.raw_display.Screen()
|
|
||||||
|
|
||||||
|
|
||||||
# Create gui
|
|
||||||
inp = BetterEdit(('prompt_ok', PROMPT))
|
|
||||||
if len(sys.argv) == 2:
|
|
||||||
# If the user specified an argument, use it as an initial expression
|
|
||||||
inp.set_edit_text(sys.argv[1])
|
|
||||||
inp.set_edit_pos(len(sys.argv[1]))
|
|
||||||
body = urwid.Text('')
|
|
||||||
body_filler = urwid.AttrMap(urwid.Filler(body, 'top'), 'body_plain')
|
|
||||||
err_bar = urwid.Text(('inp_plain', 'HELP: ^C: Exit, ^P: Pause, jq manual: https://stedolan.github.io/jq/manual'))
|
|
||||||
|
|
||||||
frame = urwid.Frame(
|
|
||||||
body_filler,
|
|
||||||
header=urwid.AttrMap(inp, 'inp_plain'),
|
|
||||||
footer=urwid.AttrMap(err_bar, 'err_bar'),
|
|
||||||
focus_part='header'
|
|
||||||
)
|
|
||||||
loop = urwid.MainLoop(frame, palette, handle_mouse=False, screen=urwid_screen)
|
|
||||||
try:
|
|
||||||
jq_man = JqManager(orig_stdin, loop)
|
|
||||||
loop.run()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
line = shlex.quote(inp.edit_text.strip())
|
|
||||||
if line.startswith("''"):
|
|
||||||
line = line[2:]
|
|
||||||
if line.endswith("''"):
|
|
||||||
line = line[:-2]
|
|
||||||
try:
|
|
||||||
orig_stdout.write(
|
|
||||||
('{}\njqed: jq editor ' + VERSION + ' https://github.com/wazzaps/jqed\n' +
|
|
||||||
'jqed: | jq {}\n').format(jq_man.out_data, line).encode())
|
|
||||||
except BrokenPipeError:
|
|
||||||
sys.stderr.write('jq {}\n'.format(line))
|
|
||||||
exit(0)
|
|
||||||
12
jsonugly
12
jsonugly
|
|
@ -1,4 +1,16 @@
|
||||||
#!/usr/bin/env ruby -w
|
#!/usr/bin/env ruby -w
|
||||||
|
#
|
||||||
|
# jsonugly - Minify JSON by removing whitespace
|
||||||
|
#
|
||||||
|
# Reads JSON from stdin or files and outputs compact/minified JSON.
|
||||||
|
# Removes all unnecessary whitespace to make JSON "ugly" but smaller.
|
||||||
|
#
|
||||||
|
# Usage: jsonugly [file ...]
|
||||||
|
# cat file.json | jsonugly
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# jsonugly data.json
|
||||||
|
# echo '{"a": 1, "b": 2}' | jsonugly # outputs {"a":1,"b":2}
|
||||||
|
|
||||||
require 'json'
|
require 'json'
|
||||||
|
|
||||||
|
|
|
||||||
79
lights
79
lights
|
|
@ -1,79 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
|
|
||||||
require 'huemote'
|
|
||||||
|
|
||||||
LIGHT_NAMES = ['Office 1', 'Office 2']
|
|
||||||
TEMPS = {
|
|
||||||
'bright' => {
|
|
||||||
brightness: 254,
|
|
||||||
temp: 4200,
|
|
||||||
},
|
|
||||||
'cloudy' => {
|
|
||||||
brightness: 220,
|
|
||||||
temp: 3500,
|
|
||||||
},
|
|
||||||
'sunrise' => {
|
|
||||||
brightness: 180,
|
|
||||||
temp: 2200,
|
|
||||||
},
|
|
||||||
'morning' => {
|
|
||||||
brightness: 220,
|
|
||||||
temp: 2600,
|
|
||||||
},
|
|
||||||
'noon' => {
|
|
||||||
brightness: 254,
|
|
||||||
temp: 4200,
|
|
||||||
},
|
|
||||||
'sunset' => {
|
|
||||||
brightness: 210,
|
|
||||||
temp: 2600,
|
|
||||||
},
|
|
||||||
'night' => {
|
|
||||||
brightness: 160,
|
|
||||||
temp: 2300,
|
|
||||||
},
|
|
||||||
'midnight' => {
|
|
||||||
brightness: 120,
|
|
||||||
temp: 2100,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
TEMPS['sunny'] = TEMPS['bright']
|
|
||||||
TEMPS['overcast'] = TEMPS['cloudy']
|
|
||||||
|
|
||||||
setting = ARGV[0]
|
|
||||||
brightness = ARGV[1] || 220
|
|
||||||
transition = ARGV[2]
|
|
||||||
Huemote.set_ip '192.168.0.2'
|
|
||||||
lights = LIGHT_NAMES.map { |name| Huemote::Light.find(name) }
|
|
||||||
|
|
||||||
def kelvin_to_mireds(temp)
|
|
||||||
[[1_000_000 / temp, 154].max, 500].min
|
|
||||||
end
|
|
||||||
|
|
||||||
case setting
|
|
||||||
when 'off'
|
|
||||||
lights.each(&:off!)
|
|
||||||
when 'on'
|
|
||||||
lights.each(&:on!)
|
|
||||||
else
|
|
||||||
lights.each(&:on!)
|
|
||||||
attrs = TEMPS[setting] || {
|
|
||||||
temp: setting.to_i,
|
|
||||||
brightness: brightness.to_i,
|
|
||||||
}
|
|
||||||
if attrs
|
|
||||||
attrs[:bri] = attrs.delete(:brightness)
|
|
||||||
if attrs[:temp]
|
|
||||||
attrs[:ct] = kelvin_to_mireds(attrs.delete(:temp))
|
|
||||||
end
|
|
||||||
if transition
|
|
||||||
# tenths of a second
|
|
||||||
attrs[:transitiontime] = transition.to_i
|
|
||||||
end
|
|
||||||
lights.each do |l|
|
|
||||||
l.send(:set!, attrs)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
puts "Unknown setting: #{setting}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
23
roll
23
roll
|
|
@ -1,4 +1,27 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
#
|
||||||
|
# roll - Random choice selector from command line arguments
|
||||||
|
#
|
||||||
|
# Randomly selects one item from the provided command line arguments.
|
||||||
|
# Useful for making decisions or selecting random items from a list.
|
||||||
|
#
|
||||||
|
# Usage: roll <choice1> [<choice2> ...]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# roll heads tails
|
||||||
|
# roll red blue green yellow
|
||||||
|
# roll "option 1" "option 2" "option 3"
|
||||||
|
|
||||||
|
if ARGV.empty? || ARGV.include?('-h') || ARGV.include?('--help')
|
||||||
|
puts "Usage: #{File.basename(__FILE__)} <choice1> [<choice2> ...]"
|
||||||
|
puts "Randomly select one item from the provided arguments"
|
||||||
|
puts ""
|
||||||
|
puts "Examples:"
|
||||||
|
puts " #{File.basename(__FILE__)} heads tails"
|
||||||
|
puts " #{File.basename(__FILE__)} red blue green yellow"
|
||||||
|
puts " #{File.basename(__FILE__)} \"option 1\" \"option 2\" \"option 3\""
|
||||||
|
exit 0
|
||||||
|
end
|
||||||
|
|
||||||
choices = ARGV
|
choices = ARGV
|
||||||
puts choices.sample
|
puts choices.sample
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# save-keyboard-shortcuts.sh
|
#
|
||||||
|
# save-keyboard-shortcuts.sh - Export macOS keyboard shortcuts to restore script
|
||||||
|
#
|
||||||
|
# Reads all NSUserKeyEquivalents from macOS preferences and generates a script
|
||||||
|
# to restore them. Useful for backing up custom keyboard shortcuts.
|
||||||
|
#
|
||||||
|
# Usage: save-keyboard-shortcuts.sh
|
||||||
|
#
|
||||||
|
# Output: Creates ~/bin/restore-keyboard-shortcuts.sh
|
||||||
|
|
||||||
DESTFILE=~/bin/restore-keyboard-shortcuts.sh
|
DESTFILE=~/bin/restore-keyboard-shortcuts.sh
|
||||||
echo '#!/bin/bash' > $DESTFILE
|
echo '#!/bin/bash' > $DESTFILE
|
||||||
|
|
@ -7,3 +15,5 @@ echo '#!/bin/bash' > $DESTFILE
|
||||||
defaults find NSUserKeyEquivalents | sed -e "s/Found [0-9]* keys in domain '\\([^']*\\)':/defaults write \\1 NSUserKeyEquivalents '/" -e "s/ NSUserKeyEquivalents = {//" -e "s/};//" -e "s/}/}'/" >> $DESTFILE
|
defaults find NSUserKeyEquivalents | sed -e "s/Found [0-9]* keys in domain '\\([^']*\\)':/defaults write \\1 NSUserKeyEquivalents '/" -e "s/ NSUserKeyEquivalents = {//" -e "s/};//" -e "s/}/}'/" >> $DESTFILE
|
||||||
echo killall cfprefsd >> $DESTFILE
|
echo killall cfprefsd >> $DESTFILE
|
||||||
chmod a+x $DESTFILE
|
chmod a+x $DESTFILE
|
||||||
|
|
||||||
|
echo "Keyboard shortcuts saved to $DESTFILE"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
#!/bin/zsh
|
#!/bin/zsh
|
||||||
|
#
|
||||||
|
# sd-before-copy - Pre-backup script for SuperDuper! to run the backup vortex
|
||||||
|
#
|
||||||
|
# Usage: sd-before-copy (called automatically by SuperDuper!)
|
||||||
|
|
||||||
# Redirect stderr to stdout because SuperDuper! interprets any output to stderr as a failure.
|
# Redirect stderr to stdout because SuperDuper! interprets any output to stderr as a failure.
|
||||||
# Mystifying decision but whatever. Easy to work around it.
|
# Mystifying decision but whatever. Easy to work around it.
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
37902
supported_devices.csv
37902
supported_devices.csv
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,18 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
#
|
||||||
|
# youtube-snarf-audio - Extract audio from YouTube videos
|
||||||
|
#
|
||||||
|
# Downloads YouTube videos and extracts audio to m4a format.
|
||||||
|
# Accepts YouTube URLs or just video IDs as arguments.
|
||||||
|
# Skips audio extraction if output file already exists.
|
||||||
|
#
|
||||||
|
# Usage: youtube-snarf-audio <youtube-url> [<youtube-url> ...]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# youtube-snarf-audio https://youtube.com/watch?v=abc123
|
||||||
|
# youtube-snarf-audio abc123
|
||||||
|
#
|
||||||
|
# Requires: youtube-dl, ffmpeg
|
||||||
|
|
||||||
require 'cgi'
|
require 'cgi'
|
||||||
require 'faraday'
|
require 'faraday'
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue