aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorPolesznyák Márk <contact@pml68.dev>2025-11-29 01:45:07 +0100
committerPolesznyák Márk <contact@pml68.dev>2025-12-29 14:50:02 +0100
commitbf7347380207d80183ce80ae6547ef08fa579c6a (patch)
treeec64896997e4bd76d89738f0b156dde7423ff107 /scripts
parentfeat: initial commit (diff)
downloaddotfiles-bf7347380207d80183ce80ae6547ef08fa579c6a.tar.gz
feat: add scripts, TARGET variable for Makefile
Diffstat (limited to '')
-rwxr-xr-xscripts/bash-completor734
-rwxr-xr-xscripts/checktodo11
-rwxr-xr-xscripts/cht.sh15
-rwxr-xr-xscripts/fcopy8
-rwxr-xr-xscripts/gitline6
-rwxr-xr-xscripts/internet4
-rwxr-xr-xscripts/origin12
-rwxr-xr-xscripts/publicip3
-rwxr-xr-xscripts/screenshot6
-rwxr-xr-xscripts/tags3
-rwxr-xr-xscripts/tmus30
11 files changed, 832 insertions, 0 deletions
diff --git a/scripts/bash-completor b/scripts/bash-completor
new file mode 100755
index 0000000..b013ec6
--- /dev/null
+++ b/scripts/bash-completor
@@ -0,0 +1,734 @@
+#!/usr/bin/env bash
+
+set -o errexit
+set -o nounset
+set -o pipefail
+set -o errtrace
+(shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit
+
+readonly VERSION=v0.2.0
+readonly ARGS=$*
+readonly SPACES_8=' '
+readonly SPACES_6=' '
+readonly SPACES_4=' '
+readonly SPACES_2=' '
+
+declare -r RED="\\e[31m"
+declare -r GREEN="\\e[32m"
+declare -r YELLOW="\\e[33m"
+declare -r CYAN="\\e[36m"
+declare -r RESET_ALL="\\e[0m"
+
+debug() {
+ printf "%b[Debug] %s%b\n" "$CYAN" "$*" "$RESET_ALL" >/dev/tty
+}
+
+warn() {
+ printf "%b[Warn] %s%b\n" "$YELLOW" "$*" "$RESET_ALL" >/dev/tty
+}
+
+error() {
+ printf "%b[Error] %s%b\n" "$RED" "$*" "$RESET_ALL" >/dev/tty
+}
+
+suggest() {
+ printf "%b[Suggest] %s%b\n" "$GREEN" "$*" "$RESET_ALL" >/dev/tty
+}
+
+# Copy from https://github.com/adoyle-h/lobash/blob/develop/src/modules/is_array.bash
+is_array() {
+ local attrs
+ # shellcheck disable=2207
+ attrs=$(declare -p "$1" 2>/dev/null | sed -E "s/^declare -([-a-zA-Z]+) .+/\\1/" || true)
+
+ # a: array
+ # A: associate array
+ if [[ ${attrs} =~ a|A ]]; then return 0; else return 1; fi
+}
+
+is_func() {
+ declare -F "$1" &>/dev/null
+}
+
+get_varname() {
+ local name=${1:-}
+ local encoded=${word_to_varname[$name]:-}
+
+ if [[ -z ${encoded} ]]; then
+ encoded=${name//[^a-zA-Z_]/_}
+ fi
+
+ echo "${encoded}"
+}
+
+is_gnu_sed() {
+ local out
+ out=$(${1:-sed} --version 2>/dev/null)
+ [[ $out =~ 'GNU sed' ]]
+}
+
+reply_words() {
+ local IFS=$'\n'
+ # shellcheck disable=2207
+ COMPREPLY=( $(IFS=', ' compgen -W "$*" -- "${cur#=}") )
+}
+
+reply_list() {
+ local IFS=', '
+ local array_list="" array_name
+ # shellcheck disable=2068
+ for array_name in "$@"; do
+ array_list="$array_list \${${array_name}[*]}"
+ done
+ array_list="${array_list[*]:1}"
+
+ IFS=$'\n'' '
+ eval "COMPREPLY=( \$(compgen -W \"$array_list\" -- \"\$cur\") )"
+}
+
+reply_files() {
+ local IFS=$'\n'
+ compopt -o nospace -o filenames
+ # shellcheck disable=2207
+ COMPREPLY=( $(compgen -A file -- "$cur") )
+}
+
+reply_files_in_pattern() {
+ compopt -o nospace -o filenames
+
+ local path
+ while read -r path; do
+ if [[ $path =~ $1 ]] || [[ -d $path ]]; then
+ COMPREPLY+=( "$path" )
+ fi
+ done < <(compgen -A file -- "$cur")
+}
+
+reply_dirs() {
+ local IFS=$'\n'
+ compopt -o nospace -o filenames
+ # shellcheck disable=2207
+ COMPREPLY=( $(compgen -A directory -- "$cur") )
+}
+
+
+make_get_varnames() {
+ echo ""
+
+ declare -p word_to_varname | sed -e "s/word_to_varname/_${cmd}_comp_word_to_varname/"
+
+ declare -f get_varname | sed -e "s/get_varname/_${cmd}_comp_util_get_varname/" -e 's/ *$//g' \
+ -e "s/word_to_varname/_${cmd}_comp_word_to_varname/"
+}
+
+make_dumped_variables() {
+ echo ""
+ local name
+ for name in $(compgen -A variable var_); do
+ declare -p "$name" | sed "s/^declare -.* var_/_${cmd}_comp_var_/"
+ done
+}
+
+make_header() {
+ cat <<EOF
+# This file is generated by [bash-completor](https://github.com/adoyle-h/bash-completor/tree/$VERSION). Do not modify it manually.
+#
+# [Usage]
+# Put "source $output" in your bashrc.
+#
+# If you want to debug the completion.
+# Search '# Uncomment this line for debug' line in this file.
+#
+# [Update Script]
+# bash-completor $ARGS
+EOF
+
+ if [[ -n ${version:-} ]]; then
+ cat <<EOF
+#
+# [Version] $version
+EOF
+ fi
+
+ if [[ -n ${license:-} ]]; then
+ cat <<EOF
+#
+# [License] $license
+EOF
+ fi
+
+ if (( ${#authors[@]} > 0 )); then
+ cat <<EOF
+#
+# [Authors]
+EOF
+
+ {
+ local IFS=$'\n'
+ local author
+ for author in "${authors[@]}"; do
+ echo "# ${author}"
+ done
+ }
+ fi
+
+ if (( ${#maintainers[@]} > 0 )); then
+ cat <<EOF
+#
+# [Maintainers]
+EOF
+
+ {
+ local IFS=$'\n'
+ local maintainer
+ for maintainer in "${maintainers[@]}"; do
+ echo "# ${maintainer}"
+ done
+ }
+ fi
+
+ if [[ -n ${description:-} ]]; then
+ cat <<EOF
+#
+# [Description]
+# ${description}
+EOF
+ fi
+
+ if [[ -n ${notice:-} ]]; then
+ cat <<EOF
+#
+# [Notice]
+# ${notice}
+EOF
+ fi
+
+ cat <<EOF
+
+# shellcheck disable=2207
+# editorconfig-checker-disable
+EOF
+}
+
+make_opts_variable() {
+ local opts_varname=$1
+ local -n opts=$opts_varname
+
+ printf '\n_%s_comp_%s=( ' "$cmd" "$opts_varname"
+ for opt in "${opts[@]}"; do
+ printf '%s ' "${opt/:*/}"
+ done
+ printf ')\n'
+}
+
+parse_action() {
+ local var=$1
+ local position=$2
+
+ if [[ ${var:0:1} == '@' ]]; then
+ case $var in
+ @hold)
+ printf ''
+ ;;
+
+ *)
+ local func_name=${var:1}
+ func_name=${func_name/:*/}
+
+ if [[ ${map_reply_funcs["reply_${func_name}"]:-} == true ]]; then
+ local func_arg=${var/@${func_name}:/}
+ if ((${#func_arg} > 0)) && [[ ${func_arg[*]:0:1} != '@' ]]; then
+ printf -- "_%s_comp_reply_%s '%s'" "$cmd" "$func_name" "$func_arg"
+ else
+ printf -- '_%s_comp_reply_%s' "$cmd" "$func_name"
+ fi
+ else
+ error "Invalid '$position': The action '$var' is not defined."
+
+ case $var in
+ @f*) suggest "Try '@files' instead of '$var'." ;;
+ @d*) suggest "Try '@dirs' instead of '$var'." ;;
+ @h*) suggest "Try '@hold' instead of '$var'." ;;
+ *) suggest "Try '@files', '@dirs', '@hold' or other reply functions. See https://github.com/adoyle-h/bash-completor/docs/syntax.md#reply-functions " ;;
+ esac
+
+ exit 5
+ fi
+ ;;
+ esac
+ else
+ if [[ -n "$var" ]]; then
+ printf -- "_%s_comp_reply_words '%s'" "$cmd" "$var"
+ else
+ printf ':'
+ fi
+ fi
+}
+
+make_reply_action() {
+ local varname=$1
+ local -n var=$varname
+ local reply
+
+ if [[ -v "$varname" ]]; then
+ reply=$(parse_action "$var" "$varname=$var")
+ elif is_array "$varname"; then
+ reply="_${cmd}_comp_reply_list '${var}'"
+ else
+ reply="_${cmd}_comp_reply_files"
+ fi
+
+ echo "$reply"
+}
+
+make_reply_set() {
+ cat <<EOF
+
+_${cmd}_comp_reply_set() {
+ local IFS=', '
+ local array_list="" array_name
+ # shellcheck disable=2068
+ for array_name in "\$@"; do
+ array_list="\$array_list \\\${_${cmd}_comp_var_\${array_name}[*]}"
+ done
+ array_list="\${array_list[*]:1}"
+
+ IFS=\$'\n'' '
+ eval "COMPREPLY=( \\\$(compgen -W \"\$array_list\" -- \"\\\$cur\") )"
+}
+EOF
+
+ map_reply_funcs[reply_set]=true
+}
+
+if is_gnu_sed; then
+ # For GNU sed
+ sed_reply_utils() {
+ declare -f "$name" | sed -e "s/reply_/_${cmd}_comp_reply_/g" -e 's/ *$//g' |\
+ sed -e ":a;N;\$!ba;s/IFS='\n'/IFS=\$'\\\\n'/g"
+ }
+else
+ # For BSD sed
+ sed_reply_utils() {
+ declare -f "$name" | sed -e "s/reply_/_${cmd}_comp_reply_/g" -e 's/ *$//g' |\
+ sed -e ':a' -e 'N' -e '$!ba' -e "s/IFS='\n'/IFS=\$'\\\\n'/g"
+ }
+fi
+
+make_reply_utils() {
+ local name
+
+ map_reply_funcs[reply_hold]=true
+
+ # Make framework and developer defined reply functions
+ for name in $(compgen -A function reply_); do
+ echo ""
+ sed_reply_utils
+
+ map_reply_funcs[$name]=true
+ done
+
+ make_reply_set
+
+ # Make developer custom reply functions
+ for name in $(compgen -A variable reply_); do
+ local -n list="$name"
+ local func_name=${list[0]}
+
+ if is_func "$func_name"; then
+ local rest=()
+ local str
+ for str in "${list[@]:1}"; do
+ rest+=("'$str'")
+ done
+
+ cat <<EOF
+
+_${cmd}_comp_${name}() {
+ _${cmd}_comp_${func_name} ${rest[@]}
+}
+EOF
+ else
+ error "Not found function '$func_name' for config '$name'"
+ exit 7
+ fi
+
+ map_reply_funcs[$name]=true
+ done
+}
+
+_make_cmd_option() {
+ local opt=$1
+ local indent=$2
+ local default_action=$3
+
+ if [[ $opt =~ : ]]; then
+ local option=${opt/:*/}
+ local var=${opt/${option}:/}
+ else
+ local option=$opt
+ local var=''
+ fi
+
+ if [[ ${option: -1} == '=' ]]; then
+ # skip --option=
+ return 0
+ fi
+
+ if [[ $option == "$opt" ]]; then
+ # skip --option without :
+ return 0
+ fi
+
+ local action
+ action=$(parse_action "$var" "$opt")
+
+ # Skip to print case condition. Because this condition action is same to default_action.
+ # default_action means "*) $default_action ;;"
+ if [[ "$action" != "$default_action" ]]; then
+ printf -- '%s) %s ;;\n' "$indent$option" "$action"
+ fi
+}
+
+_make_cmd_options() {
+ echo " # rely the value of command option"
+ local opt
+ for opt in "${opts[@]}"; do
+ _make_cmd_option "$opt" "$SPACES_6" "$reply_opts_fallback"
+ done
+}
+
+_make_equal_sign_option() {
+ local opt=$1
+ local indent="$SPACES_4"
+
+ if [[ $opt =~ : ]]; then
+ local option=${opt/:*/}
+ local var=${opt/${option}:/}
+ else
+ local option=$opt
+ local var=''
+ fi
+
+ if [[ $option =~ =$ ]]; then
+ local action
+ action=$(parse_action "$var" "$opt")
+ printf -- '%s) %s ;;\n' "$indent$option" "$action"
+ else
+ if [[ $option =~ =[@-_a-zA-Z] ]]; then
+ local recommend=${option/=/=:}
+ recommend=${recommend// /,} # developer may use space delimiter
+ warn "The option '$option' maybe missing the ':'. Do you need '${recommend}'?"
+ fi
+ fi
+}
+
+_make_equal_sign_options() {
+ for opt in "${opts[@]}"; do
+ _make_equal_sign_option "$opt"
+ done
+}
+
+make_equal_sign_opts_func() {
+ local opts_varname=$1
+ local -n opts=$opts_varname
+
+ local equal_sign_options
+ equal_sign_options=$(_make_equal_sign_options)
+
+ if [[ -n $equal_sign_options ]]; then
+ cat <<EOF
+
+_${cmd}_comp_equal_sign_${opts_varname}() {
+ case "\${1}=" in
+$equal_sign_options
+ esac
+}
+EOF
+ map_equal_signs[${opts_varname}]=true
+ fi
+}
+
+make_cmd_core() {
+ local opts_varname=$1
+ local reply_args=$2
+ local reply_opts_fallback=$3
+ local -n opts=$opts_varname
+
+ if [[ " ${opts[*]} " == *' -- '* ]]; then
+ cat <<EOF
+ if [[ \$COMP_LINE == *' -- '* ]]; then
+ # When current command line contains the "--" option, other options are forbidden.
+ ${reply_args}
+ elif [[ \${cur:0:1} == [-+] ]]; then
+EOF
+ else
+ cat <<EOF
+ if [[ \${cur:0:1} == [-+] ]]; then
+EOF
+ fi
+
+ cat <<EOF
+ # rely options of command
+ _${cmd}_comp_reply_list _${cmd}_comp_${opts_varname}
+EOF
+
+ if [[ "${opts[*]}" =~ = ]]; then
+ # The options contain equal_sign
+ cat <<EOF
+ if [[ \${COMPREPLY[*]} =~ =\$ ]]; then compopt -o nospace; fi
+EOF
+ fi
+
+ if [[ -n ${map_equal_signs[${opts_varname}]:-} ]]; then
+ cat <<EOF
+ elif [[ \${cur} == = ]]; then
+ _${cmd}_comp_equal_sign_${opts_varname} "\$prev"
+EOF
+ fi
+
+ cat <<EOF
+ elif [[ \${prev:0:1} == [-+] ]]; then
+ case "\${prev}" in
+$(_make_cmd_options)
+ *) $reply_opts_fallback ;;
+ esac
+EOF
+
+ if [[ -n ${map_equal_signs[${opts_varname}]:-} ]]; then
+ cat <<EOF
+ elif [[ \${prev} == = ]]; then
+ _${cmd}_comp_equal_sign_${opts_varname} "\${COMP_WORDS[\$(( COMP_CWORD - 2 ))]}"
+EOF
+ fi
+
+ cat <<EOF
+ else
+ # rely the argument of command
+ $reply_args
+ fi
+EOF
+}
+
+
+make_subcmd_opts() {
+ local subcmd_opts
+ for subcmd_opts in $(compgen -A variable subcmd_opts_); do
+ make_opts_variable "$subcmd_opts"
+ done
+}
+
+make_subcmds() {
+ cat <<EOF
+
+_${cmd}_comp_subcmds=( ${subcmds[*]} )
+EOF
+}
+
+make_subcmd_completion() {
+ local subcmd_varname=$1
+
+ local reply_args
+ if [[ -v "subcmd_args_${subcmd_varname}" ]]; then
+ reply_args=$(make_reply_action "subcmd_args_${subcmd_varname}")
+ else
+ reply_args=$(make_reply_action subcmd_args__fallback)
+ fi
+
+ local reply_opts_fallback
+ if [[ -v "subcmd_opts_${subcmd_varname}_fallback" ]]; then
+ reply_opts_fallback=$(make_reply_action "subcmd_opts_${subcmd_varname}_fallback")
+ else
+ reply_opts_fallback=$reply_args
+ fi
+
+ cat <<EOF
+
+_${cmd}_completions_$subcmd_varname() {
+$(make_cmd_core "subcmd_opts_${subcmd_varname}" "$reply_args" "$reply_opts_fallback")
+}
+EOF
+}
+
+make_subcmd_alias_completion() {
+ local src
+ for src in "${!subcmd_comp_alias[@]}"; do
+ printf '_%s_completions_%s() { _%s_completions_%s; }\n' \
+ "$cmd" "$(get_varname "$src")" "$cmd" "$(get_varname "${subcmd_comp_alias[$src]}")"
+ done
+}
+
+make_subcmd_completions() {
+ local subcmd subcmd_varname
+ for subcmd in "${subcmds[@]}"; do
+ subcmd_varname=$(get_varname "$subcmd")
+ if is_array "subcmd_opts_${subcmd_varname}"; then
+ make_equal_sign_opts_func "subcmd_opts_${subcmd_varname}"
+ make_subcmd_completion "$subcmd_varname"
+ fi
+ done
+
+ make_subcmd_alias_completion
+ make_subcmd_completion _fallback
+}
+
+
+make_main_completion() {
+ make_equal_sign_opts_func "cmd_opts"
+
+ cat <<EOF
+
+_${cmd}_completions() {
+ COMPREPLY=()
+ local cur=\${COMP_WORDS[COMP_CWORD]}
+ local prev=\${COMP_WORDS[COMP_CWORD-1]}
+
+ # Uncomment this line for debug
+ # echo "[COMP_CWORD:\$COMP_CWORD][cur:\$cur][prev:\$prev][WORD_COUNT:\${#COMP_WORDS[*]}][COMP_WORDS:\${COMP_WORDS[*]}]" >> bash-debug.log
+EOF
+
+ local reply_args
+
+ if $has_subcmds; then
+ cat <<EOF
+
+ if (( COMP_CWORD > 1 )); then
+ # Enter the subcmd completion
+ local subcmd_varname
+ subcmd_varname="\$(_${cmd}_comp_util_get_varname "\${COMP_WORDS[1]}")"
+ if type "_${cmd}_completions_\$subcmd_varname" &>/dev/null; then
+ "_${cmd}_completions_\$subcmd_varname"
+ else
+ # If subcmd completion function not defined, use the fallback
+ "_${cmd}_completions__fallback"
+ fi
+ return 0
+ fi
+EOF
+
+ reply_args="_${cmd}_comp_reply_list _${cmd}_comp_subcmds"
+ else
+ reply_args=$(make_reply_action cmd_args)
+ fi
+
+ local reply_opts_fallback
+ if [[ -v cmd_opts_fallback ]]; then
+ reply_opts_fallback=$(make_reply_action cmd_opts_fallback)
+ else
+ reply_opts_fallback=$(make_reply_action cmd_args)
+ fi
+
+ cat <<EOF
+
+ # Enter the cmd completion
+$(make_cmd_core "cmd_opts" "$reply_args" "$reply_opts_fallback")
+}
+
+complete -F _${cmd}_completions -o bashdefault ${cmd_name}
+# vi: sw=2 ts=2
+EOF
+}
+
+
+make() {
+ make_header
+ make_opts_variable cmd_opts
+ make_dumped_variables
+
+ if $has_subcmds; then
+ make_subcmd_opts
+ make_get_varnames
+ fi
+
+ make_reply_utils
+
+ if $has_subcmds; then
+ make_subcmds
+ make_subcmd_completions
+ fi
+ make_main_completion
+}
+
+do_make() {
+ # NOTE: Naming variable should avoid some prefixes like "reply_" and "subcmd_opts_". Search "compgen -A".
+ local conf_path=$1
+ local has_subcmds=false
+ local equal_sign_idx=0
+ local -A map_reply_funcs=() map_equal_signs=()
+ local output cmd cmd_name cmd_args notice
+ local -a authors=() maintainers=() subcmds=() cmd_opts=()
+ local -A subcmd_comp_alias=() word_to_varname=()
+
+ check_conf "$conf_path"
+
+ local output_path
+ output_path="$(dirname "$conf_path")/$output"
+ make > "$output_path"
+ printf '%bGenerated file: %s%b\n' "${GREEN}" "$output_path" "$RESET_ALL"
+}
+
+usage() {
+ cat <<EOF
+Usage: bash-completor [options]
+
+Options:
+ -c <config_path> To generate Bash completion script based on configuration
+ -h|--help Print the usage
+ --version Print the version of bash-completor
+
+Description: Quickly generate Bash completion script based on configuration.
+
+Config Syntax: https://github.com/adoyle-h/bash-completor/docs/syntax.md
+
+Project: https://github.com/adoyle-h/bash-completor
+
+Version: $VERSION
+EOF
+}
+
+check_conf() {
+ local conf_path=$1
+
+ if [[ ! -f $conf_path ]]; then
+ echo "Not found config file at $conf_path" >&2
+ exit 3
+ fi
+
+ # shellcheck disable=1090
+ . "$conf_path"
+
+ # Set default values of config options
+ cmd_name=$cmd
+ cmd=$(get_varname "$cmd_name")
+ cmd_args=${cmd_args:-@files}
+ subcmd_args__fallback=${subcmd_args__fallback:-@files}
+
+ if (( ${#subcmds[@]} > 0 )); then
+ has_subcmds=true
+ fi
+}
+
+main() {
+ if (( $# == 0 )); then usage; exit 0; fi
+
+ case "$1" in
+ -c)
+ do_make "$2"
+ ;;
+
+ -h|--help)
+ usage
+ ;;
+
+ --version)
+ echo "$VERSION"
+ ;;
+
+ *)
+ echo "Invalid option '$1'." >&2
+ exit 2
+ ;;
+ esac
+}
+
+main "$@"
diff --git a/scripts/checktodo b/scripts/checktodo
new file mode 100755
index 0000000..e0c1eeb
--- /dev/null
+++ b/scripts/checktodo
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+if [ -f "./TODO.md" ]; then
+ FILE="./TODO.md"
+elif [ -f "./TODO.txt" ]; then
+ FILE="./TODO.txt"
+else
+ FILE="$HOME/TODO.md"
+fi
+
+nvim $FILE
diff --git a/scripts/cht.sh b/scripts/cht.sh
new file mode 100755
index 0000000..6c27d29
--- /dev/null
+++ b/scripts/cht.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+selected=`cat ~/.tmux-langs ~/.tmux-coreutils | fzf`
+if [[ -z $selected ]]; then
+ exit 0
+fi
+
+read -p "Enter Query: " query
+
+if grep -qs "$selected" ~/.tmux-langs; then
+ query=`echo $query | tr ' ' '+'`
+ tmux neww bash -c "curl cht.sh/$selected/$query & while [ : ]; do sleep 1; done"
+else
+ query=`echo $query | tr ' ' '+'`
+ tmux neww bash -c "curl cht.sh/$selected~$query & while [ : ]; do sleep 1; done"
+fi
diff --git a/scripts/fcopy b/scripts/fcopy
new file mode 100755
index 0000000..d7eda81
--- /dev/null
+++ b/scripts/fcopy
@@ -0,0 +1,8 @@
+#!/bin/sh
+if [[ "$1" == *.png ]]; then
+ xclip -selection clipboard -target image/png -i $1
+elif [[ "$1" == *.jpg ]] || [[ "$1" == *.jpeg ]]; then
+ xclip -selection clipboard -target image/jpeg -i $1
+else
+ bat "$1" | xclip -sel clip
+fi
diff --git a/scripts/gitline b/scripts/gitline
new file mode 100755
index 0000000..0341f27
--- /dev/null
+++ b/scripts/gitline
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+lineNumber=${1:-$(echo -n "" | dmenu -i -p "Line number:")}
+file=${2:-$(echo -n "" | dmenu -i -p "Filename:")}
+
+git -C "$(dirname "$file")" log -L"$lineNumber",+1:"$file"
diff --git a/scripts/internet b/scripts/internet
new file mode 100755
index 0000000..dd9256b
--- /dev/null
+++ b/scripts/internet
@@ -0,0 +1,4 @@
+#!/bin/sh
+ip link set eth0 up
+ip addr add 192.168.0.66/24 broadcast + dev eth0
+ip route add default via 192.168.0.1 dev eth0
diff --git a/scripts/origin b/scripts/origin
new file mode 100755
index 0000000..ae67999
--- /dev/null
+++ b/scripts/origin
@@ -0,0 +1,12 @@
+#!/bin/sh
+dir=${PWD##*/}
+dir=${dir:-/}
+
+git remote add origin git@git.sr.ht:~pml68/"$dir"
+echo "* text=auto eol=lf
+
+# Older git versions try to fix line endings on images, this prevents it.
+*.png binary
+*.jpg binary
+*.ico binary" > .gitattributes
+git config --add push.pushOption visibility="${1:-public}"
diff --git a/scripts/publicip b/scripts/publicip
new file mode 100755
index 0000000..af29bca
--- /dev/null
+++ b/scripts/publicip
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+dig +short myip.opendns.com @resolver1.opendns.com | xclip -sel clip
diff --git a/scripts/screenshot b/scripts/screenshot
new file mode 100755
index 0000000..45bf2f3
--- /dev/null
+++ b/scripts/screenshot
@@ -0,0 +1,6 @@
+#!/bin/sh
+adb shell screencap /storage/emulated/0/Download/screen.png
+adb pull /storage/emulated/0/Download/screen.png $HOME/Downloads/screenshots/
+adb shell rm /storage/emulated/0/Download/screen.png
+xclip -selection clipboard -target image/png -i $HOME/Downloads/screenshots/screen.png
+rm $HOME/Downloads/screenshots/screen.png
diff --git a/scripts/tags b/scripts/tags
new file mode 100755
index 0000000..89dcbc6
--- /dev/null
+++ b/scripts/tags
@@ -0,0 +1,3 @@
+#!/bin/sh
+set -xe
+ctags --sort=yes --format=2 --language-force=C --c-kinds=+pmftvux --fields=+afisS --extras=+q "$@"
diff --git a/scripts/tmus b/scripts/tmus
new file mode 100755
index 0000000..9bb9d5b
--- /dev/null
+++ b/scripts/tmus
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+if [ $# -eq 1 ]; then
+ selected="$1"
+ [ -d "$1" ] || mkdir -p "$1"
+else
+ selected=$(find ~/projects/ ~/school/ ~/source/ ~/testing/ ~/git/ ~/QEMU/ ~/.config/ -mindepth 1 -maxdepth 1 -type d | fzf)
+fi
+
+if [ -z "$selected" ]; then
+ exit 0
+fi
+
+selected_name=$(basename "$selected" | tr . _)
+tmux_running=$(pgrep tmux)
+
+if [ -z "$TMUX" ] && [ -z "$tmux_running" ]; then
+ tmux new-session -s "$selected_name" -c "$selected"
+ exit 0
+fi
+
+if ! tmux has-session -t="$selected_name" 2> /dev/null; then
+ tmux new-session -ds "$selected_name" -c "$selected"
+fi
+
+if tmux list-sessions | grep -qs attached; then
+ tmux switch-client -t "$selected_name"
+else
+ tmux attach-session -t "$selected_name"
+fi