diff --git a/.github/pull.yml b/.github/pull.yml new file mode 100644 index 0000000..08de30a --- /dev/null +++ b/.github/pull.yml @@ -0,0 +1,6 @@ +version: '1' +rules: + - base: master + upstream: dmitmel:master + mergeMethod: merge + mergeUnstable: true diff --git a/.gitignore b/.gitignore index 85f3c76..349d9a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pyc node_modules/ +.venv *.md.html diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..22b40ad --- /dev/null +++ b/install.sh @@ -0,0 +1,8 @@ +sudo hostname KeanuCodespaces +rm -rf ~/.oh-my-zsh +sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended +git clone https://github.com/keanuplayz/dotfiles ~/.dotfiles +echo "source ~/.dotfiles/zsh/zshrc" >> ~/.zshrc +pip install colorama psutil distro +echo "zsh" >> ~/.bashrc +source ~/.bashrc \ No newline at end of file diff --git a/nvim/coc-languages/lua.vim b/nvim/coc-languages/lua.vim new file mode 100644 index 0000000..e68ab5d --- /dev/null +++ b/nvim/coc-languages/lua.vim @@ -0,0 +1,3 @@ +let g:coc_global_extensions += ['coc-lua'] +let s:filetypes = ['lua'] +let g:coc_filetypes += s:filetypes diff --git a/nvim/dotfiles/plugins-list.vim b/nvim/dotfiles/plugins-list.vim index 0f3b104..2993e99 100644 --- a/nvim/dotfiles/plugins-list.vim +++ b/nvim/dotfiles/plugins-list.vim @@ -2,7 +2,9 @@ Plug 'tpope/vim-eunuch' if g:vim_ide Plug 'francoiscabrol/ranger.vim' + Plug 'rbgrouleff/bclose.vim' endif + Plug 'weirongxu/coc-explorer' " }}} " Editing {{{ @@ -67,3 +69,7 @@ endif endif " }}} + +" Misc {{{ + Plug 'wakatime/vim-wakatime' +" }}} diff --git a/scripts/copy-env-var b/scripts/copy-env-var new file mode 100755 index 0000000..9a4d88d --- /dev/null +++ b/scripts/copy-env-var @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +if variable="$(set -euo pipefail; { + awk 'BEGIN{for(v in ENVIRON) print v}' +} | rofi -dmenu)" && [[ -n $variable ]]; then + + variable="${variable%% *}" + + echo ${!variable} | xclip -sel clip +fi diff --git a/scripts/discord-stream-desktop-audio b/scripts/discord-stream-desktop-audio index da925b4..1812557 100755 --- a/scripts/discord-stream-desktop-audio +++ b/scripts/discord-stream-desktop-audio @@ -9,7 +9,7 @@ guild_id = int(sys.argv[1]) voice_channel_id = int(sys.argv[2]) pulseaudio_device = sys.argv[3] -with open(os.path.expanduser("~/.config/dotfiles/discord-tools-bot-token.txt")) as f: +with open(os.path.expanduser("~/.config/dotfiles/discord-tools-user-token.txt")) as f: bot_token = f.read().strip() bot = discord.Client() @@ -33,4 +33,4 @@ async def on_ready(): voice_client.play(source, after=lambda e: print("Player error: %s" % e) if e else None) -bot.run(bot_token) +bot.run(bot_token, bot=False) diff --git a/zsh/.gitignore b/zsh/.gitignore new file mode 100644 index 0000000..95cb55a --- /dev/null +++ b/zsh/.gitignore @@ -0,0 +1 @@ +/custom/ diff --git a/zsh/completions/_gh b/zsh/completions/_gh new file mode 100644 index 0000000..b872834 --- /dev/null +++ b/zsh/completions/_gh @@ -0,0 +1,159 @@ +#compdef _gh gh + +# zsh completion for gh -*- shell-script -*- + +__gh_debug() +{ + local file="$BASH_COMP_DEBUG_FILE" + if [[ -n ${file} ]]; then + echo "$*" >> "${file}" + fi +} + +_gh() +{ + local shellCompDirectiveError=1 + local shellCompDirectiveNoSpace=2 + local shellCompDirectiveNoFileComp=4 + local shellCompDirectiveFilterFileExt=8 + local shellCompDirectiveFilterDirs=16 + + local lastParam lastChar flagPrefix requestComp out directive compCount comp lastComp + local -a completions + + __gh_debug "\n========= starting completion logic ==========" + __gh_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $CURRENT location, so we need + # to truncate the command-line ($words) up to the $CURRENT location. + # (We cannot use $CURSOR as its value does not work when a command is an alias.) + words=("${=words[1,CURRENT]}") + __gh_debug "Truncated words[*]: ${words[*]}," + + lastParam=${words[-1]} + lastChar=${lastParam[-1]} + __gh_debug "lastParam: ${lastParam}, lastChar: ${lastChar}" + + # For zsh, when completing a flag with an = (e.g., gh -n=) + # completions must be prefixed with the flag + setopt local_options BASH_REMATCH + if [[ "${lastParam}" =~ '-.*=' ]]; then + # We are dealing with a flag with an = + flagPrefix="-P ${BASH_REMATCH}" + fi + + # Prepare the command to obtain completions + requestComp="${words[1]} __complete ${words[2,-1]}" + if [ "${lastChar}" = "" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go completion code. + __gh_debug "Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi + + __gh_debug "About to call: eval ${requestComp}" + + # Use eval to handle any environment variables and such + out=$(eval ${requestComp} 2>/dev/null) + __gh_debug "completion output: ${out}" + + # Extract the directive integer following a : from the last line + local lastLine + while IFS='\n' read -r line; do + lastLine=${line} + done < <(printf "%s\n" "${out[@]}") + __gh_debug "last line: ${lastLine}" + + if [ "${lastLine[1]}" = : ]; then + directive=${lastLine[2,-1]} + # Remove the directive including the : and the newline + local suffix + (( suffix=${#lastLine}+2)) + out=${out[1,-$suffix]} + else + # There is no directive specified. Leave $out as is. + __gh_debug "No directive found. Setting do default" + directive=0 + fi + + __gh_debug "directive: ${directive}" + __gh_debug "completions: ${out}" + __gh_debug "flagPrefix: ${flagPrefix}" + + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then + __gh_debug "Completion received error. Ignoring completions." + return + fi + + compCount=0 + while IFS='\n' read -r comp; do + if [ -n "$comp" ]; then + # If requested, completions are returned with a description. + # The description is preceded by a TAB character. + # For zsh's _describe, we need to use a : instead of a TAB. + # We first need to escape any : as part of the completion itself. + comp=${comp//:/\\:} + + local tab=$(printf '\t') + comp=${comp//$tab/:} + + ((compCount++)) + __gh_debug "Adding completion: ${comp}" + completions+=${comp} + lastComp=$comp + fi + done < <(printf "%s\n" "${out[@]}") + + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then + # File extension filtering + local filteringCmd + filteringCmd='_files' + for filter in ${completions[@]}; do + if [ ${filter[1]} != '*' ]; then + # zsh requires a glob pattern to do file filtering + filter="\*.$filter" + fi + filteringCmd+=" -g $filter" + done + filteringCmd+=" ${flagPrefix}" + + __gh_debug "File filtering command: $filteringCmd" + _arguments '*:filename:'"$filteringCmd" + elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then + # File completion for directories only + local subDir + subdir="${completions[1]}" + if [ -n "$subdir" ]; then + __gh_debug "Listing directories in $subdir" + pushd "${subdir}" >/dev/null 2>&1 + else + __gh_debug "Listing directories in ." + fi + + _arguments '*:dirname:_files -/'" ${flagPrefix}" + if [ -n "$subdir" ]; then + popd >/dev/null 2>&1 + fi + elif [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ] && [ ${compCount} -eq 1 ]; then + __gh_debug "Activating nospace." + # We can use compadd here as there is no description when + # there is only one completion. + compadd -S '' "${lastComp}" + elif [ ${compCount} -eq 0 ]; then + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + __gh_debug "deactivating file completion" + else + # Perform file completion + __gh_debug "activating file completion" + _arguments '*:filename:_files'" ${flagPrefix}" + fi + else + _describe "completions" completions $(echo $flagPrefix) + fi +} + +# don't run the completion function when being source-ed or eval-ed +if [ "$funcstack[1]" = "_gh" ]; then + _gh +fi diff --git a/zsh/completions/_keybase b/zsh/completions/_keybase new file mode 100644 index 0000000..7ab54f5 --- /dev/null +++ b/zsh/completions/_keybase @@ -0,0 +1,359 @@ +#compdef keybase +#autoload + +# keybase completion, based on cli help text +# https://github.com/fnoris/keybase-zsh-completion + +# The MIT License (MIT) +# +# Copyright (c) 2014 David Tiersch +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +local curcontext="$curcontext" state line ret=1 +local -a _command_args +local -a _global_args +local -a _subcommands + +local IFS=$'\n' +_global_args=($(keybase advanced | grep '^\s*--' | sed -r 's/^[\t ]*(--[^\t ,]+)( [^\t ,]*)?,? ?(--?[^\t ]+)?[\t ]*(.*)$/(\3)\1[\4]/')) + +_arguments -C -A "-v" -A "--version"\ + '(- 1 *)'{-v,--version}'[display version information]' \ + '1: :->cmds' \ + $_global_args \ + '*:: :->args' && ret=0 + +case $state in + cmds) + _subcommands=( + "base62:base62 operations" + "cert:print the CA cert for api.keybase.io" + "chat:chat securely with keybase users" + "ctl:control the background keybase service" + "currency:manage cyrptocurrency address" + "decrypt:decrypt messages or files for keybase users" + "deprovision:revoke the current device, log out, and delete local state" + "device:manage your devices" + "dump-keyfamily:print out a user's current key family" + "encrypt:encrypt messages or files for keybase users" + "follow:verify a user's authenticity and optionally follow them" + "track:verify a user's authenticity and optionally follow them" + "fs:perform filesystem operations" + "id:identify a user and check their signature chain" + "list-followers:list thost who follow you" + "list-following:list who you or the given user is following" + "log:manage keybase log" + "login:establish a session with the keybase server" + "logout:logout and remove session information" + "paperkey:generate paper keys for recovering your account" + "passphrase:change or recover your keybase passphrase" + "pgp:manage keybase PGP keys" + "ping:ping the keybase API server" + "prove:generate a new proof" + "rekey:rekey status and actions" + "search:search for keybase users" + "sign:sign a document" + "signup:signup for a new account" + "sigs:manage signatures" + "status:show information about current user" + "unfollow:unfollow a user" + "untrack:unfollow a user" + "update:the updater" + "verify:verify message or file signatures for keybase users" + "version:print out version and build information" + "help, h:shows a list fo commands or help for one command" + ) + _describe -t subcommands 'Keybase subcommands' _subcommands && ret=0 + ;; + args) + case $line[1] in + help) + _values 'commands' \ + 'base62' \ + 'cert' \ + 'chat' \ + 'ctl' \ + 'currency' \ + 'decrypt' \ + 'deprovision' \ + 'device' \ + 'dump-keyfamily' \ + 'encrypt' \ + 'follow' \ + 'track' \ + 'fs' \ + 'id' \ + 'list-followers' \ + 'list-following' \ + 'log' \ + 'login' \ + 'logout' \ + 'paperkey' \ + 'pasphrase' \ + 'pgp' \ + 'ping' \ + 'prove' \ + 'rekey' \ + 'search' \ + 'sign' \ + 'signup' \ + 'sigs' \ + 'status' \ + 'unfollow' \ + 'untrack' \ + 'update' \ + 'verify' \ + 'version' \ + 'help, h' && ret=0 + ;; + base62) + _values "command" \ + 'decode[base62 decode]' \ + 'encode[base62 encode]' \ + 'help[Shows a list of commands or help for one command]' + ;; + chat) + _values "command" \ + 'api[JSON api]' \ + 'download[Download an attachment from a conversation]' \ + 'hide[Hide or block a conversation]' \ + 'list[List conversations, sorted by activity]' \ + 'ls[List conversations, sorted by activity]' \ + "list-unread[List conversations, with unread messages at the top]" \ + "lsur[List conversations, with unread messages at the top]" \ + 'mute[Mute or unmute a conversation]' \ + 'read[Show new messages in a conversation and mark them as read]' \ + 'report[Report a conversation (also blocks it)]' \ + 'send[Send a message to a conversation]' \ + 'upload[Upload an attachment to a conversation]' \ + 'help[Shows a list of commands or help for one command]' + ;; + ctl) + _values "command" \ + 'start[Start the backgroung keybase service]' \ + 'stop[Stop the backgroung keybase service]' \ + 'reload[Reload config file]' \ + 'restart[Restart the background keybase service]' \ + "log-rotate[Close and open the keybase service's log file]" \ + 'watchdog[Start, watch and prop up the backgound service]' \ + 'watchdog2[Start and monitor background services]' \ + 'app-exit[Exit the Keybase app]' \ + 'help[Shows a list of commands or help for one command]' + ;; + currency) + _values "command" \ + 'add[Sign a cryptocurrency (bitcoin or zcash) address into your identity]' \ + 'help[Shows a list of commands or help for one command]' + ;; + decrypt) + _command_args=( + '(--infile)--infile[Specify an input file]' \ + '(--message)--message[Provide the message on the command line]' \ + '(--outfile)--outfile[Specify an outfile (stdout by default)]' \ + '(--interactive)--interactive[Interactive prompt for decryption after sender verification]' \ + '(--force)--force[Force unprompted decryption, even on an indentify failure]' \ + '(--paperkey)--paperkey[Use a paper key for decryption]' \ + '(--encryptor-outfile)--encryptor-outfile[Write the Keybase name of the encryptor to this file]' + ) + ;; + device) + _values "command" \ + 'remove[Remove a device]' \ + 'list[List devices]' \ + 'add[Authorize a new device]' \ + 'help[Shows a list of commands or help for one command]' + ;; + encrypt) + _command_args=( + '(--binary)--binary[Output in binary (rather than ASCII/armored)]' \ + '(--infile)--infile[Specify an input file]' \ + '(message)--message[Provide the message on the command line]' \ + '(--outfile)--outfile[Specify an outfile (stdout by default)]' \ + "(--hide-recipients)--hide-recipients[Don't include recipients in metadata]" \ + "(--anonymous)--anonymous[Don't include sender or recipients in metadata. Implies --hide-recipients]" \ + "(--no-self)--no-self[Don't encrypt for yourself]" + ) + ;; + follow) + _command_args=( + "(--local)--local[Only follow locally, don't send a public statement to the server]" \ + '(-y)-y[Approve remote following without prompting]' \ + '(--skip-proof-cache)--skip-proof-cache[Skip cached proofs, force re-check]' + ) + ;; + track) + _command_args=( + "(--local)--local[Only follow locally, don't send a public statement to the server]" \ + '(-y)-y[Approve remote following without prompting]' \ + '(--skip-proof-cache)--skip-proof-cache[Skip cached proofs, force re-check]' + ) + ;; + fs) + _values "command" \ + 'ls[list directory contents]' \ + 'cp[copy one or more directory elements to dest]' \ + 'mv[move one or more directory elements to dest]' \ + 'read[output file contents to standard output]' \ + 'rm[remove one or more directory elements]' \ + 'mkdir[create directory]' \ + 'stat[stat directory element]' \ + 'get-status[get status of pending operation]' \ + 'kill[kill operation]' \ + 'ps[list running operations]' \ + 'write[write input to file]' \ + 'help[Shows a list of commands or help for one command]' + ;; + id) + _command_args=( + '(--skip-proof-cache)--skip-proof-cache[Skip cached proofs, force re-check]' + ) + ;; + list-followers) + _command_args=( + '(--verbose)--verbose[A full dump, with more gory details]' + ) + ;; + list-following) + _command_args=( + '(--filter)--filter[Provide a regex filter]' \ + '(--headers)--headers[Show column headers]' \ + '(--json)--json[Output as JSON (default is text)]' \ + '(--verbose)--verbose[A full dump, with more gory details]' + ) + ;; + log) + _values "command" \ + 'send[Send recent debug logs to keybase]' \ + 'help[Shows a list of commands or help for one command]' + ;; + login) + _command_args=( + '(--provision-by-email)--provision-by-email[Use an email address associated with a keybase account to provision a device]' + ) + ;; + passphrase) + _values "command" \ + 'change[Change your keybase account passphrase]' \ + 'recover[Recover your keybase account passphrase]' \ + 'help[Shows a list of commands or help for one command]' + ;; + pgp) + _values "command" \ + 'gen[Generate a new PGP key and write to local secret keychain]' \ + 'pull[Download the latest PGP keys for people you track]' \ + 'update[Update your public PGP keys on keybase with those exported from the local GPG keyring]' \ + 'select[Select a key as your own and register the public half with the server]' \ + 'sign[PGP sign a document]' \ + 'encrypt[PGP encrypt messages or files for keybase users]' \ + 'decrypt[PGP decrypt messages or files for keybase users]' \ + 'verify[PGP verify message or file signatures for keybase users]' \ + 'export[Export a PGP key from keybase]' \ + 'import[Import a PGP key into keybase]' \ + 'drop[Drop Keybases use of a PGP key]' \ + 'list[List the active PGP keys in your account]' \ + 'purge[Purge all PGP keys from Keybase keyring]' \ + 'help[Shows a list of commands or help for one command]' + ;; + ping) + _command_args=( + '(--gregor)--gregor[Ping the Gregor server]' + ) + ;; + prove) + _command_args=( + '(--output)--output[Output proof text to file (rather than standard out)]' \ + "(--force)--force[Don't prompt]" \ + ) + _values "prove command" \ + 'service[Supported services are: coinbase, hackernews, reddit, dns, github, twitter, web, http, https]' \ + 'service username[Username or hostname at that service]' + ;; + rekey) + _values "command" \ + 'status[Get pending rekey status]' \ + 'paper[Submit a paper key to help rekeying]' \ + 'help[Shows a list of commands or help for one command]' + ;; + search) + _command_args=( + '(--json)--json[Output as JSON]' + ) + ;; + sign) + _command_args=( + '(--binary)--binary[Output binary message (default is armored)]' \ + '(--detached)--detached[Detached signature (default is attached)]' \ + '(--infile)--infile[Specify an input file]' \ + '(--message)--message[Provide the message to sign on the command line]' \ + '(--outfile)--outfile[Specify an outfile (default is STDOUT)]' + ) + ;; + signup) + _command_args=( + '(--invite-code)--invite-code[Specify an invite code]' \ + '(--email)--email[Specify an account email]' \ + '(--username)--username[Specify a username]' + ) + ;; + sigs) + _values "command" \ + 'list[List signatures]' \ + 'revoke[Revoke a signature by sig ID]' \ + 'help[Shows a list of commands or help for one command]' + ;; + status) + _command_args=( + '(--json)--json[Output status as JSON]' \ + ) + ;; + update) + _values "command" \ + 'check[Trigger an update check]' \ + 'run[Run the update with custom options]' \ + 'check-in-use[Check if we are in use (safe for restart)]' \ + 'notify[Notify the service about an update event]' + ;; + verify) + _command_args=( + '(--detached)--detached[Specify a detached signature file]' \ + '(--infile)--infile[Specify an input file]' \ + '(--message)--message[Provide the message to verify on the command line]' \ + "(--no-output)--no-output[Don't output the verified message]" \ + '(--outfile)--outfile[Specify an outfile (default is STDOUT)]' + '(--signed-by)--signed-by[Assert signed by the given user (can use user assertion fomat)]' \ + ) + ;; + version) + _command_args=( + "(--format)--format[Alternate format for version output. Specify 's' for simple (1.2.3) or 'v' for verbose. Default (blank) includes build number (1.2.3-400)]" \ + "(--no-service)--no-service[Don't report on the service's build information]" + ) + ;; + esac + ;; +esac + +_arguments \ + $_command_args \ + && ret=0 + +return ret +s diff --git a/zsh/functions.zsh b/zsh/functions.zsh index 3bc4d0f..93353e9 100644 --- a/zsh/functions.zsh +++ b/zsh/functions.zsh @@ -6,6 +6,12 @@ bytecount() { wc -c "$@" | numfmt --to=iec-i --suffix=B; } mkcd() { mkdir -p "$@" && cd "${@[-1]}"; } +# Re-added from: +# https://github.com/dmitmel/dotfiles/blob/16f0a1cf32ec97355da2e17de1c4bb458431767b/zsh/functions.zsh#L19 +source_if_exists() { [[ -f "$1" ]] && source "$1" } + +silence() { $1 &>/dev/null } + viscd() { setopt local_options err_return local temp_file chosen_dir @@ -102,3 +108,25 @@ sudoedit() { } alias sudoe="sudoedit" alias sue="sudoedit" + +# gpg-crypt {{{ + # Encrypt the given file or directory to a given recipient + function gpg-encrypt() { + if [ "$#" -ne 2 ]; then + echo "Usage: $0 FILE/DIRECTORY RECIPIENT" >&2 + return 1 + fi + + tar -c `basename $1` | gpg --encrypt --recipient $2 -o `basename $1`.tar.gpg + } + + # Decrypt the given tar.gpg file + function gpg-decrypt() { + if [ "$#" -ne 1 ] || [[ "$1" != *.tar.gpg ]]; then + echo "Usage: $0 FILE.tar.gpg" >&2 + return 1 + fi + + gpg --quiet --decrypt $1 | tar -x + } +# }}} diff --git a/zsh/plugins.zsh b/zsh/plugins.zsh index b7fdafc..c557f6e 100644 --- a/zsh/plugins.zsh +++ b/zsh/plugins.zsh @@ -116,6 +116,15 @@ unset rustup_comp_path # }}} +# Apache2 {{{ + # Crappy solution, but only run if it's on a Raspberry Pi 4 B + if grep -q BCM2711 /proc/cpuinfo; then + _plugin apache2 'voronkovich/apache2.plugin.zsh' + fi +# }}} + +_plugin gitio 'denysdovhan/gitio-zsh' + # _plugin fzf 'junegunn/fzf' "$_checkout_latest_version" \ # build='./install --bin' \ # after_load='plugin-cfg-path path prepend bin' \ diff --git a/zsh/zshrc b/zsh/zshrc index 8fcf10a..8e02410 100644 --- a/zsh/zshrc +++ b/zsh/zshrc @@ -56,6 +56,14 @@ for script in functions options path env zplg plugins aliases completion zle pro _perf_timer_stop "$script.zsh" done +if [[ -d "$ZSH_DOTFILES/custom" ]]; then + _perf_timer_start "custom scripts" + for script in $ZSH_DOTFILES/custom/*.zsh; do + source "$script" + done + _perf_timer_stop "custom scripts" +fi + _perf_timer_stop "total" welcome