diff --git a/.env.template b/.env.template index b171248..514a590 100644 --- a/.env.template +++ b/.env.template @@ -5,6 +5,11 @@ export AWS_REGION= export AWS__EFS__LOCAL_MOUNT_POINT= export AWS__S3__MEDIA_BUCKET= export AWS__S3__MEDIA_TARGETS= +export DIRECTUS__API_TOKEN= +export DIRECTUS__BASE_URL= +export DISCORD__BOT_TOKEN= +export DISCORD__DEFAULT_AVATAR_URL= +export DISCORD__DEFAULT_CHANNEL_ID= export I3__BORDER_PIXEL_SIZE= export I3__DMENU_FONT_SIZE= export I3__GLOBAL_FONT_SIZE= @@ -13,3 +18,8 @@ export LINEAR__API_TOKEN= export REDIS_AUTH= export REDIS_HOST= export REDIS_PORT= +export TWILIO__ACCOUNT_SID= +export TWILIO__API_KEY= +export TWILIO__API_SECRET= +export TWILIO__DEFAULT_PHONE_FROM= +export TWILIO__DEFAULT_PHONE_TO= diff --git a/.env.template.descriptions b/.env.template.descriptions index 76fe677..d80d498 100644 --- a/.env.template.descriptions +++ b/.env.template.descriptions @@ -7,6 +7,13 @@ AWS__EFS__LOCAL_MOUNT_POINT | fully-qualified path to mount the EFS drive AWS__S3__MEDIA_BUCKET | s3 bucket name and filesystem targets for media backups AWS__S3__MEDIA_TARGETS | +DIRECTUS__API_TOKEN | details for a directus instance +DIRECTUS__BASE_URL | + +DISCORD__BOT_TOKEN | details for discord bot +DISCORD__DEFAULT_AVATAR_URL | +DISCORD__DEFAULT_CHANNEL_ID | + I3__BORDER_PIXEL_SIZE | custom i3 configuration settings I3__DMENU_FONT_SIZE | I3__GLOBAL_FONT_SIZE | @@ -17,3 +24,9 @@ LINEAR__API_TOKEN | linear.app project management configuration REDIS_AUTH | redis connection credentials REDIS_HOST | REDIS_PORT | + +TWILIO__ACCOUNT_SID | twilio account / credentials +TWILIO__API_KEY | +TWILIO__API_SECRET | +TWILIO__DEFAULT_PHONE_FROM | +TWILIO__DEFAULT_PHONE_TO | diff --git a/global/common.zsh b/global/common.zsh deleted file mode 100644 index d4d907d..0000000 --- a/global/common.zsh +++ /dev/null @@ -1,42 +0,0 @@ -##################################################################### - -[ ! $SCWRYPTS_ROOT ] && SCWRYPTS_ROOT="$(dirname ${0:a:h})" - -source "${0:a:h}/config.zsh" - -##################################################################### - -__SCWRYPT=1 # arbitrary; indicates scwrypts exists - -__PREFERRED_PYTHON_VERSIONS=(3.10 3.9) -__NODE_VERSION=18.0.0 - -__ENV_TEMPLATE=$SCWRYPTS_ROOT/.env.template - -##################################################################### - -__GET_PATH_TO_RELATIVE_ARGUMENT() { - [[ $1 =~ ^[.] ]] \ - && echo $(readlink -f "$EXECUTION_DIR/$1") \ - || echo "$1" \ - ; - true -} - -##################################################################### - -__RUN_SCWRYPT() { - ((SUBSCWRYPT+=1)) - { printf ' '; printf '--%.0s' {1..$SUBSCWRYPT}; printf " ($SUBSCWRYPT) "; } >&2 - echo " BEGIN SUBSCWRYPT : $@" >&2 - - SUBSCWRYPT=$SUBSCWRYPT SCWRYPTS_ENV=$ENV_NAME \ - "$SCWRYPTS_ROOT/scwrypts" $@ - EXIT_CODE=$? - - { printf ' '; printf '--%.0s' {1..$SUBSCWRYPT}; printf " ($SUBSCWRYPT) "; } >&2 - echo " END SUBSCWRYPT : $1" >&2 - ((SUBSCWRYPT-=1)) - - return $EXIT_CODE -} diff --git a/global/config.zsh b/global/config.zsh deleted file mode 100644 index 00d85d9..0000000 --- a/global/config.zsh +++ /dev/null @@ -1,44 +0,0 @@ -##################################################################### - -SCWRYPTS_CONFIG_PATH="$HOME/.config/scwrypts" -SCWRYPTS_DATA_PATH="$HOME/.local/share/scwrypts" - -SCWRYPTS_SHORTCUT='' # CTRL + SPACE -SCWRYPTS_ENV_SHORTCUT='' # CTRL + / - -##################################################################### - -SCWRYPTS_ENV_PATH="$SCWRYPTS_CONFIG_PATH/env" -SCWRYPTS_LOG_PATH="$SCWRYPTS_DATA_PATH/logs" - -SCWRYPTS_OUTPUT_PATH="$SCWRYPTS_DATA_PATH/output" -SCWRYPTS_VIRTUALENV_PATH="$SCWRYPTS_DATA_PATH/virtualenv" - -##################################################################### - -[ -f $SCWRYPTS_CONFIG_PATH/config ] && source $SCWRYPTS_CONFIG_PATH/config - -##################################################################### - -[ ! -d $SCWRYPTS_CONFIG_PATH ] && mkdir -p $SCWRYPTS_CONFIG_PATH -[ ! -d $SCWRYPTS_DATA_PATH ] && mkdir -p $SCWRYPTS_DATA_PATH - -[ ! -d $SCWRYPTS_ENV_PATH ] && mkdir -p $SCWRYPTS_ENV_PATH -[ ! -d $SCWRYPTS_LOG_PATH ] && mkdir -p $SCWRYPTS_LOG_PATH - -[ ! -d $SCWRYPTS_OUTPUT_PATH ] && mkdir -p $SCWRYPTS_OUTPUT_PATH -[ ! -d $SCWRYPTS_VIRTUALENV_PATH ] && mkdir -p $SCWRYPTS_VIRTUALENV_PATH - -export \ - SCWRYPTS_CONFIG_PATH \ - SCWRYPTS_DATA_PATH \ - SCWRYPS_SHORTCUT \ - SCWRYPTS_ENV_SHORTCUT \ - SCWRYPTS_ENV_PATH \ - SCWRYPTS_LOG_PATH \ - SCWRYPTS_OUTPUT_PATH \ - SCWRYPTS_VIRTUALENV_PATH \ - ; - -##################################################################### -true diff --git a/py/data/convert/csv-to-json.py b/py/data/convert/csv-to-json.py index 591a3f1..7b8fffc 100755 --- a/py/data/convert/csv-to-json.py +++ b/py/data/convert/csv-to-json.py @@ -1,21 +1,24 @@ #!/usr/bin/env python -from argparse import ArgumentParser - -from py.lib.data.io import add_io_arguments from py.lib.data.converter import convert +from py.lib.scwrypts import execute + +from py.lib.scwrypts.exceptions import ImportedExecutableError if __name__ != '__main__': - raise Exception('executable only; must run through scwrypts') + raise ImportedExecutableError() +##################################################################### -parser = ArgumentParser(description = 'converts csv into json') -add_io_arguments(parser) +def main(_args, stream): + return convert( + input_stream = stream.input, + input_type = 'csv', + output_stream = stream.output, + output_type = 'json', + ) -args = parser.parse_args() - -convert( - input_file = args.input_file, - input_type = 'csv', - output_file = args.output_file, - output_type = 'json', +##################################################################### +execute(main, + description = 'convert csv into json', + parse_args = [], ) diff --git a/py/data/convert/csv-to-yaml.py b/py/data/convert/csv-to-yaml.py index a0fe70a..2d738d4 100755 --- a/py/data/convert/csv-to-yaml.py +++ b/py/data/convert/csv-to-yaml.py @@ -1,21 +1,24 @@ #!/usr/bin/env python -from argparse import ArgumentParser - -from py.lib.data.io import add_io_arguments from py.lib.data.converter import convert +from py.lib.scwrypts import execute + +from py.lib.scwrypts.exceptions import ImportedExecutableError if __name__ != '__main__': - raise Exception('executable only; must run through scwrypts') + raise ImportedExecutableError() +##################################################################### -parser = ArgumentParser(description = 'converts csv into yaml') -add_io_arguments(parser) +def main(_args, stream): + return convert( + input_stream = stream.input, + input_type = 'csv', + output_stream = stream.output, + output_type = 'yaml', + ) -args = parser.parse_args() - -convert( - input_file = args.input_file, - input_type = 'csv', - output_file = args.output_file, - output_type = 'yaml', +##################################################################### +execute(main, + description = 'convert csv into yaml', + parse_args = [], ) diff --git a/py/data/convert/json-to-csv.py b/py/data/convert/json-to-csv.py index ce8826d..5663735 100755 --- a/py/data/convert/json-to-csv.py +++ b/py/data/convert/json-to-csv.py @@ -1,21 +1,24 @@ #!/usr/bin/env python -from argparse import ArgumentParser - -from py.lib.data.io import add_io_arguments from py.lib.data.converter import convert +from py.lib.scwrypts import execute + +from py.lib.scwrypts.exceptions import ImportedExecutableError if __name__ != '__main__': - raise Exception('executable only; must run through scwrypts') + raise ImportedExecutableError() +##################################################################### -parser = ArgumentParser(description = 'converts csv into json') -add_io_arguments(parser) +def main(_args, stream): + return convert( + input_stream = stream.input, + input_type = 'json', + output_stream = stream.output, + output_type = 'csv', + ) -args = parser.parse_args() - -convert( - input_file = args.input_file, - input_type = 'json', - output_file = args.output_file, - output_type = 'csv', +##################################################################### +execute(main, + description = 'convert json into csv', + parse_args = [], ) diff --git a/py/data/convert/json-to-yaml.py b/py/data/convert/json-to-yaml.py index 96ea40d..492581c 100755 --- a/py/data/convert/json-to-yaml.py +++ b/py/data/convert/json-to-yaml.py @@ -1,21 +1,24 @@ #!/usr/bin/env python -from argparse import ArgumentParser - -from py.lib.data.io import add_io_arguments from py.lib.data.converter import convert +from py.lib.scwrypts import execute + +from py.lib.scwrypts.exceptions import ImportedExecutableError if __name__ != '__main__': - raise Exception('executable only; must run through scwrypts') + raise ImportedExecutableError() +##################################################################### -parser = ArgumentParser(description = 'converts json into yaml') -add_io_arguments(parser) +def main(_args, stream): + return convert( + input_stream = stream.input, + input_type = 'json', + output_stream = stream.output, + output_type = 'yaml', + ) -args = parser.parse_args() - -convert( - input_file = args.input_file, - input_type = 'json', - output_file = args.output_file, - output_type = 'yaml', +##################################################################### +execute(main, + description = 'convert json into yaml', + parse_args = [], ) diff --git a/py/data/convert/yaml-to-csv.py b/py/data/convert/yaml-to-csv.py index 108bafa..ae8b0c9 100755 --- a/py/data/convert/yaml-to-csv.py +++ b/py/data/convert/yaml-to-csv.py @@ -1,21 +1,24 @@ #!/usr/bin/env python -from argparse import ArgumentParser - -from py.lib.data.io import add_io_arguments from py.lib.data.converter import convert +from py.lib.scwrypts import execute + +from py.lib.scwrypts.exceptions import ImportedExecutableError if __name__ != '__main__': - raise Exception('executable only; must run through scwrypts') + raise ImportedExecutableError() +##################################################################### -parser = ArgumentParser(description = 'converts yaml into csv') -add_io_arguments(parser) +def main(_args, stream): + return convert( + input_stream = stream.input, + input_type = 'yaml', + output_stream = stream.output, + output_type = 'csv', + ) -args = parser.parse_args() - -convert( - input_file = args.input_file, - input_type = 'yaml', - output_file = args.output_file, - output_type = 'csv', +##################################################################### +execute(main, + description = 'convert yaml into csv', + parse_args = [], ) diff --git a/py/data/convert/yaml-to-json.py b/py/data/convert/yaml-to-json.py index 1a7239d..31610e6 100755 --- a/py/data/convert/yaml-to-json.py +++ b/py/data/convert/yaml-to-json.py @@ -1,21 +1,24 @@ #!/usr/bin/env python -from argparse import ArgumentParser - -from py.lib.data.io import add_io_arguments from py.lib.data.converter import convert +from py.lib.scwrypts import execute + +from py.lib.scwrypts.exceptions import ImportedExecutableError if __name__ != '__main__': - raise Exception('executable only; must run through scwrypts') + raise ImportedExecutableError() +##################################################################### -parser = ArgumentParser(description = 'converts yaml into json') -add_io_arguments(parser) +def main(_args, stream): + return convert( + input_stream = stream.input, + input_type = 'yaml', + output_stream = stream.output, + output_type = 'json', + ) -args = parser.parse_args() - -convert( - input_file = args.input_file, - input_type = 'yaml', - output_file = args.output_file, - output_type = 'json', +##################################################################### +execute(main, + description = 'convert yaml into json', + parse_args = [], ) diff --git a/zsh/latex/templates/math/gitignore b/py/directus/__init__.py similarity index 100% rename from zsh/latex/templates/math/gitignore rename to py/directus/__init__.py diff --git a/py/directus/get-items.py b/py/directus/get-items.py new file mode 100755 index 0000000..3f76f9b --- /dev/null +++ b/py/directus/get-items.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python +from json import dumps + +from py.lib.fzf import fzf, fzf_tail +from py.lib.http import directus +from py.lib.scwrypts import execute + +from py.lib.scwrypts.exceptions import ImportedExecutableError + +if __name__ != '__main__': + raise ImportedExecutableError() + +##################################################################### + +def main(args, stream): + if {None} == { args.collection, args.filters, args.fields }: + args.interactive = True + + if args.interactive: + args.generate_filters_prompt = True + args.generate_fields_prompt = True + + collection = _get_or_select_collection(args) + filters = _get_or_select_filters(args, collection) + fields = _get_or_select_fields(args, collection) + + query = '&'.join([ + param for param in [ + fields, + filters, + ] + if param + ]) + + endpoint = f'items/{collection}?{query}' + + response = directus.request('GET', endpoint) + + stream.writeline(dumps({ + **response.json(), + 'scwrypts_metadata': { + 'endpoint': endpoint, + 'repeat_with': f'scwrypts -n py/directus/get-items -- -c {collection} -f \'{query}\'', + }, + })) + +def _get_or_select_collection(args): + collection = args.collection + + if collection is None: + collection = fzf( + prompt = 'select a collection', + choices = directus.get_collections(), + ) + + if not collection: + raise ValueError('collection required for query') + + return collection + +def _get_or_select_filters(args, collection): + filters = args.filters or '' + + if filters == '' and args.generate_filters_prompt: + filters = '&'.join([ + f'filter[{filter}][' + ( + operator := fzf( + prompt = f'select operator for {filter}', + choices = directus.FILTER_OPERATORS, + ) + ) + ']=' + fzf_tail(prompt = f'filter[{filter}][{operator}]') + + for filter in fzf( + prompt = 'select filter(s) [C^c to skip]', + fzf_options = '--multi', + force_list = True, + choices = directus.get_fields(collection), + ) + ]) + + return filters + +def _get_or_select_fields(args, collection): + fields = args.fields or '' + + if fields == '' and args.generate_fields_prompt: + fields = ','.join(fzf( + prompt = 'select return field(s) [C^c to get all]', + fzf_options = '--multi', + choices = directus.get_fields(collection), + force_list = True, + )) + + if fields: + fields = f'fields[]={fields}' + + return fields + + +##################################################################### +execute(main, + description = 'interactive CLI to get data from directus', + parse_args = [ + ( ['-c', '--collection'], { + "dest" : 'collection', + "default" : None, + "help" : 'the name of the collection', + "required" : False, + }), + ( ['-f', '--filters'], { + "dest" : 'filters', + "default" : None, + "help" : 'as a URL-suffix, filters for the query', + "required" : False, + }), + ( ['-d', '--fields'], { + "dest" : 'fields', + "default" : None, + "help" : 'comma-separated list of fields to include', + "required" : False, + }), + ( ['-p', '--interactive-prompt'], { + "action" : 'store_true', + "dest" : 'interactive', + "default" : False, + "help" : 'interactively generate filter prompts; implied if no flags are provided', + "required" : False, + }), + ( ['--prompt-filters'], { + "action" : 'store_true', + "dest" : 'generate_filters_prompt', + "default" : False, + "help" : '(superceded by -p) only generate filters interactively', + "required" : False, + }), + ( ['--prompt-fields'], { + "action" : 'store_true', + "dest" : 'generate_fields_prompt', + "default" : False, + "help" : '(superceded by -p) only generate filters interactively', + "required" : False, + }), + ] + + ) diff --git a/py/discord/__init__.py b/py/discord/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/discord/post-message.py b/py/discord/post-message.py new file mode 100755 index 0000000..b18d846 --- /dev/null +++ b/py/discord/post-message.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +from json import dumps +from sys import stderr + +from py.lib.http import discord +from py.lib.scwrypts import execute + +from py.lib.scwrypts.exceptions import ImportedExecutableError + +if __name__ != '__main__': + raise ImportedExecutableError() + +##################################################################### + +def main(args, stream): + if args.body is None: + print(f'reading input from {stream.input.name}', file=stderr) + args.body = ''.join(stream.readlines()).strip() + + if len(args.body) == 0: + args.body = 'PING' + + response = discord.send_message( + content = args.body, + channel_id = args.channel_id, + webhook = args.webhook, + avatar_url = args.avatar_url, + ) + + stream.writeline(dumps({ + **(response.json() if response.text != '' else {'message': 'OK'}), + 'scwrypts_metadata': {}, + })) + + +##################################################################### +execute(main, + description = 'post a message to the indicated discord channel', + parse_args = [ + ( ['-b', '--body'], { + 'dest' : 'body', + 'help' : 'message body', + 'required' : False, + }), + ( ['-c', '--channel-id'], { + 'dest' : 'channel_id', + 'help' : 'target channel id', + 'required' : False, + }), + ( ['-w', '--webhook'], { + 'dest' : 'webhook', + 'help' : 'target webhook (takes precedence over -c)', + 'required' : False, + }), + ( ['--avatar-url'], { + 'dest' : 'avatar_url', + 'help' : 'replace default avatar_url', + 'required' : False, + }), + ] + ) diff --git a/py/hello-world.py b/py/hello-world.py index 26090b8..2284ae5 100755 --- a/py/hello-world.py +++ b/py/hello-world.py @@ -1,19 +1,27 @@ #!/usr/bin/env python -from argparse import ArgumentParser +from py.lib.scwrypts import execute + +from py.lib.scwrypts.exceptions import ImportedExecutableError if __name__ != '__main__': - raise Exception('executable only; must run through scwrypts') + raise ImportedExecutableError() + +##################################################################### -parser = ArgumentParser(description = 'a simple "Hello, World!" program') -parser.add_argument( - '-m', '--message', - dest = 'message', - default = 'HELLO WORLD', - help = 'message to print to stdout', - required = False, +def main(args, stream): + stream.writeline(args.message) + + +##################################################################### +execute(main, + description = 'a simple "Hello, World!" program', + parse_args = [ + ( ['-m', '--message'], { + 'dest' : 'message', + 'default' : 'HELLO WORLD', + 'help' : 'message to print', + 'required' : False, + }), + ], ) - -args = parser.parse_args() - -print(args.message) diff --git a/py/lib/__init__.py b/py/lib/__init__.py index e69de29..21a4809 100644 --- a/py/lib/__init__.py +++ b/py/lib/__init__.py @@ -0,0 +1,6 @@ +import py.lib.data +import py.lib.fzf +import py.lib.http +import py.lib.redis +import py.lib.scwrypts +import py.lib.twilio diff --git a/py/lib/data/__init__.py b/py/lib/data/__init__.py index e69de29..374d0d2 100644 --- a/py/lib/data/__init__.py +++ b/py/lib/data/__init__.py @@ -0,0 +1 @@ +import py.lib.data.converter diff --git a/py/lib/data/converter.py b/py/lib/data/converter.py index 92edb04..82d9154 100644 --- a/py/lib/data/converter.py +++ b/py/lib/data/converter.py @@ -2,18 +2,13 @@ import csv import json import yaml -from py.lib.data.io import get_stream - -def convert(input_file, input_type, output_file, output_type): +def convert(input_stream, input_type, output_stream, output_type): if input_type == output_type: raise ValueError('input type and output type are the same') - with get_stream(input_file) as input_stream: - data = convert_input(input_stream, input_type) - - with get_stream(output_file, 'w+') as output_stream: - _write_output(output_stream, output_type, data) + data = convert_input(input_stream, input_type) + write_output(output_stream, output_type, data) def convert_input(stream, input_type): @@ -28,7 +23,8 @@ def convert_input(stream, input_type): 'yaml': _read_yaml, }[input_type](stream) -def _write_output(stream, output_type, data): + +def write_output(stream, output_type, data): supported_output_types = {'csv', 'json', 'yaml'} if output_type not in supported_output_types: @@ -40,6 +36,7 @@ def _write_output(stream, output_type, data): 'yaml': _write_yaml, }[output_type](stream, data) + ##################################################################### def _read_csv(stream): diff --git a/py/lib/fzf/__init__.py b/py/lib/fzf/__init__.py new file mode 100644 index 0000000..bfd1043 --- /dev/null +++ b/py/lib/fzf/__init__.py @@ -0,0 +1 @@ +from py.lib.fzf.client import fzf, fzf_tail, fzf_head diff --git a/py/lib/fzf/client.py b/py/lib/fzf/client.py new file mode 100644 index 0000000..22d61b6 --- /dev/null +++ b/py/lib/fzf/client.py @@ -0,0 +1,61 @@ +from pyfzf.pyfzf import FzfPrompt + +FZF_PROMPT = None + + +def fzf( # pylint: disable=too-many-arguments + choices=None, + prompt=None, + fzf_options='', + delimiter='\n', + return_type=str, + force_list=False, + ): + global FZF_PROMPT # pylint: disable=global-statement + + if choices is None: + choices = [] + + if not isinstance(return_type, type): + raise ValueError(f'return_type must be a valid python type; "{return_type}" is not a type') + + if FZF_PROMPT is None: + FZF_PROMPT = FzfPrompt() + + options = ' '.join({ + '-i', + '--layout=reverse', + '--ansi', + '--height=30%', + f'--prompt "{prompt} : "' if prompt is not None else '', + fzf_options, + }) + + selections = [ + return_type(selection) + for selection in FZF_PROMPT.prompt(choices, options, delimiter) + ] + + if not force_list: + if len(selections) == 0: + return None + + if len(selections) == 1: + return selections[0] + + return selections + + +def fzf_tail(*args, **kwargs): + return _fzf_print(*args, **kwargs)[-1] + +def fzf_head(*args, **kwargs): + return _fzf_print(*args, **kwargs)[0] + +def _fzf_print(*args, fzf_options='', **kwargs): + return fzf( + *args, + **kwargs, + fzf_options = f'--print-query {fzf_options}', + force_list = True, + ) diff --git a/py/lib/http/__init__.py b/py/lib/http/__init__.py index 6df13ed..73d515a 100644 --- a/py/lib/http/__init__.py +++ b/py/lib/http/__init__.py @@ -1 +1,5 @@ from py.lib.http.client import get_request_client + +import py.lib.http.directus +import py.lib.http.discord +import py.lib.http.linear diff --git a/py/lib/http/directus/__init__.py b/py/lib/http/directus/__init__.py new file mode 100644 index 0000000..1355255 --- /dev/null +++ b/py/lib/http/directus/__init__.py @@ -0,0 +1,2 @@ +from py.lib.http.directus.client import * +from py.lib.http.directus.constant import * diff --git a/py/lib/http/directus/client.py b/py/lib/http/directus/client.py new file mode 100644 index 0000000..07e5e00 --- /dev/null +++ b/py/lib/http/directus/client.py @@ -0,0 +1,56 @@ +from py.lib.http import get_request_client +from py.lib.scwrypts import getenv + + +REQUEST = None +COLLECTIONS = None +FIELDS = {} + + +def request(method, endpoint, **kwargs): + global REQUEST # pylint: disable=global-statement + + if REQUEST is None: + REQUEST = get_request_client( + base_url = getenv("DIRECTUS__BASE_URL"), + headers = { + 'Authorization': f'bearer {getenv("DIRECTUS__API_TOKEN")}', + } + ) + + return REQUEST(method, endpoint, **kwargs) + +def graphql(query, system=False): + return request( + 'POST', + 'graphql' if system is True else 'graphql/system', + json={'query': query}, + ) + + +def get_collections(): + global COLLECTIONS # pylint: disable=global-statement + + if COLLECTIONS is None: + COLLECTIONS = [ + item['collection'] + for item in request( + 'GET', + 'collections?limit=-1&fields[]=collection', + ).json()['data'] + ] + + return COLLECTIONS + + +def get_fields(collection): + if FIELDS.get(collection) is None: + FIELDS[collection] = [ + item['field'] + for item in request( + 'GET', + f'fields/{collection}?limit=-1&fields[]=field', + ).json()['data'] + ] + + return FIELDS[collection] diff --git a/py/lib/http/directus/constant.py b/py/lib/http/directus/constant.py new file mode 100644 index 0000000..5f3b294 --- /dev/null +++ b/py/lib/http/directus/constant.py @@ -0,0 +1,25 @@ +FILTER_OPERATORS = { + '_eq', + '_neq', + '_lt', + '_lte', + '_gt', + '_gte', + '_in', + '_nin', + '_null', + '_nnull', + '_contains', + '_ncontains', + '_starts_with', + '_ends_with', + '_nends_with', + '_between', + '_nbetween', + '_empty', + '_nempty', + '_intersects', + '_nintersects', + '_intersects_bbox', + '_nintersects_bbox', + } diff --git a/py/lib/http/discord/__init__.py b/py/lib/http/discord/__init__.py new file mode 100644 index 0000000..0935e8c --- /dev/null +++ b/py/lib/http/discord/__init__.py @@ -0,0 +1,2 @@ +from py.lib.http.discord.client import * +from py.lib.http.discord.send_message import * diff --git a/py/lib/http/discord/client.py b/py/lib/http/discord/client.py new file mode 100644 index 0000000..36ba4da --- /dev/null +++ b/py/lib/http/discord/client.py @@ -0,0 +1,20 @@ +from py.lib.http import get_request_client +from py.lib.scwrypts import getenv + +REQUEST = None + +def request(method, endpoint, **kwargs): + global REQUEST # pylint: disable=global-statement + + if REQUEST is None: + headers = {} + + if (token := getenv("DISCORD__BOT_TOKEN", required = False)) is not None: + headers['Authorization'] = f'Bot {token}' + + REQUEST = get_request_client( + base_url = 'https://discord.com/api', + headers = headers, + ) + + return REQUEST(method, endpoint, **kwargs) diff --git a/py/lib/http/discord/send_message.py b/py/lib/http/discord/send_message.py new file mode 100644 index 0000000..d7b9715 --- /dev/null +++ b/py/lib/http/discord/send_message.py @@ -0,0 +1,34 @@ +from py.lib.scwrypts import getenv +from py.lib.http.discord import request + +def send_message(content, channel_id=None, webhook=None, avatar_url=None, **kwargs): + if channel_id is None: + channel_id = getenv('DISCORD__DEFAULT_CHANNEL_ID', required=False) + + if avatar_url is None: + avatar_url = getenv('DISCORD__DEFAULT_AVATAR_URL', required=False) + + endpoint = None + + if webhook is not None: + endpoint = f'webhooks/{webhook}' + elif channel_id is not None: + endpoint = f'channels/{channel_id}/messages' + else: + raise ValueError('must provide target channel_id or webhook') + + + return request( + method = 'POST', + endpoint = endpoint, + json = { + key: value + for key, value in { + 'content': content, + 'username': 'wrobot', + 'avatar_url': avatar_url, + **kwargs, + }.items() + if value is not None + }, + ) diff --git a/py/lib/http/linear/__init__.py b/py/lib/http/linear/__init__.py new file mode 100644 index 0000000..670a494 --- /dev/null +++ b/py/lib/http/linear/__init__.py @@ -0,0 +1 @@ +from py.lib.http.linear.client import * diff --git a/py/lib/http/linear/client.py b/py/lib/http/linear/client.py new file mode 100644 index 0000000..63be5d9 --- /dev/null +++ b/py/lib/http/linear/client.py @@ -0,0 +1,20 @@ +from py.lib.http import get_request_client +from py.lib.scwrypts import getenv + +REQUEST = None + +def request(method, endpoint, **kwargs): + global REQUEST # pylint: disable=global-statement + + if REQUEST is None: + REQUEST = get_request_client( + base_url = 'https://api.linear.app', + headers = { + 'Authorization': f'bearer {getenv("LINEAR__API_TOKEN")}', + } + ) + + return REQUEST(method, endpoint, **kwargs) + +def graphql(query): + return request('POST', 'graphql', json={'query': query}) diff --git a/py/lib/linear/__init__.py b/py/lib/linear/__init__.py deleted file mode 100644 index c600274..0000000 --- a/py/lib/linear/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from py.lib.linear.client import request, graphql diff --git a/py/lib/linear/client.py b/py/lib/linear/client.py deleted file mode 100644 index 1e8ce1f..0000000 --- a/py/lib/linear/client.py +++ /dev/null @@ -1,13 +0,0 @@ -from py.lib.http import get_request_client -from py.lib.scwrypts import getenv - - -request = get_request_client( - base_url = 'https://api.linear.app', - headers = { - 'Authorization': f'bearer {getenv("LINEAR__API_TOKEN")}', - } - ) - -def graphql(query): - return request('POST', 'graphql', json={'query': query}) diff --git a/py/lib/redis/__init__.py b/py/lib/redis/__init__.py index 8b13789..248af1f 100644 --- a/py/lib/redis/__init__.py +++ b/py/lib/redis/__init__.py @@ -1 +1 @@ - +from py.lib.redis.client import get_client diff --git a/py/lib/redis/client.py b/py/lib/redis/client.py index 1e88a0d..22d3827 100644 --- a/py/lib/redis/client.py +++ b/py/lib/redis/client.py @@ -2,14 +2,18 @@ from redis import StrictRedis from py.lib.scwrypts import getenv +CLIENT = None -class RedisClient(StrictRedis): - def __init__(self): - super().__init__( +def get_client(): + global CLIENT # pylint: disable=global-statement + + if CLIENT is None: + print('getting redis client') + CLIENT = StrictRedis( host = getenv('REDIS_HOST'), port = getenv('REDIS_PORT'), password = getenv('REDIS_AUTH', required=False), decode_responses = True, ) -Client = RedisClient() + return CLIENT diff --git a/py/lib/scwrypts/__init__.py b/py/lib/scwrypts/__init__.py index 9245df2..0cc3adf 100644 --- a/py/lib/scwrypts/__init__.py +++ b/py/lib/scwrypts/__init__.py @@ -1,3 +1,6 @@ +from py.lib.scwrypts.execute import execute from py.lib.scwrypts.getenv import getenv from py.lib.scwrypts.interactive import interactive from py.lib.scwrypts.run import run + +import py.lib.scwrypts.io diff --git a/py/lib/scwrypts/exceptions.py b/py/lib/scwrypts/exceptions.py index 3eed365..367ea50 100644 --- a/py/lib/scwrypts/exceptions.py +++ b/py/lib/scwrypts/exceptions.py @@ -1,3 +1,16 @@ -class MissingVariableError(Exception): +from argparse import ArgumentError + + +class MissingVariableError(EnvironmentError): def init(self, name): super().__init__(f'Missing required environment variable "{name}"') + + +class ImportedExecutableError(ImportError): + def __init__(self): + super().__init__('executable only; must run through scwrypts') + + +class MissingFlagAndEnvironmentVariableError(EnvironmentError, ArgumentError): + def __init__(self, flags, env_var): + super().__init__(f'must provide at least one of : {{ flags: {flags} OR {env_var} }}') diff --git a/py/lib/scwrypts/execute.py b/py/lib/scwrypts/execute.py new file mode 100644 index 0000000..e85feea --- /dev/null +++ b/py/lib/scwrypts/execute.py @@ -0,0 +1,23 @@ +from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter + +from py.lib.scwrypts.io import get_combined_stream, add_io_arguments + + +def execute(main, description=None, parse_args=None, toggle_input=True, toggle_output=True): + if parse_args is None: + parse_args = [] + + parser = ArgumentParser( + description = description, + formatter_class = ArgumentDefaultsHelpFormatter, + ) + + add_io_arguments(parser, toggle_input, toggle_output) + + for a in parse_args: + parser.add_argument(*a[0], **a[1]) + + args = parser.parse_args() + + with get_combined_stream(args.input_file, args.output_file) as stream: + return main(args, stream) diff --git a/py/lib/scwrypts/getenv.py b/py/lib/scwrypts/getenv.py index 4fbfbd0..ddc8da6 100644 --- a/py/lib/scwrypts/getenv.py +++ b/py/lib/scwrypts/getenv.py @@ -1,16 +1,15 @@ from os import getenv as os_getenv from py.lib.scwrypts.exceptions import MissingVariableError -from py.lib.scwrypts.run import run def getenv(name, required=True): value = os_getenv(name, None) - if value == None: - run('zsh/scwrypts/environment/stage-variables', name) - if required and not value: raise MissingVariableError(name) + if value == '': + value = None + return value diff --git a/py/lib/scwrypts/interactive.py b/py/lib/scwrypts/interactive.py index 53d86fc..b8f477f 100644 --- a/py/lib/scwrypts/interactive.py +++ b/py/lib/scwrypts/interactive.py @@ -1,11 +1,22 @@ from bpython import embed -def interactive(function): - def main(*args, **kwargs): - print('preparing interactive environment...') - local_vars = function(*args, **kwargs) - print('environment ready; user, GO! :)') - embed(local_vars) +def interactive(variable_descriptions): + def outer(function): - return main + def inner(*args, **kwargs): + + print('\npreparing interactive environment...\n') + + local_vars = function(*args, **kwargs) + + print('\n\n'.join([ + f'>>> {x}' for x in variable_descriptions + ])) + print('\nenvironment ready; user, GO! :)\n') + + embed(local_vars) + + return inner + + return outer diff --git a/py/lib/data/io.py b/py/lib/scwrypts/io.py similarity index 61% rename from py/lib/data/io.py rename to py/lib/scwrypts/io.py index 65ccf55..eed5a60 100644 --- a/py/lib/data/io.py +++ b/py/lib/scwrypts/io.py @@ -30,6 +30,9 @@ def get_stream(filename=None, mode='r', encoding='utf-8', verbose=False, **kwarg yield stdin if is_read else stdout + if not is_read: + stdout.flush() + def add_io_arguments(parser, toggle_input=True, toggle_output=True): if toggle_input: @@ -49,3 +52,35 @@ def add_io_arguments(parser, toggle_input=True, toggle_output=True): help = 'path to output file; omit for stdout', required = False, ) + + +@contextmanager +def get_combined_stream(input_file=None, output_file=None): + with get_stream(input_file, 'r') as input_stream, get_stream(output_file, 'w+') as output_stream: + yield CombinedStream(input_stream, output_stream) + + +class CombinedStream: + def __init__(self, input_stream, output_stream): + self.input = input_stream + self.output = output_stream + + def read(self, *args, **kwargs): + return self.input.read(*args, **kwargs) + + def readline(self, *args, **kwargs): + return self.input.readline(*args, **kwargs) + + def readlines(self, *args, **kwargs): + return self.input.readlines(*args, **kwargs) + + def write(self, *args, **kwargs): + return self.output.write(*args, **kwargs) + + def writeline(self, line): + x = self.output.write(f'{line}\n') + self.output.flush() + return x + + def writelines(self, *args, **kwargs): + return self.output.writelines(*args, **kwargs) diff --git a/py/lib/scwrypts/run.py b/py/lib/scwrypts/run.py index 2b8d308..f0a607e 100644 --- a/py/lib/scwrypts/run.py +++ b/py/lib/scwrypts/run.py @@ -7,8 +7,9 @@ def run(scwrypt_name, *args): DEPTH = int(getenv('SUBSCWRYPT', '0')) DEPTH += 1 - SCWRYPTS_EXE = Path(__file__).parents[2] / 'scwrypts' + SCWRYPTS_EXE = Path(__file__).parents[3] / 'scwrypts' ARGS = ' '.join([str(x) for x in args]) + print(f'SUBSCWRYPT={DEPTH} {SCWRYPTS_EXE} {scwrypt_name} -- {ARGS}') print(f'\n {"--"*DEPTH} ({DEPTH}) BEGIN SUBSCWRYPT : {Path(scwrypt_name).name}') subprocess_run( diff --git a/py/lib/twilio/__init__.py b/py/lib/twilio/__init__.py new file mode 100644 index 0000000..2803123 --- /dev/null +++ b/py/lib/twilio/__init__.py @@ -0,0 +1,2 @@ +from py.lib.twilio.client import get_client +from py.lib.twilio.send_sms import send_sms diff --git a/py/lib/twilio/client.py b/py/lib/twilio/client.py new file mode 100644 index 0000000..20c1a84 --- /dev/null +++ b/py/lib/twilio/client.py @@ -0,0 +1,18 @@ +from twilio.rest import Client + +from py.lib.scwrypts import getenv + +CLIENT = None + +def get_client(): + global CLIENT # pylint: disable=global-statement + + if CLIENT is None: + print('loading client') + CLIENT = Client( + username = getenv('TWILIO__API_KEY'), + password = getenv('TWILIO__API_SECRET'), + account_sid = getenv('TWILIO__ACCOUNT_SID'), + ) + + return CLIENT diff --git a/py/lib/twilio/send_sms.py b/py/lib/twilio/send_sms.py new file mode 100644 index 0000000..752ebac --- /dev/null +++ b/py/lib/twilio/send_sms.py @@ -0,0 +1,57 @@ +from json import dumps +from time import sleep + +from py.lib.twilio.client import get_client + + +def send_sms(to, from_, body, max_char_count=300, stream=None): + ''' + abstraction for twilio.client.messages.create which will break + messages into multi-part SMS rather than throwing an error or + requiring the use of MMS data + + @param to messages.create parameter + @param from_ messages.create parameter + @param body messages.create parameter + @param max_char_count 1 ≤ N ≤ 1500 (default 300) + @param stream used to report success/failure (optional) + + @return a list of twilio MessageInstance objects + ''' + client = get_client() + messages = [] + + max_char_count = max(1, min(max_char_count, 1500)) + + total_sms_parts = 1 + len(body) // max_char_count + contains_multiple_parts = total_sms_parts > 1 + + for i in range(0, len(body), max_char_count): + msg_body = body[i:i+max_char_count] + current_part = 1 + i // max_char_count + + if contains_multiple_parts: + msg_body = f'{current_part}/{total_sms_parts}\n{msg_body}' + + message = client.messages.create( + to = to, + from_ = from_, + body = msg_body, + ) + + messages.append(message) + + if stream is not None: + stream.writeline( + dumps({ + 'sid': message.sid, + 'to': to, + 'from': from_, + 'body': msg_body, + }) + ) + + if contains_multiple_parts: + sleep(2 if max_char_count <= 500 else 5) + + return messages diff --git a/py/linear/comment.py b/py/linear/comment.py index 5a13fee..daf4a50 100755 --- a/py/linear/comment.py +++ b/py/linear/comment.py @@ -1,47 +1,45 @@ #!/usr/bin/env python -from argparse import ArgumentParser +from py.lib.http.linear import graphql +from py.lib.scwrypts import execute -from py.lib.data.io import get_stream, add_io_arguments -from py.lib.linear import graphql +from py.lib.scwrypts.exceptions import ImportedExecutableError if __name__ != '__main__': - raise Exception('executable only; must run through scwrypts') + raise ImportedExecutableError() + +##################################################################### -parser = ArgumentParser(description = 'comment on an issue in linear.app') +def get_query(args): + body = f'"""from wrobot:\n```\n{args.message}\n```\n"""' + return f''' + mutation CommentCreate {{ + commentCreate( + input: {{ + issueId: "{args.issue_id}" + body: {body} + }} + ) {{ success }} + }}''' -parser.add_argument( - '-i', '--issue', - dest = 'issue_id', - help = 'issue short-code (e.g. CLOUD-319)', - required = True, +def main(args, stream): + response = graphql(get_query(args)) + stream.writeline(response) + + +##################################################################### +execute(main, + description = 'comment on an inssue in linear.app', + parse_args = [ + ( ['-d', '--issue-id'], { + 'dest' : 'issue_id', + 'help' : 'issue short-code (e.g. CLOUD-319)', + 'required' : True, + }), + ( ['-m', '--message'], { + 'dest' : 'message', + 'help' : 'comment to post to the target issue', + 'required' : True, + }), + ] ) - -parser.add_argument( - '-m', '--message', - dest = 'message', - help = 'comment to post to the target issue', - required = True, - ) - -add_io_arguments(parser, toggle_input=False) - -args = parser.parse_args() - -query = f''' -mutation CommentCreate {{ - commentCreate( - input: {{ - issueId: "{args.issue_id}" - body: """from wrobot: -``` -{args.message.strip()} -```""" - }} - ) {{ success }} -}} -''' - -response = graphql(query) -with get_stream(args.output_file, 'w+') as output: - output.write(response.text) diff --git a/py/redis/interactive.py b/py/redis/interactive.py index b5bcac8..d018039 100755 --- a/py/redis/interactive.py +++ b/py/redis/interactive.py @@ -1,25 +1,26 @@ #!/usr/bin/env python -from argparse import ArgumentParser +from py.lib.redis import get_client +from py.lib.scwrypts import execute, interactive, getenv -from py.lib.redis.client import Client -from py.lib.scwrypts import interactive, getenv +from py.lib.scwrypts.exceptions import ImportedExecutableError if __name__ != '__main__': - raise Exception('executable only; must run through scwrypts') + raise ImportedExecutableError() + +##################################################################### -parser = ArgumentParser(description = 'establishes a redis client in an interactive python shell') -args = parser.parse_args() - -@interactive -def main(): +@interactive([ + f'r = StrictRedis(\'{getenv("REDIS_HOST")}:{getenv("REDIS_PORT")}\')', + ]) +def main(_args, _stream): # pylint: disable=possibly-unused-variable - r = Client - - print(f''' ->>> r = StrictRedis({getenv("REDIS_HOST")}:{getenv("REDIS_PORT")}) - ''') - + r = get_client() return locals() -main() + +##################################################################### +execute(main, + description = 'establishes a redis client in an interactive python shell', + parse_args = [], + ) diff --git a/py/requirements.txt b/py/requirements.txt index 6e7b69d..68453d8 100644 --- a/py/requirements.txt +++ b/py/requirements.txt @@ -1,3 +1,5 @@ -redis bpython +pyfzf pyyaml +redis +twilio diff --git a/py/twilio/__init__.py b/py/twilio/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/twilio/send-sms.py b/py/twilio/send-sms.py new file mode 100755 index 0000000..c089751 --- /dev/null +++ b/py/twilio/send-sms.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +from sys import stderr + +from py.lib.scwrypts import execute, getenv +from py.lib.twilio import send_sms + +from py.lib.scwrypts.exceptions import ImportedExecutableError, MissingFlagAndEnvironmentVariableError + +if __name__ != '__main__': + raise ImportedExecutableError() + +##################################################################### + +def main(args, stream): + if args.body is None: + print(f'reading input from {stream.input.name}', file=stderr) + args.body = ''.join(stream.readlines()).strip() + + if len(args.body) == 0: + args.body = 'PING' + + if args.from_ is None: + raise MissingFlagAndEnvironmentVariableError(['-f', '--from'], 'TWILIO__DEFAULT_PHONE_FROM') + + if args.to is None: + raise MissingFlagAndEnvironmentVariableError(['-t', '--to'], 'TWILIO__DEFAULT_PHONE_TO') + + send_sms( + to = args.to, + from_ = args.from_, + body = args.body, + max_char_count = args.max_char_count, + stream = stream, + ) + + +##################################################################### +execute(main, + description = 'send a simple SMS through twilio', + parse_args = [ + ( ['-t', '--to'], { + 'dest' : 'to', + 'help' : 'phone number of the receipient', + 'required' : False, + 'default' : getenv('TWILIO__DEFAULT_PHONE_TO', required=False), + }), + ( ['-f', '--from'], { + 'dest' : 'from_', + 'help' : 'phone number of the receipient', + 'required' : False, + 'default' : getenv('TWILIO__DEFAULT_PHONE_FROM', required=False), + }), + ( ['-b', '--body'], { + 'dest' : 'body', + 'help' : 'message body', + 'required' : False, + }), + ( ['--max-char-count'], { + 'dest' : 'max_char_count', + 'help' : 'separate message into parts by character count (1 < N <= 1500)', + 'required' : False, + 'default' : 300, + }), + ] + ) diff --git a/run b/run index 175d06d..5eccf9e 100755 --- a/run +++ b/run @@ -1,8 +1,7 @@ #!/bin/zsh export EXECUTION_DIR=$(pwd) +source "${0:a:h}/zsh/lib/import.driver.zsh" || exit 42 -SCWRYPTS_ROOT="${0:a:h}" -source "$SCWRYPTS_ROOT/zsh/common.zsh" || exit 42 ##################################################################### __RUN() { @@ -10,24 +9,54 @@ __RUN() { usage: scwrypts [OPTIONS ...] SCRIPT -- [SCRIPT OPTIONS ...] OPTIONS - -e, --env set environment; overwrites SCWRYPTS_ENV - -n, --no-log skip logging (useful when calling scwrypts as an api) - -l, --list print out command list and exit + -g, --group only use scripts from the indicated group + -t, --type only use scripts of the indicated type + -m, --name only run the script if there is an exact match + (requires type and group) - -h, --help display this message and exit + -e, --env set environment; overwrites SCWRYPTS_ENV + -n, --no-log skip logging and run in quiet mode + + --update update scwrypts library to latest version + + -v, --version print out scwrypts version and exit + -l, --list print out command list and exit + -h, --help display this message and exit ' cd "$SCWRYPTS_ROOT" local ENV_NAME="$SCWRYPTS_ENV" local SEARCH_PATTERNS=() + local VARSPLIT SEARCH_GROUP SEARCH_TYPE SEARCH_NAME + local ERROR=0 while [[ $# -gt 0 ]] do case $1 in + -t | --type ) + [ ! $2 ] && ERROR "missing value for argument $1" && break + SEARCH_TYPE=$2 + shift 2 + ;; + -g | --group ) + [ ! $2 ] && ERROR "missing value for argument $1" && break + SEARCH_GROUP=$2 + shift 2 + ;; + -m | --name ) + [ ! $2 ] && ERROR "missing value for argument $1" && break + SEARCH_NAME=$2 + shift 2 + ;; + + -[a-z][a-z]* ) + VARSPLIT=$(echo "$1 " | sed 's/^\(-.\)\(.*\) /\1 -\2/') + set -- $(echo " $VARSPLIT ") ${@:2} + ;; -h | --help ) - __USAGE + USAGE return 0 ;; -n | --no-log ) @@ -35,55 +64,168 @@ __RUN() { shift 1 ;; -e | --env ) - [ $ENV_NAME ] && __WARNING 'overwriting session environment' + [ ! $2 ] && ERROR "missing value for argument $1" && break + [ ! $SUBSCWRYPTS ] \ + && [ $ENV_NAME ] \ + && WARNING 'overwriting session environment' \ + ; + ENV_NAME="$2" - __STATUS "using CLI environment '$ENV_NAME'" + STATUS "using CLI environment '$ENV_NAME'" shift 2 ;; -l | --list ) - __OUTPUT_COMMAND_LIST + SCWRYPTS__GET_AVAILABLE_SCWRYPTS + return 0 + ;; + -v | --version ) + echo scwrypts $(cd "$SCWRYPTS__ROOT__scwrypts"; git describe --tags) + return 0 + ;; + --update ) + cd "$SCWRYPTS__ROOT__scwrypts" + git fetch --quiet origin main + local SYNC_STATUS=$? + + git diff --exit-code origin/main -- . >&2 + local DIFF_STATUS=$? + + [[ $SYNC_STATUS -eq 0 ]] && [[ $DIFF_STATUS -eq 0 ]] && { + SUCCESS 'already up-to-date with origin/main' + } || { + git rebase --autostash origin/main \ + && SUCCESS 'up-to-date with origin/main' \ + || { + git rebase --abort + ERROR 'unable to update scwrypts; please try manual upgrade' + REMINDER "installation in '$(pwd)'" + } + } return 0 ;; -- ) shift 1 break # pass arguments after '--' to the scwrypt ;; - -* ) - __ERROR "unrecognized argument '$1'" + --* ) + ERROR "unrecognized argument '$1'" shift 1 ;; * ) - SEARCH_PATTERNS+=$1 + SEARCH_PATTERNS+=($1) shift 1 ;; esac done - __ERROR_CHECK + [ $SEARCH_NAME ] && { + [ ! $SEARCH_TYPE ] && ERROR '--name requires --type argument' + [ ! $SEARCH_GROUP ] && ERROR '--name requires --group argument' + } + + CHECK_ERRORS ########################################## - local SCRIPT=$(__SELECT_SCRIPT $SEARCH_PATTERNS) - [ ! $SCRIPT ] && exit 2 - export SCWRYPT_NAME=$SCRIPT + local SCWRYPTS_AVAILABLE + local POTENTIAL_ERROR="no such scwrypt exists:" + + SCWRYPTS_AVAILABLE=$(SCWRYPTS__GET_AVAILABLE_SCWRYPTS) + + [ $SEARCH_NAME ] && { + POTENTIAL_ERROR+="\n NAME : '$SEARCH_NAME'" + POTENTIAL_ERROR+="\n TYPE : '$SEARCH_TYPE'" + POTENTIAL_ERROR+="\n GROUP : '$SEARCH_GROUP'" + SCWRYPTS_AVAILABLE=$({ + echo $SCWRYPTS_AVAILABLE | head -n1 + echo $SCWRYPTS_AVAILABLE | sed -e 's/\x1b\[[0-9;]*m//g' | grep "^$SEARCH_NAME *$SEARCH_TYPE *$SEARCH_GROUP\$" + }) + } + + [ ! $SEARCH_NAME ] && { + [ $SEARCH_TYPE ] && { + POTENTIAL_ERROR+="\n TYPE : '$SEARCH_TYPE'" + SCWRYPTS_AVAILABLE=$(\ + { + echo $SCWRYPTS_AVAILABLE | head -n1 + echo $SCWRYPTS_AVAILABLE | grep ' [^/]*'$SEARCH_TYPE'[^/]* ' + } \ + | awk '{$2=""; print $0;}' \ + | sed 's/ \+$/'$(printf $__COLOR_RESET)'/; s/ \+/^/g' \ + | column -ts '^' + ) + } + + [ $SEARCH_GROUP ] && { + POTENTIAL_ERROR+="\n GROUP : '$SEARCH_GROUP'" + SCWRYPTS_AVAILABLE=$( + { + echo $SCWRYPTS_AVAILABLE | head -n1 + echo $SCWRYPTS_AVAILABLE | grep "$SEARCH_GROUP"'[^/]*$' + } \ + | awk '{$NF=""; print $0;}' \ + | sed 's/ \+$/'$(printf $__COLOR_RESET)'/; s/ \+/^/g' \ + | column -ts '^' + ) + } + + [[ ${#SEARCH_PATTERNS[@]} -gt 0 ]] && { + POTENTIAL_ERROR+="\n PATTERNS : $SEARCH_PATTERNS" + local P + for P in ${SEARCH_PATTERNS[@]} + do + SCWRYPTS_AVAILABLE=$( + { + echo $SCWRYPTS_AVAILABLE | head -n1 + echo $SCWRYPTS_AVAILABLE | grep $P + } + ) + done + } + } + + [[ $(echo $SCWRYPTS_AVAILABLE | wc -l) -lt 2 ]] && ERROR "$POTENTIAL_ERROR" + + CHECK_ERRORS + + ########################################## + + local NAME="$SEARCH_NAME" + local TYPE="$SEARCH_TYPE" + local GROUP="$SEARCH_GROUP" + + [[ $(echo $SCWRYPTS_AVAILABLE | wc -l) -eq 2 ]] \ + && SCWRYPT_SELECTION=$(echo $SCWRYPTS_AVAILABLE | tail -n1) \ + || SCWRYPT_SELECTION=$(echo $SCWRYPTS_AVAILABLE | FZF "select a script to run" --header-lines 1) + [ $SCWRYPT_SELECTION ] || exit 2 + + SCWRYPTS__SEPARATE_SCWRYPT_SELECTION $SCWRYPT_SELECTION + + export SCWRYPT_NAME=$NAME + export SCWRYPT_TYPE=$TYPE + export SCWRYPT_GROUP=$GROUP + + ########################################## local ENV_REQUIRED=$(__CHECK_ENV_REQUIRED && echo 1 || echo 0) [[ $ENV_REQUIRED -eq 1 ]] && { - [ ! $ENV_NAME ] && ENV_NAME=$(__SELECT_ENV) - local ENV_FILE=$(__GET_ENV_FILE $ENV_NAME) - - [ -f "$ENV_FILE" ] && source "$ENV_FILE" \ - || __FAIL 5 "missing or invalid environment '$ENV_NAME'" + [ ! $ENV_NAME ] && ENV_NAME=$(SCWRYPTS__SELECT_ENV) + local ENV_FILE=$(SCWRYPTS__GET_ENV_FILE "$ENV_NAME") + source "$ENV_FILE" || FAIL 5 "missing or invalid environment '$ENV_NAME'" export ENV_NAME } + ########################################## + [ ! $SUBSCWRYPT ] \ && [[ $ENV_NAME =~ prod ]] \ - && { __VALIDATE_UPSTREAM_TIMELINE || __ABORT; } + && { __VALIDATE_UPSTREAM_TIMELINE || ABORT; } - local RUN_STRING=$(__GET_RUN_STRING $SCRIPT $ENV_NAME) + ########################################## + + local RUN_STRING=$(SCWRYPTS__GET_RUNSTRING $SCWRYPT_NAME $SCWRYPT_TYPE $SCWRYPT_GROUP) [ ! $RUN_STRING ] && exit 3 ########################################## @@ -93,7 +235,7 @@ __RUN() { local HEADER=$( [ $SUBSCWRYPT ] && return 0 echo '=====================================================================' - echo "script : $SCRIPT" + echo "script : $SCWRYPT_GROUP $SCWRYPT_TYPE $SCWRYPT_NAME" echo "run at : $(date)" echo "config : $ENV_NAME" [ ! $LOGFILE ] && echo '\033[1;33m------------------------------------------\033[0m' @@ -130,111 +272,17 @@ __RUN() { ##################################################################### -__OUTPUT_COMMAND_LIST() { - local LAST_TYPE LAST_SUBSET - for SCRIPT in $(__GET_AVAILABLE_SCRIPTS) - do - TYPE=$(echo $SCRIPT | sed 's/\/.*//') - SUBSET=$(echo $SCRIPT | sed 's/.*\/\(.*\)\/[^\/]*$/\1/') - [[ ! $LAST_TYPE =~ $TYPE ]] && { - echo >&2 - echo "\\033[1;32m$TYPE scwrypts\\033[0m" >&2 - LAST_SUBSET='' - } - [ $LAST_SUBSET ] && [[ ! $LAST_SUBSET =~ $SUBSET ]] && { - echo >&2 - } - printf ' - ' >&2 - echo $SCRIPT - LAST_TYPE=$TYPE - LAST_SUBSET=$SUBSET - done -} - -##################################################################### - -__SELECT_SCRIPT() { - local SCRIPT - local SCRIPTS=$(__GET_AVAILABLE_SCRIPTS) - local SEARCH=($@) - - [[ ${#SEARCH[@]} -eq 0 ]] && { - SCRIPT=$(echo $SCRIPTS | __FZF 'select a script') - } - - [[ ${#SEARCH[@]} -eq 1 ]] && [ -f ./$SEARCH ] && { - SCRIPT=$SEARCH - } - - [ ! $SCRIPT ] && [[ ${#SEARCH[@]} -gt 0 ]] && { - SCRIPT=$SCRIPTS - for PATTERN in $SEARCH - do - SCRIPT=$(echo $SCRIPT | grep $PATTERN) - done - - [ ! $SCRIPT ] && __FAIL 2 "no script found by name '$@'" - - [[ $(echo $SCRIPT | wc -l) -gt 1 ]] && { - __STATUS "more than one script matched '$@'" - SCRIPT=$(echo $SCRIPT | __FZF 'select a script') - } - } - - echo $SCRIPT -} - -__GET_RUN_STRING() { - local SCRIPT="$1" - local ENV_NAME="$2" - local TYPE=$(echo $SCRIPT | sed 's/\/.*$//') - - local RUN_STRING - - local _VIRTUALENV="$SCWRYPTS_VIRTUALENV_PATH/$TYPE/bin/activate" - [ -f $_VIRTUALENV ] && source $_VIRTUALENV - - case $TYPE in - py ) __CHECK_DEPENDENCY python || return 1 - RUN_STRING="python -m $(echo $SCRIPT | sed 's/\//./g; s/\.py$//; s/\.\.//')" - - CURRENT_PYTHON_VERSION=$(python --version | sed 's/^[^0-9]*\(3\.[^.]*\).*$/\1/') - - echo $__PREFERRED_PYTHON_VERSIONS | grep -q $CURRENT_PYTHON_VERSION || { - __WARNING "only tested on the following python versions: $(printf ', %s.x' ${__PREFERRED_PYTHON_VERSIONS[@]} | sed 's/^, //')" - __WARNING 'compatibility may vary' - } - ;; - - zsh ) __CHECK_DEPENDENCY zsh || return 1 - RUN_STRING="noglob ./$SCRIPT" - ;; - - zx ) __CHECK_DEPENDENCY zx || return 1 - RUN_STRING="FORCE_COLOR=3 ./$SCRIPT.mjs" - ;; - - * ) __ERROR "unsupported script type '$SCRIPT_TYPE'" - return 2 - ;; - esac - - RUN_STRING="SCWRYPTS_ENV='$ENV_NAME' $RUN_STRING" - [ -f $_VIRTUALENV ] && RUN_STRING="source '$_VIRTUALENV'; $RUN_STRING" - - echo $RUN_STRING -} - __CHECK_ENV_REQUIRED() { [ $CI ] && return 1 - echo $SCRIPT | grep -q 'zsh/scwrypts/logs' && return 1 + echo $SCWRYPT_NAME | grep -q 'scwrypts/logs/' && return 1 + echo $SCWRYPT_NAME | grep -q 'scwrypts/environment/' && return 1 return 0 } __VALIDATE_UPSTREAM_TIMELINE() { - __STATUS "on '$ENV_NAME'; checking diff against origin/main" + STATUS "on '$ENV_NAME'; checking diff against origin/main" git fetch --quiet origin main local SYNC_STATUS=$? @@ -243,14 +291,14 @@ __VALIDATE_UPSTREAM_TIMELINE() { local DIFF_STATUS=$? [[ $SYNC_STATUS -eq 0 ]] && [[ $DIFF_STATUS -eq 0 ]] && { - __SUCCESS 'up-to-date with origin/main' + SUCCESS 'up-to-date with origin/main' } || { - __WARNING - [[ $SYNC_STATUS -ne 0 ]] && __WARNING 'unable to synchronize with origin/main' - [[ $DIFF_STATUS -ne 0 ]] && __WARNING 'your branch differs from origin/main (diff listed above)' - __WARNING + WARNING + [[ $SYNC_STATUS -ne 0 ]] && WARNING 'unable to synchronize with origin/main' + [[ $DIFF_STATUS -ne 0 ]] && WARNING 'your branch differs from origin/main (diff listed above)' + WARNING - __yN 'continue?' || return 1 + yN 'continue?' || return 1 } } @@ -262,7 +310,7 @@ __GET_LOGFILE() { || [[ $SCRIPT =~ interactive ]] \ && return 0 - echo "$SCWRYPTS_LOG_PATH/$(echo $SCRIPT | sed 's/^\.\///; s/\//\%/g').log" + echo "$SCWRYPTS_LOG_PATH/$(echo $GROUP/$TYPE/$NAME | sed 's/^\.\///; s/\//\%/g').log" } ##################################################################### diff --git a/scwrypts.plugin.zsh b/scwrypts.plugin.zsh index af4a44c..d83fa37 100644 --- a/scwrypts.plugin.zsh +++ b/scwrypts.plugin.zsh @@ -1,26 +1,32 @@ -DONT_EXIT=1 source ${0:a:h}/zsh/common.zsh +NO_EXPORT_CONFIG=1 source "${0:a:h}/zsh/lib/import.driver.zsh" || return 42 + ##################################################################### -__SCWRYPTS() { - local SCRIPT=$(__GET_AVAILABLE_SCRIPTS | __FZF 'select a script') +SCWRYPTS__ZSH_PLUGIN() { + local SCWRYPT_SELECTION=$(SCWRYPTS__GET_AVAILABLE_SCWRYPTS | FZF 'select a script' --header-lines 1) + local NAME + local TYPE + local GROUP zle clear-command-line - [ ! $SCRIPT ] && { zle accept-line; return 0; } + [ ! $SCWRYPT_SELECTION ] && { zle accept-line; return 0; } + + SCWRYPTS__SEPARATE_SCWRYPT_SELECTION $SCWRYPT_SELECTION which scwrypts >/dev/null 2>&1\ && RBUFFER="scwrypts" || RBUFFER="$SCWRYPTS_ROOT/scwrypts" - RBUFFER+=" $SCRIPT" + RBUFFER+=" --name $NAME --group $GROUP --type $TYPE" zle accept-line } -zle -N scwrypts __SCWRYPTS +zle -N scwrypts SCWRYPTS__ZSH_PLUGIN bindkey $SCWRYPTS_SHORTCUT scwrypts ##################################################################### -__SCWRYPTS_ENV() { +SCWRYPTS__ZSH_PLUGIN_ENV() { local RESET='reset' local SELECTED=$(\ - { [ $SCWRYPTS_ENV ] && echo $RESET; __GET_ENV_NAMES; } \ - | __FZF 'select an environment' \ + { [ $SCWRYPTS_ENV ] && echo $RESET; SCWRYPTS__GET_ENV_NAMES; } \ + | FZF 'select an environment' \ ) zle clear-command-line @@ -32,5 +38,5 @@ __SCWRYPTS_ENV() { zle accept-line } -zle -N scwrypts-setenv __SCWRYPTS_ENV +zle -N scwrypts-setenv SCWRYPTS__ZSH_PLUGIN_ENV bindkey $SCWRYPTS_ENV_SHORTCUT scwrypts-setenv diff --git a/zsh/aws/common.zsh b/zsh/aws/common.zsh deleted file mode 100644 index 8e1daf7..0000000 --- a/zsh/aws/common.zsh +++ /dev/null @@ -1,13 +0,0 @@ -_DEPENDENCIES+=( - aws - jq -) -_REQUIRED_ENV+=( - AWS_ACCOUNT - AWS_PROFILE - AWS_REGION -) -source ${0:a:h}/../common.zsh -##################################################################### - -_AWS() { aws --profile $AWS_PROFILE --region $AWS_REGION --output json $@; } diff --git a/zsh/aws/ecr/common.zsh b/zsh/aws/ecr/common.zsh deleted file mode 100644 index e3d45e5..0000000 --- a/zsh/aws/ecr/common.zsh +++ /dev/null @@ -1,6 +0,0 @@ -_DEPENDENCIES+=( - docker -) -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### diff --git a/zsh/aws/ecr/login b/zsh/aws/ecr/login deleted file mode 100755 index 29053f0..0000000 --- a/zsh/aws/ecr/login +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### - -__STATUS "performing AWS ECR docker login" - -_AWS ecr get-login-password | docker login \ - --username AWS \ - --password-stdin \ - "$AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com" \ - && __SUCCESS "logged in to 'AWS:$AWS_ACCOUNT:$AWS_REGION'" \ - || __FAIL 1 "unable to login to '$AWS_ACCOUNT' in '$AWS_REGION'" diff --git a/zsh/aws/efs/common.zsh b/zsh/aws/efs/common.zsh deleted file mode 100644 index b47f7e1..0000000 --- a/zsh/aws/efs/common.zsh +++ /dev/null @@ -1,6 +0,0 @@ -_DEPENDENCIES+=() -_REQUIRED_ENV+=( - AWS__EFS__LOCAL_MOUNT_POINT -) -source ${0:a:h}/../common.zsh -##################################################################### diff --git a/zsh/aws/efs/unmount b/zsh/aws/efs/unmount deleted file mode 100755 index fe46c42..0000000 --- a/zsh/aws/efs/unmount +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### - -_EFS_DISCONNECT() { - [ ! -d "$AWS__EFS__LOCAL_MOUNT_POINT" ] && { - __STATUS 'no efs currently mounted' - exit 0 - } - - local MOUNTED=$(ls "$AWS__EFS__LOCAL_MOUNT_POINT") - [ ! $MOUNTED ] && { - __STATUS 'no efs currently mounted' - exit 0 - } - - __GETSUDO || exit 1 - - - local SELECTED=$(echo $MOUNTED | __FZF 'select a file system to unmount') - [ ! $SELECTED ] && __ABORT - - local EFS="$AWS__EFS__LOCAL_MOUNT_POINT/$SELECTED" - __STATUS "unmounting '$SELECTED'" - sudo umount $EFS >/dev/null 2>&1 - sudo rmdir $EFS \ - && __SUCCESS "done" \ - || __FAIL 2 "failed to unmount '$EFS'" -} - -##################################################################### -_EFS_DISCONNECT diff --git a/zsh/aws/eks/common.zsh b/zsh/aws/eks/common.zsh deleted file mode 100644 index 1110be3..0000000 --- a/zsh/aws/eks/common.zsh +++ /dev/null @@ -1,6 +0,0 @@ -_DEPENDENCIES+=( - kubectl -) -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### diff --git a/zsh/aws/eks/login b/zsh/aws/eks/login deleted file mode 100755 index 08b4835..0000000 --- a/zsh/aws/eks/login +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### - -__STATUS "performing AWS ECR docker login" - -CLUSTER_NAME=$(\ - _AWS eks list-clusters \ - | jq -r '.[] | .[]' \ - | __FZF 'select a cluster' -) -[ ! $CLUSTER_NAME ] && __ABORT - -__STATUS "updating kubeconfig for '$CLUSTER_NAME'" -_AWS eks update-kubeconfig --name $CLUSTER_NAME \ - && __SUCCESS "kubeconfig updated with '$CLUSTER_NAME'" \ - || __ERROR "failed to update kubeconfig; do you have permissions to access '$CLUSTER_NAME'?" diff --git a/zsh/aws/route53/common.zsh b/zsh/aws/route53/common.zsh deleted file mode 100644 index 7464e76..0000000 --- a/zsh/aws/route53/common.zsh +++ /dev/null @@ -1,6 +0,0 @@ -_DEPENDENCIES+=( - cli53 -) -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### diff --git a/zsh/aws/s3/common.zsh b/zsh/aws/s3/common.zsh deleted file mode 100644 index 1191a72..0000000 --- a/zsh/aws/s3/common.zsh +++ /dev/null @@ -1,4 +0,0 @@ -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### diff --git a/zsh/aws/s3/media-sync/common.zsh b/zsh/aws/s3/media-sync/common.zsh deleted file mode 100644 index eb2c7e8..0000000 --- a/zsh/aws/s3/media-sync/common.zsh +++ /dev/null @@ -1,30 +0,0 @@ -_DEPENDENCIES+=() -_REQUIRED_ENV+=( - AWS__S3__MEDIA_TARGETS - AWS__S3__MEDIA_BUCKET -) -source ${0:a:h}/../common.zsh -##################################################################### - -AWS__S3__MEDIA_TARGETS=($(echo $AWS__S3__MEDIA_TARGETS | sed 's/,/\n/g')) - -__SYNC_MEDIA() { - local ACTION="$1" - local REMOTE_TARGET="s3://$AWS__S3__MEDIA_BUCKET/$2" - local LOCAL_TARGET="$HOME/$2" - - local A B - case $ACTION in - push ) A="$LOCAL_TARGET"; B="$REMOTE_TARGET" ;; - pull ) A="$REMOTE_TARGET"; B="$LOCAL_TARGET" ;; - - * ) __ERROR "unknown action '$1'"; return 1 ;; - esac - - local FLAGS=(${@:3}) - - __STATUS "${ACTION}ing $2" - _AWS s3 sync $A $B $FLAGS \ - && __SUCCESS "$2 up-to-date" \ - || { __ERROR "unable to sync $2 (see above)"; return 1; } -} diff --git a/zsh/aws/s3/media-sync/pull b/zsh/aws/s3/media-sync/pull deleted file mode 100755 index 27874aa..0000000 --- a/zsh/aws/s3/media-sync/pull +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### - -__PULL_ALL_MEDIA() { - local FLAGS=($@) - local FAILED_COUNT=0 - - __STATUS 'starting media download from s3' - - local TARGET - for TARGET in $AWS__S3__MEDIA_TARGETS - do - __SYNC_MEDIA pull $TARGET $FLAGS || ((FAILED_COUNT+=1)) - done - - [[ $FAILED_COUNT -eq 0 ]] \ - && __SUCCESS 'local media files now up-to-date' \ - || __FAIL $FAILED_COUNT 'unable to download one or more targets' \ - ; -} - -##################################################################### - -__PULL_ALL_MEDIA $@ diff --git a/zsh/aws/s3/media-sync/push b/zsh/aws/s3/media-sync/push deleted file mode 100755 index 95884c3..0000000 --- a/zsh/aws/s3/media-sync/push +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### - -__PUSH_ALL_MEDIA() { - local FLAGS=($@) - local FAILED_COUNT=0 - - __STATUS 'starting media upload to s3' - - local TARGET - for TARGET in $AWS__S3__MEDIA_TARGETS - do - __SYNC_MEDIA push $TARGET $FLAGS || ((FAILED_COUNT+=1)) - done - - [[ $FAILED_COUNT -eq 0 ]] \ - && __SUCCESS 's3 media files now up-to-date' \ - || __FAIL $FAILED_COUNT 'unable to upload one or more targets' \ - ; -} - -##################################################################### - -__PUSH_ALL_MEDIA $@ diff --git a/zsh/aws/README.md b/zsh/cloud/aws/README.md similarity index 100% rename from zsh/aws/README.md rename to zsh/cloud/aws/README.md diff --git a/zsh/cloud/aws/ecr/login b/zsh/cloud/aws/ecr/login new file mode 100755 index 0000000..3bc78fb --- /dev/null +++ b/zsh/cloud/aws/ecr/login @@ -0,0 +1,10 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use cloud/aws/ecr + +CHECK_ENVIRONMENT +##################################################################### + +ECR_LOGIN $@ diff --git a/zsh/aws/efs/mount b/zsh/cloud/aws/efs/mount similarity index 51% rename from zsh/aws/efs/mount rename to zsh/cloud/aws/efs/mount index 29b8d7f..28d381b 100755 --- a/zsh/aws/efs/mount +++ b/zsh/cloud/aws/efs/mount @@ -1,37 +1,40 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=(jq) +REQUIRED_ENV+=(AWS__EFS__LOCAL_MOUNT_POINT) + +use cloud/aws/cli + +CHECK_ENVIRONMENT ##################################################################### -_EFS_CONNECT() { - __GETSUDO || exit 1 +EFS_CONNECT() { + GETSUDO || exit 1 [ ! -d $AWS__EFS__LOCAL_MOUNT_POINT ] && { sudo mkdir $AWS__EFS__LOCAL_MOUNT_POINT \ - && __STATUS "created local mount point '$AWS__EFS__LOCAL_MOUNT_POINT'" + && STATUS "created local mount point '$AWS__EFS__LOCAL_MOUNT_POINT'" } local FS_ID=$(\ - _AWS efs describe-file-systems \ + AWS efs describe-file-systems \ | jq -r '.[] | .[] | .FileSystemId' \ - | __FZF 'select a filesystem to mount' \ + | FZF 'select a filesystem to mount' \ ) - [ ! $FS_ID ] && __ABORT + [ ! $FS_ID ] && ABORT local MOUNT_POINT="$AWS__EFS__LOCAL_MOUNT_POINT/$FS_ID" [ -d "$MOUNT_POINT" ] && sudo rmdir "$MOUNT_POINT" >/dev/null 2>&1 [ -d "$MOUNT_POINT" ] && { - __STATUS "$FS_ID is already mounted" + STATUS "$FS_ID is already mounted" exit 0 } - local MOUNT_TARGETS=$(_AWS efs describe-mount-targets --file-system-id $FS_ID) + local MOUNT_TARGETS=$(AWS efs describe-mount-targets --file-system-id $FS_ID) local ZONE=$(\ echo $MOUNT_TARGETS \ | jq -r '.[] | .[] | .AvailabilityZoneName' \ - | sort -u | __FZF 'select availability zone'\ + | sort -u | FZF 'select availability zone'\ ) - [ ! $ZONE ] && __ABORT + [ ! $ZONE ] && ABORT local MOUNT_IP=$(\ echo $MOUNT_TARGETS \ @@ -39,15 +42,15 @@ _EFS_CONNECT() { | head -n1 \ ) - __SUCCESS 'ready to mount!' - __REMINDER 'your device must be connected to the appropriate VPN' + SUCCESS 'ready to mount!' + REMINDER 'for private file-systems, you must be connected to the appropriate VPN' - __STATUS "file system id : $FS_ID" - __STATUS "availability zone : $ZONE" - __STATUS "file system ip : $MOUNT_IP" - __STATUS "local mount point : $MOUNT_POINT" + STATUS "file system id : $FS_ID" + STATUS "availability zone : $ZONE" + STATUS "file system ip : $MOUNT_IP" + STATUS "local mount point : $MOUNT_POINT" - __Yn 'proceed?' || __ABORT + Yn 'proceed?' || ABORT sudo mkdir $MOUNT_POINT \ && sudo mount \ @@ -55,12 +58,12 @@ _EFS_CONNECT() { -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport \ $MOUNT_IP:/ \ "$MOUNT_POINT" \ - && __SUCCESS "mounted at '$MOUNT_POINT'" \ + && SUCCESS "mounted at '$MOUNT_POINT'" \ || { sudo rmdir $MOUNT_POINT >/dev/null 2>&1 - __FAIL 2 "unable to mount '$FS_ID'" + FAIL 2 "unable to mount '$FS_ID'" } } ##################################################################### -_EFS_CONNECT +EFS_CONNECT $@ diff --git a/zsh/cloud/aws/efs/unmount b/zsh/cloud/aws/efs/unmount new file mode 100755 index 0000000..358e229 --- /dev/null +++ b/zsh/cloud/aws/efs/unmount @@ -0,0 +1,37 @@ +#!/bin/zsh +DEPENDENCIES+=(jq) +REQUIRED_ENV+=(AWS__EFS__LOCAL_MOUNT_POINT) + +use cloud/aws/cli + +CHECK_ENVIRONMENT +##################################################################### + +EFS_DISCONNECT() { + [ ! -d "$AWS__EFS__LOCAL_MOUNT_POINT" ] && { + STATUS 'no efs currently mounted' + exit 0 + } + + local MOUNTED=$(ls "$AWS__EFS__LOCAL_MOUNT_POINT") + [ ! $MOUNTED ] && { + STATUS 'no efs currently mounted' + exit 0 + } + + GETSUDO || exit 1 + + + local SELECTED=$(echo $MOUNTED | FZF 'select a file system to unmount') + [ ! $SELECTED ] && ABORT + + local EFS="$AWS__EFS__LOCAL_MOUNT_POINT/$SELECTED" + STATUS "unmounting '$SELECTED'" + sudo umount $EFS >/dev/null 2>&1 + sudo rmdir $EFS \ + && SUCCESS "done" \ + || FAIL 2 "failed to unmount '$EFS'" +} + +##################################################################### +EFS_DISCONNECT $@ diff --git a/zsh/cloud/aws/eks/login b/zsh/cloud/aws/eks/login new file mode 100755 index 0000000..527c4aa --- /dev/null +++ b/zsh/cloud/aws/eks/login @@ -0,0 +1,10 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use cloud/aws/eks + +CHECK_ENVIRONMENT +##################################################################### + +EKS_CLUSTER_LOGIN $@ diff --git a/zsh/aws/rds/create-backup b/zsh/cloud/aws/rds/create-backup similarity index 60% rename from zsh/aws/rds/create-backup rename to zsh/cloud/aws/rds/create-backup index bffa2d1..7edde9b 100755 --- a/zsh/aws/rds/create-backup +++ b/zsh/cloud/aws/rds/create-backup @@ -1,14 +1,18 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use cloud/aws/rds +use db/postgres + +CHECK_ENVIRONMENT ##################################################################### -RDS_INTERACTIVE_LOGIN() { +CREATE_BACKUP() { local DB_HOST DB_PORT DB_NAME DB_USER DB_PASS - GET_DATABASE_CREDENTIALS $@ || return 1 + RDS__GET_DATABASE_CREDENTIALS $@ || return 1 - __RUN_SCWRYPT 'zsh/db/postgres/pg_dump' -- \ + PG_DUMP \ --host $DB_HOST \ --port $DB_PORT \ --name $DB_NAME \ @@ -17,6 +21,5 @@ RDS_INTERACTIVE_LOGIN() { ; } - ##################################################################### -RDS_INTERACTIVE_LOGIN $@ +CREATE_BACKUP $@ diff --git a/zsh/aws/rds/interactive-login b/zsh/cloud/aws/rds/interactive-login similarity index 68% rename from zsh/aws/rds/interactive-login rename to zsh/cloud/aws/rds/interactive-login index a25d999..e59fbce 100755 --- a/zsh/aws/rds/interactive-login +++ b/zsh/cloud/aws/rds/interactive-login @@ -1,14 +1,18 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use cloud/aws/rds +use db/postgres + +CHECK_ENVIRONMENT ##################################################################### RDS_INTERACTIVE_LOGIN() { local DB_HOST DB_PORT DB_NAME DB_USER DB_PASS - GET_DATABASE_CREDENTIALS $@ || return 1 + RDS__GET_DATABASE_CREDENTIALS $@ || return 1 - __RUN_SCWRYPT 'zsh/db/interactive/postgres' -- \ + POSTGRES__LOGIN_INTERACTIVE \ --host $DB_HOST \ --port $DB_PORT \ --name $DB_NAME \ diff --git a/zsh/aws/rds/load-backup b/zsh/cloud/aws/rds/load-backup similarity index 60% rename from zsh/aws/rds/load-backup rename to zsh/cloud/aws/rds/load-backup index e5487e6..eee9a02 100755 --- a/zsh/aws/rds/load-backup +++ b/zsh/cloud/aws/rds/load-backup @@ -1,14 +1,18 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use cloud/aws/rds +use db/postgres + +CHECK_ENVIRONMENT ##################################################################### -RDS_INTERACTIVE_LOGIN() { +LOAD_BACKUP() { local DB_HOST DB_PORT DB_NAME DB_USER DB_PASS - GET_DATABASE_CREDENTIALS $@ || return 1 + RDS__GET_DATABASE_CREDENTIALS $@ || return 1 - __RUN_SCWRYPT 'zsh/db/postgres/pg_restore' -- \ + PG_RESTORE \ --host $DB_HOST \ --port $DB_PORT \ --name $DB_NAME \ @@ -19,4 +23,4 @@ RDS_INTERACTIVE_LOGIN() { ##################################################################### -RDS_INTERACTIVE_LOGIN $@ +LOAD_BACKUP $@ diff --git a/zsh/aws/route53/backup b/zsh/cloud/aws/route53/backup similarity index 65% rename from zsh/aws/route53/backup rename to zsh/cloud/aws/route53/backup index b6df8a7..562e114 100755 --- a/zsh/aws/route53/backup +++ b/zsh/cloud/aws/route53/backup @@ -1,21 +1,22 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=(cli53) +REQUIRED_ENV+=(AWS_PROFILE) + +CHECK_ENVIRONMENT ##################################################################### -_ROUTE53_BACKUP() { +ROUTE53_BACKUP() { local BACKUP_PATH="$SCWRYPTS_OUTPUT_PATH/$ENV_NAME/aws-dns-backup/$(date '+%Y-%m-%d')" mkdir -p $BACKUP_PATH >/dev/null 2>&1 local DOMAIN local JOBS=() - for DOMAIN in $(_ROUTE53_GET_DOMAINS) + for DOMAIN in $(ROUTE53_GET_DOMAINS) do - ( __STATUS "creating '$BACKUP_PATH/$DOMAIN.txt'" \ + ( STATUS "creating '$BACKUP_PATH/$DOMAIN.txt'" \ && cli53 export --profile $AWS_PROFILE $DOMAIN > "$BACKUP_PATH/$DOMAIN.txt" \ - && __SUCCESS "backed up '$DOMAIN'" \ - || __ERROR "failed to back up '$DOMAIN'" \ + && SUCCESS "backed up '$DOMAIN'" \ + || ERROR "failed to back up '$DOMAIN'" \ ) & JOBS+=$! done @@ -24,7 +25,7 @@ _ROUTE53_BACKUP() { for P in ${JOBS[@]}; do wait $P >/dev/null 2>&1; done } -_ROUTE53_GET_DOMAINS() { +ROUTE53_GET_DOMAINS() { cli53 list --profile $AWS_PROFILE \ | awk '{print $2;}' \ | sed '1d; s/\.$//'\ @@ -32,4 +33,4 @@ _ROUTE53_GET_DOMAINS() { } ##################################################################### -_ROUTE53_BACKUP +ROUTE53_BACKUP diff --git a/zsh/cloud/media-sync/pull b/zsh/cloud/media-sync/pull new file mode 100755 index 0000000..00bc144 --- /dev/null +++ b/zsh/cloud/media-sync/pull @@ -0,0 +1,10 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use cloud/media-sync + +CHECK_ENVIRONMENT +##################################################################### + +MEDIA_SYNC__PULL $@ diff --git a/zsh/cloud/media-sync/push b/zsh/cloud/media-sync/push new file mode 100755 index 0000000..20cc77e --- /dev/null +++ b/zsh/cloud/media-sync/push @@ -0,0 +1,10 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use cloud/media-sync + +CHECK_ENVIRONMENT +##################################################################### + +MEDIA_SYNC__PUSH $@ diff --git a/zsh/common.zsh b/zsh/common.zsh deleted file mode 100644 index 6c1f5f9..0000000 --- a/zsh/common.zsh +++ /dev/null @@ -1,31 +0,0 @@ -##################################################################### - -source ${0:a:h}/../global/common.zsh -source ${0:a:h}/utils/utils.module.zsh \ - || { [ $DONT_EXIT ] && return 1 || exit 1; } - -##################################################################### - -__GET_ENV_FILES() { ls $SCWRYPTS_CONFIG_PATH/env | sort -r } -[ ! "$(__GET_ENV_FILES)" ] && { - cp $__ENV_TEMPLATE "$SCWRYPTS_CONFIG_PATH/env/dev" - cp $__ENV_TEMPLATE "$SCWRYPTS_CONFIG_PATH/env/local" - cp $__ENV_TEMPLATE "$SCWRYPTS_CONFIG_PATH/env/prod" -} - -__GET_ENV_NAMES() { __GET_ENV_FILES | sed 's/.*\///'; } -__GET_ENV_FILE() { echo "$SCWRYPTS_CONFIG_PATH/env/$1"; } - -__SELECT_OR_CREATE_ENV() { __GET_ENV_NAMES | __FZF_TAIL 'select/create an environment'; } -__SELECT_ENV() { __GET_ENV_NAMES | __FZF 'select an environment'; } - -##################################################################### - -__GET_AVAILABLE_SCRIPTS() { - cd $SCWRYPTS_ROOT; - find . -mindepth 2 -type f -executable \ - | grep -v '\.git' \ - | grep -v 'node_modules' \ - | sed 's/^\.\///; s/\.[^.]*$//' \ - ; -} diff --git a/zsh/config/settings b/zsh/config/settings deleted file mode 100755 index 1fe07f6..0000000 --- a/zsh/config/settings +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### -__EDIT "$CONFIG__USER_SETTINGS" diff --git a/zsh/config/update b/zsh/config/update deleted file mode 100755 index c0f971f..0000000 --- a/zsh/config/update +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### - -__STATUS 'updating all config files and links' -__RUN_SCWRYPT zsh/config/symlinks || exit 1 -__RUN_SCWRYPT zsh/config/terminfo || exit 2 -__SUCCESS 'finished updating config files and links' diff --git a/zsh/db/common.zsh b/zsh/db/common.zsh deleted file mode 100644 index 146325e..0000000 --- a/zsh/db/common.zsh +++ /dev/null @@ -1,24 +0,0 @@ -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### - - -GET_POSTGRES_LOGIN_ARGS() { - while [[ $# -gt 0 ]] - do - case $1 in - --host | -h ) _HOST="$2"; shift 2 ;; - --name | -d ) _NAME="$2"; shift 2 ;; - --pass | -w ) _PASS="$2"; shift 2 ;; - --port | -p ) _PORT="$2"; shift 2 ;; - --user | -U ) _USER="$2"; shift 2 ;; - * ) shift 1 ;; - esac - done - - [ ! $_HOST ] && _HOST=127.0.0.1 - [ ! $_NAME ] && _NAME=postgres - [ ! $_PORT ] && _PORT=5432 - [ ! $_USER ] && _USER=postgres -} diff --git a/zsh/db/interactive/common.zsh b/zsh/db/interactive/common.zsh deleted file mode 100644 index 1191a72..0000000 --- a/zsh/db/interactive/common.zsh +++ /dev/null @@ -1,4 +0,0 @@ -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### diff --git a/zsh/db/interactive/postgres b/zsh/db/interactive/postgres deleted file mode 100755 index 9b310dd..0000000 --- a/zsh/db/interactive/postgres +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=( - pgcli -) -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### - -_LOGIN_POSTGRES() { - local _HOST _NAME _PASS _PORT _USER - GET_POSTGRES_LOGIN_ARGS $@ - - local DATA_DIR="$SCWRYPTS_DATA_PATH/db/$_HOST" - [ ! -d $DATA_DIR ] && mkdir -p $DATA_DIR - cd $DATA_DIR - - __STATUS "performing login : $_USER@$_HOST:$_PORT/$_NAME" - __STATUS "working directory : $DATA_DIR" - - PGPASSWORD="$_PASS" pgcli \ - --host $_HOST \ - --port $_PORT \ - --user $_USER \ - --dbname $_NAME \ - ; -} - -##################################################################### -_LOGIN_POSTGRES $@ diff --git a/zsh/db/postgres/common.zsh b/zsh/db/postgres/common.zsh deleted file mode 100644 index 1191a72..0000000 --- a/zsh/db/postgres/common.zsh +++ /dev/null @@ -1,4 +0,0 @@ -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### diff --git a/zsh/db/postgres/interactive-pgcli b/zsh/db/postgres/interactive-pgcli new file mode 100755 index 0000000..fda3987 --- /dev/null +++ b/zsh/db/postgres/interactive-pgcli @@ -0,0 +1,9 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use db/postgres + +CHECK_ENVIRONMENT +##################################################################### +POSTGRES__LOGIN_INTERACTIVE $@ diff --git a/zsh/db/postgres/pg_dump b/zsh/db/postgres/pg_dump index 9a62e55..4f09fc4 100755 --- a/zsh/db/postgres/pg_dump +++ b/zsh/db/postgres/pg_dump @@ -1,44 +1,9 @@ #!/bin/zsh -_DEPENDENCIES+=( - pg_dump -) -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use db/postgres + +CHECK_ENVIRONMENT ##################################################################### - -BACKUP_POSTGRES() { - local _HOST _NAME _PASS _PORT _USER - GET_POSTGRES_LOGIN_ARGS $@ - - local DATA_DIR="$SCWRYPTS_DATA_PATH/db/$_HOST/$_NAME/pg_dump" - [ ! -d $DATA_DIR ] && mkdir -p $DATA_DIR - cd $DATA_DIR - - local OUTPUT_FILE="$DATA_DIR/$_NAME.dump" - [ -f $OUTPUT_FILE ] && { - local BACKUP_COUNT=$(ls "$DATA_DIR/$_NAME."*".dump" | wc -l) - ls "$DATA_DIR/$_NAME."*".dump" - - __INFO "discovered previous dump for '$_HOST/$_NAME'" - __INFO "backing up previous dump to '$_NAME.$BACKUP_COUNT.dump'" - - mv "$OUTPUT_FILE" "$DATA_DIR/$_NAME.$BACKUP_COUNT.dump" - } - - __STATUS "making backup of : $_USER@$_HOST:$_PORT/$_NAME" - __STATUS "output file : $OUTPUT_FILE" - - PGPASSWORD="$_PASS" pg_dump \ - --verbose \ - --format custom \ - --host "$_HOST" \ - --port "$_PORT" \ - --username "$_USER" \ - --dbname "$_NAME" \ - --file "$OUTPUT_FILE" \ - && { __SUCCESS "finished backup of '$_HOST/$_NAME'"; __SUCCESS "saved to '$OUTPUT_FILE'"; } \ - || { __ERROR "error creating backup for '$_HOST/$_NAME' (see above)"; return 1; } -} - -##################################################################### -BACKUP_POSTGRES $@ +PG_DUMP $@ diff --git a/zsh/db/postgres/pg_restore b/zsh/db/postgres/pg_restore index 885eb3d..3b37396 100755 --- a/zsh/db/postgres/pg_restore +++ b/zsh/db/postgres/pg_restore @@ -1,55 +1,9 @@ #!/bin/zsh -_DEPENDENCIES+=( - pg_dump -) -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use db/postgres + +CHECK_ENVIRONMENT ##################################################################### - -BACKUP_POSTGRES() { - local _HOST _NAME _PASS _PORT _USER - GET_POSTGRES_LOGIN_ARGS $@ - - local DATA_DIR="$SCWRYPTS_DATA_PATH/db/$_HOST/$_NAME/pg_restore" - [ ! -d $DATA_DIR ] && mkdir -p $DATA_DIR - cd $DATA_DIR - - local INPUT_FILE="$DATA_DIR/$_NAME.dump" - - [ ! -f $INPUT_FILE ] && { - local DUMP="$(dirname $DATA_DIR)/pg_dump/$_NAME.dump" - __STATUS $DUMP - ls $DUMP - - [ -f "$DUMP" ] && { - __SUCCESS "discovered previous scwrypts dump" - __SUCCESS "$DUMP" - __Yn 'restore from this backup?' && INPUT_FILE="$DUMP" - } - - [ ! -f "$INPUT_FILE" ] && { - __STATUS 'place backup in the following location:' - __STATUS "$INPUT_FILE" - } - - while [ ! -f $INPUT_FILE ]; do sleep 1; done - } - - __STATUS "backup file : $DATA_DIR" - __STATUS "database : $_USER@$_HOST:$_PORT/$_NAME" - - PGPASSWORD="$_PASS" pg_restore \ - --verbose \ - --single-transaction \ - --format custom \ - --host "$_HOST" \ - --port "$_PORT" \ - --username "$_USER" \ - --dbname "$_NAME" \ - "$INPUT_FILE" \ - && { __SUCCESS "finished restoring backup for '$_HOST/$_NAME'"; } \ - || { __ERROR "error restoring backup for '$_HOST/$_NAME' (see above)"; return 1; } -} - -##################################################################### -BACKUP_POSTGRES $@ +PG_RESTORE $@ diff --git a/zsh/db/postgres/run-sql b/zsh/db/postgres/run-sql new file mode 100755 index 0000000..0808c0a --- /dev/null +++ b/zsh/db/postgres/run-sql @@ -0,0 +1,51 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use db/postgres + +CHECK_ENVIRONMENT +##################################################################### + +RUN_SQL_POSTGRES() { + local _PASS _ARGS=() + POSTGRES__SET_LOGIN_ARGS $@ + + local INPUT_FILE="$FILENAME" + + local SQL_DIR="$SCWRYPTS_DATA_PATH/sql" + [ ! -d $SQL_DIR ] && mkdir -p $SQL_DIR + + cd $SQL_DIR + + [[ $(ls "*.sql" 2>&1 | wc -l) -eq 0 ]] && { + ERROR "you haven't made any SQL commands yet" + REMINDER "add '.sql' files here: '$SQL_DIR/'" + return 1 + } + + [ ! $INPUT_FILE ] && INPUT_FILE=$(FZF 'select a sql file to run') + [ ! $INPUT_FILE ] && ABORT + + [ ! -f "$INPUT_FILE" ] && FAIL 2 "no such sql file '$SQL_DIR/$INPUT_FILE'" + + STATUS "loading '$INPUT_FILE' preview..." + LESS "$INPUT_FILE" + + STATUS "login : $_USER@$_HOST:$_PORT/$_NAME" + STATUS "command : '$INPUT_FILE'" + + yN 'run this command?' || ABORT + + STATUS "running '$INPUT_FILE'" + + PSQL < $INPUT_FILE \ + && SUCCESS "finished running '$INPUT_FILE'" \ + || FAIL 3 "something went wrong running '$INPUT_FILE' (see above)" +} + +##################################################################### +WARNING +WARNING 'this function is in a beta state' +WARNING +RUN_SQL_POSTGRES $@ diff --git a/zsh/db/run-sql/common.zsh b/zsh/db/run-sql/common.zsh deleted file mode 100644 index 1191a72..0000000 --- a/zsh/db/run-sql/common.zsh +++ /dev/null @@ -1,4 +0,0 @@ -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### diff --git a/zsh/db/run-sql/postgres b/zsh/db/run-sql/postgres deleted file mode 100755 index 5da5727..0000000 --- a/zsh/db/run-sql/postgres +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=( - psql -) -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### - -_RUN_SQL_POSTGRES() { - local _HOST _NAME _PASS _PORT _USER INPUT_FILE - - while [[ $# -gt 0 ]] - do - case $1 in - --host | -h ) _HOST="$2"; shift 2 ;; - --name | -d ) _NAME="$2"; shift 2 ;; - --pass | -w ) _PASS="$2"; shift 2 ;; - --port | -p ) _PORT="$2"; shift 2 ;; - --user | -U ) _USER="$2"; shift 2 ;; - --file | -i ) INPUT_FILE="$2"; shift 2 ;; - * ) shift 1 ;; - esac - done - - [ ! $_HOST ] && _HOST=127.0.0.1 - [ ! $_NAME ] && _NAME=postgres - [ ! $_PORT ] && _PORT=5432 - [ ! $_USER ] && _USER=postgres - - local SQL_DIR="$SCWRYPTS_DATA_PATH/sql" - [ ! -d $SQL_DIR ] && mkdir -p $SQL_DIR - cd $SQL_DIR - - [[ $(ls "*.sql" 2>&1 | wc -l) -eq 0 ]] && { - __ERROR "you haven't made any SQL commands yet" - __REMINDER "add '.sql' files here: '$SQL_DIR/'" - exit 1 - } - - [ ! $INPUT_FILE ] && INPUT_FILE=$(\ - __FZF 'select a sql file to run' - ) - [ ! $INPUT_FILE ] && __ABORT - - [ ! -f $INPUT_FILE ] && { - __FAIL 2 "no such sql file '$SQL_DIR/$INPUT_FILE'" - } - - __STATUS "loading $INPUT_FILE preview..." - _LESS $INPUT_FILE - - __STATUS "login : $_USER@$_HOST:$_PORT/$_NAME" - __STATUS "command : ./$INPUT_FILE" - - __yN 'run this command?' || __ABORT - - __STATUS "running './$INPUT_FILE'" - PGPASSWORD="$_PASS" psql \ - -h $_HOST \ - -p $_PORT \ - -U $_USER \ - -d $_NAME \ - < $INPUT_FILE \ - && __SUCCESS "finished running './$INPUT_FILE'" \ - || __FAIL 3 "something went wrong running './$INPUT_FILE' (see above)" -} - -##################################################################### -__WARNING -__WARNING 'this function is in a beta state' -__WARNING -_RUN_SQL_POSTGRES $@ diff --git a/zsh/docker/cleanup b/zsh/docker/cleanup new file mode 100755 index 0000000..1889924 --- /dev/null +++ b/zsh/docker/cleanup @@ -0,0 +1,19 @@ +#!/bin/zsh +DEPENDENCIES+=(docker) +REQUIRED_ENV+=() + +CHECK_ENVIRONMENT +##################################################################### + +DOCKER_CLEAN() { + WARNING 'this will prune all docker resources from the current machine' + WARNING 'pruned resources are PERMANENTLY DELETED' + yN 'continue?' || return 1 + + SUCCESS "CONTAINER : $(docker container prune -f 2>/dev/null | tail -n 1)" + SUCCESS "IMAGE : $(docker image prune -f 2>/dev/null | tail -n 1)" + SUCCESS "VOLUME : $(docker volume prune -f 2>/dev/null | tail -n 1)" +} + +##################################################################### +DOCKER_CLEAN $@ diff --git a/zsh/git/common.zsh b/zsh/git/common.zsh deleted file mode 100644 index 75a9cfa..0000000 --- a/zsh/git/common.zsh +++ /dev/null @@ -1,6 +0,0 @@ -_DEPENDENCIES+=( - git -) -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### diff --git a/zsh/git/package/build b/zsh/git/package/build deleted file mode 100755 index 15b64f2..0000000 --- a/zsh/git/package/build +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### -__RUN_SCWRYPT zsh/git/package/install -- --only-build $@ diff --git a/zsh/git/package/download b/zsh/git/package/download deleted file mode 100755 index 5f9f293..0000000 --- a/zsh/git/package/download +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### -__RUN_SCWRYPT zsh/git/package/install -- --only-pull $@ diff --git a/zsh/git/package/update b/zsh/git/package/update deleted file mode 100755 index d05939e..0000000 --- a/zsh/git/package/update +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### -__RUN_SCWRYPT zsh/git/package/install -- --update $@ diff --git a/zsh/hello-world b/zsh/hello-world index e70ad43..46193d6 100755 --- a/zsh/hello-world +++ b/zsh/hello-world @@ -1,7 +1,8 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +CHECK_ENVIRONMENT ##################################################################### -__SUCCESS 'hello world!' +SUCCESS 'hello world!' diff --git a/zsh/i3/common.zsh b/zsh/i3/common.zsh deleted file mode 100644 index dd798e5..0000000 --- a/zsh/i3/common.zsh +++ /dev/null @@ -1,14 +0,0 @@ -_DEPENDENCIES+=( - i3 - i3-msg -) -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### - -[ ! $DISPLAY ] && export DISPLAY=:0 - -_NOTIFY() { - __CHECK_DEPENDENCY notify-send || return 0 - notify-send "SCWRYPTS $SCWRYPT_NAME" $@ -} diff --git a/zsh/latex/build-pdf b/zsh/latex/build-pdf deleted file mode 100755 index 0902495..0000000 --- a/zsh/latex/build-pdf +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=( - pdflatex - rg -) -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### - -PDFLATEX() { - [ ! $1 ] && __FAIL 1 'must provide filename' - local FILENAME=$(GET_MAIN_LATEX_FILENAME "$1") - - local ARGS=(-interaction=nonstopmode) - ARGS+=("$FILENAME") - - cd "$(dirname $FILENAME)" - - __STATUS 'running compile (1/2)' - pdflatex ${ARGS[@]} \ - || __FAIL 2 'first compile failed (see above)' - - __STATUS 'running compile (2/2)' - pdflatex ${ARGS[@]} >/dev/null 2>&1 \ - || __FAIL 3 'second compile failed :c' - - __SUCCESS "created '$(echo $FILENAME | sed 's/\.[^.]*$/.pdf/')'" -} - -##################################################################### -PDFLATEX $@ diff --git a/zsh/latex/common.zsh b/zsh/latex/common.zsh deleted file mode 100644 index d91aa77..0000000 --- a/zsh/latex/common.zsh +++ /dev/null @@ -1,34 +0,0 @@ -_DEPENDENCIES+=( - rg -) -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### - -GET_MAIN_LATEX_FILENAME() { - local FILENAME=$(__GET_PATH_TO_RELATIVE_ARGUMENT "$1") - local DIRNAME="$FILENAME" - - for _ in {1..3} - do - CHECK_IS_MAIN_LATEX_FILE && return 0 - DIRNAME="$(dirname "$FILENAME")" - __STATUS "checking '$DIRNAME'" - [[ $DIRNAME =~ ^$HOME$ ]] && break - FILENAME=$( - rg -l --max-depth 1 'documentclass' "$DIRNAME/" \ - | grep '\.tex$' \ - | head -n1 \ - ) - __STATUS "here is '$FILENAME'" - done - - __WARNING 'unable to find documentclass; pdflatex will probably fail' - echo "$1" -} - -CHECK_IS_MAIN_LATEX_FILE() { - [ ! $FILENAME ] && return 1 - grep -q 'documentclass' $FILENAME 2>/dev/null && echo $FILENAME || return 3 -} - diff --git a/zsh/latex/get-pdf b/zsh/latex/get-pdf deleted file mode 100755 index 42a776c..0000000 --- a/zsh/latex/get-pdf +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### - -GET_PDF() { - local FILENAME=$(GET_MAIN_LATEX_FILENAME "$1" | sed 's/\.[^.]*$/.pdf/') - [ $FILENAME ] && [ -f $FILENAME ] || __FAIL 1 "no compiled pdf found for '$1'; have you run 'build-pdf'?" - __SUCCESS 'found main pdf' - echo $FILENAME -} - -##################################################################### -GET_PDF $@ diff --git a/zsh/lib/cloud/aws/cli.module.zsh b/zsh/lib/cloud/aws/cli.module.zsh new file mode 100644 index 0000000..71ec4fa --- /dev/null +++ b/zsh/lib/cloud/aws/cli.module.zsh @@ -0,0 +1,21 @@ +##################################################################### + +DEPENDENCIES+=( + aws +) + +REQUIRED_ENV+=( + AWS_ACCOUNT + AWS_PROFILE + AWS_REGION +) + +##################################################################### + +AWS() { + aws \ + --profile $AWS_PROFILE \ + --region $AWS_REGION \ + --output json \ + $@ +} diff --git a/zsh/lib/cloud/aws/ecr.module.zsh b/zsh/lib/cloud/aws/ecr.module.zsh new file mode 100644 index 0000000..0d3b72c --- /dev/null +++ b/zsh/lib/cloud/aws/ecr.module.zsh @@ -0,0 +1,28 @@ +##################################################################### + +DEPENDENCIES+=( + docker +) + +REQUIRED_ENV+=( + AWS_ACCOUNT + AWS_REGION +) + +use cloud/aws/cli + +##################################################################### + +ECR_LOGIN() { + STATUS "performing AWS ECR docker login" + AWS ecr get-login-password \ + | docker login \ + --username AWS \ + --password-stdin \ + "$AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com" \ + && SUCCESS "authenticated docker for '$AWS_ACCOUNT' in '$AWS_REGION'" \ + || { + ERROR "unable to authenticate docker for '$AWS_ACCOUNT' in '$AWS_REGION'" + return 1 + } +} diff --git a/zsh/lib/cloud/aws/eks.module.zsh b/zsh/lib/cloud/aws/eks.module.zsh new file mode 100644 index 0000000..8c4f195 --- /dev/null +++ b/zsh/lib/cloud/aws/eks.module.zsh @@ -0,0 +1,60 @@ +##################################################################### + +DEPENDENCIES+=( + kubectl +) + +REQUIRED_ENV+=( + AWS_ACCOUNT + AWS_REGION +) + +use cloud/aws/cli + +##################################################################### + +EKS_CLUSTER_LOGIN() { + local USAGE=" + usage: [...options...] + + options + -c, --cluster-name (optional) login a specific cluster + + + Interactively sets the default kubeconfig to match the selected + cluster in EKS. Also creates the kubeconfig entry if it does not + already exist. + " + + local CLUSTER_NAME + + while [[ $# -gt 0 ]] + do + case $1 in + -c | --cluster-name ) CLUSTER_NAME="$2"; shift 1 ;; + + * ) [ ! $APPLICATION ] && APPLICATION="$1" \ + || ERROR "extra positional argument '$1'" + ;; + esac + shift 1 + done + + [ ! $CLUSTER_NAME ] && CLUSTER_NAME=$(\ + AWS eks list-clusters \ + | jq -r '.[] | .[]' \ + | FZF 'select a cluster' + ) + + [ ! $CLUSTER_NAME ] && ERROR 'must select a valid cluster or use -c flag' + + CHECK_ERRORS + + ########################################## + + STATUS 'creating / updating kubeconfig for EKS cluster' + STATUS "updating kubeconfig for '$CLUSTER_NAME'" + AWS eks update-kubeconfig --name $CLUSTER_NAME \ + && SUCCESS "kubeconfig updated with '$CLUSTER_NAME'" \ + || ERROR "failed to update kubeconfig; do you have permissions to access '$CLUSTER_NAME'?" +} diff --git a/zsh/aws/rds/common.zsh b/zsh/lib/cloud/aws/rds.module.zsh similarity index 59% rename from zsh/aws/rds/common.zsh rename to zsh/lib/cloud/aws/rds.module.zsh index c87857f..ed929ef 100644 --- a/zsh/aws/rds/common.zsh +++ b/zsh/lib/cloud/aws/rds.module.zsh @@ -1,9 +1,48 @@ -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh ##################################################################### -GET_DATABASE_CREDENTIALS() { +DEPENDENCIES+=( + docker +) + +REQUIRED_ENV+=( + AWS_ACCOUNT + AWS_REGION +) + +use cloud/aws/cli + +##################################################################### + +RDS__SELECT_DATABASE() { + local DATABASES=$(_RDS__GET_AVAILABLE_DATABASES) + [ ! $DATABASES ] && FAIL 1 'no databases available' + + local ID=$(\ + echo $DATABASES | jq -r '.instance + " @ " + .cluster' \ + | FZF 'select a database (instance@cluster)' \ + ) + [ ! $ID ] && ABORT + + local INSTANCE=$(echo $ID | sed 's/ @ .*$//') + local CLUSTER=$(echo $ID | sed 's/^.* @ //') + + echo $DATABASES | jq "select (.instance == \"$INSTANCE\" and .cluster == \"$CLUSTER\")" +} + +_RDS__GET_AVAILABLE_DATABASES() { + AWS rds describe-db-instances \ + | jq -r '.[] | .[] | { + instance: .DBInstanceIdentifier, + cluster: .DBClusterIdentifier, + type: .Engine, + host: .Endpoint.Address, + port: .Endpoint.Port, + user: .MasterUsername, + database: .DBName + }' +} + +RDS__GET_DATABASE_CREDENTIALS() { local PRINT_PASSWORD=0 local ERRORS=0 @@ -12,22 +51,22 @@ GET_DATABASE_CREDENTIALS() { case $1 in --print-password ) PRINT_PASSWORD=1 ;; * ) - __WARNING "unrecognized argument $1" + WARNING "unrecognized argument $1" ERRORS+=1 ;; esac shift 1 done - __ERROR_CHECK + CHECK_ERRORS ########################################## - local DATABASE=$(SELECT_DATABASE) - [ ! $DATABASE ] && __ABORT + local DATABASE=$(RDS__SELECT_DATABASE) + [ ! $DATABASE ] && ABORT DB_HOST="$(echo $DATABASE | jq -r '.host')" - [ ! $DB_HOST ] && { __ERROR 'unable to find host'; return 2; } + [ ! $DB_HOST ] && { ERROR 'unable to find host'; return 2; } DB_PORT="$(echo $DATABASE | jq -r '.port')" [ ! $DB_PORT ] && DB_PORT=5432 @@ -37,37 +76,37 @@ GET_DATABASE_CREDENTIALS() { local AUTH_METHOD=$(\ echo "iam\nsecretsmanager\nuser-input" \ - | __FZF 'select an authentication method' \ + | FZF 'select an authentication method' \ ) - [ ! $AUTH_METHOD ] && __ABORT + [ ! $AUTH_METHOD ] && ABORT case $AUTH_METHOD in - iam ) GET_AUTH__IAM ;; - secretsmanager ) GET_AUTH__SECRETSMANAGER ;; - user-input ) GET_AUTH__USER_INPUT ;; + iam ) _RDS_AUTH__iam ;; + secretsmanager ) _RDS_AUTH__secretsmanager ;; + user-input ) _RDS_AUTH__userinput ;; esac - __STATUS - __STATUS "host : $DB_HOST" - __STATUS "type : $DB_TYPE" - __STATUS "port : $DB_PORT" - __STATUS "database : $DB_NAME" - __STATUS "username : $DB_USER" - [[ $PRINT_PASSWORD -eq 1 ]] && __STATUS "password : $DB_PASS" - __STATUS + STATUS + STATUS "host : $DB_HOST" + STATUS "type : $DB_TYPE" + STATUS "port : $DB_PORT" + STATUS "database : $DB_NAME" + STATUS "username : $DB_USER" + [[ $PRINT_PASSWORD -eq 1 ]] && STATUS "password : $DB_PASS" + STATUS } -GET_AUTH__IAM() { +_RDS_AUTH__iam() { DB_PASS=$(\ - _AWS rds generate-db-auth-token \ + AWS rds generate-db-auth-token \ --hostname $DB_HOST \ --port $DB_PORT \ --username $DB_USER \ ) } -GET_AUTH__SECRETSMANAGER() { - local CREDENTIALS=$(GET_SECRETSMANAGER_CREDENTIALS) +_RDS_AUTH__secretsmanager() { + local CREDENTIALS=$(_RDS__GET_SECRETSMANAGER_CREDENTIALS) echo $CREDENTIALS | jq -e '.pass' >/dev/null 2>&1 \ && DB_PASS="'$(echo $CREDENTIALS | jq -r '.pass' | sed "s/'/'\"'\"'/g")'" @@ -87,44 +126,15 @@ GET_AUTH__SECRETSMANAGER() { && DB_NAME=$(echo $CREDENTIALS | jq -r '.dbname') } -GET_SECRETSMANAGER_CREDENTIALS() { +_RDS__GET_SECRETSMANAGER_CREDENTIALS() { local ID=$(\ - _AWS secretsmanager list-secrets \ + AWS secretsmanager list-secrets \ | jq -r '.[] | .[] | .Name' \ - | __FZF 'select a secret' \ + | FZF 'select a secret' \ ) [ ! $ID ] && return 1 - _AWS secretsmanager get-secret-value --secret-id "$ID" \ + AWS secretsmanager get-secret-value --secret-id "$ID" \ | jq -r '.SecretString' | jq } -SELECT_DATABASE() { - local DATABASES=$(GET_AVAILABLE_DATABASES) - [ ! $DATABASES ] && __FAIL 1 'no databases available' - - local ID=$(\ - echo $DATABASES | jq -r '.instance + " @ " + .cluster' \ - | __FZF 'select a database (instance@cluster)' \ - ) - [ ! $ID ] && __ABORT - - local INSTANCE=$(echo $ID | sed 's/ @ .*$//') - local CLUSTER=$(echo $ID | sed 's/^.* @ //') - - echo $DATABASES | jq "select (.instance == \"$INSTANCE\" and .cluster == \"$CLUSTER\")" -} - -GET_AVAILABLE_DATABASES() { - _AWS rds describe-db-instances \ - | jq -r '.[] | .[] | { - instance: .DBInstanceIdentifier, - cluster: .DBClusterIdentifier, - type: .Engine, - host: .Endpoint.Address, - port: .Endpoint.Port, - user: .MasterUsername, - database: .DBName - }' -} - diff --git a/zsh/lib/cloud/media-sync.module.zsh b/zsh/lib/cloud/media-sync.module.zsh new file mode 100644 index 0000000..438fe2e --- /dev/null +++ b/zsh/lib/cloud/media-sync.module.zsh @@ -0,0 +1,69 @@ +##################################################################### + +DEPENDENCIES+=() + +REQUIRED_ENV+=( + MEDIA_SYNC__TARGETS + MEDIA_SYNC__S3_BUCKET +) + +use cloud/aws/cli + +##################################################################### + +MEDIA_SYNC__PUSH() { + local FLAGS=($@) + local FAILED_COUNT=0 + + STATUS 'starting media upload to s3' + + local TARGET + for TARGET in ${MEDIA_SYNC__TARGETS[@]} + do + _MEDIA_SYNC push $TARGET $FLAGS || ((FAILED_COUNT+=1)) + done + + [[ $FAILED_COUNT -eq 0 ]] \ + && SUCCESS 's3 media files now up-to-date' \ + || FAIL $FAILED_COUNT 'unable to upload one or more targets' \ + ; +} + +MEDIA_SYNC__PULL() { + local FLAGS=($@) + local FAILED_COUNT=0 + + STATUS 'starting media download from s3' + + local TARGET + for TARGET in ${MEDIA_SYNC__TARGETS[@]} + do + _MEDIA_SYNC pull $TARGET $FLAGS || ((FAILED_COUNT+=1)) + done + + [[ $FAILED_COUNT -eq 0 ]] \ + && SUCCESS 'local media files now up-to-date' \ + || FAIL $FAILED_COUNT 'unable to download one or more targets' \ + ; +} + +_MEDIA_SYNC() { + local ACTION="$1" + local REMOTE_TARGET="s3://$MEDIA_SYNC__S3_BUCKET/$2" + local LOCAL_TARGET="$HOME/$2" + + local A B + case $ACTION in + push ) A="$LOCAL_TARGET"; B="$REMOTE_TARGET" ;; + pull ) A="$REMOTE_TARGET"; B="$LOCAL_TARGET" ;; + + * ) ERROR "unknown action '$1'"; return 1 ;; + esac + + local FLAGS=(${@:3}) + + STATUS "${ACTION}ing $2" + AWS s3 sync $A $B $FLAGS \ + && SUCCESS "$2 up-to-date" \ + || { ERROR "unable to sync $2 (see above)"; return 1; } +} diff --git a/zsh/lib/config.group.zsh b/zsh/lib/config.group.zsh new file mode 100644 index 0000000..fd5329b --- /dev/null +++ b/zsh/lib/config.group.zsh @@ -0,0 +1,15 @@ +export SCWRYPTS_ROOT__scwrypts="$SCWRYPTS_ROOT" +export SCWRYPTS_LIBRARY_ROOT__scwrypts="$SCWRYPTS_ROOT/zsh/lib" +export SCWRYPTS_COLOR__scwrypts='\033[0;32m' + +export SCWRYPTS_ENV_PATH__scwrypts="$SCWRYPTS_CONFIG_PATH/scwrypts/env" +[ ! -d "$SCWRYPTS_ENV_PATH__scwrypts" ] && mkdir -p "$SCWRYPTS_ENV_PATH__scwrypts" + +export SCWRYPTS_ENV_TEMPLATE__scwrypts="$SCWRYPTS_ROOT__scwrypts/.env.template" +export SCWRYPTS_ENV_TEMPLATE_DESCRIPTIONS__scwrypts="$SCWRYPTS_ROOT__scwrypts/.env.template.descriptions" + +export SCWRYPTS_VIRTUALENV_PATH__scwrypts="$SCWRYPTS_DATA_PATH/virtualenv" +[ ! -d "$SCWRYPTS_VIRTUALENV_PATH__scwrypts" ] && mkdir -p "$SCWRYPTS_VIRTUALENV_PATH__scwrypts" + +export SCWRYPTS_PREFERRED_PYTHON_VERSIONS__scwrypts=(3.11 3.10 3.9) +export SCWRYPTS_NODE_VERSION__scwrypts=18.0.0 diff --git a/zsh/lib/config.user.zsh b/zsh/lib/config.user.zsh new file mode 100644 index 0000000..72c8c91 --- /dev/null +++ b/zsh/lib/config.user.zsh @@ -0,0 +1,15 @@ +# +# configuration options for scwrypts +# + +SCWRYPTS_GROUPS=() + +SCWRYPTS_CONFIG_PATH="$HOME/.config/scwrypts" +SCWRYPTS_DATA_PATH="$HOME/.local/share/scwrypts" + +SCWRYPTS_SHORTCUT='' # CTRL + SPACE +SCWRYPTS_ENV_SHORTCUT='' # CTRL + / + +SCWRYPTS_ENV_PATH="$SCWRYPTS_CONFIG_PATH/environments" +SCWRYPTS_LOG_PATH="$SCWRYPTS_DATA_PATH/logs" +SCWRYPTS_OUTPUT_PATH="$SCWRYPTS_DATA_PATH/output" diff --git a/zsh/lib/config.zsh b/zsh/lib/config.zsh new file mode 100644 index 0000000..ebeb82c --- /dev/null +++ b/zsh/lib/config.zsh @@ -0,0 +1,42 @@ +[[ $__SCWRYPT -eq 1 ]] && return 0 +##################################################################### + +[ ! $SCWRYPTS_ROOT ] \ + && SCWRYPTS_ROOT="$(cd $(dirname "${0:a:h}"); git rev-parse --show-toplevel)" + +##################################################################### + +DEFAULT_CONFIG="$SCWRYPTS_ROOT/zsh/lib/config.user.zsh" +source "$DEFAULT_CONFIG" + +USER_CONFIG_OVERRIDES="$SCWRYPTS_CONFIG_PATH/config.zsh" +[ ! -f "$USER_CONFIG_OVERRIDES" ] && { + mkdir -p $(dirname "$USER_CONFIG_OVERRIDES") + cp "$DEFAULT_CONFIG" "$USER_CONFIG_OVERRIDES" +} +source "$USER_CONFIG_OVERRIDES" + +[ ! -d $SCWRYPTS_CONFIG_PATH ] && mkdir -p $SCWRYPTS_CONFIG_PATH +[ ! -d $SCWRYPTS_DATA_PATH ] && mkdir -p $SCWRYPTS_DATA_PATH +[ ! -d $SCWRYPTS_ENV_PATH ] && mkdir -p $SCWRYPTS_ENV_PATH +[ ! -d $SCWRYPTS_LOG_PATH ] && mkdir -p $SCWRYPTS_LOG_PATH +[ ! -d $SCWRYPTS_OUTPUT_PATH ] && mkdir -p $SCWRYPTS_OUTPUT_PATH + +export \ + SCWRYPTS_GROUPS \ + SCWRYPTS_CONFIG_PATH \ + SCWRYPTS_DATA_PATH \ + SCWRYPTS_SHORTCUT \ + SCWRYPTS_ENV_SHORTCUT \ + SCWRYPTS_LOG_PATH \ + SCWRYPTS_OUTPUT_PATH \ + ; + +SCWRYPTS_GROUPS+=(scwrypts) # 'scwrypts' group is required! +SCWRYPTS_GROUPS=($(echo $SCWRYPTS_GROUPS | sed 's/\s\+/\n/g' | sort -u)) + +source "$SCWRYPTS_ROOT/zsh/lib/config.group.zsh" \ + || FAIL 69 'failed to set up scwrypts group; aborting' + +##################################################################### +__SCWRYPT=1 # arbitrary; indicates currently inside a scwrypt diff --git a/zsh/lib/db/postgres.module.zsh b/zsh/lib/db/postgres.module.zsh new file mode 100644 index 0000000..77b32d0 --- /dev/null +++ b/zsh/lib/db/postgres.module.zsh @@ -0,0 +1,158 @@ +##################################################################### + +DEPENDENCIES+=( + pg_dump + pg_restore + psql + pgcli +) + +REQUIRED_ENV+=() + +##################################################################### + +PSQL() { + [[ ${#ARGS[@]} -eq 0 ]] && POSTGRES__SET_LOGIN_ARGS $@ + + eval PGPASSWORD=$_PASS psql ${_ARGS[@]} +} + +##################################################################### + +PG_DUMP() { + local _HOST _NAME _PORT _USER _FILE + local DATA_DIR _PASS _ARGS=() + POSTGRES__SET_LOGIN_ARGS --verbose $@ + + local OUTPUT_FILE="$DATA_DIR/backup.$(date '+%Y-%m-%d.%H-%M')" + + STATUS " + making backup of : $_USER@$_HOST:$_PORT/$_NAME + + (compressed) : '$OUTPUT_FILE.dump' + (safe-raw) : '$OUTPUT_FILE.sql' + (raw) : '$OUTPUT_FILE.raw.sql' + " + + : \ + && STATUS "creating compressed backup..." \ + && eval PGPASSWORD=$_PASS pg_dump ${_ARGS[@]} --format custom --file "$OUTPUT_FILE.dump" \ + && SUCCESS "completed compressed backup" \ + && STATUS "creating raw backup..." \ + && eval PGPASSWORD=$_PASS pg_dump ${_ARGS[@]} > "$OUTPUT_FILE.raw.sql" \ + && SUCCESS "completed raw backup" \ + && STATUS "creating single-transaction raw backup..." \ + && { echo "BEGIN;"; cat "$OUTPUT_FILE.raw.sql"; echo "END;" } > "$OUTPUT_FILE.sql" \ + && SUCCESS "completed single-transaction raw backup" \ + || { ERROR "error creating backup for '$_HOST/$_NAME' (see above)"; return 1; } +} + +##################################################################### + +PG_RESTORE() { + local _HOST _NAME _PORT _USER + local _PASS _ARGS=() + local _FILE + POSTGRES__SET_LOGIN_ARGS $@ + + local INPUT_FILE=$(find "$DATA_DIR"/backup.* -type f | FZF 'select database file to restore') + + [ $INPUT_FILE ] && [ -f "$INPUT_FILE" ] || { + ERROR 'no file selected or missing backup file; aborting' + REMINDER " + backups must be *.sql or *.dump files starting with the prefix 'backup.' + in the following directory: + + '$DATA_DIR' + " + return 1 + } + + local RAW=1 + [[ $INPUT_FILE =~ \\.dump$ ]] && RAW=0 + + STATUS " + loading backup for : $_USER@$_HOST:$_PORT/$_NAME + + file : '$INPUT_FILE' + " + + local EXIT_CODE + [[ $RAW -eq 1 ]] && { + REMINDER " + loading a backup from a raw sql dump may result in data loss + + make sure your database is ready to accept the database file! + " + + yN 'continue?' || ABORT + + PSQL < "$INPUT_FILE" + EXIT_CODE=$? + } + + [[ $RAW -eq 0 ]] && { + PGPASSWORD="$_PASS" pg_restore ${_ARGS[@]} \ + --verbose \ + --format custom \ + --single-transaction \ + "$INPUT_FILE" + EXIT_CODE=$? + } + + [[ $EXIT_CODE -eq 0 ]] \ + && SUCCESS "finished restoring backup for '$_HOST/$_NAME'" \ + || ERROR "error restoring backup for '$_HOST/$_NAME' (see above)" \ + ; + + return $EXIT_CODE +} + +##################################################################### + +POSTGRES__LOGIN_INTERACTIVE() { + local _PASS _ARGS=() + POSTGRES__SET_LOGIN_ARGS $@ + + STATUS "performing login : $_USER@$_HOST:$_PORT/$_NAME" + STATUS "working directory : $DATA_DIR" + + eval PGPASSWORD=$_PASS pgcli ${_ARGS[@]} +} + +##################################################################### + +POSTGRES__SET_LOGIN_ARGS() { + while [[ $# -gt 0 ]] + do + case $1 in + --host ) _ARGS+=(-h $2); _HOST="$2"; shift 1 ;; + --name ) _ARGS+=(-d $2); _NAME="$2"; shift 1 ;; + --port ) _ARGS+=(-p $2); _PORT="$2"; shift 1 ;; + --user ) _ARGS+=(-U $2); _USER="$2"; shift 1 ;; + + --pass ) _PASS="$2"; shift 1 ;; + + --file ) _FILE="$2"; shift 1 ;; + + * ) _ARGS+=($1) ;; + esac + shift 1 + done + + [ $_FILE ] && [ ! -f "$_FILE" ] && { + ERROR "no such file '$_FILE'" + exit 1 + } + + [ $_HOST ] && [ $_NAME ] \ + && DATA_DIR="$SCWRYPTS_DATA_PATH/db/$_HOST/$_NAME" \ + || DATA_DIR="$EXECUTION_DIR/temp-db" \ + ; + + [ ! -d "$DATA_DIR" ] && mkdir -p "$DATA_DIR" + cd "$DATA_DIR" + + return 0 +} + diff --git a/zsh/lib/import.driver.zsh b/zsh/lib/import.driver.zsh new file mode 100644 index 0000000..d466591 --- /dev/null +++ b/zsh/lib/import.driver.zsh @@ -0,0 +1,147 @@ +[[ $SCWRYPTS_IMPORT_DRIVER_READY -eq 1 ]] && return 0 + ################################################################### +# # +# usage: use [OPTIONS ...] zsh/module/path # +# # + ################################################################### +# # +# OPTIONS: # +# # +# -g, --group lookup library root from friendly group # +# name (requires configuration) # +# (default: scwrypts) # +# # +# -r, --library-root fully qualified path to a library root # +# # +# --check-environment check environment immediately rather than # +# wait for downstream CHECK_ENVIRONMENT call # +# # +# # +# Allows for import-style library loading in zsh. No matter what # +# scwrypt is run, this function (and required helpers) are *also* # +# loaded, ensuring that 'use' is always available in scwrypts # +# context. # +# # +# # +# Friendly group-names can be configured by setting the variable # +# 'SCWRYPTS_LIBRARY_ROOT__' to the fully qualified path # +# to the root directory of the modules library. # +# # +# # + ################################################################### + +source "${0:a:h}/config.zsh" + +use() { + local SCWRYPTS_LIBRARY SCWRYPTS_LIBRARY_ROOT SCWRYPTS_LIBRARY_GROUP + local DEFER_ENVIRONMENT_CHECK=1 + + while [[ $# -gt 0 ]] + do + case $1 in + -g | --group ) + [ $SCWRYPTS_LIBRARY_ROOT ] && ERROR 'specify only one of {(-g), (-r)}' + SCWRYPTS_LIBRARY_GROUP=$2 + shift 1 + ;; + -r | --library-root ) + [ $SCWRYPTS_LIBRARY_GROUP ] && ERROR 'specify only one of {(-g), (-r)}' + SCWRYPTS_LIBRARY_ROOT=$2 + shift 1 + ;; + --check-environment ) + DEFER_ENVIRONMENT_CHECK=0 + ;; + * ) + [ ! $SCWRYPTS_LIBRARY ] \ + && SCWRYPTS_LIBRARY=$1 \ + || ERROR 'too many arguments; expected exactly 1 argument' \ + + ;; + esac + shift 1 + done + + [ ! $SCWRYPTS_LIBRARY ] && ERROR 'no library specified for import' + + : \ + && [ ! $SCWRYPTS_LIBRARY_GROUP ] \ + && [ ! $SCWRYPTS_LIBRARY_ROOT ] \ + && SCWRYPTS_LIBRARY_GROUP=scwrypts + + [ ! $SCWRYPTS_LIBRARY_ROOT ] && SCWRYPTS_LIBRARY_ROOT=$(GET_SCWRYPTS_LIBRARY_ROOT) + [ ! $SCWRYPTS_LIBRARY_ROOT ] && ERROR "unable to determine library root from group name '$SCWRYPTS_LIBRARY_GROUP'" + + ##################################################################### + + local LIBRARY_FILE LIBRARY_FILE_TEMP + + [ ! $LIBRARY_FILE ] \ + && LIBRARY_FILE_TEMP="$SCWRYPTS_LIBRARY_ROOT/$SCWRYPTS_LIBRARY.module.zsh" \ + && [ -f "$LIBRARY_FILE_TEMP" ] \ + && LIBRARY_FILE="$LIBRARY_FILE_TEMP" + + [ ! $LIBRARY_FILE ] \ + && LIBRARY_FILE_TEMP="$SCWRYPTS_LIBRARY_ROOT/$SCWRYPTS_LIBRARY/$(echo $SCWRYPTS_LIBRARY | sed 's/.*\///').module.zsh" \ + && [ -f "$LIBRARY_FILE_TEMP" ] \ + && LIBRARY_FILE="$LIBRARY_FILE_TEMP" \ + + [ ! $LIBRARY_FILE ] \ + && ERROR "no such library '$SCWRYPTS_LIBRARY_GROUP/$SCWRYPTS_LIBRARY'" + + ##################################################################### + + CHECK_ERRORS --no-fail || { + ((IMPORT_ERRORS+=1)) + return 1 + } + + ##################################################################### + + IS_LOADED && return 0 + + source "$LIBRARY_FILE" || { + ((IMPORT_ERRORS+=1)) + ERROR "import error for '$SCWRYPTS_LIBRARY_GROUP/$SCWRYPTS_LIBRARY'" + return 1 + } + + [[ $DEFER_ENVIRONMENT_CHECK -eq 0 ]] && { + CHECK_ENVIRONMENT || { + ((IMPORT_ERRORS+=1)) + ERROR "import error for '$SCWRYPTS_LIBRARY_GROUP/$SCWRYPTS_LIBRARY'" + return 1 + } + } + + IS_LOADED --set +} + +GET_SCWRYPTS_LIBRARY_ROOT() { + eval echo '$SCWRYPTS_LIBRARY_ROOT__'$SCWRYPTS_LIBRARY_GROUP +} + +IS_LOADED() { + local VARIABLE_NAME="SCWRYPTS_LIBRARY_LOADED__${SCWRYPTS_LIBRARY_GROUP}__$(echo $SCWRYPTS_LIBRARY | sed 's|[/-]|_|g')" + + [[ $1 =~ ^--set$ ]] \ + && eval $VARIABLE_NAME=1 \ + + [[ $(eval echo '$'$VARIABLE_NAME || echo 0) -eq 1 ]] +} + + +# temporary definitions for first load +CHECK_ERRORS() { return 0; unset -f CHECK_ERRORS; } +CHECK_ENVIRONMENT() { return 0; unset -f CHECK_ENVIRONMENT; } +ERROR() { echo $@ >&2; exit 1; } + +##################################################################### + +# ensures that zsh/utils and zsh/scwrypts/meta are always present! + +use utils +use scwrypts/meta + +##################################################################### +SCWRYPTS_IMPORT_DRIVER_READY=1 diff --git a/zsh/youtube/common.zsh b/zsh/lib/media/youtube.module.zsh similarity index 88% rename from zsh/youtube/common.zsh rename to zsh/lib/media/youtube.module.zsh index 3a81a29..528429d 100644 --- a/zsh/youtube/common.zsh +++ b/zsh/lib/media/youtube.module.zsh @@ -1,9 +1,12 @@ -_DEPENDENCIES+=( - youtube-dl +##################################################################### + +DEPENDENCIES+=( ffmpeg + youtube-dl ) -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh + +REQUIRED_ENV+=() + ##################################################################### YT__GLOBAL_ARGS=( diff --git a/zsh/lib/misc/k8s-helper.plugin.zsh b/zsh/lib/misc/k8s-helper.plugin.zsh new file mode 100644 index 0000000..38e9125 --- /dev/null +++ b/zsh/lib/misc/k8s-helper.plugin.zsh @@ -0,0 +1,26 @@ +K8s_HELPER__NAMESPACE_KEY='k8s-helper' +K8s_HELPER__REDIS_HOST=127.0.0.1 +K8s_HELPER__REDIS_PORT=6379 +K8s_HELPER__REDIS_AUTH= + +REDIS_CLI() { + local ARGS=() + [ $K8s_HELPER__REDIS_HOST ] && ARGS+=(-h $K8s_HELPER__REDIS_HOST) + [ $K8s_HELPER__REDIS_PORT ] && ARGS+=(-p $K8s_HELPER__REDIS_PORT) + [ $K8s_HELPER__REDIS_AUTH ] && ARGS+=(-a $K8s_HELPER__REDIS_AUTH --no-auth-warning) + redis-cli ${ARGS[@|} $@ +} + +K8s_HELPER__SET_NAMESPACE() { + REDIS_CLI hset $K8s_HELPER__NAMESPACE_KEY namespace $@ +} + +K8s_HELPER__GET_NAMESPACE() { + REDIS_CLI hget $K8s_HELPER__NAMESPACE_KEY namespace +} + +K8s_HELPER__KUBECTL() { + kubectl -n $(K8s_HELPER__GET_NAMESPACE) $@ +} + +alias k='K8s_HELPER__PREFIX ' diff --git a/zsh/lib/office/latex.module.zsh b/zsh/lib/office/latex.module.zsh new file mode 100644 index 0000000..d49f453 --- /dev/null +++ b/zsh/lib/office/latex.module.zsh @@ -0,0 +1,44 @@ +##################################################################### + +DEPENDENCIES+=( + rg + pdflatex +) + +REQUIRED_ENV+=() + +##################################################################### + +LATEX__GET_MAIN_FILENAME() { + local FILENAME=$(SCWRYPTS__GET_PATH_TO_RELATIVE_ARGUMENT "$1") + local DIRNAME="$FILENAME" + + for _ in {1..3} + do + CHECK_IS_MAIN_LATEX_FILE && return 0 + DIRNAME="$(dirname "$FILENAME")" + STATUS "checking '$DIRNAME'" + [[ $DIRNAME =~ ^$HOME$ ]] && break + FILENAME=$( + rg -l --max-depth 1 'documentclass' "$DIRNAME/" \ + | grep '\.tex$' \ + | head -n1 \ + ) + STATUS "here is '$FILENAME'" + done + + WARNING 'unable to find documentclass; pdflatex will probably fail' + echo "$1" +} + +LATEX__CHECK_IS_MAIN_FILE() { + [ ! $FILENAME ] && return 1 + grep -q 'documentclass' $FILENAME 2>/dev/null && echo $FILENAME || return 3 +} + +LATEX__GET_PDF() { + local FILENAME=$(LATEX__GET_MAIN_FILENAME "$1" | sed 's/\.[^.]*$/.pdf/') + [ $FILENAME ] && [ -f $FILENAME ] || FAIL 1 "no compiled pdf found for '$1'; have you run 'build-pdf'?" + SUCCESS 'found main pdf' + echo $FILENAME +} diff --git a/zsh/lib/office/memo.module.zsh b/zsh/lib/office/memo.module.zsh new file mode 100644 index 0000000..df7a381 --- /dev/null +++ b/zsh/lib/office/memo.module.zsh @@ -0,0 +1,15 @@ +##################################################################### + +DEPENDENCIES+=() +REQUIRED_ENV+=() + +##################################################################### + +set +o noglob + +MEMO__FILETYPE=md +MEMO__DIR="$SCWRYPTS_DATA_PATH/memo" + +[ ! -d $MEMO__DIR ] && mkdir -p $MEMO__DIR + +MEMO__LIST_ALL() { ls $MEMO__DIR | sed "s/\.$MEMO__FILETYPE$//" | sort; } diff --git a/zsh/lib/redis/redis.module.zsh b/zsh/lib/redis/redis.module.zsh new file mode 100644 index 0000000..fa4d8a7 --- /dev/null +++ b/zsh/lib/redis/redis.module.zsh @@ -0,0 +1,43 @@ +##################################################################### + +DEPENDENCIES+=( + redis-cli +) + +REQUIRED_ENV+=() + +##################################################################### + +REDIS() { + [[ ${#ARGS[@]} -eq 0 ]] && REDIS__SET_LOGIN_ARGS $@ + + redis-cli ${#ARGS[@]} +} + + +REDIS__SET_LOGIN_ARGS() { + while [[ $# -gt 0 ]] + do + case $1 in + --host ) _ARGS+=(-h $2); _HOST="$2"; shift 1 ;; + --port ) _ARGS+=(-p $2); _PORT="$2"; shift 1 ;; + --pass ) _ARGS+=(-a $2); _PASS="$2"; shift 1 ;; + + --file ) _FILE="$2"; shift 1 ;; + + * ) _ARGS+=($1) ;; + esac + shift 1 + done + + [ $_FILE ] && [ ! -f "$_FILE" ] && { + ERROR "no such file '$_FILE'" + exit 1 + } + + return 0 +} + +REDIS__ENABLED() { + REDIS ping 2>&1 | grep -qi pong +} diff --git a/zsh/lib/scwrypts/environment-files.module.zsh b/zsh/lib/scwrypts/environment-files.module.zsh new file mode 100644 index 0000000..2c7402a --- /dev/null +++ b/zsh/lib/scwrypts/environment-files.module.zsh @@ -0,0 +1,94 @@ +##################################################################### + +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use utils + +##################################################################### + +SCWRYPTS__SELECT_ENV() { + SCWRYPTS__GET_ENV_NAMES | FZF 'select an environment' +} + +SCWRYPTS__SELECT_OR_CREATE_ENV() { + SCWRYPTS__GET_ENV_NAMES | FZF_TAIL 'select / create an environment' +} + +SCWRYPTS__GET_ENV_FILE() { + local NAME="$1" + + echo "$SCWRYPTS_ENV_PATH/$NAME" + + SCWRYPTS__GET_ENV_NAMES | grep -q $NAME \ + || { ERROR "no environment '$NAME' exists"; return 1; } +} + +SCWRYPTS__GET_ENV_TEMPLATE_FILES() { + local GROUP + for GROUP in ${SCWRYPTS_GROUPS[@]} + do + eval echo '$SCWRYPTS_ENV_TEMPLATE__'$GROUP + done +} + +SCWRYPTS__GET_ENV_NAMES() { + SCWRYPTS__INIT_ENVIRONMENTS || { + ERROR 'environment initialization error' + return 1 + } + ls "$SCWRYPTS_ENV_PATH" | sort -r +} + +SCWRYPTS__INIT_ENVIRONMENTS() { + [ ! -d "$SCWRYPTS_ENV_PATH" ] && mkdir -p "$SCWRYPTS_ENV_PATH" + [[ $(ls "$SCWRYPTS_ENV_PATH" | wc -l) -gt 0 ]] && return 0 + + STATUS "initializing environments for scwrypts" + + local BASIC_ENV + for BASIC_ENV in local dev prod + do + GENERATE_TEMPLATE > "$SCWRYPTS_ENV_PATH/$BASIC_ENV" + done +} + +##################################################################### + +_SED() { sed --follow-symlinks $@; } + +GENERATE_TEMPLATE() { + echo "#!/bin/zsh" + echo '#####################################################################' + echo "### scwrypts runtime configuration ##################################" + echo '#####################################################################' + local FILE GROUP CONTENT + local VARIABLE DESCRIPTION + for GROUP in ${SCWRYPTS_GROUPS[@]} + do + FILE=$(eval echo '$SCWRYPTS_ENV_TEMPLATE__'$GROUP) + + CONTENT=$(GET_VARIABLE_NAMES "$FILE" | sed 's/^/export /; s/$/=/') + + while read DESCRIPTION_LINE + do + VARIABLE=$(echo $DESCRIPTION_LINE | sed 's/ \+| .*$//') + DESCRIPTION=$(echo $DESCRIPTION_LINE | sed 's/^.* | //') + [ ! $DESCRIPTION ] && continue + + CONTENT=$(echo "$CONTENT" | sed "/^export $VARIABLE=/i #" | sed "/^export $VARIABLE=/i # $DESCRIPTION") + done < <(_SED -n '/^[^ ]\+ \+| /p' "$FILE.descriptions") + + echo "$CONTENT" | sed 's/^#$//' + echo '\n#####################################################################' + done +} + +GET_VARIABLE_NAMES() { + local FILE="$1" + grep '^export' "$FILE" \ + | sed 's/^export //; s/=.*//' \ + | grep -v '__[a-z]\+$' \ + ; +} + diff --git a/zsh/lib/scwrypts/meta.module.zsh b/zsh/lib/scwrypts/meta.module.zsh new file mode 100644 index 0000000..dc82c0c --- /dev/null +++ b/zsh/lib/scwrypts/meta.module.zsh @@ -0,0 +1,22 @@ +##################################################################### + +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use scwrypts/environment-files +use scwrypts/run + +##################################################################### + +SCWRYPTS__RUN() { + local EXIT_CODE=0 + ((SUBSCWRYPT+=1)) + + echo "--- START SUBSCWRYPT=$SUBSCWRYPT $@" + SUBSCWRYPT=$SUBSCWRYPT $SCWRYPTS_ROOT/run $@ + EXIT_CODE=$? + + ((SUBSCWRYPT-=1)) + return $EXIT_CODE + echo "--- END SUBSCWRYPT=$SUBSCWRYPT $@" +} diff --git a/zsh/lib/scwrypts/run.module.zsh b/zsh/lib/scwrypts/run.module.zsh new file mode 100644 index 0000000..30c37d7 --- /dev/null +++ b/zsh/lib/scwrypts/run.module.zsh @@ -0,0 +1,120 @@ +##################################################################### + +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use scwrypts/environment-files + +##################################################################### + +SCWRYPTS__GET_AVAILABLE_SCWRYPTS() { + local TYPE_COLOR='\033[0;37m' + local GROUP GROUP_PATH GROUP_COLOR LOOKUP_PIDS=() + { + echo 'NAME^TYPE^GROUP' + for GROUP in ${SCWRYPTS_GROUPS} + do + GROUP_PATH=$(eval echo '$SCWRYPTS_ROOT__'$GROUP) + GROUP_COLOR=$(eval echo '$SCWRYPTS_COLOR__'$GROUP) + { + cd "$GROUP_PATH" + find . -mindepth 2 -type f -executable \ + | grep -v '\.git' \ + | grep -v 'node_modules' \ + | sed "s/^\\.\\///; s/\\.[^.]*$//" \ + | sed "s|\\([^/]*\\)/\(.*\)$|$(printf $__COLOR_RESET)\\2^$(printf $TYPE_COLOR)\\1^$(printf $GROUP_COLOR)$GROUP$(printf $__COLOR_RESET)|" \ + ; + } & + LOOKUP_PIDS+=($!) + done + for p in ${LOOKUP_PIDS[@]}; do wait $p; done + } | column -t -s '^' +} + +SCWRYPTS__SEPARATE_SCWRYPT_SELECTION() { + set -- $(echo $@ | sed -e 's/\x1b\[[0-9;]*m//g') + while [[ $# -gt 0 ]] + do + [ ! $NAME ] && NAME=$1 && shift 1 && continue + [ ! $TYPE ] && TYPE=$1 && shift 1 && continue + [ ! $GROUP ] && GROUP=$1 && shift 1 && continue + shift 1 + done +} + +SCWRYPTS__GET_RUNSTRING() { + # accepts a selected line from SCWRYPTS__GET_AVAILABLE_SCWRYPTS + local NAME="$1" + local TYPE="$2" + local GROUP="$3" + local GROUP_PATH=$(eval echo '$SCWRYPTS_ROOT__'$GROUP) + local RUNSTRING + + [ $NAME ] && [ $TYPE ] && [ $GROUP ] || { + ERROR 'missing required information to get runstring' + return 1 + } + [ $ENV_REQUIRED ] && [[ $ENV_REQUIRED -eq 1 ]] && [ ! $ENV_NAME ] && { + ERROR 'missing required information to get runstring' + return 1 + } + + typeset -f SCWRYPTS__GET_RUNSTRING__${GROUP}__${TYPE} >/dev/null 2>&1 && { + RUNSTRING=$(SCWRYPTS__GET_RUNSTRING__${GROUP}__${TYPE}) + [ ! $RUNSTRING ] && { + ERROR "SCWRYPTS__GET_RUNSTRING__${GROUP}__${TYPE} error" + return 2 + } + } + + typeset -f SCWRYPTS__GET_RUNSTRING__${TYPE} >/dev/null 2>&1 && { + RUNSTRING=$(SCWRYPTS__GET_RUNSTRING__${TYPE}) + [ ! $RUNSTRING ] && { + ERROR "SCWRYPTS__GET_RUNSTRING__${TYPE} error" + return 3 + } + } + + [ ! $RUNSTRING ] && { + ERROR "type ${TYPE} (group ${GROUP}) has no supported runstring generator" + return 4 + } + + RUNSTRING="SCWRYPTS_ENV=$ENV_NAME; $RUNSTRING" + RUNSTRING="source $SCWRYPTS_ROOT/zsh/lib/import.driver.zsh; $RUNSTRING" + + local _VIRTUALENV=$(eval echo '$SCWRYPTS_VIRTUALENV_PATH__'$GROUP'/$TYPE/bin/activate') + [ -f $_VIRTUALENV ] && RUNSTRING="source $_VIRTUALENV; $RUNSTRING" + + local G SCWRYPTSENV + for G in ${SCWRYPTS__GROUPS[@]} + do + SCWRYPTSENV=$(eval echo '$SCWRYPTS_ENV_PATH__'$GROUP'/$ENV_NAME') + [ -f $SCWRYPTSENV ] && RUNSTRING="source $SCWRYPTSENV; $RUNSTRING" + done + + echo "$RUNSTRING" +} + +SCWRYPTS__GET_RUNSTRING__zsh() { + __CHECK_DEPENDENCY zsh || return 1 + + echo "source $GROUP_PATH/$TYPE/$NAME" +} + +SCWRYPTS__GET_RUNSTRING__py() { + __CHECK_DEPENDENCY python || return 1 + CURRENT_PYTHON_VERSION=$(python --version | sed 's/^[^0-9]*\(3\.[^.]*\).*$/\1/') + echo $SCWRYPTS_PREFERRED_PYTHON_VERSIONS__scwrypts | grep -q $CURRENT_PYTHON_VERSION || { + WARNING "only tested on the following python versions: $(printf ', %s.x' ${SCWRYPTS_PREFERRED_PYTHON_VERSIONS__scwrypts[@]} | sed 's/^, //')" + WARNING 'compatibility may vary' + } + + echo "cd $GROUP_PATH; python -m $(echo $TYPE/$NAME | sed 's/\//./g; s/\.py$//; s/\.\.//')" +} + +SCWRYPTS__GET_RUNSTRING__zx() { + __CHECK_DEPENDENCY zx || return 1 + + echo "FORCE_COLOR=3; cd $GROUP_PATH; ./$TYPE/$NAME.js" +} diff --git a/zsh/lib/scwrypts/scwrypts.module.zsh b/zsh/lib/scwrypts/scwrypts.module.zsh new file mode 100644 index 0000000..e69de29 diff --git a/zsh/lib/scwrypts/virtualenv.module.zsh b/zsh/lib/scwrypts/virtualenv.module.zsh new file mode 100644 index 0000000..ce96eb6 --- /dev/null +++ b/zsh/lib/scwrypts/virtualenv.module.zsh @@ -0,0 +1,127 @@ +##################################################################### + +DEPENDENCIES+=( + virtualenv + nodeenv +) +REQUIRED_ENV+=() + +use utils + +##################################################################### + +AVAILABLE_VIRTUALENVS=(py zx) + +REFRESH_VIRTUALENV() { + local GROUP="$1" + local TYPE="$2" + [ ! $TYPE ] && { + ERROR 'no virtualenv type specified' + return 1 + } + STATUS "refreshing $GROUP/$TYPE virtual environment" + DELETE_VIRTUALENV $GROUP $TYPE \ + && UPDATE_VIRTUALENV $GROUP $TYPE \ + && SUCCESS 'successfully refreshed virtual environment' \ + || { ERROR 'something went wrong during refresh (see above)'; return 1; } \ + ; +} + +UPDATE_VIRTUALENV() { + local GROUP="$1" + local TYPE="$2" + [ ! $TYPE ] && { + ERROR 'no virtualenv type specified' + return 1 + } + + local VIRTUALENV_PATH=$(GET_VIRTUALENV_PATH $GROUP $TYPE) + + [ ! -d $VIRTUALENV_PATH ] && CREATE_VIRTUALENV__${GROUP}__${TYPE} $VIRTUALENV_PATH + + STATUS "updating $TYPE virtual environment" + + source $VIRTUALENV_PATH/bin/activate || { + ERROR 'failed to activate virtualenv; did create fail?' + return 1 + } + + cd $SCWRYPTS_ROOT + local UPDATE_CODE=0 + case $TYPE in + py ) cd py; pip install -r requirements.txt; UPDATE_CODE=$? ;; + zx ) cd zx; npm install ;; + esac + UPDATE_CODE=$? + [[ $UPDATE_CODE -eq 0 ]] \ + && SUCCESS "$TYPE virtual environment up-to-date" \ + || ERROR "failed to update $TYPE virtual environment (see errors above)" \ + ; + + deactivate_node >/dev/null 2>&1 + deactivate >/dev/null 2>&1 + return $UPDATE_CODE +} + +DELETE_VIRTUALENV() { + local GROUP="$1" + local TYPE="$2" + local VIRTUALENV_PATH="$(GET_VIRTUALENV_PATH $GROUP $TYPE)" + + STATUS "dropping $TYPE virtual environment artifacts" + + [ ! -d $VIRTUALENV_PATH ] && { + SUCCESS "no $TYPE environment detected" + return 0 + } + + rm -rf $VIRTUALENV_PATH \ + && SUCCESS "succesfully cleaned up $TYPE virtual environment" \ + || { ERROR "unabled to remove '$VIRTUALENV_PATH'"; return 1; } +} + +GET_VIRTUALENV_PATH() { + local GROUP="$1" + local TYPE="$2" + eval echo '$SCWRYPTS_VIRTUALENV_PATH__'$GROUP/$TYPE +} + +##################################################################### + +CREATE_VIRTUALENV__scwrypts__py() { + local VIRTUALENV_PATH="$1" + + STATUS 'creating python virtual environment' + local PY PYTHON + for PY in $(echo $SCWRYPTS_PREFERRED_PYTHON_VERSIONS__scwrypts) + do + which python$PY >/dev/null 2>&1 && { + PYTHON=$(which python$PY) + break + } + done + [ ! $PYTHON ] && { + ERROR 'python>=3.9 not available; skipping python env' + return 1 + } + + STATUS 'setting up virtualenv' + virtualenv $VIRTUALENV_PATH --python="$PYTHON" \ + && SUCCESS 'python virtualenv created' \ + || { + ERROR "unable to create '$VIRTUALENV_PATH' with '$PYTHON'" + return 2 + } +} + +CREATE_VIRTUALENV__scwrypts__zx() { + local VIRTUALENV_PATH="$1" + + STATUS 'setting up nodeenv' + nodeenv $VIRTUALENV_PATH --node=$SCWRYPTS_NODE_VERSION__scwrypts \ + && SUCCESS 'node virtualenv created' \ + || { + ERROR "unable to create '$VIRTUALENV_PATH' with '$SCWRYPTS__NODE_VERSION'" + return 2 + } +} diff --git a/zsh/config/common.zsh b/zsh/lib/system/config/config.module.zsh similarity index 59% rename from zsh/config/common.zsh rename to zsh/lib/system/config/config.module.zsh index 171efe3..cf1f723 100644 --- a/zsh/config/common.zsh +++ b/zsh/lib/system/config/config.module.zsh @@ -1,14 +1,18 @@ -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -DEFAULT_CONFIG="${0:a:h}/default.conf.zsh" -source ${0:a:h}/../common.zsh ##################################################################### +DEPENDENCIES+=() +REQUIRED_ENV+=() + +##################################################################### + +DEFAULT_CONFIG="${0:a:h}/default.conf.zsh" + SAFE_SYMLINKS=1 -# in case dotfiles.zsh is sourced... allow user to provide initial config ;) +# in case dotfiles.zsh is sourced; allows users to provide initial config [ ! $CONFIG__USER_SETTINGS ] \ && CONFIG__USER_SETTINGS="$SCWRYPTS_CONFIG_PATH/dotfiles.zsh" [ ! -f "$CONFIG__USER_SETTINGS" ] && cp "$DEFAULT_CONFIG" "$CONFIG__USER_SETTINGS" -source $CONFIG__USER_SETTINGS + +source "$CONFIG__USER_SETTINGS" diff --git a/zsh/config/default.conf.zsh b/zsh/lib/system/config/default.conf.zsh similarity index 100% rename from zsh/config/default.conf.zsh rename to zsh/lib/system/config/default.conf.zsh diff --git a/zsh/lib/system/desktop/notify.module.zsh b/zsh/lib/system/desktop/notify.module.zsh new file mode 100644 index 0000000..ae0f62f --- /dev/null +++ b/zsh/lib/system/desktop/notify.module.zsh @@ -0,0 +1,16 @@ +##################################################################### + +DEPENDENCIES+=( + notify-send +) + +REQUIRED_ENV+=() + +##################################################################### + +NOTIFY() { + local D=$DISPLAY + [ ! $D ] && D=:0 + + DISPLAY=$D notify-send "SCWRYPTS $SCWRYPT_NAME" $@ +} diff --git a/zsh/git/package/common.zsh b/zsh/lib/system/packages/git.module.zsh similarity index 50% rename from zsh/git/package/common.zsh rename to zsh/lib/system/packages/git.module.zsh index ca7c09b..08a6638 100644 --- a/zsh/git/package/common.zsh +++ b/zsh/lib/system/packages/git.module.zsh @@ -1,6 +1,12 @@ -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh +##################################################################### + +DEPENDENCIES+=( + git + make +) + +REQUIRED_ENV+=() + ##################################################################### PACKAGE_INSTALL_DIR="$HOME/.local/share/source-packages" @@ -10,19 +16,19 @@ PACKAGE_INSTALL_DIR="$HOME/.local/share/source-packages" CLONE() { cd "$PACKAGE_INSTALL_DIR" - __STATUS "downloading $NAME" + STATUS "downloading $NAME" git clone "$TARGET" "$NAME" \ - && __SUCCESS "successfully downloaded '$NAME'" \ - || __FAIL 1 "failed to download '$NAME'" \ + && SUCCESS "successfully downloaded '$NAME'" \ + || FAIL 1 "failed to download '$NAME'" \ ; } PULL() { - __STATUS "updating '$NAME'" + STATUS "updating '$NAME'" cd "$PACKAGE_INSTALL_DIR/$NAME" git pull origin $(git rev-parse --abbrev-ref HEAD) \ - && __SUCCESS "successfully updated '$NAME'" \ - || __FAIL 1 "failed to update '$NAME'" \ + && SUCCESS "successfully updated '$NAME'" \ + || FAIL 1 "failed to update '$NAME'" \ ; } @@ -34,10 +40,10 @@ BUILD() { CHECK_MAKE && { MAKE && return 0 || return 1; } CHECK_MAKEPKG && { MAKEPKG && return 0 || return 2; } - __WARNING 'could not detect supported installation method' + WARNING 'could not detect supported installation method' - __REMINDER 'complete manual installation in the directory below:' - __REMINDER "$PACKAGE_INSTALL_DIR/$NAME" + REMINDER 'complete manual installation in the directory below:' + REMINDER "$PACKAGE_INSTALL_DIR/$NAME" } CHECK_MAKE() { [ -f ./Makefile ]; } @@ -45,30 +51,28 @@ CHECK_MAKEPKG() { [ -f ./PKGBUILD ]; } MAKE() { [[ $CLEAN -eq 1 ]] && { - __STATUS "cleaning '$NAME'" + STATUS "cleaning '$NAME'" make clean } - __STATUS "building '$NAME'" + STATUS "building '$NAME'" make \ - && __SUCCESS "finished building '$NAME'" \ - || __FAIL 1 "build failed for '$NAME' (see above)"\ + && SUCCESS "finished building '$NAME'" \ + || FAIL 1 "build failed for '$NAME' (see above)"\ ; - __STATUS "installing '$NAME'" - __GETSUDO + STATUS "installing '$NAME'" + GETSUDO sudo make install \ - && __SUCCESS "succesfully installed '$NAME'" \ - || __FAIL 2 "failed to install '$NAME' (see above)"\ + && SUCCESS "succesfully installed '$NAME'" \ + || FAIL 2 "failed to install '$NAME' (see above)"\ ; } MAKEPKG() { - __STATUS "installing '$NAME'" + STATUS "installing '$NAME'" yes | makepkg -si \ - && __SUCCESS "succesfully installed '$NAME'" \ - || __FAIL 1 "failed to install '$NAME' (see above)"\ + && SUCCESS "succesfully installed '$NAME'" \ + || FAIL 1 "failed to install '$NAME' (see above)"\ ; } - -##################################################################### diff --git a/zsh/lib/system/vim/vim.module.zsh b/zsh/lib/system/vim/vim.module.zsh new file mode 100644 index 0000000..b1b593c --- /dev/null +++ b/zsh/lib/system/vim/vim.module.zsh @@ -0,0 +1,11 @@ +##################################################################### + +DEPENDENCIES+=( + vim +) + +REQUIRED_ENV+=() + +##################################################################### + +VIM() { vim $@ /dev/tty; } diff --git a/zsh/lib/system/vim/vundle.module.zsh b/zsh/lib/system/vim/vundle.module.zsh new file mode 100644 index 0000000..e360a79 --- /dev/null +++ b/zsh/lib/system/vim/vundle.module.zsh @@ -0,0 +1,53 @@ +##################################################################### + +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use system/vim + +##################################################################### + +VUNDLE__PLUGIN_DIR="$HOME/.vim/bundle" +VUNDLE__BUILD_DEFINITIONS="$SCWRYPTS_CONFIG_PATH/vundle.zsh" + +[ ! -f $VUNDLE__BUILD_DEFINITIONS ] && { + { + echo -e "#\n# Scwrypts Build Definitions\n#\n" + } > $VUNDLE__BUILD_DEFINITIONS +} + +VUNDLE__PLUGIN_LIST=$(ls $VUNDLE__PLUGIN_DIR | grep -v 'Vundle.vim' | grep -v 'build.zsh') +source $VUNDLE__BUILD_DEFINITIONS +for PLUGIN in $(echo $VUNDLE__PLUGIN_LIST) +do + typeset -f VUNDLE__BUILD__$PLUGIN >/dev/null 2>/dev/null || { + echo -e "\nVUNDLE__BUILD__$PLUGIN() {\n # ... build steps from $HOME/.vim/$PLUGIN \n}" \ + >> $VUNDLE__BUILD_DEFINITIONS + VUNDLE__BUILD__$PLUGIN() {} + } +done + +##################################################################### + +VUNDLE__PLUGIN_INSTALL() { + VIM +PluginInstall +qall \ + && SUCCESS 'successfully installed Vundle.vim plugins' \ + || FAIL 1 'failed to install Vundle.vim plugins' +} + +VUNDLE__REBUILD_PLUGINS() { + local ERRORS=0 + + local PLUGIN + for PLUGIN in $(echo $VUNDLE__PLUGIN_LIST) + do + cd "$VUNDLE__PLUGIN_DIR/$PLUGIN" + STATUS "building '$PLUGIN'" + VUNDLE__BUILD__$PLUGIN \ + && SUCCESS "finished building '$PLUGIN'" \ + || ERROR "failed to build '$PLUGIN' (see above)" \ + ; + done + + return $ERRORS +} diff --git a/zsh/utils/README.md b/zsh/lib/utils/README.md similarity index 89% rename from zsh/utils/README.md rename to zsh/lib/utils/README.md index 84f71cf..9927c65 100644 --- a/zsh/utils/README.md +++ b/zsh/lib/utils/README.md @@ -11,7 +11,7 @@ Doing so will *also* check for path dependencies and required environment variab ```shell #!/bin/zsh source ./path/to/utils.plugin.zsh -__SUCCESS 'ZSH utilities online!' +SUCCESS 'ZSH utilities online!' ``` Checkout [io](./io.zsh) and [os](./os.zsh) for available simple functions. @@ -20,14 +20,14 @@ Checkout [io](./io.zsh) and [os](./os.zsh) for available simple functions. Ensures dependent programs are available for execution. Specify a simple name to check the current `PATH`, or give a fully-qualified path for arbitrary dependency inclusion. -Include a dependency by adding to the `_DEPENDENCIES` array. +Include a dependency by adding to the `DEPENDENCIES` array. *Always using `+=` makes your dependencies extensible to other scripts :)* If any dependencies are missing, `source utils.module.zsh` will return an error code and count the number of missing dependencies in the variable `DEP_ERROR_COUNT`. ```shell #!/bin/zsh -_DEPENDENCIES+=( +DEPENDENCIES+=( path-executable-1 path-executable-2 /path/to/arbitrary/program @@ -39,7 +39,7 @@ echo "missing $DEP_ERROR required dependencies" ### Environment Similar to [Dependencies](#dependencies), `environment.zsh` ensures a list of environment variables are *set to non-empty values*. -Include an environment variable by adding to the `_REQUIRED_ENV` array. +Include an environment variable by adding to the `REQUIRED_ENV` array. *Something something use `+=` here too ;)* If any environment variables are missing, `source utils.module.zsh` will return an error code and count the number of missing variables in `ENV_ERROR_COUNT`. @@ -48,7 +48,7 @@ Missing environment variables will be added to the environment template (*exclus ```shell #!/bin/zsh -_REQUIRED_ENV+=( +REQUIRED_ENV+=( AWS_PROFILE AWS_REGION ) diff --git a/zsh/utils/colors.zsh b/zsh/lib/utils/colors.zsh similarity index 100% rename from zsh/utils/colors.zsh rename to zsh/lib/utils/colors.zsh diff --git a/zsh/utils/credits.zsh b/zsh/lib/utils/credits.zsh similarity index 100% rename from zsh/utils/credits.zsh rename to zsh/lib/utils/credits.zsh diff --git a/zsh/utils/dependencies.zsh b/zsh/lib/utils/dependencies.zsh similarity index 61% rename from zsh/utils/dependencies.zsh rename to zsh/lib/utils/dependencies.zsh index 558c28a..3dc49a7 100644 --- a/zsh/utils/dependencies.zsh +++ b/zsh/lib/utils/dependencies.zsh @@ -1,7 +1,9 @@ __CHECK_DEPENDENCIES() { local DEP ERROR=0 - for DEP in $*; do __CHECK_DEPENDENCY $DEP || ((ERROR+=1)); done + DEPENDENCIES=($(echo $DEPENDENCIES | sed 's/ \+/\n/g' | sort -u)) + + for DEP in ${DEPENDENCIES[@]}; do __CHECK_DEPENDENCY $DEP || ((ERROR+=1)); done __CHECK_COREUTILS || ((ERROR+=$?)) return $ERROR @@ -11,7 +13,7 @@ __CHECK_DEPENDENCY() { local DEPENDENCY="$1" [ ! $DEPENDENCY ] && return 1 command -v $DEPENDENCY >/dev/null 2>&1 || { - __ERROR "'$1' required but not installed. $(__CREDITS $1)" + ERROR "'$1' required but not available on PATH $(__CREDITS $1)" return 1 } } @@ -27,14 +29,14 @@ __CHECK_COREUTILS() { __CHECK_DEPENDENCY $UTIL || { ((MISSING_DEPENDENCY_COUNT+=1)); continue; } $UTIL --version 2>&1 | grep -q 'GNU' || { - __WARNING "non-GNU version of $UTIL detected" + WARNING "non-GNU version of $UTIL detected" ((NON_GNU_DEPENDENCY_COUNT+=1)) } done [[ $NON_GNU_DEPENDENCY_COUNT -gt 0 ]] && { - __WARNING 'scripts rely on GNU coreutils; functionality may be limited' - __IS_MACOS && __REMINDER 'GNU coreutils can be installed and linked through Homebrew' + WARNING 'scripts rely on GNU coreutils; functionality may be limited' + IS_MACOS && REMINDER 'GNU coreutils can be installed and linked through Homebrew' } return $MISSING_DEPENDENCY_COUNT diff --git a/zsh/utils/environment.zsh b/zsh/lib/utils/environment.zsh similarity index 53% rename from zsh/utils/environment.zsh rename to zsh/lib/utils/environment.zsh index 8332800..caf604c 100644 --- a/zsh/utils/environment.zsh +++ b/zsh/lib/utils/environment.zsh @@ -1,6 +1,7 @@ __CHECK_REQUIRED_ENV() { local VAR ERROR=0 - for VAR in $*; do __CHECK_ENV_VAR $VAR || ((ERROR+=1)); done + REQUIRED_ENV=($(echo $REQUIRED_ENV | sed 's/\s\+/\n/g' | sort -u)) + for VAR in ${REQUIRED_ENV[@]}; do __CHECK_ENV_VAR $VAR || ((ERROR+=1)); done return $ERROR } @@ -17,9 +18,9 @@ __CHECK_ENV_VAR() { local VALUE=$(eval echo '$'$NAME) [ $VALUE ] && return 0 - local SELECTION_VALUES=$(eval echo '$'$NAME'__select' | sed 's/,/\n/g') - [ $SELECTION_VALUES ] && { - local SELECTION=$(echo $SELECTION_VALUES | __FZF "select a value for '$NAME'") + local SELECTION_VALUES=$(eval echo '$'$NAME'__select' | sed 's/,/\n/g; s/ /\n/g') + [[ $ERROR -eq 0 ]] && [[ ${#SELECTION_VALUES[@]} -gt 0 ]] && { + local SELECTION=$(echo $SELECTION_VALUES | FZF "select a value for '$NAME'") [ $SELECTION ] && { export $NAME=$SELECTION return 0 @@ -27,23 +28,11 @@ __CHECK_ENV_VAR() { } [ $VALUE ] && return 0 - [ $__SCWRYPT ] && { - # scwrypts exclusive (missing vars staged in env.template) - local LINE="export $NAME=" - - grep -q -- "^$LINE" "$__ENV_TEMPLATE" || { - __STATUS 'staging new variable in template' - - echo "$LINE" >> "$__ENV_TEMPLATE" \ - && __RUN_SCWRYPT zsh/scwrypts/environment/synchronize -- --no-prompt - } - } - [ $OPTIONAL ] && { [ $DEFAULT_VALUE ] && $NAME="$DEFAULT_VALUE" return 0 } || { - __ERROR "'$NAME' required" + ERROR "'$NAME' required" return 1 } } diff --git a/zsh/lib/utils/io.zsh b/zsh/lib/utils/io.zsh new file mode 100644 index 0000000..ef9a78d --- /dev/null +++ b/zsh/lib/utils/io.zsh @@ -0,0 +1,152 @@ +PRINT() { + local MESSAGE + local LAST_LINE_END='\n' + local STDERR=1 + local STDOUT=0 + + local LTRIM=1 + while [[ $# -gt 0 ]] + do + case $1 in + -n | --no-trim-tabs ) LTRIM=0 ;; + -x | --no-line-end ) LAST_LINE_END='' ;; + -o | --use-stdout ) STDOUT=1; STDERR=0 ;; + * ) MESSAGE+="$(echo $1) " ;; + esac + shift 1 + done + + local STYLED_MESSAGE="${COLOR}$({ + printf "${COLOR}" + while IFS='' read line + do + [[ $PREFIX =~ ^[[:space:]]\+$ ]] && printf '\n' + + printf "${PREFIX} : $(echo "$line" | sed 's/^ \+//; s/ \+$//')" + + PREFIX=$(echo $PREFIX | sed 's/./ /g') + done <<< $MESSAGE + })${__COLOR_RESET}${LAST_LINE_END}" + [[ $STDERR -eq 1 ]] && printf $STYLED_MESSAGE >&2 + [[ $STDOUT -eq 1 ]] && printf $STYLED_MESSAGE + + return 0 +} + +[ ! $ERRORS ] && ERRORS=0 +ERROR() { PREFIX="ERROR ✖" COLOR=$__RED PRINT "$@"; ((ERRORS+=1)); } +SUCCESS() { PREFIX="SUCCESS ✔" COLOR=$__GREEN PRINT "$@"; } +WARNING() { PREFIX="WARNING " COLOR=$__ORANGE PRINT "$@"; } +STATUS() { PREFIX="STATUS " COLOR=$__BLUE PRINT "$@"; } +REMINDER() { PREFIX="REMINDER " COLOR=$__PURPLE PRINT "$@"; } +INFO() { PREFIX="INFO " COLOR=$__WHITE PRINT "$@"; } + +PROMPT() { + PREFIX="PROMPT " COLOR=$__CYAN PRINT "$@" + PREFIX="USER " COLOR=$__CYAN PRINT '' --no-line-end +} + +FAIL() { ERROR "${@:2}"; exit $1; } +ABORT() { FAIL 69 'user abort'; } + +CHECK_ERRORS() { + local FAIL_OUT=1 + + while [[ $# -gt 0 ]] + do + case $1 in + -n | --no-fail ) FAIL_OUT=0 ;; + esac + shift 1 + done + + [ ! $ERRORS ] && ERRORS=0 + [[ $ERRORS -ne 0 ]] && USAGE + [[ $ERRORS -eq 0 ]] || { + [[ $FAIL_OUT -eq 1 ]] \ + && exit $ERRORS \ + || return $ERRORS + } +} + +USAGE() { + [ ! $USAGE ] && return 0 + USAGE=$(echo $USAGE | sed "s/^\t\+//; s/\s\+$//") + + local USAGE_LINE=$(\ + echo $USAGE \ + | grep -i '^ *usage *:' \ + | sed "s;^[^:]*:;& scwrypts $SCWRYPT_NAME --;" \ + | sed 's/ \{2,\}/ /g; s/scwrypts -- scwrypts/scwrypts/' \ + ) + local THE_REST=$(echo $USAGE | grep -vi '^ *usage *:' | sed 'N;/^\n$/D;P;D;') + + { echo; printf "$__DARK_BLUE $USAGE_LINE$__COLOR_RESET\n"; echo $THE_REST; echo } >&2 +} + +INPUT() { + PROMPT "${@:2}" + READ $1 + local VALUE=$(eval echo '$'$1) + [ $VALUE ] +} + +Yn() { + PROMPT "$@ [Yn]" + [ $CI ] && { echo y; return 0; } + + local Yn; READ -k Yn; echo + [[ $Yn =~ [nN] ]] && return 1 || return 0 +} + +yN() { + PROMPT "$@ [yN]" + [ $CI ] && { echo y; return 0; } + + local yN; READ -k yN; echo + [[ $yN =~ [yY] ]] && return 0 || return 1 +} + +##################################################################### + +GETSUDO() { + echo "\\033[1;36mPROMPT  : checking sudo password...\\033[0m" >&2 + sudo echo hi >/dev/null 2>&1 /dev/tty; } + +FZF() { + [ $CI ] && { + ERROR 'currently in CI, but FZF requires user input' + exit 1 + } + + local SELECTION=$(fzf -i --height=30% --layout=reverse --prompt "$1 : " ${@:2}) + PROMPT "$1" + echo $SELECTION >&2 + echo $SELECTION +} +FZF_HEAD() { FZF $@ --print-query | sed '/^$/d' | head -n1; } # prefer user input over selected +FZF_TAIL() { FZF $@ --print-query | sed '/^$/d' | tail -n1; } # prefer selected over user input + +READ() { + [ $CI ] && { + INFO 'currently in CI, skipping READ' + return 0 + } + read $@ /dev/tty + SUCCESS "finished editing '$1'!" +} diff --git a/zsh/lib/utils/os.zsh b/zsh/lib/utils/os.zsh new file mode 100644 index 0000000..c5276d0 --- /dev/null +++ b/zsh/lib/utils/os.zsh @@ -0,0 +1,12 @@ +IS_MACOS() { uname -s | grep -q 'Darwin'; } + +OPEN() { + local OPEN='' + { + command -v xdg-open && OPEN=xdg-open + command -v open && OPEN=open + } >/dev/null 2>&1 + + [ ! $OPEN ] && { ERROR 'unable to detect default open command (e.g. xdg-open)'; return 1 } + $OPEN $@ +} diff --git a/zsh/lib/utils/utils.module.zsh b/zsh/lib/utils/utils.module.zsh new file mode 100644 index 0000000..868c2f4 --- /dev/null +++ b/zsh/lib/utils/utils.module.zsh @@ -0,0 +1,76 @@ +##################################################################### + +DEPENDENCIES+=(fzf) # (extensible) list of PATH dependencies +REQUIRED_ENV+=() # (extensible) list of required environment variables + +##################################################################### + +source ${0:a:h}/colors.zsh +source ${0:a:h}/io.zsh +source ${0:a:h}/os.zsh +source ${0:a:h}/credits.zsh + +##################################################################### + +source ${0:a:h}/dependencies.zsh +source ${0:a:h}/environment.zsh + +##################################################################### + +CHECK_ENVIRONMENT() { + local ENVIRONMENT_STATUS=0 + + __CHECK_DEPENDENCIES $DEPENDENCIES + local MISSING_DEPENDENCIES=$? + + __CHECK_REQUIRED_ENV $REQUIRED_ENV + local MISSING_ENVIRONMENT_VARIABLES=$? + + ########################################## + + local ERROR_MESSAGE="" + [[ $MISSING_DEPENDENCIES -ne 0 ]] && { + ((ENVIRONMENT_STATUS+=1)) + ERROR_MESSAGE+="\n$MISSING_DEPENDENCIES missing " + + [[ $MISSING_DEPENDENCIES -eq 1 ]] \ + && ERROR_MESSAGE+='dependency' \ + || ERROR_MESSAGE+='dependencies' \ + ; + } + + [[ $MISSING_ENVIRONMENT_VARIABLES -ne 0 ]] && { + ((ENVIRONMENT_STATUS+=2)) + ERROR_MESSAGE+="\n$MISSING_ENVIRONMENT_VARIABLES missing environment variable" + + [[ $MISSING_ENVIRONMENT_VARIABLES -gt 1 ]] && ERROR_MESSAGE+=s + } + + [ $IMPORT_ERRORS ] && [[ $IMPORT_ERRORS -ne 0 ]] && { + ((ENVIRONMENT_STATUS+=4)) + ERROR_MESSAGE+="\n$IMPORT_ERRORS import error" + + [[ $IMPORT_ERRORS -gt 1 ]] && ERROR_MESSAGE+=s + } + + ########################################## + + [[ ENVIRONMENT_STATUS -eq 0 ]] || { + ERROR_MESSAGE=$(echo $ERROR_MESSAGE | sed '1d; s/^/ /') + ERROR "environment errors found (see above)\n$ERROR_MESSAGE" + } + + [[ $MISSING_ENVIRONMENT_VARIABLES -ne 0 ]] && { + REMINDER " + to quickly update missing environment variables, run: + 'scwrypts zsh/scwrypts/environment/edit' + " + } + + [[ $ENVIRONMENT_STATUS -eq 0 ]] || { + [[ $NO_EXIT -eq 1 ]] && return $ENVIRONMENT_STATUS + exit $ENVIRONMENT_STATUS + } +} + +CHECK_ENVIRONMENT diff --git a/zsh/youtube/README.md b/zsh/media/youtube/README.md similarity index 100% rename from zsh/youtube/README.md rename to zsh/media/youtube/README.md diff --git a/zsh/media/youtube/download b/zsh/media/youtube/download new file mode 100755 index 0000000..a91e3c2 --- /dev/null +++ b/zsh/media/youtube/download @@ -0,0 +1,28 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use media/youtube + +CHECK_ENVIRONMENT +##################################################################### + +DOWNLOAD_VIDEO() { + local URLS=($@) + + [[ ${#URLS[@]} -eq 0 ]] && URLS=($(echo '' | FZF_HEAD 'enter URL')) + [[ ${#URLS[@]} -eq 0 ]] && ABORT + + local FILENAME=$(YT__GET_FILENAME $URLS) + [ ! $FILENAME ] && ERROR "unable to download '$URLS'" + + SUCCESS "Found '$FILENAME'" + Yn "Proceed with download?" || return 1 + + YT__DOWNLOAD $URLS \ + && SUCCESS "downloaded to '$YT__OUTPUT_DIR/$FILENAME'" \ + || { ERROR "failed to download '$FILENAME'"; return 2; } +} + +##################################################################### +DOWNLOAD_VIDEO $@ diff --git a/zsh/media/youtube/get-audio-clip b/zsh/media/youtube/get-audio-clip new file mode 100755 index 0000000..927d245 --- /dev/null +++ b/zsh/media/youtube/get-audio-clip @@ -0,0 +1,54 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use media/youtube + +CHECK_ENVIRONMENT +##################################################################### + +GET_AUDIO_CLIP() { + local URLS=($@) + + [[ ${#URLS[@]} -eq 0 ]] && URLS=($(echo '' | FZF_HEAD 'enter URL')) + [[ ${#URLS[@]} -eq 0 ]] && ABORT + + local FILENAME=$(YT__GET_FILENAME $URLS) + [ ! $FILENAME ] && ERROR "unable to download '$URLS'" + + INPUT_FILE="$YT__OUTPUT_DIR/$FILENAME" + + [ ! -f "$INPUT_FILE" ] && { + SCWRYPTS__RUN youtube/download -- $URLS || return 1 + } + + SUCCESS "video download '$FILENAME' detected!" + + LENGTH=$(GET_VIDEO_LENGTH "$INPUT_FILE") + [ ! $LENGTH ] && { ERROR "unable to determine video length for '$INPUT_FILE'"; return 2; } + START_TIME=$(echo 0 | FZF_HEAD "enter start time (0 ≤ t < $LENGTH)") + [ ! $START_TIME ] && ABORT + END_TIME=$(echo $LENGTH | FZF_HEAD "enter end time ($START_TIME > t ≥ $LENGTH)") + [ ! $END_TIME ] && ABORT + + STATUS + STATUS "video : $FILENAME" + STATUS "start time : $START_TIME" + STATUS "end time : $END_TIME" + STATUS + OUTPUT_FILE=$(echo '' \ + | FZF_HEAD 'what should I call this clip? (.mp3)' \ + | sed 's/\.mp3$//' \ + ) + [ ! $OUTPUT_FILE ] && ABORT + OUTPUT_FILE="$YT__OUTPUT_DIR/$OUTPUT_FILE.mp3" + + ffmpeg -i "$INPUT_FILE" -q:a 0 -map a \ + -ss $START_TIME -t $(($END_TIME - $START_TIME))\ + "$OUTPUT_FILE" \ + && SUCCESS "created clip '$OUTPUT_FILE'" \ + || { ERROR "error creating clip '$(basename $OUTPUT_FILE)' (see above)"; return 3; } +} + +##################################################################### +GET_AUDIO_CLIP $@ diff --git a/zsh/memo/common.zsh b/zsh/memo/common.zsh deleted file mode 100644 index 32c8b7d..0000000 --- a/zsh/memo/common.zsh +++ /dev/null @@ -1,19 +0,0 @@ -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### - -set +o noglob -MEMO__FILETYPE=md -MEMO__DIR="$SCWRYPTS_DATA_PATH/memo" -[ ! -d $MEMO__DIR ] && mkdir -p $MEMO__DIR - -LIST_MEMOS() { ls $MEMO__DIR | sed "s/\.$MEMO__FILETYPE$//" | sort; } - -# TODO : remove deprecated migration -[ -d $HOME/.memos ] && { - __Yn 'detected legacy memos folder; upgrade now?' && { - mv $HOME/.memos/* $MEMO__DIR - rmdir "$HOME/.memos" - } -} diff --git a/zsh/memo/remove b/zsh/memo/remove deleted file mode 100755 index c486854..0000000 --- a/zsh/memo/remove +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### - -OPEN_MEMO() { - local MEMO_NAME=$(LIST_MEMOS | __FZF 'select a memo to delete') - local MEMO_FILE="$MEMO__DIR/$MEMO_NAME.$MEMO__FILETYPE" - [ "$MEMO_NAME" ] && [ -f "$MEMO_FILE" ] || __ABORT - - __STATUS "--- START OF MEMO ---------------------------------------------------" - cat "$MEMO_FILE" - __STATUS "--- END OF MEMO -----------------------------------------------------" - - __WARNING - __WARNING 'memos are not backed up by default; deletion is permanent!' - __WARNING - - __yN 'are you sure you want to delete this memo?' || __ABORT - - __STATUS "deleting memo '$MEMO_FILE'" - rm "$MEMO_FILE" \ - && __SUCCESS "removed memo '$MEMO_NAME'" \ - || __FAIL 1 "failed to remove memo '$MEMO_NAME'" \ - ; -} - - - -##################################################################### -OPEN_MEMO $@ diff --git a/zsh/office/latex/build-pdf b/zsh/office/latex/build-pdf new file mode 100755 index 0000000..7dd4122 --- /dev/null +++ b/zsh/office/latex/build-pdf @@ -0,0 +1,31 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use office/latex + +CHECK_ENVIRONMENT +##################################################################### + +PDFLATEX() { + [ ! $1 ] && FAIL 1 'must provide filename' + local FILENAME=$(LATEX__GET_MAIN_FILENAME "$1") + + local ARGS=(-interaction=nonstopmode) + ARGS+=("$FILENAME") + + cd "$(dirname $FILENAME)" + + STATUS 'running compile (1/2)' + pdflatex ${ARGS[@]} \ + || FAIL 2 'first compile failed (see above)' + + STATUS 'running compile (2/2)' + pdflatex ${ARGS[@]} >/dev/null 2>&1 \ + || FAIL 3 'second compile failed :c' + + SUCCESS "created '$(echo $FILENAME | sed 's/\.[^.]*$/.pdf/')'" +} + +##################################################################### +PDFLATEX $@ diff --git a/zsh/latex/cleanup b/zsh/office/latex/cleanup similarity index 51% rename from zsh/latex/cleanup rename to zsh/office/latex/cleanup index 28fc576..df40aa2 100755 --- a/zsh/latex/cleanup +++ b/zsh/office/latex/cleanup @@ -1,19 +1,22 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use office/latex + +CHECK_ENVIRONMENT ##################################################################### CLEAN_LATEX_LOGFILES() { - local DIRECTORY=$(__GET_PATH_TO_RELATIVE_ARGUMENT ".") - [ $1 ] && DIRECTORY="$(dirname "$(GET_MAIN_LATEX_FILENAME "$1")")" + local DIRECTORY=$(SCWRYPTS__GET_PATH_TO_RELATIVE_ARGUMENT ".") + [ $1 ] && DIRECTORY="$(dirname "$(LATEX__GET_MAIN_FILENAME "$1")")" [ $DIRECTORY ] && [ -d $DIRECTORY ] \ - || __FAIL 1 'unable to parse valid directory' + || FAIL 1 'unable to parse valid directory' cd $DIRECTORY rm $(ls | grep '\.\(aux\)\|\(log\)\|\(pdf\)\|\(out\)\|\(dvi\)$') - __SUCCESS "cleaned up latex artifacts in '$DIRECTORY'" + SUCCESS "cleaned up latex artifacts in '$DIRECTORY'" } ##################################################################### diff --git a/zsh/latex/create-new b/zsh/office/latex/create-new similarity index 69% rename from zsh/latex/create-new rename to zsh/office/latex/create-new index 1cee37a..9939994 100755 --- a/zsh/latex/create-new +++ b/zsh/office/latex/create-new @@ -1,29 +1,28 @@ #!/bin/zsh -_DEPENDENCIES+=( - pdflatex - rg -) -_REQUIRED_ENV+=() +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use office/latex + +CHECK_ENVIRONMENT +##################################################################### TEMPLATE_DIR="${0:a:h}/templates" -source ${0:a:h}/common.zsh -##################################################################### - CREATE_NEW_LATEX_DOCUMENT_FROM_TEMPLATE() { local DOCUMENT_DIR="$EXECUTION_DIR" - local TEMPLATE=$(GET_TEMPLATES | __FZF 'select a template') - [ ! $TEMPLATE ] && __ABORT - __SUCCESS "selected template '$TEMPLATE'" + local TEMPLATE=$(GET_TEMPLATES | FZF 'select a template') + [ ! $TEMPLATE ] && ABORT + SUCCESS "selected template '$TEMPLATE'" - __INPUT DOC_TITLE 'document title' || __ABORT + INPUT DOC_TITLE 'document title' || ABORT local DOCUMENT_FILE="$DOCUMENT_DIR/$(SLUGIFY_TITLE).tex" - [ -f "$DOCUMENT_FILE" ] && __FAIL 1 "'$(basename $DOCUMENT_FILE)' already exists" + [ -f "$DOCUMENT_FILE" ] && FAIL 1 "'$(basename $DOCUMENT_FILE)' already exists" - __INPUT DOC_ID 'document id/subtitle' - __INPUT AUTHOR 'author name' - __INPUT AUTHOR_ID 'author id/title' + INPUT DOC_ID 'document id/subtitle' + INPUT AUTHOR 'author name' + INPUT AUTHOR_ID 'author id/title' { PRINT_TITLE_INFO @@ -37,7 +36,7 @@ CREATE_NEW_LATEX_DOCUMENT_FROM_TEMPLATE() { [[ ! $TEMPLATE =~ ^basic$ ]] \ && mkdir "$DOCUMENT_DIR/sections" "$DOCUMENT_DIR/graphics" - __SUCCESS "finished generating '$(basename $DOCUMENT_FILE)' from '$TEMPLATE'" + SUCCESS "finished generating '$(basename $DOCUMENT_FILE)' from '$TEMPLATE'" } GET_TEMPLATES() { diff --git a/zsh/office/latex/get-pdf b/zsh/office/latex/get-pdf new file mode 100755 index 0000000..9aa399d --- /dev/null +++ b/zsh/office/latex/get-pdf @@ -0,0 +1,10 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use office/latex + +CHECK_ENVIRONMENT +##################################################################### + +LATEX__GET_PDF $@ diff --git a/zsh/latex/open-pdf b/zsh/office/latex/open-pdf similarity index 61% rename from zsh/latex/open-pdf rename to zsh/office/latex/open-pdf index 9343eaf..5071ffb 100755 --- a/zsh/latex/open-pdf +++ b/zsh/office/latex/open-pdf @@ -1,14 +1,17 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use office/latex + +CHECK_ENVIRONMENT ##################################################################### OPEN_PDF() { - local PDF=$(__RUN_SCWRYPT latex/get-pdf -n -- $1) + local PDF=$(LATEX__GET_PDF $@) [ ! $PDF ] && return 1 - __OPEN "$PDF" + OPEN "$PDF" } ##################################################################### diff --git a/zsh/latex/templates/basic/template.tex b/zsh/office/latex/templates/basic/template.tex similarity index 100% rename from zsh/latex/templates/basic/template.tex rename to zsh/office/latex/templates/basic/template.tex diff --git a/zsh/latex/templates/gitignore b/zsh/office/latex/templates/gitignore similarity index 100% rename from zsh/latex/templates/gitignore rename to zsh/office/latex/templates/gitignore diff --git a/zsh/latex/templates/main.tex b/zsh/office/latex/templates/main.tex similarity index 100% rename from zsh/latex/templates/main.tex rename to zsh/office/latex/templates/main.tex diff --git a/zsh/latex/templates/math/code.sty b/zsh/office/latex/templates/math/code.sty similarity index 100% rename from zsh/latex/templates/math/code.sty rename to zsh/office/latex/templates/math/code.sty diff --git a/zsh/latex/templates/math/formatting.sty b/zsh/office/latex/templates/math/formatting.sty similarity index 100% rename from zsh/latex/templates/math/formatting.sty rename to zsh/office/latex/templates/math/formatting.sty diff --git a/zsh/office/latex/templates/math/gitignore b/zsh/office/latex/templates/math/gitignore new file mode 100644 index 0000000..e69de29 diff --git a/zsh/latex/templates/math/imports.sty b/zsh/office/latex/templates/math/imports.sty similarity index 100% rename from zsh/latex/templates/math/imports.sty rename to zsh/office/latex/templates/math/imports.sty diff --git a/zsh/latex/templates/math/shorthand.sty b/zsh/office/latex/templates/math/shorthand.sty similarity index 100% rename from zsh/latex/templates/math/shorthand.sty rename to zsh/office/latex/templates/math/shorthand.sty diff --git a/zsh/latex/templates/math/template.tex b/zsh/office/latex/templates/math/template.tex similarity index 100% rename from zsh/latex/templates/math/template.tex rename to zsh/office/latex/templates/math/template.tex diff --git a/zsh/latex/templates/times-new-roman-12/custom-headers.sty b/zsh/office/latex/templates/times-new-roman-12/custom-headers.sty similarity index 100% rename from zsh/latex/templates/times-new-roman-12/custom-headers.sty rename to zsh/office/latex/templates/times-new-roman-12/custom-headers.sty diff --git a/zsh/latex/templates/times-new-roman-12/formatting.sty b/zsh/office/latex/templates/times-new-roman-12/formatting.sty similarity index 100% rename from zsh/latex/templates/times-new-roman-12/formatting.sty rename to zsh/office/latex/templates/times-new-roman-12/formatting.sty diff --git a/zsh/latex/templates/times-new-roman-12/imports.sty b/zsh/office/latex/templates/times-new-roman-12/imports.sty similarity index 100% rename from zsh/latex/templates/times-new-roman-12/imports.sty rename to zsh/office/latex/templates/times-new-roman-12/imports.sty diff --git a/zsh/latex/templates/times-new-roman-12/template.tex b/zsh/office/latex/templates/times-new-roman-12/template.tex similarity index 100% rename from zsh/latex/templates/times-new-roman-12/template.tex rename to zsh/office/latex/templates/times-new-roman-12/template.tex diff --git a/zsh/memo/open b/zsh/office/memo/open similarity index 57% rename from zsh/memo/open rename to zsh/office/memo/open index 4df0608..5cffb1b 100755 --- a/zsh/memo/open +++ b/zsh/office/memo/open @@ -1,30 +1,31 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use office/memo + +CHECK_ENVIRONMENT ##################################################################### OPEN_MEMO() { - local MEMO_NAME=$(LIST_MEMOS | __FZF_TAIL 'select/create a memo') - [ ! "$MEMO_NAME" ] && __ABORT + local MEMO_NAME=$(MEMO__LIST_ALL | FZF_TAIL 'select/create a memo') + [ ! "$MEMO_NAME" ] && ABORT MEMO_FILE="$MEMO__DIR/$MEMO_NAME.$MEMO__FILETYPE" [ ! -f $MEMO_FILE ] && { - __STATUS "creating memo '$MEMO_NAME'" + STATUS "creating memo '$MEMO_NAME'" echo "# $MEMO_NAME" > "$MEMO_FILE" \ - && __SUCCESS "created memo '$MEMO_NAME'" \ - || __FAIL 1 "failed to create '$MEMO_FILE'" \ + && SUCCESS "created memo '$MEMO_NAME'" \ + || FAIL 1 "failed to create '$MEMO_FILE'" \ ; } DATESTRING="## $(date '+%A, %B %-d, %Y')" grep -q "$DATESTRING" "$MEMO_FILE" || echo "$DATESTRING" >> "$MEMO_FILE" - __EDIT "$MEMO_FILE" + EDIT "$MEMO_FILE" } - - ##################################################################### OPEN_MEMO $@ diff --git a/zsh/office/memo/remove b/zsh/office/memo/remove new file mode 100755 index 0000000..b80f2b1 --- /dev/null +++ b/zsh/office/memo/remove @@ -0,0 +1,33 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use office/memo + +CHECK_ENVIRONMENT +##################################################################### + +DELETE_MEMO() { + local MEMO_NAME=$(MEMO__LIST_ALL | FZF 'select a memo to delete') + local MEMO_FILE="$MEMO__DIR/$MEMO_NAME.$MEMO__FILETYPE" + [ "$MEMO_NAME" ] && [ -f "$MEMO_FILE" ] || ABORT + + STATUS "--- START OF MEMO ---------------------------------------------------" + cat "$MEMO_FILE" + STATUS "--- END OF MEMO -----------------------------------------------------" + + WARNING ' + memos are not backed up by default; deletion is permanent! + ' + + yN 'are you sure you want to delete this memo?' || ABORT + + STATUS "deleting memo '$MEMO_FILE'" + rm "$MEMO_FILE" \ + && SUCCESS "removed memo '$MEMO_NAME'" \ + || FAIL 1 "failed to remove memo '$MEMO_NAME'" \ + ; +} + +##################################################################### +DELETE_MEMO $@ diff --git a/zsh/redis/common.zsh b/zsh/redis/common.zsh deleted file mode 100644 index 3b4f44b..0000000 --- a/zsh/redis/common.zsh +++ /dev/null @@ -1,22 +0,0 @@ -_DEPENDENCIES+=( - redis-cli -) -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh - -[ ! $SCWRYPTS_CACHE_HOST ] && SCWRYPTS_CACHE_HOST=localhost -[ ! $SCWRYPTS_CACHE_PORT ] && SCWRYPTS_CACHE_PORT=6379 -##################################################################### - -_REDIS() { - local ARGS=() - - ARGS+=(-h $SCWRYPTS_CACHE_HOST) - ARGS+=(-p $SCWRYPTS_CACHE_PORT) - - [ $SCWRYPTS_CACHE_AUTH ] && ARGS+=(-a $SCWRYPTS_CACHE_AUTH) - - redis-cli ${ARGS[@]} $@ -} - -CACHE_ENABLED=$(_REDIS ping 2>&1 | grep -qi pong && echo 1 || echo 0) diff --git a/zsh/redis/curl b/zsh/redis/curl index beef55d..20c04bd 100755 --- a/zsh/redis/curl +++ b/zsh/redis/curl @@ -1,12 +1,16 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use redis + +CHECK_ENVIRONMENT ##################################################################### CURL_WITH_CACHE() { [ ! $TTL ] && TTL=10 - [[ $CACHE_ENABLED -eq 0 ]] && { + + [[ $(REDIS__ENABLED) -eq 0 ]] && { curl $@ return $? } @@ -26,7 +30,7 @@ CURL_WITH_CACHE() { done local KEY=$(GET_URL_KEY $URL) - local OUTPUT=$(_REDIS get $KEY 2>&1) + local OUTPUT=$(REDIS get $KEY 2>&1) [ $OUTPUT ] && { [[ ${#ARGS[@]} -gt 0 ]] && __WARN "cache hit found; ignoring arguments ($ARGS)" echo $OUTPUT @@ -36,8 +40,8 @@ CURL_WITH_CACHE() { local OUTPUT=$(curl -s $@) [ ! $OUTPUT ] && return 1 - _REDIS set $KEY "$OUTPUT" >/dev/null - _REDIS expire $KEY $TTL >/dev/null + REDIS set $KEY "$OUTPUT" >/dev/null + REDIS expire $KEY $TTL >/dev/null echo $OUTPUT } diff --git a/zsh/scwrypts/README.md b/zsh/scwrypts/README.md index d6e3337..7041c74 100644 --- a/zsh/scwrypts/README.md +++ b/zsh/scwrypts/README.md @@ -27,7 +27,7 @@ Command | Description You can make a child environment by naming an environment `.`. Children inherit all parent-set values, and **parent-set values overwrite child-set values**. Remember that synchronize runs *every time you edit an environment*, so changes propagate to children immediately. -Inherited values are denoted by `# inherited from ` in the environment file. +Inherited values are denoted by `# from ` in the environment file. Nested children will inherit values from all parents. diff --git a/zsh/scwrypts/common.zsh b/zsh/scwrypts/common.zsh deleted file mode 100644 index 1191a72..0000000 --- a/zsh/scwrypts/common.zsh +++ /dev/null @@ -1,4 +0,0 @@ -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### diff --git a/zsh/scwrypts/configure b/zsh/scwrypts/configure index 16d6760..ade04ee 100755 --- a/zsh/scwrypts/configure +++ b/zsh/scwrypts/configure @@ -1,37 +1,36 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use scwrypts/meta + +CHECK_ENVIRONMENT ##################################################################### -[ ! -f $SCWRYPTS_CONFIG_PATH/config ] && { - __STATUS 'first-time setup detected; creating local configuration override...' - touch $SCWRYPTS_CONFIG_PATH/config \ - && __SUCCESS 'created!' \ - || __FAIL 1 "unable to create config at '$SCWRYPTS_CONFIG_PATH/config'" - { - echo '#' - echo '# configuration for scwrypts' - echo '#' - sed -n '1d; /^###/q; p' $SCWRYPTS_ROOT/global/config.zsh | sed '$d' - } > $SCWRYPTS_CONFIG_PATH/config +USER_CONFIG_OVERRIDES="$SCWRYPTS_CONFIG_PATH/config.zsh" - __EDIT $SCWRYPTS_CONFIG_PATH/config +[ ! -f "$USER_CONFIG_OVERRIDES" ] && { + STATUS 'first-time setup detected; creating local configuration override...' + cp "$DEFAULT_CONFIG" "$USER_CONFIG_OVERRIDES" - __STATUS 'attempting to build virtual environments' - __RUN_SCWRYPT zsh/scwrypts/virtualenv/update-all \ - && __SUCCESS 'finished updating virtualenvs' \ - || __WARNING 'unable to create one or more virtualenv (see above)' \ + EDIT $USER_CONFIG_OVERRIDES + + STATUS 'attempting first-time build for virtual environments' + SCWRYPTS__RUN --name scwrypts/virtualenv/update-all --group scwrypts --type zsh \ + && SUCCESS 'finished updating virtualenvs' \ + || WARNING 'unable to create one or more virtualenv (see above)' \ ; - __REMINDER - __REMINDER 'use "zsh/scwrypts/virtualenv/update-all" to update environments' - __REMINDER '(equivalent to "npm install" or "pip install -r requirements.txt")' - __REMINDER + REMINDER ' + in the future, you can use the following scwrypt to update required virtual + environments (equivalent to "npm install" or "pip install -r requirements"): + + scwrypts --name scwrypts/virtualenv/update-all --group scwrypts --type zsh + ' } || { - __EDIT $SCWRYPTS_CONFIG_PATH/config + EDIT "$USER_CONFIG_OVERRIDES" } -__SUCCESS 'saved new configuration' -__REMINDER 'changes which affect the hot-key plugin will require a ZSHRC reload' +SUCCESS 'saved new configuration' +REMINDER 'changes which affect the hot-key plugin will require a ZSHRC reload' diff --git a/zsh/scwrypts/environment/common.zsh b/zsh/scwrypts/environment/common.zsh deleted file mode 100644 index 360cf75..0000000 --- a/zsh/scwrypts/environment/common.zsh +++ /dev/null @@ -1,14 +0,0 @@ -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### - -_SORT_ENV() { - local ENV_FILE="$1" - - _SED -i "/^# /d; /^$/d" "$ENV_FILE" - _SED -i "s/^[A-Z]/export &/; s/^[^#=]\\+$/&=/" "$ENV_FILE" - LC_COLLATE=C sort -uo "$ENV_FILE" "$ENV_FILE" -} - -_SED() { sed --follow-symlinks $@; } diff --git a/zsh/scwrypts/environment/copy b/zsh/scwrypts/environment/copy index 7939a15..5ce82c7 100755 --- a/zsh/scwrypts/environment/copy +++ b/zsh/scwrypts/environment/copy @@ -1,37 +1,33 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use scwrypts/environment-files + +CHECK_ENVIRONMENT ##################################################################### -__PROMPT 'choose an environment to copy' -TEMPLATE_ENV_NAME=$(__SELECT_ENV) -[ ! $TEMPLATE_ENV_NAME ] && __ABORT +PROMPT 'choose an environment to copy' +TEMPLATE_ENV_NAME=$(SCWRYPTS__SELECT_ENV) +[ ! $TEMPLATE_ENV_NAME ] && ABORT -__STATUS "selected '$TEMPLATE_ENV_NAME'" +STATUS "selected '$TEMPLATE_ENV_NAME'" -__PROMPT 'enter new environment name' -ENV_NAME=$(echo '' | __FZF_HEAD 'new environment') -[ ! $ENV_NAME ] && __ABORT +PROMPT 'enter new environment name' +ENV_NAME=$(echo '' | FZF_HEAD 'new environment') +[ ! $ENV_NAME ] && ABORT -TEMPLATE_ENV_FILE=$(__GET_ENV_FILE $TEMPLATE_ENV_NAME) -ENV_FILE=$(__GET_ENV_FILE $ENV_NAME) +TEMPLATE_ENV_FILE=$(SCWRYPTS__GET_ENV_FILE $TEMPLATE_ENV_NAME 2>/dev/null) +ENV_FILE=$(SCWRYPTS__GET_ENV_FILE $ENV_NAME) -[ -f "$ENV_FILE" ] && __FAIL 2 "'$ENV_NAME' already exists" +[ -f "$ENV_FILE" ] && FAIL 2 "'$ENV_NAME' already exists" -__STATUS "creating environment '$ENV_NAME'" -cp "$TEMPLATE_ENV_FILE" "$ENV_FILE" \ - && __SUCCESS "created '$ENV_NAME'" \ - || __FAIL 3 "unable to create '$ENV_NAME'" +STATUS "creating environment '$ENV_NAME'" +cat "$TEMPLATE_ENV_FILE" \ + | sed 's/ from.*//' \ + > "$ENV_FILE" \ + && SCWRYPTS__RUN --name scwrypts/environment/synchronize --group scwrypts --type zsh -- --no-prompt \ + && SUCCESS "created '$ENV_NAME'" \ + || FAIL 3 "something went wrong creating '$ENV_NAME'" -__STATUS 'stripping inherited values' -_SED -i 's/ # inherited from.*$//' "$ENV_FILE" 2>/dev/null - -__RUN_SCWRYPT zsh/scwrypts/environment/synchronize -- --no-prompt \ - || __FAIL 4 'failed to run environment sync' - -__RUN_SCWRYPT zsh/scwrypts/environment/edit -- $ENV_NAME \ - || __FAIL 4 'failed to edit new environment' - ; - -__SUCCESS "finished copy environment '$TEMPLATE_ENV_NAME > $ENV_NAME'" +SUCCESS "finished copy environment '$TEMPLATE_ENV_NAME > $ENV_NAME'" diff --git a/zsh/scwrypts/environment/delete b/zsh/scwrypts/environment/delete index 7c6d61c..63c29a7 100755 --- a/zsh/scwrypts/environment/delete +++ b/zsh/scwrypts/environment/delete @@ -1,25 +1,28 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use scwrypts/environment-files + +CHECK_ENVIRONMENT ##################################################################### -__PROMPT 'choose an environment to delete' -ENV_NAME=$(__SELECT_ENV) -[ ! $ENV_NAME ] && __ABORT +PROMPT 'choose an environment to delete' +ENV_NAME=$(SCWRYPTS__SELECT_ENV) +[ ! $ENV_NAME ] && ABORT -ENV_FILE=$(__GET_ENV_FILE $ENV_NAME) +ENV_FILE=$(SCWRYPTS__GET_ENV_FILE $ENV_NAME) -__STATUS "preparing to remove '$ENV_NAME'" +STATUS "preparing to remove '$ENV_NAME'" -__WARNING -__WARNING "the '$ENV_NAME' environment will be removed" -__WARNING 'configured options and stored credentials will be lost forever' -__WARNING +WARNING " + the '$ENV_NAME' environment will be removed + configured options and stored credentials will be lost forever + " -__yN 'continue?' || __ABORT +yN 'continue?' || ABORT -__STATUS "removing environment" +STATUS "removing environment" rm "$ENV_FILE" \ - && __SUCCESS "removed '$ENV_NAME'" \ - || __FAIL 3 "unable to remove '$ENV_FILE'; is it protected?" + && SUCCESS "removed '$ENV_NAME'" \ + || FAIL 3 "unable to remove '$ENV_FILE'; is it protected?" diff --git a/zsh/scwrypts/environment/edit b/zsh/scwrypts/environment/edit index 58c7f19..471fc92 100755 --- a/zsh/scwrypts/environment/edit +++ b/zsh/scwrypts/environment/edit @@ -1,7 +1,10 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use scwrypts/environment-files + +CHECK_ENVIRONMENT ##################################################################### [ $1 ] && ENV_NAME="$1" @@ -9,34 +12,23 @@ source ${0:a:h}/common.zsh [ ! $1 ] && { [ $SCWRYPTS_ENV ] \ && ENV_NAME=$SCWRYPTS_ENV \ - || ENV_NAME=$(__SELECT_OR_CREATE_ENV) + || ENV_NAME=$(SCWRYPTS__SELECT_OR_CREATE_ENV) } -[ ! $ENV_NAME ] && __ABORT +[ ! $ENV_NAME ] && ABORT -ENV_FILE=$(__GET_ENV_FILE $ENV_NAME) -[ ! -f $ENV_FILE ] && { - __STATUS "Creating '$ENV_NAME'..." \ - && cp $__ENV_TEMPLATE $ENV_FILE \ - && __RUN_SCWRYPT zsh/scwrypts/environment/synchronize -- --no-prompt \ - && __SUCCESS "created '$ENV_NAME'" \ - || { __ERROR "failed to create '$ENV_FILE'"; exit 1; } +ENV_FILE=$(SCWRYPTS__GET_ENV_FILE $ENV_NAME 2>/dev/null) +[ ! -f "$ENV_FILE" ] && { + STATUS "Creating '$ENV_NAME'..." \ + && touch "$ENV_FILE" \ + && SCWRYPTS__RUN --name scwrypts/environment/synchronize --group scwrypts --type zsh -- --no-prompt \ + && SUCCESS "created '$ENV_NAME'" \ + || { ERROR "failed to create '$ENV_FILE'"; exit 1; } } -__EDIT $ENV_FILE -_SORT_ENV $ENV_FILE +EDIT $ENV_FILE -while read line -do - ENV_VAR=$(echo "$line" | _SED 's/=.*$//; s/^export //') - grep -q "$ENV_VAR" $__ENV_TEMPLATE || { - ((NEW_VAR+=1)) - echo "export $ENV_VAR=" >> $__ENV_TEMPLATE - __STATUS "detected new variable '$ENV_VAR'" - } -done < $ENV_FILE - -__RUN_SCWRYPT zsh/scwrypts/environment/synchronize -- --no-prompt \ - || __FAIL 4 'failed to run environment sync' \ +SCWRYPTS__RUN --name scwrypts/environment/synchronize --group scwrypts --type zsh -- --no-prompt \ + || FAIL 4 'failed to run environment sync' \ ; -__SUCCESS "environment '$ENV_NAME' successfully modified" +SUCCESS "environment '$ENV_NAME' successfully modified" diff --git a/zsh/scwrypts/environment/stage-variables b/zsh/scwrypts/environment/stage-variables index afc38e0..2354bb7 100755 --- a/zsh/scwrypts/environment/stage-variables +++ b/zsh/scwrypts/environment/stage-variables @@ -1,7 +1,8 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +CHECK_ENVIRONMENT ##################################################################### __CHECK_REQUIRED_ENV $@ diff --git a/zsh/scwrypts/environment/synchronize b/zsh/scwrypts/environment/synchronize index b0609e2..98ff59f 100755 --- a/zsh/scwrypts/environment/synchronize +++ b/zsh/scwrypts/environment/synchronize @@ -1,154 +1,170 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use scwrypts/environment-files + +CHECK_ENVIRONMENT ##################################################################### -_SYNCHRONIZE() { +SYNCHRONIZE() { while [[ $# -gt 0 ]] do case $1 in --no-prompt ) SLIENT=1; shift 1 ;; - * ) __WARNING "argument '$1' not recognized" + * ) WARNING "argument '$1' not recognized" shift 1 ;; esac done + local TEMPLATE_FILE [ ! $SLIENT ] && { - __yN 'change the template before sync?' && __EDIT $__ENV_TEMPLATE + yN 'change the template(s) before sync?' && { + EDIT $( + for TEMPLATE_FILE in $(SCWRYPTS__GET_ENV_TEMPLATE_FILES) + do + [ -f "$TEMPLATE_FILE" ] && echo "$TEMPLATE_FILE" + [ -f "$TEMPLATE_FILE.descriptions" ] && echo "$TEMPLATE_FILE.descriptions" + done + ) + } + + for TEMPLATE_FILE in $(SCWRYPTS__GET_ENV_TEMPLATE_FILES) + do + { + echo '#!/bin/zsh' + cat "$TEMPLATE_FILE" \ + | sed ' + /__[a-z_]\+=$/d; + /^#/d; /^$/d; + s/^[A-Z]/export &/; s/^[^#=]\\+$/&=/; + ' \ + | LC_COLLATE=C sort -u + } > "$TEMPLATE_FILE.temp" + mv "$TEMPLATE_FILE.temp" "$TEMPLATE_FILE" + done } - _SORT_ENV "$__ENV_TEMPLATE" - _SED -i '/__[a-z_]\+=$/d' "$__ENV_TEMPLATE" - git add $__ENV_TEMPLATE >/dev/null 2>&1 + local ENVIRONMENTS ENVIRONMENT_FILES + local FILE NAME ENVIRONMENT_FILE - ENVIRONMENTS=$(__GET_ENV_NAMES | sort -r) + ENVIRONMENTS=($(SCWRYPTS__GET_ENV_NAMES | sort -r)) + ENVIRONMENT_FILES=($( + for NAME in ${ENVIRONMENTS[@]} + do + SCWRYPTS__GET_ENV_FILE $NAME + done + )) - _CLEAR_INHERITED_VARIABLES - _INSERT_NEW_VARIABLES - _REMOVE_OLD_VARIABLES - _SORT_AND_CASCADE - _ADD_DESCRIPTIONS + STATUS 'generating working environment files...' + for FILE in ${ENVIRONMENT_FILES[@]} + do + GENERATE_TEMP_ENVIRONMENT_FILE "$FILE" + done - __SUCCESS 'finished sync!' + STATUS 'cascading environment values to children...' + for NAME in ${ENVIRONMENTS[@]} + do + CASCADE_ENVIRONMENT $NAME + done + + STATUS 'cleaning up working space...' + for FILE in ${ENVIRONMENT_FILES[@]} + do + CLEANUP_ENVIRONMENT_FILE "$FILE" + done + SUCCESS 'finished sync!' } ##################################################################### -_CLEAR_INHERITED_VARIABLES() { - for ENV_NAME in $(echo $ENVIRONMENTS) - do - ENV_FILE=$(__GET_ENV_FILE $ENV_NAME) - _SED -i 's/ # inherited from.*//' "$ENV_FILE" - done -} - -_INSERT_NEW_VARIABLES() { - __STATUS 'inserting new environment variables...' - - local ENV_NAME ENV_FILE line - while read line - do - for ENV_NAME in $(echo $ENVIRONMENTS) - do - ENV_FILE=$(__GET_ENV_FILE $ENV_NAME) - grep -q "$line" $ENV_FILE || { - echo $line >> $ENV_FILE && __STATUS "added '$line' to '$ENV_NAME'" - } - done - done < <(_SED -n '/^./p' "$__ENV_TEMPLATE") -} - -_REMOVE_OLD_VARIABLES() { - __STATUS 'removing old environment variables...' - - local ENV_NAME ENV_FILE line - for ENV_NAME in $(echo $ENVIRONMENTS) - do - ENV_FILE=$(__GET_ENV_FILE $ENV_NAME) - while read line - do - ENV_VAR=$(echo "$line" | _SED 's/=.*/=/') - echo $ENV_VAR | grep -q '__[a-z_]\+=' && continue - - grep -q "$ENV_VAR" "$__ENV_TEMPLATE" || { - _SED -i "\\%$ENV_VAR%d" "$ENV_FILE" - echo "$ENV_VAR" | grep -qv '^#' \ - && __WARNING "removed unwanted '$ENV_VAR' from '$ENV_NAME'" - } - done < $ENV_FILE - done -} - -_SORT_AND_CASCADE() { - local ENV_NAM ENV_FILE - - for ENV_NAME in $(echo $ENVIRONMENTS) - do - ENV_FILE=$(__GET_ENV_FILE $ENV_NAME) - _CASCADE_ENVIRONMENT $ENV_NAME - done - - for ENV_NAME in $(echo $ENVIRONMENTS) - do - ENV_FILE=$(__GET_ENV_FILE $ENV_NAME) - _SORT_ENV "$ENV_FILE" - done -} - -_CASCADE_ENVIRONMENT() { +CASCADE_ENVIRONMENT() { local PARENT_NAME="$1" - local PARENT_FILE=$(__GET_ENV_FILE $PARENT_NAME) + local PARENT_FILE="$(SCWRYPTS__GET_ENV_FILE $PARENT_NAME).temp" - local CHILD_NAMES=$(echo $ENVIRONMENTS | grep "^$PARENT_NAME\\.") - [ ! $CHILD_NAMES ] && return 0 + local CHILD_NAMES=($(echo $ENVIRONMENTS | sed 's/ \+/\n/g' |grep "^$PARENT_NAME\\.")) + [[ ${#CHILD_NAMES[@]} -eq 0 ]] && return 0 - __STATUS "cascading '$PARENT_NAME' to children" - for CHILD_NAME in $(echo $CHILD_NAMES) + local PARENT_VARIABLES="$(READ_PARENT_VARIABLES "$PARENT_NAME" "$PARENT_FILE")" + [ ! $PARENT_VARIABLES ] && return 0 + + local CHILD_NAME CHILD_FILE CONTENT + for CHILD_NAME in ${CHILD_NAMES[@]} do - __SUCCESS "detected child '$CHILD_NAME'" + STATUS "propagating environment '$PARENT_NAME' to child '$CHILD_NAME'" + CHILD_FILE="$(SCWRYPTS__GET_ENV_FILE $CHILD_NAME).temp" + CONTENT=$(cat "$CHILD_FILE") + echo "$PARENT_VARIABLES" | ADD_LINES + echo "$CONTENT" > "$CHILD_FILE" done - local PARENT_VAR VAR_PATTERN CHILD_NAME CHILD_FILE - - while read PARENT_VAR - do - VAR_PATTERN=$(echo "$PARENT_VAR" | _SED 's/=.*/=/; s/\//\/\//g') - __STATUS "propagating '$(echo $VAR_PATTERN | _SED 's/^export \([^=]*\)=/\1/')' to children" - - PARENT_VAR+=" # inherited from $PARENT_NAME" - - for CHILD_NAME in $(echo $CHILD_NAMES) - do - CHILD_FILE=$(__GET_ENV_FILE $CHILD_NAME) - - _SED -i "/^$VAR_PATTERN/d" "$CHILD_FILE" - echo $PARENT_VAR >> "$CHILD_FILE" - done - done < <(_SED -n '/^[^#][^=]*=[^#]\+$/p' "$PARENT_FILE") - - __SUCCESS "finished '$PARENT_NAME' propagation" -} - -_ADD_DESCRIPTIONS() { - __STATUS 'updating descriptions' - while read DESCRIPTION_LINE - do - ENV_VAR=$(echo $DESCRIPTION_LINE | _SED 's/ \+| .*$//') - DESCRIPTION=$(echo $DESCRIPTION_LINE | _SED 's/^.* | //') - for ENV_NAME in $(echo $ENVIRONMENTS) - do - _SED -i "/^export $ENV_VAR=/i # $DESCRIPTION" "$(__GET_ENV_FILE $ENV_NAME)" - done - done < <(_SED -n '/^[^ ]\+ \+| /p' "$__ENV_TEMPLATE.descriptions") - - for ENV_NAME in $(echo $ENVIRONMENTS) - do - _SED -i "/^# /i \ " "$(__GET_ENV_FILE $ENV_NAME)" - _SED -i "s/^ $//" "$(__GET_ENV_FILE $ENV_NAME)" - done + SUCCESS "finished '$PARENT_NAME' propagation" } ##################################################################### -_SYNCHRONIZE $@ + +GENERATE_TEMP_ENVIRONMENT_FILE() { + local FILE="$1" + local CONTENT=$(GENERATE_TEMPLATE) + + READ_POPULATED_VARIABLES "$FILE" | ADD_LINES + + echo "$CONTENT" > "$FILE.temp" +} + +ADD_LINES() { + local LINE VARIABLE SHORT VALUE + while read LINE + do + VARIABLE=$(echo $LINE | sed 's/=.*$//') + echo $CONTENT | grep -qi "^$VARIABLE" || { + echo $LINE | grep -qi '__[a-z_]\+=' || { + WARNING "skipping variable $(echo $LINE | sed 's/^export //; s/=.*//') + (must be included in a template before it can be added)" + continue + } + SHORT=$(echo "$VARIABLE" | sed 's/__[a-z].*//') + CONTENT=$(echo "$CONTENT" | sed "/^$SHORT/a $LINE") + } + + CONTENT=$(echo "$CONTENT" | sed "s%^$VARIABLE.*$%$LINE%") + done +} + +READ_POPULATED_VARIABLES() { + local FILE="$1" + cat "$FILE" \ + | grep -v '^#' \ + | grep -v '=$' \ + | grep -v '^$' \ + | grep -v ' # from ' \ + | awk '/^[^=]+$/{printf "%s_____",$0;next}7' \ + | sed 's/\(_____\)\(export\)/\1\n\2/; s/\(_____\)$/\1\n/' \ + | sed 's/^.*_____.*$/_____&/' \ + | sed -z 's/[\n ] *_____/_____/g' \ + | grep -v '^$' \ + ; +} + +READ_PARENT_VARIABLES() { + local PARENT_NAME="$1" + local PARENT_FILE="$2" + READ_POPULATED_VARIABLES "$PARENT_FILE" \ + | sed 's/_____/ /g; s/\s\+/ /g' \ + | sed 's/( /(/; s/ )/)/' \ + | sed "s/$/ # from $PARENT_NAME/" \ + | grep -v '__[a-z_]\+=' \ + ; +} + +CLEANUP_ENVIRONMENT_FILE() { + cat "$1.temp" \ + | sed 's/_____$//g; s/_____/\n/g' \ + > "$1" + + rm "$1.temp" +} + +##################################################################### +SYNCHRONIZE $@ diff --git a/zsh/scwrypts/logs/clear b/zsh/scwrypts/logs/clear index 037210e..c9d0779 100755 --- a/zsh/scwrypts/logs/clear +++ b/zsh/scwrypts/logs/clear @@ -1,21 +1,22 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +CHECK_ENVIRONMENT ##################################################################### cd $SCWRYPTS_ROOT -__STATUS "Found $(ls $SCWRYPTS_LOG_PATH | wc -l) log files" -__WARNING -__WARNING 'this will permanently clear all local cloud script logs found in' -__WARNING "'$SCWRYPTS_LOG_PATH'" -__WARNING +STATUS "Found $(ls $SCWRYPTS_LOG_PATH | wc -l) log files" +WARNING " + this will permanently clear all local cloud script logs found in + '$SCWRYPTS_LOG_PATH' +" -__yN 'continue?' || __ABORT +yN 'continue?' || ABORT -__STATUS 'removing logfiles' +STATUS 'removing logfiles' rm -rf $SCWRYPTS_LOG_PATH/* \ - && __SUCCESS 'done' \ - || { __ERROR 'failed :c'; exit 2; }\ + && SUCCESS 'done' \ + || { ERROR 'failed :c'; exit 2; }\ ; diff --git a/zsh/scwrypts/logs/common.zsh b/zsh/scwrypts/logs/common.zsh deleted file mode 100644 index 1191a72..0000000 --- a/zsh/scwrypts/logs/common.zsh +++ /dev/null @@ -1,4 +0,0 @@ -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### diff --git a/zsh/scwrypts/logs/view b/zsh/scwrypts/logs/view index a39ffcf..5c83c53 100755 --- a/zsh/scwrypts/logs/view +++ b/zsh/scwrypts/logs/view @@ -1,14 +1,15 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +CHECK_ENVIRONMENT ##################################################################### cd $SCWRYPTS_ROOT -__PROMPT 'select a script log' -LOG_FILE=$(ls -t $SCWRYPTS_LOG_PATH | __FZF 'logfile') -[ ! $LOG_FILE ] && { __ERROR 'user abort'; exit 1; } +PROMPT 'select a script log' +LOG_FILE=$(ls -t $SCWRYPTS_LOG_PATH | FZF 'logfile') +[ ! $LOG_FILE ] && { ERROR 'user abort'; exit 1; } -__STATUS 'opening logfile' -__LESS "$SCWRYPTS_LOG_PATH/$LOG_FILE" -__SUCCESS 'done' +STATUS 'opening logfile' +LESS "$SCWRYPTS_LOG_PATH/$LOG_FILE" +SUCCESS 'done' diff --git a/zsh/scwrypts/virtualenv/common.zsh b/zsh/scwrypts/virtualenv/common.zsh deleted file mode 100644 index 55fdcf4..0000000 --- a/zsh/scwrypts/virtualenv/common.zsh +++ /dev/null @@ -1,124 +0,0 @@ -_DEPENDENCIES+=( - virtualenv - nodeenv -) -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### - -__AVAILABLE_VIRTUALENVS=(python node) - -##################################################################### - -__REFRESH_VIRTUALENV() { - local TYPE="$1" - [ ! $TYPE ] && { - __ERROR 'no virtualenv type specified' - return 1 - } - __STATUS "refreshing $TYPE virtual environment" - __DELETE_VIRTUALENV $TYPE \ - && __UPDATE_VIRTUALENV $TYPE \ - && __SUCCESS 'successfully refreshed virtual environment' \ - || { ERROR 'something went wrong during refresh (see above)'; return 1; } \ - ; -} - -__UPDATE_VIRTUALENV() { - local TYPE="$1" - [ ! $TYPE ] && { - __ERROR 'no virtualenv type specified' - return 1 - } - - local VIRTUALENV_PATH=$(__GET_VIRTUALENV_PATH $TYPE) - - [ ! -d $VIRTUALENV_PATH ] && __CREATE_VIRTUALENV_$TYPE $VIRTUALENV_PATH - - __STATUS "updating $TYPE virtual environment" - - source $VIRTUALENV_PATH/bin/activate || { - __ERROR 'failed to activate virtualenv; did create fail?' - return 1 - } - - cd $SCWRYPTS_ROOT - local UPDATE_CODE=0 - case $TYPE in - python ) cd py; pip install -r requirements.txt; UPDATE_CODE=$? ;; - node ) cd zx; npm install ;; - esac - UPDATE_CODE=$? - [[ $UPDATE_CODE -eq 0 ]] \ - && __SUCCESS "$TYPE virtual environment up-to-date" \ - || __ERROR "failed to update $TYPE virtual environment (see errors above)" \ - ; - - deactivate_node >/dev/null 2>&1 - deactivate >/dev/null 2>&1 - return $UPDATE_CODE -} - -__DELETE_VIRTUALENV() { - local TYPE="$1" - local VIRTUALENV_PATH="$(__GET_VIRTUALENV_PATH $TYPE)" - - __STATUS "dropping $TYPE virtual environment artifacts" - - [ ! -d $VIRTUALENV_PATH ] && { - __SUCCESS "no $TYPE environment detected" - return 0 - } - - rm -rf $VIRTUALENV_PATH \ - && __SUCCESS "succesfully cleaned up $TYPE virtual environment" \ - || { __ERROR "unabled to remove '$VIRTUALENV_PATH'"; return 1; } -} - -__GET_VIRTUALENV_PATH() { - local TYPE="$1" - case $TYPE in - python ) echo "$SCWRYPTS_VIRTUALENV_PATH/py" ;; - node ) echo "$SCWRYPTS_VIRTUALENV_PATH/zx" ;; - esac -} - -##################################################################### - -__CREATE_VIRTUALENV_python() { - local VIRTUALENV_PATH="$1" - - __STATUS 'creating python virtual environment' - local PY PYTHON - for PY in $(echo $__PREFERRED_PYTHON_VERSIONS) - do - which python$PY >/dev/null 2>&1 && { - PYTHON=$(which python$PY) - break - } - done - [ ! $PYTHON ] && { - __ERROR 'python>=3.9 not available; skipping python env' - return 1 - } - - __STATUS 'setting up virtualenv' - virtualenv $VIRTUALENV_PATH --python="$PYTHON" \ - && __SUCCESS 'python virtualenv created' \ - || { - __ERROR "unable to create '$VIRTUALENV_PATH' with '$PYTHON'" - return 2 - } -} - -__CREATE_VIRTUALENV_node() { - local VIRTUALENV_PATH="$1" - - __STATUS 'setting up nodeenv' - nodeenv $VIRTUALENV_PATH --node=$__NODE_VERSION \ - && __SUCCESS 'node virtualenv created' \ - || { - __ERROR "unable to create '$VIRTUALENV_PATH' with '$__NODE_VERSION'" - return 2 - } -} diff --git a/zsh/scwrypts/virtualenv/refresh b/zsh/scwrypts/virtualenv/refresh index 236f0bf..2bbc5cd 100755 --- a/zsh/scwrypts/virtualenv/refresh +++ b/zsh/scwrypts/virtualenv/refresh @@ -1,18 +1,30 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use scwrypts/virtualenv + +CHECK_ENVIRONMENT ##################################################################### -ENV_TYPE=$(echo $__AVAILABLE_VIRTUALENVS | sed 's/ \+/\n/' | __FZF 'select an environment to refresh') -[ ! $ENV_TYPE ] && __ABORT +while [[ $# -gt 0 ]] +do + case $1 in + -g | --group ) ENV_GROUP="$2"; shift 1 ;; + esac + shift 1 +done -__REMINDER -__REMINDER "this will permanently remove all artifacts for the scwrypts $ENV_TYPE environment" -__REMINDER "(safe unless you have put something important in $(__GET_VIRTUALENV_PATH $ENV_TYPE))" -__REMINDER +[ ! $ENV_GROUP ] && ENV_GROUP=scwrypts +ENV_TYPE=$(echo $AVAILABLE_VIRTUALENVS | sed 's/ \+/\n/' | FZF 'select an environment to refresh') +[ ! $ENV_TYPE ] && ABORT -__Yn "drop and recreate $ENV_TYPE virtual environment?" || __ABORT +REMINDER " + this will permanently remove all artifacts for the scwrypts $ENV_TYPE environment + (safe unless you have put something important in $(GET_VIRTUALENV_PATH $ENV_GROUP $ENV_TYPE)) + " -__REFRESH_VIRTUALENV $ENV_TYPE +Yn "drop and recreate $ENV_TYPE virtual environment?" || ABORT + +REFRESH_VIRTUALENV $ENV_GROUP $ENV_TYPE diff --git a/zsh/scwrypts/virtualenv/update-all b/zsh/scwrypts/virtualenv/update-all index dd7dd5b..c4d9363 100755 --- a/zsh/scwrypts/virtualenv/update-all +++ b/zsh/scwrypts/virtualenv/update-all @@ -1,18 +1,25 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +echo hey +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use scwrypts/virtualenv + +CHECK_ENVIRONMENT ##################################################################### -__STATUS 'beginning update for all environments' +STATUS 'beginning update for all environments' FAILED_COUNT=0 -for ENV_TYPE in $(echo $__AVAILABLE_VIRTUALENVS) +for ENV_GROUP in ${SCWRYPTS_GROUPS[@]} do - __UPDATE_VIRTUALENV $ENV_TYPE || ((FAILED_COUNT+=1)) + for ENV_TYPE in ${AVAILABLE_VIRTUALENVS[@]} + do + UPDATE_VIRTUALENV $ENV_GROUP $ENV_TYPE || ((FAILED_COUNT+=1)) + done done [[ $FAILED_COUNT -eq 0 ]] \ - && __SUCCESS 'all environments up-to-date' \ - || __FAIL $FAILED_COUNT 'failed to update one or more environments' + && SUCCESS 'all environments up-to-date' \ + || FAIL $FAILED_COUNT 'failed to update one or more environments' diff --git a/zsh/system/config/settings b/zsh/system/config/settings new file mode 100755 index 0000000..ce99066 --- /dev/null +++ b/zsh/system/config/settings @@ -0,0 +1,9 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use system/config + +CHECK_ENVIRONMENT +##################################################################### +EDIT "$CONFIG__USER_SETTINGS" diff --git a/zsh/config/symlinks b/zsh/system/config/symlinks similarity index 63% rename from zsh/config/symlinks rename to zsh/system/config/symlinks index 66be218..e036421 100755 --- a/zsh/config/symlinks +++ b/zsh/system/config/symlinks @@ -1,7 +1,10 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use system/config + +CHECK_ENVIRONMENT ##################################################################### SETUP_SYMLINKS() { @@ -12,10 +15,10 @@ SETUP_SYMLINKS() { } SETUP_SYMLINK() { - [ ! $2 ] && __FAIL 1 'must provide SOURCE_CONFIG and TARGET_CONFIG' + [ ! $2 ] && FAIL 1 'must provide SOURCE_CONFIG and TARGET_CONFIG' local SOURCE_CONFIG="$1" - [ ! -f "$SOURCE_CONFIG" ] && [ ! -d "$SOURCE_CONFIG" ] && __FAIL 2 "no such file or directory '$SOURCE_CONFIG'" + [ ! -f "$SOURCE_CONFIG" ] && [ ! -d "$SOURCE_CONFIG" ] && FAIL 2 "no such file or directory '$SOURCE_CONFIG'" local TARGET_CONFIG="$HOME/.config/$2" @@ -27,8 +30,8 @@ SETUP_SYMLINK() { rm "$TARGET_CONFIG" >/dev/null 2>&1 ln -s "$SOURCE_CONFIG" "$TARGET_CONFIG" \ - && __SUCCESS "successfully linked '$(basename $(dirname $TARGET_CONFIG))/$(basename $TARGET_CONFIG)'" \ - || __FAIL 3 "failed to create link '$TARGET_CONFIG'" \ + && SUCCESS "successfully linked '$(basename $(dirname $TARGET_CONFIG))/$(basename $TARGET_CONFIG)'" \ + || FAIL 3 "failed to create link '$TARGET_CONFIG'" \ ; } diff --git a/zsh/config/terminfo b/zsh/system/config/terminfo similarity index 55% rename from zsh/config/terminfo rename to zsh/system/config/terminfo index 0202b50..6f35186 100755 --- a/zsh/config/terminfo +++ b/zsh/system/config/terminfo @@ -1,25 +1,26 @@ #!/bin/zsh -_DEPENDENCIES+=( - tic -) -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=(tic) +REQUIRED_ENV+=() + +use system/config + +CHECK_ENVIRONMENT ##################################################################### SETUP_TERMINFO() { [ ! $TERMINFO_PATH ] && return 0 - [ ! -d $TERMINFO_PATH ] && __FAIL 1 "TERMINFO_PATH='$TERMINFO_PATH' does not exist" + [ ! -d $TERMINFO_PATH ] && FAIL 1 "TERMINFO_PATH='$TERMINFO_PATH' does not exist" local ERRORS=0 for TERMINFO in $(find $TERMINFO_PATH -type f) do tic -x $TERMINFO >/dev/null 2>&1 \ - && __SUCCESS "added '$(basename $TERMINFO)'" \ - || __ERROR "failed to add '$(basename $TERMINFO)'" \ + && SUCCESS "added '$(basename $TERMINFO)'" \ + || ERROR "failed to add '$(basename $TERMINFO)'" \ ; done - __ERROR_CHECK + CHECK_ERRORS } ##################################################################### diff --git a/zsh/system/config/update b/zsh/system/config/update new file mode 100755 index 0000000..dcef326 --- /dev/null +++ b/zsh/system/config/update @@ -0,0 +1,13 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use scwrypts/meta + +CHECK_ENVIRONMENT +##################################################################### + +STATUS 'updating all config files and links' +SCWRYPTS__RUN --name system/config/symlinks --group scwrypts --type zsh || exit 1 +SCWRYPTS__RUN --name system/config/terminfo --group scwrypts --type zsh || exit 2 +SUCCESS 'finished updating config files and links' diff --git a/zsh/i3/create-local-font-override b/zsh/system/i3/create-local-font-override similarity index 76% rename from zsh/i3/create-local-font-override rename to zsh/system/i3/create-local-font-override index 8792ad1..15fd59a 100755 --- a/zsh/i3/create-local-font-override +++ b/zsh/system/i3/create-local-font-override @@ -1,15 +1,12 @@ #!/bin/zsh -_DEPENDENCIES+=( +DEPENDENCIES+=( diff ) -_REQUIRED_ENV+=( +REQUIRED_ENV+=( I3__MODEL_CONFIG ) -source ${0:a:h}/common.zsh -__CHECK_ENV_VAR I3__GLOBAL_FONT_SIZE --optional -__CHECK_ENV_VAR I3__DMENU_FONT_SIZE --optional -__CHECK_ENV_VAR I3__BORDER_PIXEL_SIZE --optional +CHECK_ENVIRONMENT ##################################################################### REGEX_FONT='^\(font [^0-9]*\)\(.*\)' @@ -45,23 +42,23 @@ INSTALL() { case $1 in -f | --force ) FORCE=1 ;; -n | --no-link ) AUTOLINK=0 ;; - -h | --help ) __USAGE; exit 0 ;; + -h | --help ) USAGE; exit 0 ;; esac shift 1 done - __STATUS 'reading local i3config' + STATUS 'reading local i3config' [[ ^$I3__MODEL_CONFIG$ =~ ^$HOME/.config/i3/config$ ]] && { - __STATUS "model configuration is default configuration" + STATUS "model configuration is default configuration" I3__MODEL_CONFIG="$I3__MODEL_CONFIG.template" [ ! -f "$I3__MODEL_CONFIG" ] && { - __STATUS "creating template" + STATUS "creating template" cp "$HOME/.config/i3/config" "$I3__MODEL_CONFIG.template" } - __STATUS "referring to '$I3__MODEL_CONFIG'" + STATUS "referring to '$I3__MODEL_CONFIG'" } local CONFIG=$(cat "$I3__MODEL_CONFIG") - [ ! $CONFIG ] && __FAIL 1 "failed to read config at '$I3__MODEL_CONFIG'" + [ ! $CONFIG ] && FAIL 1 "failed to read config at '$I3__MODEL_CONFIG'" local CONFIG_FILE="$HOME/.config/i3/config" [ ! -d $(dirname "$CONFIG_FILE") ] && mkdir -p "$(dirname "$CONFIG_FILE")" @@ -69,17 +66,17 @@ INSTALL() { [ -f "$CONFIG_FILE" ] && mv "$CONFIG_FILE" "$CONFIG_FILE.bak" [ $I3__GLOBAL_FONT_SIZE ] && { - __STATUS "setting global font size to '$I3__GLOBAL_FONT_SIZE'" + STATUS "setting global font size to '$I3__GLOBAL_FONT_SIZE'" CONFIG=$(echo $CONFIG | sed "s/$REGEX_FONT/\\1$I3__GLOBAL_FONT_SIZE/") } [ $I3__DMENU_FONT_SIZE ] && { - __STATUS "setting dmenu font size to '$I3__DMENU_FONT_SIZE'" + STATUS "setting dmenu font size to '$I3__DMENU_FONT_SIZE'" CONFIG=$(echo $CONFIG | sed "s/$REGEX_DMENU/\\1$I3__DMENU_FONT_SIZE'/") } [ $I3__BORDER_PIXEL_SIZE ] && { - __STATUS "setting border pixel size to '$I3__BORDER_PIXEL_SIZE'" + STATUS "setting border pixel size to '$I3__BORDER_PIXEL_SIZE'" CONFIG=$(echo $CONFIG | sed "s/$REGEX_BORDER/\\1$I3__BORDER_PIXEL_SIZE/") } @@ -87,14 +84,14 @@ INSTALL() { [ -f "$CONFIG_FILE.bak" ] \ && diff "$CONFIG_FILE" "$CONFIG_FILE.bak" -q >/dev/null \ && mv "$CONFIG_FILE.bak" "$CONFIG_FILE" \ - && __INFO "no changes were made" \ + && INFO "no changes were made" \ ; [[ $AUTOLINK -eq 1 ]] \ && diff "$CONFIG_FILE" "$I3__MODEL_CONFIG" -q >/dev/null \ && rm "$CONFIG_FILE" \ && ln -s "$I3__MODEL_CONFIG" "$CONFIG_FILE" \ - && __INFO "output is the same as model, i3config has been linked to model" \ + && INFO "output is the same as model, i3config has been linked to model" \ ; [[ $FORCE -eq 1 ]] && rm "$CONFIG.bak" >/dev/null 2>&1 diff --git a/zsh/i3/launch-or-show b/zsh/system/i3/launch-or-show similarity index 71% rename from zsh/i3/launch-or-show rename to zsh/system/i3/launch-or-show index 1b0f46b..d8275c3 100755 --- a/zsh/i3/launch-or-show +++ b/zsh/system/i3/launch-or-show @@ -1,15 +1,18 @@ #!/bin/zsh -_DEPENDENCIES+=( +DEPENDENCIES+=( + i3-msg xdotool xrandr - i3-msg ) -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +REQUIRED_ENV+=() + +use system/desktop/notify + +CHECK_ENVIRONMENT ##################################################################### LAUNCH_OR_SHOW() { - __INFO $@ + INFO $@ local USAGE=" usage: [client-class] [...options...] @@ -28,10 +31,11 @@ LAUNCH_OR_SHOW() { Makes it easy to bind appications to key shortcuts without having to spin up redundant instances or cycle through the scratchpad queue. - Depending on state, performs one of three useful functions - 1) starts application - 2) adds application window to the scratchpad - 3) pulls application from scratchpad to foreground on active screen + Performs a variety of tasks based on states: + 1) starts and application + 2) adds all instances of the specified application to the scratchpad + 3) (toggle) hides all visible instances + 4) (toggle) shows all scratchpad-hidden instances " local APPLICATION CLIENT_CLASS @@ -55,38 +59,38 @@ LAUNCH_OR_SHOW() { -a | --always-launch ) ALWAYS_LAUNCH=1 ;; -n | --no-resize ) RESIZE=0 ;; - -h | --help ) __USAGE; exit 0 ;; + -h | --help ) USAGE; exit 0 ;; * ) [ ! $APPLICATION ] && APPLICATION="$1" \ - || __ERROR "extra positional argument '$1'" + || ERROR "extra positional argument '$1'" esac shift 1 done - [ ! $APPLICATION ] && __ERROR 'path-executable required' + [ ! $APPLICATION ] && ERROR 'path-executable required' [ ! $CLIENT_CLASS ] && CLIENT_CLASS=$APPLICATION [ $APPLICATION ] && { __CHECK_DEPENDENCY $APPLICATION || { - __ERROR "$APPLICATION is not installed" - _NOTIFY "ERROR: $APPLICATION not found" + ERROR "$APPLICATION is not installed" + NOTIFY "ERROR: $APPLICATION not found" } } - __ERROR_CHECK + ERROR_CHECK local LAUNCH_APP=$ALWAYS_LAUNCH - __STATUS "looking for window process ids" + STATUS "looking for window process ids" xdotool search --class $CLIENT_CLASS || LAUNCH_APP=1 [[ $LAUNCH_APP -eq 1 ]] && { - __STATUS 'launching application' + STATUS 'launching application' i3-msg "exec --no-startup-id $APPLICATION;" sleep .5 } - __STATUS 'getting target window size' + STATUS 'getting target window size' WINDOW_SIZE=$(\ xrandr \ | grep 'connected primary' \ @@ -94,19 +98,19 @@ LAUNCH_OR_SHOW() { | awk -v f=$SCALE -v x=$XFFSET -v y=$YFFSET \ '{print int($1*f+x)," ",int($2*f+y);}'\ ) - __INFO "window size: $WINDOW_SIZE" + INFO "window size: $WINDOW_SIZE" - __STATUS 'moving window to scratchpad' + STATUS 'moving window to scratchpad' i3-msg "[class=$CLIENT_CLASS] move scratchpad" [[ $RESIZE -eq 1 ]] \ - && __STATUS 'resizing window' \ + && STATUS 'resizing window' \ && i3-msg "[class=$CLIENT_CLASS] resize set $WINDOW_SIZE" - __STATUS 'pulling window from scratchpad to foreground' + STATUS 'pulling window from scratchpad to foreground' i3-msg "[class=$CLIENT_CLASS] scratchpad show" - __STATUS 'moving window to center of current screen' + STATUS 'moving window to center of current screen' i3-msg "[class=$CLIENT_CLASS] move position center" } diff --git a/zsh/system/packages/build b/zsh/system/packages/build new file mode 100755 index 0000000..52547f5 --- /dev/null +++ b/zsh/system/packages/build @@ -0,0 +1,9 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use scwrypts/meta + +CHECK_ENVIRONMENT +##################################################################### +SCWRYPTS__RUN --name system/packages/install --group scwrypts --type zsh -- --only-build $@ diff --git a/zsh/system/packages/download b/zsh/system/packages/download new file mode 100755 index 0000000..30f58af --- /dev/null +++ b/zsh/system/packages/download @@ -0,0 +1,9 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use scwrypts/meta + +CHECK_ENVIRONMENT +##################################################################### +SCWRYPTS__RUN --name system/packages/install --group scwrypts --type zsh -- --only-pull $@ diff --git a/zsh/git/package/install b/zsh/system/packages/install similarity index 72% rename from zsh/git/package/install rename to zsh/system/packages/install index 23e7234..92b3086 100755 --- a/zsh/git/package/install +++ b/zsh/system/packages/install @@ -1,7 +1,10 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use system/packages/git + +CHECK_ENVIRONMENT ##################################################################### INSTALL() { @@ -38,41 +41,41 @@ INSTALL() { -p | --only-pull ) SKIP_BUILD=1 ;; -c | --clean ) CLEAN=1 ;; - -h | --help ) __USAGE; exit 0 ;; + -h | --help ) USAGE; exit 0 ;; - -* ) __ERROR "unknown argument '$1'" ;; + -* ) ERROR "unknown argument '$1'" ;; * ) [ ! $TARGET ] && TARGET="$1" \ - || __ERROR "extra positional argument '$1'" \ + || ERROR "extra positional argument '$1'" \ ; ;; esac shift 1 done - [[ $SKIP_PULL -eq 1 ]] && [[ $SKIP_BUILD -eq 1 ]] && __ERROR 'only one of [-b | -p] can be specified' + [[ $SKIP_PULL -eq 1 ]] && [[ $SKIP_BUILD -eq 1 ]] && ERROR 'only one of [-b | -p] can be specified' [ ! $TARGET ] && [ ! $NAME ] && { [[ $SKIP_BUILD -eq 1 ]] && { - __ERROR 'cannot skip build without specifying package local-name' + ERROR 'cannot skip build without specifying package local-name' } || { UPDATE=1 - NAME=$(ls "$PACKAGE_INSTALL_DIR" | __FZF 'select a package to update') - [ ! $NAME ] && __ERROR 'target-url required' + NAME=$(ls "$PACKAGE_INSTALL_DIR" | FZF 'select a package to update') + [ ! $NAME ] && ERROR 'target-url required' } } - __ERROR_CHECK + ERROR_CHECK #################################################### [ ! $NAME ] && { NAME=$(echo $TARGET | sed 's/.*\///; s/\.git$//') - __INFO "using default name '$NAME'" + INFO "using default name '$NAME'" } [ -d "$PACKAGE_INSTALL_DIR/$NAME" ] && [[ $SKIP_PULL -eq 0 ]] && { - [[ $UPDATE -eq 0 ]] && __Yn "package '$NAME' already exists; update now?" && UPDATE=1 + [[ $UPDATE -eq 0 ]] && Yn "package '$NAME' already exists; update now?" && UPDATE=1 [[ $UPDATE -eq 1 ]] && PULL || return 1 } diff --git a/zsh/system/packages/update b/zsh/system/packages/update new file mode 100755 index 0000000..709d7cb --- /dev/null +++ b/zsh/system/packages/update @@ -0,0 +1,9 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use scwrypts/meta + +CHECK_ENVIRONMENT +##################################################################### +SCWRYPTS__RUN --name system/packages/install --group scwrypts --type zsh -- --update $@ diff --git a/zsh/system/vim/vundle/edit-build-actions b/zsh/system/vim/vundle/edit-build-actions new file mode 100755 index 0000000..f2a0a9a --- /dev/null +++ b/zsh/system/vim/vundle/edit-build-actions @@ -0,0 +1,9 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use system/vim/vundle + +CHECK_ENVIRONMENT +##################################################################### +EDIT "$VUNDLE__BUILD_DEFINITIONS" diff --git a/zsh/vim/vundle/install b/zsh/system/vim/vundle/install similarity index 56% rename from zsh/vim/vundle/install rename to zsh/system/vim/vundle/install index fbd1f01..9a42a36 100755 --- a/zsh/vim/vundle/install +++ b/zsh/system/vim/vundle/install @@ -1,12 +1,15 @@ #!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use system/vim/vundle + +CHECK_ENVIRONMENT ##################################################################### PLUGIN_INSTALL() { - VUNDLE_PLUGIN_INSTALL || return 1 - VUNDLE_REBUILD_PLUGINS || return 2 + VUNDLE__PLUGIN_INSTALL || return 1 + VUNDLE__REBUILD_PLUGINS || return 2 } ##################################################################### diff --git a/zsh/system/vim/vundle/rebuild b/zsh/system/vim/vundle/rebuild new file mode 100755 index 0000000..210ad6e --- /dev/null +++ b/zsh/system/vim/vundle/rebuild @@ -0,0 +1,9 @@ +#!/bin/zsh +DEPENDENCIES+=() +REQUIRED_ENV+=() + +use system/vim/vundle + +CHECK_ENVIRONMENT +##################################################################### +VUNDLE__REBUILD_PLUGINS $@ diff --git a/zsh/utils/io.zsh b/zsh/utils/io.zsh deleted file mode 100644 index 12d19bf..0000000 --- a/zsh/utils/io.zsh +++ /dev/null @@ -1,114 +0,0 @@ -__PRINT() { - local COLOR="$1" - local MESSAGE="$2" - - local LINE_END - [ $3 ] && LINE_END='' || LINE_END='\n' - - printf "${COLOR}${MESSAGE}${__COLOR_RESET}${LINE_END}" -} - -[ ! $ERRORS ] && export ERRORS=0 -__ERROR() { __PRINT $__RED "ERROR ✖ : $@" >&2; ((ERRORS+=1)); } -__SUCCESS() { __PRINT $__GREEN "SUCCESS ✔ : $@" >&2; } -__WARNING() { __PRINT $__ORANGE "WARNING  : $@" >&2; } -__STATUS() { __PRINT $__BLUE "STATUS : $@" >&2; } -__REMINDER() { __PRINT $__PURPLE "REMINDER  : $@" >&2; } -__INFO() { __PRINT $__WHITE "INFO  : $@" >&2; } - -__PROMPT() { - __PRINT $__CYAN "PROMPT  : $@" >&2 - __PRINT $__CYAN "USER  : " --no-end >&2 -} - -__FAIL() { __ERROR "${@:2}"; exit $1; } -__ABORT() { __FAIL 69 'user abort'; } - - -__ERROR_CHECK() { - [ ! $ERRORS ] && ERRORS=0 - [[ $ERRORS -ne 0 ]] && __USAGE - [[ $ERRORS -eq 0 ]] || exit $ERRORS -} - -__USAGE() { - [ ! $USAGE ] && return 0 - USAGE=$(echo $USAGE | sed "s/^\t\+//; s/\s\+$//") - - local USAGE_LINE=$(\ - echo $USAGE \ - | grep -i '^ *usage *:' \ - | sed "s;^[^:]*:;& scwrypts $SCWRYPT_NAME --;" \ - | sed 's/ \{2,\}/ /g; s/scwrypts -- scwrypts/scwrypts/' \ - ) - local THE_REST=$(echo $USAGE | grep -vi '^ *usage *:' | sed 'N;/^\n$/D;P;D;') - - { echo; __PRINT $__DARK_BLUE "$USAGE_LINE"; echo $THE_REST; echo } >&2 -} - -__INPUT() { - __PROMPT "${@:2}" - __READ $1 - local VALUE=$(eval echo '$'$1) - [ $VALUE ] -} - -__Yn() { - __PROMPT "$@ [Yn]" - [ $CI ] && { echo y; return 0; } - - local Yn; __READ -k Yn; echo - [[ $Yn =~ [nN] ]] && return 1 || return 0 -} - -__yN() { - __PROMPT "$@ [yN]" - [ $CI ] && { echo y; return 0; } - - local yN; __READ -k yN; echo - [[ $yN =~ [yY] ]] && return 0 || return 1 -} - -##################################################################### - -__GETSUDO() { - echo "\\033[1;36mPROMPT  : checking sudo password...\\033[0m" >&2 - sudo echo hi >/dev/null 2>&1 /dev/tty; } - -__FZF() { - [ $CI ] && { - __ERROR 'currently in CI, but __FZF requires user input' - exit 1 - } - - local SELECTION=$(fzf -i --height=30% --layout=reverse --prompt "$1 : " ${@:2}) - __PROMPT "$1" - echo $SELECTION >&2 - echo $SELECTION -} -__FZF_HEAD() { __FZF $@ --print-query | sed '/^$/d' | head -n1; } # prefer user input over selected -__FZF_TAIL() { __FZF $@ --print-query | sed '/^$/d' | tail -n1; } # prefer selected over user input - -__READ() { - [ $CI ] && { - __INFO 'currently in CI, skipping __READ' - return 0 - } - read $@ /dev/tty - __SUCCESS "finished editing '$1'!" -} diff --git a/zsh/utils/os.zsh b/zsh/utils/os.zsh deleted file mode 100644 index 73d2993..0000000 --- a/zsh/utils/os.zsh +++ /dev/null @@ -1,12 +0,0 @@ -__IS_MACOS() { uname -s | grep -q 'Darwin'; } - -__OPEN() { - local OPEN='' - { - command -v xdg-open && OPEN=xdg-open - command -v open && OPEN=open - } >/dev/null 2>&1 - - [ ! $OPEN ] && { __ERROR 'unable to detect default open command (e.g. xdg-open)'; return 1 } - $OPEN $@ -} diff --git a/zsh/utils/utils.module.zsh b/zsh/utils/utils.module.zsh deleted file mode 100644 index 54ddf15..0000000 --- a/zsh/utils/utils.module.zsh +++ /dev/null @@ -1,40 +0,0 @@ -##################################################################### - -_DEPENDENCIES+=(fzf) # (extensible) list of PATH dependencies -_REQUIRED_ENV+=() # (extensible) list of required environment variables - -##################################################################### - -source ${0:a:h}/colors.zsh -source ${0:a:h}/io.zsh -source ${0:a:h}/os.zsh -source ${0:a:h}/credits.zsh - -##################################################################### - -IMPORT_ERROR=0 - -source ${0:a:h}/dependencies.zsh -_DEP_ERROR=0 -_DEPENDENCIES=($(echo $_DEPENDENCIES | sort -u)) -__CHECK_DEPENDENCIES $_DEPENDENCIES || _DEP_ERROR=$? - -source ${0:a:h}/environment.zsh -_ENV_ERROR=0 -_REQUIRED_ENV=($(echo $_REQUIRED_ENV | sort -u)) -__CHECK_REQUIRED_ENV $_REQUIRED_ENV || _ENV_ERROR=$? - -[[ $_ENV_ERROR -ne 0 ]] && { - __REMINDER 'to update missing environment variables, run:' - __REMINDER "'scwrypts zsh/scwrypts/environment/edit'" -} - -((IMPORT_ERROR+=$_DEP_ERROR)) -((IMPORT_ERROR+=$_ENV_ERROR)) - -[[ $IMPORT_ERROR -ne 0 ]] && { - __ERROR "encountered $IMPORT_ERROR import error(s)" -} - -##################################################################### -[[ $IMPORT_ERROR -eq 0 ]] diff --git a/zsh/vim/common.zsh b/zsh/vim/common.zsh deleted file mode 100644 index 6634dc0..0000000 --- a/zsh/vim/common.zsh +++ /dev/null @@ -1,8 +0,0 @@ -_DEPENDENCIES+=( - vim -) -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### - -_VIM() { vim $@ /dev/tty; } diff --git a/zsh/vim/vundle/common.zsh b/zsh/vim/vundle/common.zsh deleted file mode 100644 index faf4eb9..0000000 --- a/zsh/vim/vundle/common.zsh +++ /dev/null @@ -1,49 +0,0 @@ -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/../common.zsh -##################################################################### - -VUNDLE_PLUGIN_DIR="$HOME/.vim/bundle" -VUNDLE_BUILD_DEFINITIONS="$SCWRYPTS_CONFIG_PATH/vundle.zsh" - -[ ! -f $VUNDLE_BUILD_DEFINITIONS ] && { - { - echo -e "#\n# Scwrypts Build Definitions\n#\n" - } > $VUNDLE_BUILD_DEFINITIONS -} - -VUNDLE_PLUGIN_LIST=$(ls $VUNDLE_PLUGIN_DIR | grep -v 'Vundle.vim' | grep -v 'build.zsh') -source $VUNDLE_BUILD_DEFINITIONS -for PLUGIN in $(echo $VUNDLE_PLUGIN_LIST) -do - typeset -f VUNDLE_BUILD__$PLUGIN >/dev/null 2>/dev/null || { - echo -e "\nVUNDLE_BUILD__$PLUGIN() {\n # ... build steps from $HOME/.vim/$PLUGIN \n}" \ - >> $VUNDLE_BUILD_DEFINITIONS - VUNDLE_BUILD__$PLUGIN() {} - } -done - -##################################################################### - -VUNDLE_PLUGIN_INSTALL() { - _VIM +PluginInstall +qall \ - && __SUCCESS 'successfully installed Vundle.vim plugins' \ - || __FAIL 1 'failed to install Vundle.vim plugins' -} - -VUNDLE_REBUILD_PLUGINS() { - local ERRORS=0 - - local PLUGIN - for PLUGIN in $(echo $VUNDLE_PLUGIN_LIST) - do - cd "$VUNDLE_PLUGIN_DIR/$PLUGIN" - __STATUS "building '$PLUGIN'" - VUNDLE_BUILD__$PLUGIN \ - && __SUCCESS "finished building '$PLUGIN'" \ - || __ERROR "failed to build '$PLUGIN' (see above)" \ - ; - done - - return $ERRORS -} diff --git a/zsh/vim/vundle/edit-build-actions b/zsh/vim/vundle/edit-build-actions deleted file mode 100755 index 95a4d56..0000000 --- a/zsh/vim/vundle/edit-build-actions +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### -__EDIT "$VUNDLE_BUILD_DEFINITIONS" diff --git a/zsh/vim/vundle/rebuild b/zsh/vim/vundle/rebuild deleted file mode 100755 index 888eb38..0000000 --- a/zsh/vim/vundle/rebuild +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### -VUNDLE_REBUILD_PLUGINS $@ diff --git a/zsh/youtube/download b/zsh/youtube/download deleted file mode 100755 index 33105e1..0000000 --- a/zsh/youtube/download +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### - -DOWNLOAD_VIDEO() { - local URLS=($@) - - [[ ${#URLS[@]} -eq 0 ]] && URLS=($(echo '' | __FZF_HEAD 'enter URL')) - [[ ${#URLS[@]} -eq 0 ]] && __ABORT - - local FILENAME=$(YT__GET_FILENAME $URLS) - [ ! $FILENAME ] && __ERROR "unable to download '$URLS'" - - __SUCCESS "Found '$FILENAME'" - __Yn "Proceed with download?" || return 1 - - YT__DOWNLOAD $URLS \ - && __SUCCESS "downloaded to '$YT__OUTPUT_DIR/$FILENAME'" \ - || { __ERROR "failed to download '$FILENAME'"; return 2; } -} - -##################################################################### -DOWNLOAD_VIDEO $@ diff --git a/zsh/youtube/get-audio-clip b/zsh/youtube/get-audio-clip deleted file mode 100755 index dfaa82c..0000000 --- a/zsh/youtube/get-audio-clip +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/zsh -_DEPENDENCIES+=() -_REQUIRED_ENV+=() -source ${0:a:h}/common.zsh -##################################################################### - -GET_AUDIO_CLIP() { - local URLS=($@) - - [[ ${#URLS[@]} -eq 0 ]] && URLS=($(echo '' | __FZF_HEAD 'enter URL')) - [[ ${#URLS[@]} -eq 0 ]] && __ABORT - - local FILENAME=$(YT__GET_FILENAME $URLS) - [ ! $FILENAME ] && __ERROR "unable to download '$URLS'" - - INPUT_FILE="$YT__OUTPUT_DIR/$FILENAME" - - [ ! -f "$INPUT_FILE" ] && { - __RUN_SCWRYPT youtube/download -- $URLS || return 1 - } - - __SUCCESS "video download '$FILENAME' detected!" - - LENGTH=$(GET_VIDEO_LENGTH "$INPUT_FILE") - [ ! $LENGTH ] && { __ERROR "unable to determine video length for '$INPUT_FILE'"; return 2; } - START_TIME=$(echo 0 | __FZF_HEAD "enter start time (0 ≤ t < $LENGTH)") - [ ! $START_TIME ] && __ABORT - END_TIME=$(echo $LENGTH | __FZF_HEAD "enter end time ($START_TIME > t ≥ $LENGTH)") - [ ! $END_TIME ] && __ABORT - - __STATUS - __STATUS "video : $FILENAME" - __STATUS "start time : $START_TIME" - __STATUS "end time : $END_TIME" - __STATUS - OUTPUT_FILE=$(echo '' \ - | __FZF_HEAD 'what should I call this clip? (.mp3)' \ - | sed 's/\.mp3$//' \ - ) - [ ! $OUTPUT_FILE ] && __ABORT - OUTPUT_FILE="$YT__OUTPUT_DIR/$OUTPUT_FILE.mp3" - - ffmpeg -i "$INPUT_FILE" -q:a 0 -map a \ - -ss $START_TIME -t $(($END_TIME - $START_TIME))\ - "$OUTPUT_FILE" \ - && __SUCCESS "created clip '$OUTPUT_FILE'" \ - || { __ERROR "error creating clip '$(basename $OUTPUT_FILE)' (see above)"; return 3; } -} - -##################################################################### -GET_AUDIO_CLIP $@ diff --git a/zx/hello-world.mjs b/zx/hello-world.js similarity index 99% rename from zx/hello-world.mjs rename to zx/hello-world.js index d227d83..66f49a5 100755 --- a/zx/hello-world.mjs +++ b/zx/hello-world.js @@ -2,4 +2,5 @@ const hello = ` _ _ _____ _ _ ___ __ _____ ____ _ ____ \n| | | | ____| | | | / _ \\ \\ \\ / / _ \\| _ \\| | | _ \\ \n| |_| | _| | | | | | | | | \\ \\ /\\ / / | | | |_) | | | | | |\n| _ | |___| |___| |__| |_| | \\ V V /| |_| | _ <| |___| |_| |\n|_| |_|_____|_____|_____\\___/ \\_/\\_/ \\___/|_| \\_\\_____|____/ \n ` + console.log(chalk.green.bgBlack.bold(hello)) diff --git a/zx/package.json b/zx/package.json index 92a615b..7d59bd7 100644 --- a/zx/package.json +++ b/zx/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "zx scripts for scwrypts", "main": "index.js", + "type": "module", "scripts": { "test": "", "preinstall": "npm i -g zx"