Compare commits
No commits in common. "main" and "v3.3.3" have entirely different histories.
@ -1,301 +0,0 @@
|
|||||||
---
|
|
||||||
version: 2.1
|
|
||||||
|
|
||||||
orbs:
|
|
||||||
python: circleci/python@2.1.1
|
|
||||||
|
|
||||||
|
|
||||||
executors:
|
|
||||||
archlinux:
|
|
||||||
docker:
|
|
||||||
- image: archlinux:base-devel
|
|
||||||
resource_class: small
|
|
||||||
working_directory: /
|
|
||||||
|
|
||||||
python:
|
|
||||||
docker:
|
|
||||||
- image: cimg/python:3.11
|
|
||||||
resource_class: small
|
|
||||||
|
|
||||||
nodejs:
|
|
||||||
docker:
|
|
||||||
- image: node:18
|
|
||||||
resource_class: medium
|
|
||||||
|
|
||||||
|
|
||||||
commands:
|
|
||||||
archlinux-run:
|
|
||||||
description: execute command steps in the archlinux container from the CI user
|
|
||||||
parameters:
|
|
||||||
_name:
|
|
||||||
type: string
|
|
||||||
command:
|
|
||||||
type: string
|
|
||||||
working_directory:
|
|
||||||
type: string
|
|
||||||
default: /home/ci
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: << parameters._name >>
|
|
||||||
working_directory: << parameters.working_directory >>
|
|
||||||
command: su ci -c '<< parameters.command >>'
|
|
||||||
|
|
||||||
|
|
||||||
custom:
|
|
||||||
archlinux:
|
|
||||||
prepare:
|
|
||||||
- &archlinux-prepare
|
|
||||||
run:
|
|
||||||
name: prepare archlinux dependencies
|
|
||||||
command: |
|
|
||||||
pacman --noconfirm -Syu git openssh ca-certificates-utils
|
|
||||||
useradd -m ci
|
|
||||||
echo "ci ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
|
|
||||||
|
|
||||||
temp-downgrade-fakeroot:
|
|
||||||
- &archlinux-temp-downgrade-fakeroot
|
|
||||||
run:
|
|
||||||
name: downgrade fakeroot to v1.34 (v1.35 and v1.36 are confirmed to break)
|
|
||||||
command: |
|
|
||||||
pacman -U --noconfirm https://archive.archlinux.org/packages/f/fakeroot/fakeroot-1.34-1-x86_64.pkg.tar.zst
|
|
||||||
|
|
||||||
clone-aur:
|
|
||||||
- &archlinux-clone-aur
|
|
||||||
archlinux-run:
|
|
||||||
_name: clone aur/scwrypts
|
|
||||||
command: git clone https://aur.archlinux.org/scwrypts.git aur
|
|
||||||
|
|
||||||
clone-scwrypts:
|
|
||||||
- &archlinux-clone-scwrypts
|
|
||||||
run:
|
|
||||||
name: clone wrynegade/scwrypts
|
|
||||||
working_directory: /home/ci
|
|
||||||
command: |
|
|
||||||
GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no" git clone -b "$(echo $CIRCLE_BRANCH | grep . || echo $CIRCLE_TAG)" "$CIRCLE_REPOSITORY_URL" scwrypts
|
|
||||||
chown -R ci:ci ./scwrypts
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
require-full-semver:
|
|
||||||
executor: python
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: check CIRCLE_TAG for full semantic version
|
|
||||||
command: |
|
|
||||||
: \
|
|
||||||
&& [ $CIRCLE_TAG ] \
|
|
||||||
&& [[ $CIRCLE_TAG =~ ^v[0-9]*.[0-9]*.[0-9]*$ ]] \
|
|
||||||
;
|
|
||||||
|
|
||||||
aur-test:
|
|
||||||
executor: archlinux
|
|
||||||
steps:
|
|
||||||
- *archlinux-prepare
|
|
||||||
- *archlinux-temp-downgrade-fakeroot
|
|
||||||
- *archlinux-clone-aur
|
|
||||||
- *archlinux-clone-scwrypts
|
|
||||||
- archlinux-run:
|
|
||||||
_name: test aur build on current source
|
|
||||||
working_directory: /home/ci/aur
|
|
||||||
command: >-
|
|
||||||
:
|
|
||||||
&& PKGVER=$(sed -n "s/^pkgver=//p" ./PKGBUILD)
|
|
||||||
&& cp -r ../scwrypts ../scwrypts-$PKGVER
|
|
||||||
&& rm -rf ../scwrypts-$PKGVER/.circleci
|
|
||||||
&& rm -rf ../scwrypts-$PKGVER/.git
|
|
||||||
&& rm -rf ../scwrypts-$PKGVER/.gitattributes
|
|
||||||
&& rm -rf ../scwrypts-$PKGVER/.gitignore
|
|
||||||
&& rm -rf ../scwrypts-$PKGVER/.github
|
|
||||||
&& tar -czf scwrypts.tar.gz ../scwrypts-$PKGVER
|
|
||||||
&& echo "source=(scwrypts.tar.gz)" >> PKGBUILD
|
|
||||||
&& echo "sha256sums=(SKIP)" >> PKGBUILD
|
|
||||||
&& makepkg --noconfirm -si
|
|
||||||
&& echo validating scwrypts version
|
|
||||||
&& scwrypts --version | grep "^scwrypts v$PKGVER$"
|
|
||||||
;
|
|
||||||
|
|
||||||
aur-publish:
|
|
||||||
executor: archlinux
|
|
||||||
steps:
|
|
||||||
- *archlinux-prepare
|
|
||||||
- *archlinux-temp-downgrade-fakeroot
|
|
||||||
- *archlinux-clone-aur
|
|
||||||
- archlinux-run:
|
|
||||||
_name: update PKGBUILD and .SRCINFO
|
|
||||||
working_directory: /home/ci/aur
|
|
||||||
command: >-
|
|
||||||
:
|
|
||||||
&& NEW_VERSION=$(echo $CIRCLE_TAG | sed 's/^v//')
|
|
||||||
&& sed "s/pkgver=.*/pkgver=$NEW_VERSION/; s/^pkgrel=.*/pkgrel=1/; /sha256sums/d" PKGBUILD -i
|
|
||||||
&& makepkg -g >> PKGBUILD
|
|
||||||
&& makepkg --printsrcinfo > .SRCINFO
|
|
||||||
;
|
|
||||||
- archlinux-run:
|
|
||||||
_name: sanity check for version build
|
|
||||||
working_directory: /home/ci/aur
|
|
||||||
command: >-
|
|
||||||
:
|
|
||||||
&& makepkg --noconfirm -si
|
|
||||||
&& scwrypts --version
|
|
||||||
&& scwrypts --version | grep -q "^scwrypts $CIRCLE_TAG\$"
|
|
||||||
;
|
|
||||||
- archlinux-run:
|
|
||||||
_name: publish new version
|
|
||||||
working_directory: /home/ci/aur
|
|
||||||
command: >-
|
|
||||||
:
|
|
||||||
&& git add PKGBUILD .SRCINFO
|
|
||||||
&& git -c user.email=yage@yage.io -c user.name=yage commit -am "$CIRCLE_TAG"
|
|
||||||
&& eval $(ssh-agent)
|
|
||||||
&& echo -e $SSH_KEY_PRIVATE__AUR | ssh-add -
|
|
||||||
&& git remote add upstream ssh://aur@aur.archlinux.org/scwrypts.git
|
|
||||||
&& GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no" git push upstream
|
|
||||||
;
|
|
||||||
|
|
||||||
nodejs-test:
|
|
||||||
executor: nodejs
|
|
||||||
working_directory: ~/scwrypts/zx/lib
|
|
||||||
steps:
|
|
||||||
- checkout:
|
|
||||||
path: ~/scwrypts
|
|
||||||
|
|
||||||
- restore_cache:
|
|
||||||
name: restore pnpm cache
|
|
||||||
keys:
|
|
||||||
- pnpm-packages-{{ checksum "pnpm-lock.yaml" }}
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: pnpm install
|
|
||||||
command: |
|
|
||||||
corepack enable
|
|
||||||
corepack prepare pnpm@latest-8 --activate
|
|
||||||
pnpm config set store-dir .pnpm-store
|
|
||||||
pnpm install
|
|
||||||
|
|
||||||
- save_cache:
|
|
||||||
name: save pnpm cache
|
|
||||||
key: pnpm-packages-{{ checksum "pnpm-lock.yaml" }}
|
|
||||||
paths:
|
|
||||||
- .pnpm-store
|
|
||||||
|
|
||||||
- run: pnpm test
|
|
||||||
- run: pnpm lint
|
|
||||||
- run: pnpm build
|
|
||||||
|
|
||||||
nodejs-publish:
|
|
||||||
executor: nodejs
|
|
||||||
working_directory: ~/scwrypts/zx/lib
|
|
||||||
steps:
|
|
||||||
- checkout:
|
|
||||||
path: ~/scwrypts
|
|
||||||
|
|
||||||
- restore_cache:
|
|
||||||
name: restore pnpm cache
|
|
||||||
keys:
|
|
||||||
- pnpm-packages-{{ checksum "pnpm-lock.yaml" }}
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: pnpm install
|
|
||||||
command: |
|
|
||||||
corepack enable
|
|
||||||
corepack prepare pnpm@latest-8 --activate
|
|
||||||
pnpm config set store-dir .pnpm-store
|
|
||||||
pnpm install
|
|
||||||
|
|
||||||
- save_cache:
|
|
||||||
name: save pnpm cache
|
|
||||||
key: pnpm-packages-{{ checksum "pnpm-lock.yaml" }}
|
|
||||||
paths:
|
|
||||||
- .pnpm-store
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: publish
|
|
||||||
command: |
|
|
||||||
: \
|
|
||||||
&& [ $CIRCLE_TAG ] \
|
|
||||||
&& pnpm build \
|
|
||||||
&& pnpm version $CIRCLE_TAG \
|
|
||||||
&& pnpm set //registry.npmjs.org/:_authToken=$NPM_TOKEN \
|
|
||||||
&& pnpm publish --no-git-checks \
|
|
||||||
;
|
|
||||||
|
|
||||||
python-test:
|
|
||||||
executor: python
|
|
||||||
working_directory: ~/scwrypts/py/lib
|
|
||||||
steps:
|
|
||||||
- checkout:
|
|
||||||
path: ~/scwrypts
|
|
||||||
- run:
|
|
||||||
name: pytest
|
|
||||||
command: |
|
|
||||||
: \
|
|
||||||
&& pip install . .[test] \
|
|
||||||
&& pytest \
|
|
||||||
;
|
|
||||||
- run: pip install build && python -m build
|
|
||||||
|
|
||||||
python-publish:
|
|
||||||
executor: python
|
|
||||||
working_directory: ~/scwrypts/py/lib
|
|
||||||
steps:
|
|
||||||
- checkout:
|
|
||||||
path: ~/scwrypts
|
|
||||||
- run: pip install build && python -m build
|
|
||||||
- run: pip install twine && twine upload dist/*
|
|
||||||
|
|
||||||
|
|
||||||
workflows:
|
|
||||||
test:
|
|
||||||
jobs:
|
|
||||||
- aur-test:
|
|
||||||
&dev-filters
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
ignore: /^main$/
|
|
||||||
|
|
||||||
- python-test: *dev-filters
|
|
||||||
- nodejs-test: *dev-filters
|
|
||||||
|
|
||||||
publish:
|
|
||||||
jobs:
|
|
||||||
- require-full-semver:
|
|
||||||
filters:
|
|
||||||
&only-run-on-full-semver-tag-filters
|
|
||||||
tags:
|
|
||||||
only: /^v\d+\.\d+\.\d+.*$/
|
|
||||||
branches:
|
|
||||||
ignore: /^.*$/
|
|
||||||
|
|
||||||
- aur-test:
|
|
||||||
&only-publish-for-full-semver
|
|
||||||
filters: *only-run-on-full-semver-tag-filters
|
|
||||||
requires:
|
|
||||||
- require-full-semver
|
|
||||||
- aur-publish:
|
|
||||||
#
|
|
||||||
# there's a crazy-low-chance race-condition between this job and the GH Action '../.github/workflows/automatic-release.yaml'
|
|
||||||
# - automatic-release creates the release artifact, but takes no more than 15-30 seconds (current avg:16s max:26s)
|
|
||||||
# - this publish step requires the release artifact, but waits for all language-repository publishes to complete first (a few minutes at least)
|
|
||||||
#
|
|
||||||
# if something goes wrong, this step can be safely rerun after fixing the release artifact :)
|
|
||||||
#
|
|
||||||
filters: *only-run-on-full-semver-tag-filters
|
|
||||||
context: [aur-yage]
|
|
||||||
requires:
|
|
||||||
- aur-test
|
|
||||||
- python-publish
|
|
||||||
- nodejs-publish
|
|
||||||
|
|
||||||
- python-test: *only-publish-for-full-semver
|
|
||||||
- python-publish:
|
|
||||||
filters: *only-run-on-full-semver-tag-filters
|
|
||||||
context: [pypi-yage]
|
|
||||||
requires:
|
|
||||||
- python-test
|
|
||||||
|
|
||||||
- nodejs-test: *only-publish-for-full-semver
|
|
||||||
- nodejs-publish:
|
|
||||||
filters: *only-run-on-full-semver-tag-filters
|
|
||||||
context: [npm-wrynegade]
|
|
||||||
requires:
|
|
||||||
- nodejs-test
|
|
@ -12,9 +12,13 @@ export DISCORD__DEFAULT_AVATAR_URL=
|
|||||||
export DISCORD__DEFAULT_CHANNEL_ID=
|
export DISCORD__DEFAULT_CHANNEL_ID=
|
||||||
export DISCORD__DEFAULT_USERNAME=
|
export DISCORD__DEFAULT_USERNAME=
|
||||||
export DISCORD__DEFAULT_WEBHOOK=
|
export DISCORD__DEFAULT_WEBHOOK=
|
||||||
|
export I3__BORDER_PIXEL_SIZE=
|
||||||
|
export I3__DMENU_FONT_SIZE=
|
||||||
|
export I3__GLOBAL_FONT_SIZE=
|
||||||
|
export I3__MODEL_CONFIG=
|
||||||
export LINEAR__API_TOKEN=
|
export LINEAR__API_TOKEN=
|
||||||
export MEDIA_SYNC__S3_BUCKET=
|
export MEDIA_SYNC__S3_BUCKET
|
||||||
export MEDIA_SYNC__TARGETS=
|
export MEDIA_SYNC__TARGETS
|
||||||
export REDIS_AUTH=
|
export REDIS_AUTH=
|
||||||
export REDIS_HOST=
|
export REDIS_HOST=
|
||||||
export REDIS_PORT=
|
export REDIS_PORT=
|
||||||
|
@ -15,6 +15,11 @@ DISCORD__DEFAULT_CHANNEL_ID |
|
|||||||
DISCORD__DEFAULT_USERNAME |
|
DISCORD__DEFAULT_USERNAME |
|
||||||
DISCORD__DEFAULT_WEBHOOK |
|
DISCORD__DEFAULT_WEBHOOK |
|
||||||
|
|
||||||
|
I3__BORDER_PIXEL_SIZE | custom i3 configuration settings
|
||||||
|
I3__DMENU_FONT_SIZE |
|
||||||
|
I3__GLOBAL_FONT_SIZE |
|
||||||
|
I3__MODEL_CONFIG |
|
||||||
|
|
||||||
LINEAR__API_TOKEN | linear.app project management configuration
|
LINEAR__API_TOKEN | linear.app project management configuration
|
||||||
|
|
||||||
MEDIA_SYNC__S3_BUCKET | s3 bucket name and filesystem targets for media backups
|
MEDIA_SYNC__S3_BUCKET | s3 bucket name and filesystem targets for media backups
|
||||||
|
19
.github/workflows/automatic-release.yaml
vendored
19
.github/workflows/automatic-release.yaml
vendored
@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
name: Automatic Tag-release
|
|
||||||
|
|
||||||
on: # yamllint disable-line rule:truthy
|
|
||||||
push:
|
|
||||||
branches-ignore:
|
|
||||||
- '**'
|
|
||||||
tags:
|
|
||||||
- 'v*.*.*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
automatic-tag-release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: marvinpinto/action-automatic-releases@latest
|
|
||||||
with:
|
|
||||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
|
||||||
prerelease: false
|
|
16
.github/workflows/update-semver.yaml
vendored
16
.github/workflows/update-semver.yaml
vendored
@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
name: Update Semver
|
|
||||||
|
|
||||||
on: # yamllint disable-line rule:truthy
|
|
||||||
push:
|
|
||||||
branches-ignore:
|
|
||||||
- '**'
|
|
||||||
tags:
|
|
||||||
- 'v*.*.*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-semver:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: rickstaa/action-update-semver@v1
|
|
@ -6,9 +6,6 @@ In modern developer / dev-ops workflows, scripts require a complex configuration
|
|||||||
Without a better solution, the developer is cursed to copy lines-upon-lines of variables into terminals, create random text artifacts, or maybe even commit secure credentials into source.
|
Without a better solution, the developer is cursed to copy lines-upon-lines of variables into terminals, create random text artifacts, or maybe even commit secure credentials into source.
|
||||||
Scwrypts leverages ZSH to give hot-key access to run scripts in such environments.
|
Scwrypts leverages ZSH to give hot-key access to run scripts in such environments.
|
||||||
|
|
||||||
## Major Version Upgrade Notice
|
|
||||||
|
|
||||||
Please refer to [Version 3 to Version 4 Upgrade Path](./docs/upgrade/v3-to-v4.md) when upgrading from scwrypts v3 to scwrypts v4!
|
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
Due to the wide variety of resources used by scripting libraries, the user is expected to manually resolve dependencies.
|
Due to the wide variety of resources used by scripting libraries, the user is expected to manually resolve dependencies.
|
||||||
@ -51,8 +48,6 @@ There are a few notable changes to this runtime:
|
|||||||
- User yes/no prompts will **always be YES**
|
- User yes/no prompts will **always be YES**
|
||||||
- Other user input will default to an empty string
|
- Other user input will default to an empty string
|
||||||
- Logs will not be captured
|
- Logs will not be captured
|
||||||
- Setting the environment variable `SCWRYPTS_GROUP_LOADER__[a-z_]\+` will source the file indicated in the variable (this allows custom groups without needing to modify the `config.zsh` directly)
|
|
||||||
- In GitHub actions, `*.scwrypts.zsh` groups are detected automatically from the `$GITHUB_WORKSPACE`; set `SCWRYPTS_GITHUB_NO_AUTOLOAD=true` to disable
|
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
69
action.yaml
69
action.yaml
@ -1,69 +0,0 @@
|
|||||||
--- # allow running scwrypts in Github Actions
|
|
||||||
name: scwrypts
|
|
||||||
author: yage
|
|
||||||
description: check required dependencies and run a scwrypt
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
scwrypt:
|
|
||||||
description: "args / identifiers for scwrypts CLI (e.g. '--name <scwrypt-name> --group <group-name> --type <type-name>')"
|
|
||||||
required: true
|
|
||||||
|
|
||||||
args:
|
|
||||||
description: "arguments to pass to the scwrypt-to-be-run"
|
|
||||||
required: false
|
|
||||||
|
|
||||||
version:
|
|
||||||
description: "scwrypts version; defaults to latest (minimum v3.7.0)"
|
|
||||||
required: false
|
|
||||||
|
|
||||||
scwrypts-env:
|
|
||||||
description: "override value for SCWRYPTS_ENV"
|
|
||||||
required: false
|
|
||||||
default: "ci.github-actions"
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: composite
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: wrynegade/scwrypts
|
|
||||||
path: ./wrynegade/scwrypts
|
|
||||||
ref: ${{ inputs.version }}
|
|
||||||
fetch-tags: true
|
|
||||||
|
|
||||||
- name: check dependencies
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
CI: true
|
|
||||||
SCWRYPTS_PLUGIN_ENABLED__ci: 1
|
|
||||||
run: |
|
|
||||||
[ $CI_SCWRYPTS_READY ] && [[ $CI_SCWRYPTS_READY -eq 1 ]] && echo 'setup completed previously' && exit 0
|
|
||||||
|
|
||||||
echo "updating package dependencies"
|
|
||||||
{
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install --yes zsh fzf ripgrep
|
|
||||||
|
|
||||||
for D in $($GITHUB_WORKSPACE/wrynegade/scwrypts/scwrypts -n --name check-all-dependencies --group ci --type zsh)
|
|
||||||
do
|
|
||||||
echo "--- installing $D ---"
|
|
||||||
( sudo apt-get install --yes $D; exit 0; )
|
|
||||||
done
|
|
||||||
} > $HOME/.scwrypts.apt-get.log 2>&1
|
|
||||||
|
|
||||||
echo "updating virtual dependencies"
|
|
||||||
$GITHUB_WORKSPACE/wrynegade/scwrypts/scwrypts \
|
|
||||||
--name scwrypts/virtualenv/update-all \
|
|
||||||
--group scwrypts \
|
|
||||||
--type zsh \
|
|
||||||
> $HOME/.scwrypts.virtualenv.log 2>&1
|
|
||||||
|
|
||||||
echo "CI_SCWRYPTS_READY=1" >> $GITHUB_ENV
|
|
||||||
exit 0
|
|
||||||
|
|
||||||
- name: run scwrypt
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
CI: true
|
|
||||||
SCWRYPTS_ENV: ${{ inputs.scwrypts-env }}
|
|
||||||
run: $GITHUB_WORKSPACE/wrynegade/scwrypts/scwrypts ${{inputs.scwrypt}} -- ${{inputs.args}} || exit 1
|
|
@ -1,254 +0,0 @@
|
|||||||
# Scwrypts Upgrade v3 to v4 Notes
|
|
||||||
|
|
||||||
Scwrypts v4 brings a big update to the *runstring for `zsh`-type scwrypts*.
|
|
||||||
I've found some of the boilerplate required by each individual script to be confusing and terse, and the goal here is to make it easier and safer to write and run scwrypts in this critical format.
|
|
||||||
|
|
||||||
Jump to [Technical Bits](#technical-bits) if you just want to get started with migration steps.
|
|
||||||
The actual migration _should take less than a minute per script_.
|
|
||||||
This document is deliberately verbose for future reference when I don't remember how any of this works.
|
|
||||||
|
|
||||||
## Ideology and History
|
|
||||||
|
|
||||||
Originally (scwrypts v2 and below) wanted to preserve the direct-use of individual scwrypts.
|
|
||||||
In those versions, executable files could be executed directly (outside of the `scwrypts` function) and still operate with minimal, unwanted consequences.
|
|
||||||
This resulted in a rigid structure which made code-sharing difficult at small scales and untenable in many non-trivial cases.
|
|
||||||
|
|
||||||
Scwrypts v3, begrudgingly introduced a pseudo-import syntax with `use`.
|
|
||||||
This sought to combat the issues of code-sharing and open up the structure of executable scwrypts to the scwrypts-writer.
|
|
||||||
Beyond just clarity, this allowed external libraries to be written and cross-referenced.
|
|
||||||
Although "importing" is an odd (anti?)feature to shell scripting, the way libraries could be written and reused was too helpful and I succumbed to write the `import.driver.zsh` module.
|
|
||||||
|
|
||||||
|
|
||||||
Throughout v3, I tried to maintain the "executability" of individual scwrypts.
|
|
||||||
It's ugly though.
|
|
||||||
Every individual scwrypt relies on `import.driver.zsh` and the context set up by the `scwrypts` executable.
|
|
||||||
While you _could_ run the executable file directly, it would misbehave at-best and fail pretty reliably.
|
|
||||||
|
|
||||||
So... here's v4!
|
|
||||||
Scwrypts v4 accepts the reality that, although `zsh` scwrypts are zsh, they do not stand alone without the proper context setup provided by `scwrypts`.
|
|
||||||
To improve usability, I've abstracted much of the boilerplate so you never have to see it.
|
|
||||||
I've injected safety mechanisms like `--help` arguments and utility mechanisms like flag separation (`-abc` is really `-a -b -c`) into all v4 zsh scwrypts.
|
|
||||||
|
|
||||||
You don't have to worry about checking the context, v4 does that for you!
|
|
||||||
|
|
||||||
You don't have to worry about execution, v4 does that for you!
|
|
||||||
|
|
||||||
So!
|
|
||||||
|
|
||||||
Are you coupling your zsh scripts to `scwrypts` when you write them? Yes.
|
|
||||||
Is that a bad thing? I don't think so.
|
|
||||||
Shell-scripting is such a critical coupler to real-life systems.
|
|
||||||
High-risk-high-impact to SLAs means we cannot allow context mistakes by sysadmins and users.
|
|
||||||
Reusability between local machine, cloud runtime, and CI pipelines is a must.
|
|
||||||
And if you have a need to reign all that in to single, isolated executable files...
|
|
||||||
|
|
||||||
...then good luck <3
|
|
||||||
|
|
||||||
## Technical Bits
|
|
||||||
|
|
||||||
Big idea: let's get rid of v3 boilerplate and make things easy.
|
|
||||||
|
|
||||||
### Your executable must be in a MAIN function
|
|
||||||
|
|
||||||
A main function in shell scripts?
|
|
||||||
Weird!
|
|
||||||
Don't worry, it's easy.
|
|
||||||
|
|
||||||
Take your original scwrypt, and slap the executable stuff into a function called `MAIN` (yes, it must be _exactly_, all-caps `MAIN`):
|
|
||||||
|
|
||||||
```diff
|
|
||||||
#!/bin/zsh
|
|
||||||
#####################################################################
|
|
||||||
DEPENDENCIES+=(dep-function-a dep-function-b)
|
|
||||||
REQUIRED_ENV+=()
|
|
||||||
|
|
||||||
use do/awesome/stuff --group my-custom-library
|
|
||||||
|
|
||||||
CHECK_ENVIRONMENT
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
- echo "do some stuff here"
|
|
||||||
- # ... etc ...
|
|
||||||
- SUCCESS "completed the stuff"
|
|
||||||
+ MAIN() {
|
|
||||||
+ echo "do some stuff here"
|
|
||||||
+ # ... etc ...
|
|
||||||
+ SUCCESS "completed the stuff
|
|
||||||
+ }
|
|
||||||
```
|
|
||||||
|
|
||||||
**Don't invoke the function!**
|
|
||||||
Scwrypts will now do that on your behalf.
|
|
||||||
I've already written many scwrypts which _almost_ used this syntax.
|
|
||||||
All I had to do in this case was delete the function invocation at the end:
|
|
||||||
|
|
||||||
```diff
|
|
||||||
# ... top boilerplate ...
|
|
||||||
MAIN() {
|
|
||||||
SUCCESS "look at me I'm so cool I already wrote this in a main function"
|
|
||||||
}
|
|
||||||
-
|
|
||||||
- #####################################################################
|
|
||||||
- MAIN $@
|
|
||||||
```
|
|
||||||
|
|
||||||
Again, **do not invoke the function**. Just name it `MAIN` and you're good-to-go!
|
|
||||||
|
|
||||||
### Great news!
|
|
||||||
|
|
||||||
Great news!
|
|
||||||
We have finished with *all of the necessary steps* to migrate to v4!
|
|
||||||
Easy!
|
|
||||||
|
|
||||||
While you're here, let's do a couple more things to cleanup your scwrypts (I promise they are also easy and will take less than a few seconds for each)!
|
|
||||||
|
|
||||||
### Remove the boilerplate
|
|
||||||
|
|
||||||
Were you confused by all that garbage at the top?
|
|
||||||
Awesome!
|
|
||||||
Just get rid of any of it you don't use.
|
|
||||||
|
|
||||||
While you _probably_ will still need whatever dependencies you already defined, feel free to get rid of empty config lists like `DEPENDENCIES+=()`.
|
|
||||||
For non-empty lists, the syntax remains the same (use the `+=` and make sure it's an array-type `()` just like before!)
|
|
||||||
|
|
||||||
Also you can ditch the `CHECK_ENVIRONMENT`.
|
|
||||||
While it won't hurt, v4 already does this, so just get rid of it.
|
|
||||||
Here's my recommended formatting:
|
|
||||||
```diff
|
|
||||||
#!/bin/zsh
|
|
||||||
- #####################################################################
|
|
||||||
DEPENDENCIES+=(dep-function-a dep-function-b)
|
|
||||||
- REQUIRED_ENV+=()
|
|
||||||
|
|
||||||
use do/awesome/stuff --group my-custom-library
|
|
||||||
-
|
|
||||||
- CHECK_ENVIRONMENT
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
MAIN() {
|
|
||||||
echo "do some stuff here"
|
|
||||||
# ... etc ...
|
|
||||||
SUCCESS "completed the stuff
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Get rid of `--help` argument processing
|
|
||||||
|
|
||||||
Scwrypts v4 injects the `--help` argument into every zsh scwrypt.
|
|
||||||
So there's no need to process it manually anymore!
|
|
||||||
|
|
||||||
We can now eliminate the help case from any MAIN body or library function:
|
|
||||||
|
|
||||||
```diff
|
|
||||||
MAIN() {
|
|
||||||
while [[ $# -gt 0 ]]
|
|
||||||
do
|
|
||||||
case $1 in
|
|
||||||
# ... a bunch of cases ...
|
|
||||||
- -h | --help ) USAGE; return 0 ;;
|
|
||||||
# ... a bunch of cases ...
|
|
||||||
esac
|
|
||||||
shift 1
|
|
||||||
done
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
While you probably weren't doing this, you can also do the same for any logic which splits arguments input like `-abc` which should be read as `-a -b -c`.
|
|
||||||
If you know how to do this, you know how to get rid of it.
|
|
||||||
|
|
||||||
### Write some help docs
|
|
||||||
|
|
||||||
Okay this one might take a little bit of time if you haven't done it already (but this is the last recommended step! hang in there and make your stuff better!).
|
|
||||||
If you _have_ done it already, typically by writing a variable called "USAGE" in your code, maybe consider the _new and improved_ way to write your help strings.
|
|
||||||
|
|
||||||
Returning to our original `MAIN()` example, I'll add some options parsing so we should now look something like this:
|
|
||||||
```sh
|
|
||||||
#!/bin/zsh
|
|
||||||
DEPENDENCIES+=(dep-function-a dep-function-b)
|
|
||||||
|
|
||||||
use do/awesome/stuff --group my-custom-library
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
MAIN() {
|
|
||||||
local A
|
|
||||||
local B=false
|
|
||||||
local ARGS=()
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]
|
|
||||||
do
|
|
||||||
case $1 in
|
|
||||||
-a | --option-a ) A=$2; shift 1 ;;
|
|
||||||
|
|
||||||
-b | --allow-b ) B=true ;;
|
|
||||||
|
|
||||||
* ) ARGS+=($1) ;;
|
|
||||||
esac
|
|
||||||
shift 1
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "A : $A\nB : $B\nARGS : $ARGS"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
All we have to do is add some usage variables and we're done!
|
|
||||||
I want to call out a few specific ones:
|
|
||||||
- `USAGE__options` provides descriptions for CLI flags like `-a` `--some-flag` and `-a <some value>` (reminder, you *don't* need '-h, --help' anymore!)
|
|
||||||
- `USAGE__args` provides descriptions for non-flag CLI arguments, where order matters (e.g. `cat file-a file-b ... etc`)
|
|
||||||
- `USAGE__description` provides the human-readable description of what your function does
|
|
||||||
- `USAGE__usage` you probably don't need to adjust this one, but it will be everything after the `--` in the usage-line. Defaults to include `[...options...]`, but I suppose you might want to write `USAGE__usage+=' [...args...]` if you 1) have args and 2) are really specific about your help strings.
|
|
||||||
|
|
||||||
Just add another section to define these values before declaring `MAIN`:
|
|
||||||
```sh
|
|
||||||
#!/bin/zsh
|
|
||||||
DEPENDENCIES+=(dep-function-a dep-function-b)
|
|
||||||
|
|
||||||
use do/awesome/stuff --group my-custom-library
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
USAGE__options='
|
|
||||||
-a, --option-a <string> sets the value of the A variable
|
|
||||||
-b, --allow-b enables the B option
|
|
||||||
'
|
|
||||||
|
|
||||||
# remember there's no specific formatting here, just write it nice
|
|
||||||
USAGE__args='
|
|
||||||
N-args All remaining args are inserted into the ARGS variable
|
|
||||||
'
|
|
||||||
|
|
||||||
USAGE__description="
|
|
||||||
This is my cool example function. It's really neato, but does
|
|
||||||
very little.
|
|
||||||
"
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
MAIN() {
|
|
||||||
# ... etc ...
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, when we run `scwrypts my sample -- --help`, we get:
|
|
||||||
```txt
|
|
||||||
usage: scwrypts my sample -- [...options...]
|
|
||||||
|
|
||||||
args:
|
|
||||||
N-args All remaining args are inserted into the ARGS variable
|
|
||||||
|
|
||||||
options:
|
|
||||||
-a, --option-a <string> sets the value of the A variable
|
|
||||||
-b, --allow-b enables the B option
|
|
||||||
|
|
||||||
-h, --help display this message and exit
|
|
||||||
|
|
||||||
This is my cool example function. It's really neato, but does
|
|
||||||
very little.
|
|
||||||
```
|
|
||||||
|
|
||||||
### All done
|
|
||||||
|
|
||||||
No more recommendations at this time.
|
|
||||||
Someday I'll have an auto-formatter and a language server to help with go-to-definition, but that's still for the future.
|
|
||||||
|
|
||||||
Thanks for your time and welcome to v4!
|
|
@ -1 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
@ -1,10 +0,0 @@
|
|||||||
# Kubernetes `kubectl` Helper Plugin
|
|
||||||
|
|
||||||
Leverages a local `redis` application to quickly and easily set an alias `k` for `kubectl --context <some-context> --namespace <some-namespace>`.
|
|
||||||
Much like scwrypts environments, `k` aliases are *only* shared amongst session with the same `SCWRYPTS_ENV` to prevent accidental cross-contamination.
|
|
||||||
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
Enable the plugin in `~/.config/scwrypts/config.zsh` by adding `SCWRYPTS_PLUGIN_ENABLED__KUBECTL=1`.
|
|
||||||
Use `k` as your new `kubectl` and checkout `k --help` and `k meta --help`.
|
|
@ -1,33 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
||||||
#####################################################################
|
|
||||||
MAIN() {
|
|
||||||
cd "$SCWRYPTS_ROOT__scwrypts/"
|
|
||||||
|
|
||||||
DEPENDENCIES+=()
|
|
||||||
for group in ${SCWRYPTS_GROUPS[@]}
|
|
||||||
do
|
|
||||||
[[ $group =~ ^ci$ ]] && continue
|
|
||||||
|
|
||||||
GROUP_HOME="$(eval 'echo $SCWRYPTS_ROOT__'$group)"
|
|
||||||
[ $GROUP_HOME ] && [ -d "$GROUP_HOME" ] || continue
|
|
||||||
|
|
||||||
STATUS "checking dependencies for $group"
|
|
||||||
DEPENDENCIES+=($(
|
|
||||||
for file in $(
|
|
||||||
{
|
|
||||||
cd "$GROUP_HOME"
|
|
||||||
rg -l '^DEPENDENCIES\+=\($'
|
|
||||||
rg -l '^DEPENDENCIES\+=\([^)]\+\)'
|
|
||||||
} | grep -v '\.md$' | grep -v 'check-all-dependencies$')
|
|
||||||
do
|
|
||||||
sed -z 's/.*DEPENDENCIES+=(\([^)]*\)).*/\1\n/; s/#.*\n//g; s/\s\+/\n/g' "$GROUP_HOME/$file"
|
|
||||||
done
|
|
||||||
))
|
|
||||||
done
|
|
||||||
|
|
||||||
DEPENDENCIES=(zsh $(echo $DEPENDENCIES | sed 's/ /\n/g' | sort -u | grep '^[-_a-zA-Z]\+$'))
|
|
||||||
|
|
||||||
STATUS "discovered dependencies: ($DEPENDENCIES)"
|
|
||||||
echo $DEPENDENCIES | sed 's/ /\n/g'
|
|
||||||
CHECK_ENVIRONMENT && SUCCESS "all dependencies satisfied"
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
SCWRYPTS_GROUPS+=(ci)
|
|
||||||
|
|
||||||
export SCWRYPTS_TYPE__ci=zsh
|
|
||||||
export SCWRYPTS_ROOT__ci="$SCWRYPTS_ROOT__scwrypts/plugins/ci"
|
|
||||||
export SCWRYPTS_COLOR__ci='\033[0m'
|
|
@ -1,2 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
||||||
export SCWRYPTS_KUBECTL_REDIS=
|
|
@ -1 +0,0 @@
|
|||||||
SCWRYPTS_KUBECTL_REDIS | (currently only 'managed') 'managed' or 'custom' redis configuration
|
|
@ -1,4 +0,0 @@
|
|||||||
export SCWRYPTS_KUBECTL_REDIS_HOST__managed=127.0.0.1
|
|
||||||
export SCWRYPTS_KUBECTL_REDIS_PORT__managed=26379
|
|
||||||
export SCWRYPTS_KUBECTL_REDIS_AUTH__managed=
|
|
||||||
export SCWRYPTS_KUBECTL_REDIS_KEY_PREFIX__managed=
|
|
@ -1,10 +0,0 @@
|
|||||||
# Kubernetes `kubectl` Helper Plugin
|
|
||||||
|
|
||||||
Leverages a local `redis` application to quickly and easily set an alias `k` for `kubectl --context <some-context> --namespace <some-namespace>`.
|
|
||||||
Much like scwrypts environments, `k` aliases are *only* shared amongst session with the same `SCWRYPTS_ENV` to prevent accidental cross-contamination.
|
|
||||||
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
Enable the plugin in `~/.config/scwrypts/config.zsh` by adding `SCWRYPTS_PLUGIN_ENABLED__KUBECTL=1`.
|
|
||||||
Use `k` as your new `kubectl` and checkout `k --help` and `k meta --help`.
|
|
@ -1,46 +0,0 @@
|
|||||||
#####################################################################
|
|
||||||
command -v compdef >/dev/null 2>&1 || return 0
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
for CLI in kubectl helm flux
|
|
||||||
do
|
|
||||||
eval "_${CLI[1]}() {
|
|
||||||
local SUBSESSION=0
|
|
||||||
echo \${words[2]} | grep -q '^[0-9]\\+$' && SUBSESSION=\${words[2]}
|
|
||||||
|
|
||||||
local PASSTHROUGH_WORDS=($CLI)
|
|
||||||
[[ \$CURRENT -gt 2 ]] && echo \${words[2]} | grep -qv '^[0-9]\\+$' && {
|
|
||||||
local KUBECONTEXT=\$(k \$SUBSESSION meta get context)
|
|
||||||
local NAMESPACE=\$(k \$SUBSESSION meta get namespace)
|
|
||||||
|
|
||||||
[ \$KUBECONTEXT ] \
|
|
||||||
&& PASSTHROUGH_WORDS+=($([[ $CLI =~ ^helm$ ]] && echo '--kube-context' || echo '--context') \$KUBECONTEXT) \
|
|
||||||
;
|
|
||||||
[ \$NAMESPACE ] \
|
|
||||||
&& PASSTHROUGH_WORDS+=(--namespace \$NAMESPACE) \
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
local DELIMIT_COUNT=0
|
|
||||||
local WORD
|
|
||||||
for WORD in \${words[@]:1}
|
|
||||||
do
|
|
||||||
case \$WORD in
|
|
||||||
[0-9]* ) continue ;;
|
|
||||||
-- )
|
|
||||||
echo \$words | grep -q 'exec' && ((DELIMIT_COUNT+=1))
|
|
||||||
[[ \$DELIMIT_COUNT -eq 0 ]] && ((DELIMIT_COUNT+=1)) && continue
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
PASSTHROUGH_WORDS+=(\"\$WORD\")
|
|
||||||
done
|
|
||||||
|
|
||||||
echo \"\$words\" | grep -q '\\s\\+$' && PASSTHROUGH_WORDS+=(' ')
|
|
||||||
|
|
||||||
words=\"\$PASSTHROUGH_WORDS\"
|
|
||||||
_$CLI
|
|
||||||
}
|
|
||||||
"
|
|
||||||
|
|
||||||
compdef _${CLI[1]} ${CLI[1]}
|
|
||||||
done
|
|
@ -1,198 +0,0 @@
|
|||||||
[[ $SCWRYPTS_KUBECTL_DRIVER_READY -eq 1 ]] && return 0
|
|
||||||
|
|
||||||
unalias k h f >/dev/null 2>&1
|
|
||||||
k() { _SCWRYPTS_KUBECTL_DRIVER kubectl $@; }
|
|
||||||
h() { _SCWRYPTS_KUBECTL_DRIVER helm $@; }
|
|
||||||
f() { _SCWRYPTS_KUBECTL_DRIVER flux $@; }
|
|
||||||
|
|
||||||
_SCWRYPTS_KUBECTL_DRIVER() {
|
|
||||||
[ ! $SCWRYPTS_ENV ] && {
|
|
||||||
ERROR "must set SCWRYPTS_ENV in order to use '$(echo $CLI | head -c1)'"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
which REDIS >/dev/null 2>&1 \
|
|
||||||
|| eval "$(scwrypts -n --name meta/get-static-redis-definition --type zsh --group kubectl)"
|
|
||||||
|
|
||||||
local CLI="$1"; shift 1
|
|
||||||
|
|
||||||
local SCWRYPTS_GROUP CUSTOM_COMMANDS=(meta)
|
|
||||||
for SCWRYPTS_GROUP in ${SCWRYPTS_GROUPS[@]}
|
|
||||||
do
|
|
||||||
CUSTOM_COMMANDS+=($(eval echo '$SCWRYPTS_KUBECTL_CUSTOM_COMMANDS__'$SCWRYPTS_GROUP))
|
|
||||||
done
|
|
||||||
|
|
||||||
##########################################
|
|
||||||
|
|
||||||
local USAGE="
|
|
||||||
usage: - [...args...] [...options...] -- [...$CLI options...]
|
|
||||||
|
|
||||||
args: -
|
|
||||||
|
|
||||||
options: -
|
|
||||||
--subsession [0-9] use indicated subsession (use for script clarity instead of positional arg)
|
|
||||||
|
|
||||||
-h, --help display this help dialogue
|
|
||||||
-v, --verbose output debugging information
|
|
||||||
|
|
||||||
description: -
|
|
||||||
"
|
|
||||||
|
|
||||||
local USAGE__usage=$(echo $CLI | head -c1)
|
|
||||||
|
|
||||||
local USAGE__args="$(
|
|
||||||
{
|
|
||||||
echo "\\033[0;32m[0-9]\\033[0m^if the first argument is a number 0-9, uses or creates a subsession (default 0)"
|
|
||||||
echo " ^ "
|
|
||||||
for C in ${CUSTOM_COMMANDS[@]}
|
|
||||||
do
|
|
||||||
echo "\\033[0;32m$C\\033[0m^$(SCWRYPTS_KUBECTL_CUSTOM_COMMAND_DESCRIPTION__$C 2>/dev/null)"
|
|
||||||
done
|
|
||||||
} | column -ts '^'
|
|
||||||
)"
|
|
||||||
|
|
||||||
local USAGE__options="
|
|
||||||
-n, --namespace set the namespace
|
|
||||||
-k, --context set the context
|
|
||||||
"
|
|
||||||
|
|
||||||
local USAGE__description="
|
|
||||||
Provides 'k' (kubectl), 'h' (helm), and 'f' (flux) shorthands to the respective
|
|
||||||
utility. These functions leverage redis and scwrypts environments to
|
|
||||||
allow quick selection of contexts and namespaces usable across all
|
|
||||||
active shell instances.
|
|
||||||
|
|
||||||
The scwrypts group 'kubectl' has simple selection executables for
|
|
||||||
kubecontext and namespace, but also provides the library to enable
|
|
||||||
enriched, use-case-sensitive setup of kubernetes context.
|
|
||||||
|
|
||||||
All actions are scoped to the current SCWRYPTS_ENV
|
|
||||||
currently : \\033[0;33m$SCWRYPTS_ENV\\033[0m
|
|
||||||
|
|
||||||
"
|
|
||||||
|
|
||||||
##########################################
|
|
||||||
|
|
||||||
local USER_ARGS=()
|
|
||||||
|
|
||||||
local CUSTOM_COMMAND=0
|
|
||||||
local VERBOSE=0
|
|
||||||
local HELP=0
|
|
||||||
local ERRORS=0
|
|
||||||
|
|
||||||
local COMMAND_SWITCH_CASE="@($(echo $CUSTOM_COMMANDS | sed 's/ /|/g'))"
|
|
||||||
|
|
||||||
[ ! $SUBSESSION ] && local SUBSESSION=0
|
|
||||||
[[ $1 =~ ^[0-9]$ ]] && SUBSESSION=$1 && shift 1
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]
|
|
||||||
do
|
|
||||||
case $1 in
|
|
||||||
meta )
|
|
||||||
CUSTOM_COMMAND=$1
|
|
||||||
SCWRYPTS_KUBECTL_CUSTOM_COMMAND_PARSE__$1 ${@:2}
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
|
|
||||||
-v | --verbose ) VERBOSE=1 ;;
|
|
||||||
-h | --help ) HELP=1 ;;
|
|
||||||
|
|
||||||
--subsession ) SUBSESSION=$2; shift 1 ;;
|
|
||||||
|
|
||||||
-n | --namespace )
|
|
||||||
_SCWRYPTS_KUBECTL_DRIVER kubectl meta set namespace $2
|
|
||||||
shift 1
|
|
||||||
;;
|
|
||||||
|
|
||||||
-k | --context | --kube-context )
|
|
||||||
_SCWRYPTS_KUBECTL_DRIVER kubectl meta set context $2
|
|
||||||
shift 1
|
|
||||||
;;
|
|
||||||
|
|
||||||
-- )
|
|
||||||
echo $USER_ARGS | grep -q 'exec' && USER_ARGS+=(--)
|
|
||||||
shift 1
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
|
|
||||||
* )
|
|
||||||
[ ! $CUSTOM_COMMAND ] && {
|
|
||||||
for C in ${CUSTOM_COMMANDS[@]}
|
|
||||||
do
|
|
||||||
[[ $1 =~ ^$C$ ]] && {
|
|
||||||
SCWRYPTS_KUBECTL_CUSTOM_COMMAND_PARSE__$1 ${@:2}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
done
|
|
||||||
}
|
|
||||||
USER_ARGS+=($1)
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
shift 1
|
|
||||||
done
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do USER_ARGS+=($1); shift 1; done
|
|
||||||
|
|
||||||
|
|
||||||
CHECK_ERRORS --no-fail || return 1
|
|
||||||
|
|
||||||
[[ $HELP -eq 1 ]] && { USAGE; return 0; }
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
local STRICT=$(_SCWRYPTS_KUBECTL_SETTINGS get strict || echo 1)
|
|
||||||
|
|
||||||
case $CUSTOM_COMMAND in
|
|
||||||
0 )
|
|
||||||
local CLI_ARGS=()
|
|
||||||
|
|
||||||
local CONTEXT=$(k meta get context)
|
|
||||||
local NAMESPACE=$(k meta get namespace)
|
|
||||||
|
|
||||||
[ $CONTEXT ] && [[ $CLI =~ ^helm$ ]] && CLI_ARGS+=(--kube-context $CONTEXT)
|
|
||||||
[ $CONTEXT ] && [[ $CLI =~ ^kubectl$ ]] && CLI_ARGS+=(--context $CONTEXT)
|
|
||||||
[ $CONTEXT ] && [[ $CLI =~ ^flux$ ]] && CLI_ARGS+=(--context $CONTEXT)
|
|
||||||
|
|
||||||
[[ $STRICT -eq 1 ]] && {
|
|
||||||
[ $CONTEXT ] || ERROR "missing kubectl 'context'"
|
|
||||||
[ $NAMESPACE ] || ERROR "missing kubectl 'namespace'"
|
|
||||||
|
|
||||||
CHECK_ERRORS --no-fail --no-usage || {
|
|
||||||
ERROR "with 'strict' settings enabled, context and namespace must be set!"
|
|
||||||
REMINDER "
|
|
||||||
these values can be set directly with
|
|
||||||
$(echo $CLI | head -c1) meta set (namespace|context)
|
|
||||||
"
|
|
||||||
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[ $NAMESPACE ] && CLI_ARGS+=(--namespace $NAMESPACE)
|
|
||||||
[[ $VERBOSE -eq 1 ]] && {
|
|
||||||
REMINDER "
|
|
||||||
context '$CONTEXT'
|
|
||||||
namespace '$NAMESPACE'
|
|
||||||
environment '$SCWRYPTS_ENV'
|
|
||||||
subsession '$SUBSESSION'
|
|
||||||
"
|
|
||||||
STATUS "running $CLI ${CLI_ARGS[@]} ${USER_ARGS[@]}"
|
|
||||||
} || {
|
|
||||||
[[ $(_SCWRYPTS_KUBECTL_SETTINGS get context) =~ ^show$ ]] && {
|
|
||||||
REMINDER "$SCWRYPTS_ENV.$SUBSESSION : $CLI ${CLI_ARGS[@]} ${USER_ARGS[@]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$CLI ${CLI_ARGS[@]} ${USER_ARGS[@]}
|
|
||||||
;;
|
|
||||||
* ) SCWRYPTS_KUBECTL_CUSTOM_COMMAND__$CUSTOM_COMMAND ${USER_ARGS[@]} ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
_SCWRYPTS_KUBECTL_SETTINGS() {
|
|
||||||
# (get setting-name) or (set setting-name setting-value)
|
|
||||||
REDIS h$1 ${SCWRYPTS_ENV}:kubectl:settings ${@:2} | grep .
|
|
||||||
}
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
source ${0:a:h}/kubectl.completion.zsh
|
|
||||||
source ${0:a:h}/meta.zsh
|
|
@ -1,147 +0,0 @@
|
|||||||
SCWRYPTS_KUBECTL_CUSTOM_COMMAND_PARSE__meta() {
|
|
||||||
USAGE__usage+=" meta"
|
|
||||||
USAGE__args="
|
|
||||||
- get output value of meta variable
|
|
||||||
- set interactively configure value of meta variable
|
|
||||||
- clear clear current subsession variables
|
|
||||||
|
|
||||||
(settings args)
|
|
||||||
- show output context for every command
|
|
||||||
- hide (default) hide output context for every command
|
|
||||||
|
|
||||||
- strict (default) require context *and* namespace to be set
|
|
||||||
- loose do not require context and namespace to be set
|
|
||||||
"
|
|
||||||
USAGE__options=''
|
|
||||||
USAGE__description=$(SCWRYPTS_KUBECTL_CUSTOM_COMMAND_DESCRIPTION__meta)
|
|
||||||
|
|
||||||
META_SUBARGS="
|
|
||||||
- namespace
|
|
||||||
- context
|
|
||||||
"
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]
|
|
||||||
do
|
|
||||||
case $1 in
|
|
||||||
-h | --help ) HELP=1 ;;
|
|
||||||
|
|
||||||
set )
|
|
||||||
USAGE__usage+=" set"
|
|
||||||
USAGE__args="set (namespace|context)"
|
|
||||||
USAGE__description="interactively set a namespace or context for '$SCWRYPTS_ENV'"
|
|
||||||
case $2 in
|
|
||||||
namespace | context ) USER_ARGS+=($1 $2 $3); [ $3 ] && shift 1 ;;
|
|
||||||
-h | --help ) HELP=1 ;;
|
|
||||||
'' )
|
|
||||||
: \
|
|
||||||
&& SCWRYPTS_KUBECTL_CUSTOM_COMMAND__meta set context \
|
|
||||||
&& SCWRYPTS_KUBECTL_CUSTOM_COMMAND__meta set namespace \
|
|
||||||
;
|
|
||||||
return $?
|
|
||||||
;;
|
|
||||||
|
|
||||||
* ) ERROR "cannot set '$2'" ;;
|
|
||||||
esac
|
|
||||||
shift 1
|
|
||||||
;;
|
|
||||||
|
|
||||||
get )
|
|
||||||
USAGE__usage+=" get"
|
|
||||||
USAGE__args="get (namespace|context|all)"
|
|
||||||
USAGE__description="output the current namespace or context for '$SCWRYPTS_ENV'"
|
|
||||||
case $2 in
|
|
||||||
namespace | context | all ) USER_ARGS+=($1 $2) ;;
|
|
||||||
|
|
||||||
-h | --help ) HELP=1 ;;
|
|
||||||
|
|
||||||
* ) ERROR "cannot get '$2'" ;;
|
|
||||||
esac
|
|
||||||
shift 1
|
|
||||||
;;
|
|
||||||
|
|
||||||
copy )
|
|
||||||
USAGE__usage+=" copy"
|
|
||||||
USAGE__args+="copy [0-9]"
|
|
||||||
USAGE__description="copy current subsession ($SUBSESSION) to target subsession id"
|
|
||||||
case $2 in
|
|
||||||
[0-9] ) USER_ARGS+=($1 $2) ;;
|
|
||||||
-h | --help ) HELP=1 ;;
|
|
||||||
* ) ERROR "target session must be a number [0-9]" ;;
|
|
||||||
esac
|
|
||||||
shift 1
|
|
||||||
;;
|
|
||||||
|
|
||||||
clear | show | hide | strict | loose ) USER_ARGS+=($1) ;;
|
|
||||||
|
|
||||||
* ) ERROR "no meta command '$1'"
|
|
||||||
esac
|
|
||||||
shift 1
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
SCWRYPTS_KUBECTL_CUSTOM_COMMAND__meta() {
|
|
||||||
case $1 in
|
|
||||||
get )
|
|
||||||
[[ $2 =~ ^all$ ]] && {
|
|
||||||
local CONTEXT=$(REDIS get --prefix current:context | grep . || echo "\\033[1;31mnone set\\033[0m")
|
|
||||||
local NAMESPACE=$(REDIS get --prefix current:namespace | grep . || echo "\\033[1;31mnone set\\033[0m")
|
|
||||||
echo "
|
|
||||||
environment : $SCWRYPTS_ENV
|
|
||||||
context : $CONTEXT
|
|
||||||
namespace : $NAMESPACE
|
|
||||||
|
|
||||||
CLI settings
|
|
||||||
command context : $(_SCWRYPTS_KUBECTL_SETTINGS get context)
|
|
||||||
strict mode : $([[ $STRICT -eq 1 ]] && echo "on" || echo "\\033[1;31moff\\033[0m")
|
|
||||||
" | sed 's/^ \+//' >&2
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
REDIS get --prefix current:$2
|
|
||||||
;;
|
|
||||||
|
|
||||||
set )
|
|
||||||
scwrypts -n --name set-$2 --type zsh --group kubectl -- $3 --subsession $SUBSESSION >/dev/null \
|
|
||||||
&& SUCCESS "$2 set"
|
|
||||||
;;
|
|
||||||
|
|
||||||
copy )
|
|
||||||
: \
|
|
||||||
&& STATUS "copying $1 to $2" \
|
|
||||||
&& scwrypts -n --name set-context --type zsh --group kubectl -- --subsession $2 $(k meta get context | grep . || echo 'reset') \
|
|
||||||
&& scwrypts -n --name set-namespace --type zsh --group kubectl -- --subsession $2 $(k meta get namespace | grep . || echo 'reset') \
|
|
||||||
&& SUCCESS "subsession $1 copied to $2" \
|
|
||||||
;
|
|
||||||
;;
|
|
||||||
|
|
||||||
clear )
|
|
||||||
scwrypts -n --name set-context --type zsh --group kubectl -- --subsession $SUBSESSION reset >/dev/null \
|
|
||||||
&& SUCCESS "subsession $SUBSESSION reset to default"
|
|
||||||
;;
|
|
||||||
|
|
||||||
show )
|
|
||||||
_SCWRYPTS_KUBECTL_SETTINGS set context show >/dev/null \
|
|
||||||
&& SUCCESS "now showing full command context"
|
|
||||||
;;
|
|
||||||
|
|
||||||
hide )
|
|
||||||
_SCWRYPTS_KUBECTL_SETTINGS set context hide >/dev/null \
|
|
||||||
&& SUCCESS "now hiding command context"
|
|
||||||
;;
|
|
||||||
|
|
||||||
loose )
|
|
||||||
_SCWRYPTS_KUBECTL_SETTINGS set strict 0 >/dev/null \
|
|
||||||
&& WARNING "now running in 'loose' mode"
|
|
||||||
;;
|
|
||||||
|
|
||||||
strict )
|
|
||||||
_SCWRYPTS_KUBECTL_SETTINGS set strict 1 >/dev/null \
|
|
||||||
&& SUCCESS "now running in 'strict' mode"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
SCWRYPTS_KUBECTL_CUSTOM_COMMAND_DESCRIPTION__meta() {
|
|
||||||
[ $CLI ] || CLI='kubectl'
|
|
||||||
echo "operations for $CLI session variables and other CLI settings"
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
||||||
use kubectl --group kubectl
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
MAIN() {
|
|
||||||
KUBECTL__GET_CONTEXT
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
||||||
use kubectl --group kubectl
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
MAIN() {
|
|
||||||
KUBECTL__GET_NAMESPACE
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
SCWRYPTS_GROUPS+=(kubectl)
|
|
||||||
|
|
||||||
export SCWRYPTS_TYPE__kubectl=zsh
|
|
||||||
export SCWRYPTS_ROOT__kubectl="$SCWRYPTS_ROOT__scwrypts/plugins/kubectl"
|
|
||||||
export SCWRYPTS_COLOR__kubectl='\033[0;31m'
|
|
||||||
|
|
||||||
SCWRYPTS_STATIC_CONFIG__kubectl+=(
|
|
||||||
"$SCWRYPTS_ROOT__kubectl/.config/static/redis.zsh"
|
|
||||||
)
|
|
||||||
|
|
||||||
source "$SCWRYPTS_ROOT__kubectl/driver/kubectl.driver.zsh"
|
|
@ -1,158 +0,0 @@
|
|||||||
#####################################################################
|
|
||||||
|
|
||||||
DEPENDENCIES+=(
|
|
||||||
kubectl
|
|
||||||
)
|
|
||||||
|
|
||||||
REQUIRED_ENV+=()
|
|
||||||
|
|
||||||
use redis --group kubectl
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
KUBECTL() {
|
|
||||||
local NAMESPACE=$(REDIS get --prefix "current:namespace")
|
|
||||||
local CONTEXT=$(KUBECTL__GET_CONTEXT)
|
|
||||||
|
|
||||||
local KUBECTL_ARGS=()
|
|
||||||
[ $NAMESPACE ] && KUBECTL_ARGS+=(--namespace $NAMESPACE)
|
|
||||||
[ $CONTEXT ] && KUBECTL_ARGS+=(--context $CONTEXT)
|
|
||||||
|
|
||||||
kubectl ${KUBECTL_ARGS[@]} $@
|
|
||||||
}
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
KUBECTL__GET_CONTEXT() { REDIS get --prefix "current:context"; }
|
|
||||||
|
|
||||||
KUBECTL__SET_CONTEXT() {
|
|
||||||
local CONTEXT=$1
|
|
||||||
[ ! $CONTEXT ] && return 1
|
|
||||||
|
|
||||||
[[ $CONTEXT =~ reset ]] && {
|
|
||||||
: \
|
|
||||||
&& REDIS del --prefix "current:context" \
|
|
||||||
&& KUBECTL__SET_NAMESPACE reset \
|
|
||||||
;
|
|
||||||
return $?
|
|
||||||
}
|
|
||||||
|
|
||||||
: \
|
|
||||||
&& REDIS set --prefix "current:context" "$CONTEXT" \
|
|
||||||
&& KUBECTL__SET_NAMESPACE reset \
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
KUBECTL__SELECT_CONTEXT() {
|
|
||||||
KUBECTL__LIST_CONTEXTS | FZF 'select a context'
|
|
||||||
}
|
|
||||||
|
|
||||||
KUBECTL__LIST_CONTEXTS() {
|
|
||||||
echo reset
|
|
||||||
local ALL_CONTEXTS=$(KUBECTL config get-contexts -o name | sort)
|
|
||||||
|
|
||||||
echo $ALL_CONTEXTS | grep -v '^arn:aws:eks'
|
|
||||||
|
|
||||||
[[ $AWS_ACCOUNT ]] && {
|
|
||||||
echo $ALL_CONTEXTS | grep "^arn:aws:eks:.*:$AWS_ACCOUNT"
|
|
||||||
true
|
|
||||||
} || {
|
|
||||||
echo $ALL_CONTEXTS | grep '^arn:aws:eks'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
KUBECTL__GET_NAMESPACE() { REDIS get --prefix "current:namespace"; }
|
|
||||||
|
|
||||||
KUBECTL__SET_NAMESPACE() {
|
|
||||||
local NAMESPACE=$1
|
|
||||||
[ ! $NAMESPACE ] && return 1
|
|
||||||
|
|
||||||
[[ $NAMESPACE =~ reset ]] && {
|
|
||||||
REDIS del --prefix "current:namespace"
|
|
||||||
return $?
|
|
||||||
}
|
|
||||||
|
|
||||||
REDIS set --prefix "current:namespace" "$NAMESPACE"
|
|
||||||
}
|
|
||||||
|
|
||||||
KUBECTL__SELECT_NAMESPACE() {
|
|
||||||
KUBECTL__LIST_NAMESPACES | FZF 'select a namespace'
|
|
||||||
}
|
|
||||||
|
|
||||||
KUBECTL__LIST_NAMESPACES() {
|
|
||||||
echo reset
|
|
||||||
echo default
|
|
||||||
KUBECTL get namespaces -o name | sed 's/^namespace\///' | sort
|
|
||||||
}
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
KUBECTL__SERVE() {
|
|
||||||
[ $CONTEXT ] || local CONTEXT=$(KUBECTL__GET_CONTEXT)
|
|
||||||
[ $CONTEXT ] || ERROR 'must configure a context in which to serve'
|
|
||||||
|
|
||||||
[ $NAMESPACE ] || local NAMESPACE=$(KUBECTL__GET_NAMESPACE)
|
|
||||||
[ $NAMESPACE ] || ERROR 'must configure a namespace in which to serve'
|
|
||||||
|
|
||||||
CHECK_ERRORS --no-fail --no-usage || return 1
|
|
||||||
|
|
||||||
[ $SERVICE ] && SERVICE=$(KUBECTL__LIST_SERVICES | jq -c "select (.service == \"$SERVICE\")" || echo $SERVICE)
|
|
||||||
[ $SERVICE ] || local SERVICE=$(KUBECTL__SELECT_SERVICE)
|
|
||||||
[ $SERVICE ] || ERROR 'must provide or select a service'
|
|
||||||
|
|
||||||
KUBECTL__LIST_SERVICES | grep -q "^$SERVICE$"\
|
|
||||||
|| ERROR "no service '$SERVICE' in '$CONFIG/$NAMESPACE'"
|
|
||||||
|
|
||||||
CHECK_ERRORS --no-fail --no-usage || return 1
|
|
||||||
|
|
||||||
##########################################
|
|
||||||
|
|
||||||
SERVICE_PASSWORD="$(KUBECTL__GET_SERVICE_PASSWORD)"
|
|
||||||
KUBECTL__SERVICE_PARSE
|
|
||||||
|
|
||||||
REMINDER "attempting to serve ${NAMESPACE}/${SERVICE_NAME}:${SERVICE_PORT}"
|
|
||||||
[ $SERVICE_PASSWORD ] && REMINDER "password : $SERVICE_PASSWORD"
|
|
||||||
|
|
||||||
KUBECTL port-forward service/$SERVICE_NAME $SERVICE_PORT
|
|
||||||
}
|
|
||||||
|
|
||||||
KUBECTL__SELECT_SERVICE() {
|
|
||||||
[ $NAMESPACE ] || local NAMESPACE=$(KUBECTL__GET_NAMESPACE)
|
|
||||||
[ $NAMESPACE ] || return 1
|
|
||||||
|
|
||||||
local SERVICES=$(KUBECTL__LIST_SERVICES)
|
|
||||||
local SELECTED=$({
|
|
||||||
echo "namespace service port"
|
|
||||||
echo $SERVICES \
|
|
||||||
| jq -r '.service + " " + .port' \
|
|
||||||
| sed "s/^/$NAMESPACE /" \
|
|
||||||
;
|
|
||||||
} \
|
|
||||||
| column -t \
|
|
||||||
| FZF 'select a service' --header-lines=1 \
|
|
||||||
| awk '{print $2;}' \
|
|
||||||
)
|
|
||||||
|
|
||||||
echo $SERVICES | jq -c "select (.service == \"$SELECTED\")"
|
|
||||||
}
|
|
||||||
|
|
||||||
KUBECTL__LIST_SERVICES() {
|
|
||||||
KUBECTL get service --no-headers\
|
|
||||||
| awk '{print "{\"service\":\""$1"\",\"ip\":\""$3"\",\"port\":\""$5"\"}"}' \
|
|
||||||
| jq -c 'select (.ip != "None")' \
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
KUBECTL__GET_SERVICE_PASSWORD() {
|
|
||||||
[ $PASSWORD_SECRET ] && [ $PASSWORD_KEY ] || return 0
|
|
||||||
|
|
||||||
KUBECTL get secret $PASSWORD_SECRET -o jsonpath="{.data.$PASSWORD_KEY}" \
|
|
||||||
| base64 --decode
|
|
||||||
}
|
|
||||||
|
|
||||||
KUBECTL__SERVICE_PARSE() {
|
|
||||||
SERVICE_NAME=$(echo $SERVICE | jq -r .service)
|
|
||||||
SERVICE_PORT=$(echo $SERVICE | jq -r .port | sed 's|/.*$||')
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
#####################################################################
|
|
||||||
|
|
||||||
DEPENDENCIES+=(
|
|
||||||
redis-cli
|
|
||||||
docker
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO; allow custom redis configuration
|
|
||||||
export SCWRYPTS_KUBECTL_REDIS=managed
|
|
||||||
|
|
||||||
REQUIRED_ENV+=(
|
|
||||||
SCWRYPTS_KUBECTL_REDIS
|
|
||||||
)
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
REDIS() {
|
|
||||||
[ ! $USAGE ] && local USAGE="
|
|
||||||
usage: [...options...]
|
|
||||||
|
|
||||||
options:
|
|
||||||
--subsession [0-9] use a particular subsession
|
|
||||||
|
|
||||||
-p, --prefix apply dynamic prefix to the next command line argument
|
|
||||||
|
|
||||||
--get-prefix output key prefix for current session+subsession
|
|
||||||
--get-static-definition output the static ZSH function definition for REDIS
|
|
||||||
|
|
||||||
additional arguments and options are passed through to 'redis-cli'
|
|
||||||
"
|
|
||||||
|
|
||||||
local REDIS_ARGS=() USER_ARGS=()
|
|
||||||
|
|
||||||
[ $SUBSESSION ] || local SUBSESSION=0
|
|
||||||
|
|
||||||
local REDIS_PREFIX=$(eval echo '$SCWRYPTS_KUBECTL_REDIS_KEY_PREFIX__'$SCWRYPTS_KUBECTL_REDIS)
|
|
||||||
[ $REDIS_PREFIX ] && REDIS_PREFIX+=':'
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]
|
|
||||||
do
|
|
||||||
case $1 in
|
|
||||||
-p | --prefix ) USER_ARGS+=("${REDIS_PREFIX}${SCWRYPTS_ENV}:${SUBSESSION}:$2"); shift 1 ;;
|
|
||||||
|
|
||||||
--subsession ) SUBSESSION=$2; shift 1 ;;
|
|
||||||
|
|
||||||
--get-prefix ) echo $REDIS_PREFIX; return 0 ;;
|
|
||||||
--get-static-definition ) ECHO_STATIC_DEFINITION=1 ;;
|
|
||||||
|
|
||||||
* ) USER_ARGS+=($1) ;;
|
|
||||||
esac
|
|
||||||
shift 1
|
|
||||||
done
|
|
||||||
|
|
||||||
local REDIS_HOST=$(eval echo '$SCWRYPTS_KUBECTL_REDIS_HOST__'$SCWRYPTS_KUBECTL_REDIS)
|
|
||||||
local REDIS_PORT=$(eval echo '$SCWRYPTS_KUBECTL_REDIS_PORT__'$SCWRYPTS_KUBECTL_REDIS)
|
|
||||||
local REDIS_AUTH=$(eval echo '$SCWRYPTS_KUBECTL_REDIS_AUTH__'$SCWRYPTS_KUBECTL_REDIS)
|
|
||||||
|
|
||||||
[ $REDIS_HOST ] && REDIS_ARGS+=(-h $REDIS_HOST)
|
|
||||||
[ $REDIS_PORT ] && REDIS_ARGS+=(-p $REDIS_PORT)
|
|
||||||
[ $REDIS_AUTH ] && REDIS_ARGS+=(-a $REDIS_AUTH)
|
|
||||||
|
|
||||||
REDIS_ARGS+=(--raw)
|
|
||||||
|
|
||||||
[[ $ECHO_STATIC_DEFINITION -eq 1 ]] && {
|
|
||||||
echo "REDIS() {\
|
|
||||||
local USER_ARGS=(); \
|
|
||||||
[ ! \$SUBSESSION ] && local SUBSESSION=0 ;\
|
|
||||||
while [[ \$# -gt 0 ]]; \
|
|
||||||
do \
|
|
||||||
case \$1 in
|
|
||||||
-p | --prefix ) USER_ARGS+=(\"${REDIS_PREFIX}\${SCWRYPTS_ENV}:\${SUBSESSION}:\$2\"); shift 1 ;; \
|
|
||||||
* ) USER_ARGS+=(\$1) ;; \
|
|
||||||
esac; \
|
|
||||||
shift 1; \
|
|
||||||
done; \
|
|
||||||
redis-cli ${REDIS_ARGS[@]} \${USER_ARGS[@]}; \
|
|
||||||
}" | sed 's/\s\+/ /g'
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
redis-cli ${REDIS_ARGS[@]} ${USER_ARGS[@]}
|
|
||||||
}
|
|
||||||
|
|
||||||
REDIS ping | grep -qi pong || {
|
|
||||||
RPID=$(docker ps -a | grep scwrypts-kubectl-redis | awk '{print $1;}')
|
|
||||||
[ $RPID ] && STATUS 'refreshing redis instance' && docker rm -f $RPID
|
|
||||||
unset RPID
|
|
||||||
|
|
||||||
docker run \
|
|
||||||
--detach \
|
|
||||||
--name scwrypts-kubectl-redis \
|
|
||||||
--publish $SCWRYPTS_KUBECTL_REDIS_PORT__managed:6379 \
|
|
||||||
redis >/dev/null 2>&1
|
|
||||||
|
|
||||||
STATUS 'awaiting redis connection'
|
|
||||||
until REDIS ping 2>/dev/null | grep -qi pong; do sleep 0.5; done
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
||||||
use redis --group kubectl
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
MAIN() {
|
|
||||||
echo $(REDIS --get-static-definition)
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
||||||
use kubectl --group kubectl
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
MAIN() {
|
|
||||||
local USAGE="
|
|
||||||
usage: [service] [...options...]
|
|
||||||
|
|
||||||
args:
|
|
||||||
service (optional) name of the service to forward locally
|
|
||||||
|
|
||||||
options:
|
|
||||||
--context override context
|
|
||||||
--namespace override namespace
|
|
||||||
--subsession REDIS subsession (default 0)
|
|
||||||
|
|
||||||
to show a required password on screen, use both:
|
|
||||||
--password-secret Secret resource
|
|
||||||
--password-key key within Secret's 'data'
|
|
||||||
|
|
||||||
-h, --help show this dialogue and exit
|
|
||||||
"
|
|
||||||
local CONTEXT NAMESPACE SERVICE
|
|
||||||
local SUBSESSION=0
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]
|
|
||||||
do
|
|
||||||
case $1 in
|
|
||||||
--context ) CONTEXT=$2; shift 1 ;;
|
|
||||||
--namespace ) NAMESPACE=$2; shift 1 ;;
|
|
||||||
--subsession ) SUBSESSION=$2; shift 1 ;;
|
|
||||||
|
|
||||||
--password-secret ) PASSWORD_SECRET=$2; shift 1 ;;
|
|
||||||
--password-key ) PASSWORD_KEY=$2; shift 1 ;;
|
|
||||||
|
|
||||||
-h | --help ) USAGE; return 0 ;;
|
|
||||||
|
|
||||||
* )
|
|
||||||
[ $SERVICE ] && ERROR "unexpected argument '$2'"
|
|
||||||
SERVICE=$1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
shift 1
|
|
||||||
done
|
|
||||||
|
|
||||||
CHECK_ERRORS
|
|
||||||
|
|
||||||
KUBECTL__SERVE
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
||||||
use kubectl --group kubectl
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
MAIN() {
|
|
||||||
local USAGE="
|
|
||||||
usage: [context] [...options...]
|
|
||||||
|
|
||||||
args:
|
|
||||||
context (optional) the full name of the kubeconfig context to set
|
|
||||||
|
|
||||||
options:
|
|
||||||
--subsession REDIS subsession (default 0)
|
|
||||||
|
|
||||||
-h, --help show this dialogue and exit
|
|
||||||
"
|
|
||||||
local CONTEXT
|
|
||||||
local SUBSESSION=0
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]
|
|
||||||
do
|
|
||||||
case $1 in
|
|
||||||
--subsession ) SUBSESSION=$2; shift 1 ;;
|
|
||||||
|
|
||||||
-h | --help ) USAGE; return 0 ;;
|
|
||||||
|
|
||||||
* )
|
|
||||||
[ $CONTEXT ] && ERROR "unexpected argument '$2'"
|
|
||||||
CONTEXT=$1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
shift 1
|
|
||||||
done
|
|
||||||
|
|
||||||
[ $CONTEXT ] || CONTEXT=$(KUBECTL__SELECT_CONTEXT)
|
|
||||||
[ $CONTEXT ] || ERROR 'must provide or select a valid kube context'
|
|
||||||
|
|
||||||
CHECK_ERRORS
|
|
||||||
|
|
||||||
KUBECTL__SET_CONTEXT $CONTEXT
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
||||||
use kubectl --group kubectl
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
MAIN() {
|
|
||||||
local USAGE="
|
|
||||||
usage: [namespace] [...options...]
|
|
||||||
|
|
||||||
args:
|
|
||||||
namespace (optional) the full name of the namespace context to set
|
|
||||||
|
|
||||||
options:
|
|
||||||
--subsession REDIS subsession (default 0)
|
|
||||||
|
|
||||||
-h, --help show this dialogue and exit
|
|
||||||
"
|
|
||||||
local NAMESPACE
|
|
||||||
local SUBSESSION=0
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]
|
|
||||||
do
|
|
||||||
case $1 in
|
|
||||||
--subsession ) SUBSESSION=$2; shift 1 ;;
|
|
||||||
|
|
||||||
-h | --help ) USAGE; return 0 ;;
|
|
||||||
|
|
||||||
* )
|
|
||||||
[ $NAMESPACE ] && ERROR "unexpected argument '$2'"
|
|
||||||
NAMESPACE=$1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
shift 1
|
|
||||||
done
|
|
||||||
|
|
||||||
[ $NAMESPACE ] || NAMESPACE=$(KUBECTL__SELECT_NAMESPACE)
|
|
||||||
[ $NAMESPACE ] || ERROR 'must provide or select a valid namespace'
|
|
||||||
|
|
||||||
CHECK_ERRORS
|
|
||||||
|
|
||||||
KUBECTL__SET_NAMESPACE $NAMESPACE
|
|
||||||
}
|
|
@ -1,11 +1,13 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from scwrypts import execute
|
from py.lib.data.converter import convert
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
from scwrypts.data import convert
|
|
||||||
|
|
||||||
|
|
||||||
description = 'convert csv into json'
|
|
||||||
parse_args = []
|
|
||||||
|
|
||||||
def main(_args, stream):
|
def main(_args, stream):
|
||||||
return convert(
|
return convert(
|
||||||
@ -16,5 +18,7 @@ def main(_args, stream):
|
|||||||
)
|
)
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
if __name__ == '__main__':
|
execute(main,
|
||||||
execute(main, description, parse_args)
|
description = 'convert csv into json',
|
||||||
|
parse_args = [],
|
||||||
|
)
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from scwrypts import execute
|
from py.lib.data.converter import convert
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
from scwrypts.data import convert
|
|
||||||
|
|
||||||
|
|
||||||
description = 'convert csv into yaml'
|
|
||||||
parse_args = []
|
|
||||||
|
|
||||||
def main(_args, stream):
|
def main(_args, stream):
|
||||||
return convert(
|
return convert(
|
||||||
@ -16,5 +18,7 @@ def main(_args, stream):
|
|||||||
)
|
)
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
if __name__ == '__main__':
|
execute(main,
|
||||||
execute(main, description, parse_args)
|
description = 'convert csv into yaml',
|
||||||
|
parse_args = [],
|
||||||
|
)
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from scwrypts import execute
|
from py.lib.data.converter import convert
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
from scwrypts.data import convert
|
|
||||||
|
|
||||||
|
|
||||||
description = 'convert json into csv'
|
|
||||||
parse_args = []
|
|
||||||
|
|
||||||
def main(_args, stream):
|
def main(_args, stream):
|
||||||
return convert(
|
return convert(
|
||||||
@ -16,5 +18,7 @@ def main(_args, stream):
|
|||||||
)
|
)
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
if __name__ == '__main__':
|
execute(main,
|
||||||
execute(main, description, parse_args)
|
description = 'convert json into csv',
|
||||||
|
parse_args = [],
|
||||||
|
)
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from scwrypts import execute
|
from py.lib.data.converter import convert
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
from scwrypts.data import convert
|
|
||||||
|
|
||||||
|
|
||||||
description = 'convert json into yaml'
|
|
||||||
parse_args = []
|
|
||||||
|
|
||||||
def main(_args, stream):
|
def main(_args, stream):
|
||||||
return convert(
|
return convert(
|
||||||
@ -16,5 +18,7 @@ def main(_args, stream):
|
|||||||
)
|
)
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
if __name__ == '__main__':
|
execute(main,
|
||||||
execute(main, description, parse_args)
|
description = 'convert json into yaml',
|
||||||
|
parse_args = [],
|
||||||
|
)
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from scwrypts import execute
|
from py.lib.data.converter import convert
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
from scwrypts.data import convert
|
|
||||||
|
|
||||||
|
|
||||||
description = 'convert yaml into csv'
|
|
||||||
parse_args = []
|
|
||||||
|
|
||||||
def main(_args, stream):
|
def main(_args, stream):
|
||||||
return convert(
|
return convert(
|
||||||
@ -16,5 +18,7 @@ def main(_args, stream):
|
|||||||
)
|
)
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
if __name__ == '__main__':
|
execute(main,
|
||||||
execute(main, description, parse_args)
|
description = 'convert yaml into csv',
|
||||||
|
parse_args = [],
|
||||||
|
)
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from scwrypts import execute
|
from py.lib.data.converter import convert
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
from scwrypts.data import convert
|
|
||||||
|
|
||||||
|
|
||||||
description = 'convert yaml into json'
|
|
||||||
parse_args = []
|
|
||||||
|
|
||||||
def main(_args, stream):
|
def main(_args, stream):
|
||||||
return convert(
|
return convert(
|
||||||
@ -16,5 +18,7 @@ def main(_args, stream):
|
|||||||
)
|
)
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
if __name__ == '__main__':
|
execute(main,
|
||||||
execute(main, description, parse_args)
|
description = 'convert yaml into json',
|
||||||
|
parse_args = [],
|
||||||
|
)
|
||||||
|
@ -1,55 +1,16 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from scwrypts import execute
|
|
||||||
#####################################################################
|
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
|
||||||
from scwrypts.fzf import fzf, fzf_tail
|
from py.lib.fzf import fzf, fzf_tail
|
||||||
from scwrypts.http import directus
|
from py.lib.http import directus
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
description = 'interactive CLI to get data from directus'
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
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,
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
|
|
||||||
def main(args, stream):
|
def main(args, stream):
|
||||||
if {None} == { args.collection, args.filters, args.fields }:
|
if {None} == { args.collection, args.filters, args.fields }:
|
||||||
@ -135,6 +96,50 @@ def _get_or_select_fields(args, collection):
|
|||||||
|
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
if __name__ == '__main__':
|
execute(main,
|
||||||
execute(main, description, parse_args)
|
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,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
)
|
||||||
|
@ -1,40 +1,16 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from scwrypts import execute
|
|
||||||
#####################################################################
|
|
||||||
from json import dumps
|
from json import dumps
|
||||||
from sys import stderr
|
from sys import stderr
|
||||||
|
|
||||||
from scwrypts.http import discord
|
from py.lib.http import discord
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
description = 'post a message to the indicated discord channel'
|
if __name__ != '__main__':
|
||||||
parse_args = [
|
raise ImportedExecutableError()
|
||||||
( ['-b', '--body'], {
|
|
||||||
'dest' : 'content',
|
#####################################################################
|
||||||
'help' : 'message body',
|
|
||||||
'required' : False,
|
|
||||||
}),
|
|
||||||
( ['-c', '--channel-id'], {
|
|
||||||
'dest' : 'channel_id',
|
|
||||||
'help' : 'override default target channel id',
|
|
||||||
'required' : False,
|
|
||||||
}),
|
|
||||||
( ['-w', '--webhook'], {
|
|
||||||
'dest' : 'webhook',
|
|
||||||
'help' : 'override default target webhook (takes precedence over -c)',
|
|
||||||
'required' : False,
|
|
||||||
}),
|
|
||||||
( ['--avatar-url'], {
|
|
||||||
'dest' : 'avatar_url',
|
|
||||||
'help' : 'override default avatar_url',
|
|
||||||
'required' : False,
|
|
||||||
}),
|
|
||||||
( ['--username'], {
|
|
||||||
'dest' : 'username',
|
|
||||||
'help' : 'override default username',
|
|
||||||
'required' : False,
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
|
|
||||||
def main(args, stream):
|
def main(args, stream):
|
||||||
if args.content is None:
|
if args.content is None:
|
||||||
@ -53,5 +29,33 @@ def main(args, stream):
|
|||||||
|
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
if __name__ == '__main__':
|
execute(main,
|
||||||
execute(main, description, parse_args)
|
description = 'post a message to the indicated discord channel',
|
||||||
|
parse_args = [
|
||||||
|
( ['-b', '--body'], {
|
||||||
|
'dest' : 'content',
|
||||||
|
'help' : 'message body',
|
||||||
|
'required' : False,
|
||||||
|
}),
|
||||||
|
( ['-c', '--channel-id'], {
|
||||||
|
'dest' : 'channel_id',
|
||||||
|
'help' : 'override default target channel id',
|
||||||
|
'required' : False,
|
||||||
|
}),
|
||||||
|
( ['-w', '--webhook'], {
|
||||||
|
'dest' : 'webhook',
|
||||||
|
'help' : 'override default target webhook (takes precedence over -c)',
|
||||||
|
'required' : False,
|
||||||
|
}),
|
||||||
|
( ['--avatar-url'], {
|
||||||
|
'dest' : 'avatar_url',
|
||||||
|
'help' : 'override default avatar_url',
|
||||||
|
'required' : False,
|
||||||
|
}),
|
||||||
|
( ['--username'], {
|
||||||
|
'dest' : 'username',
|
||||||
|
'help' : 'override default username',
|
||||||
|
'required' : False,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@ -1,21 +1,27 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from scwrypts import execute
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
|
|
||||||
description = 'a simple "Hello, World!" program'
|
|
||||||
parse_args = [
|
|
||||||
( ['-m', '--message'], {
|
|
||||||
'dest' : 'message',
|
|
||||||
'default' : 'HELLO WORLD',
|
|
||||||
'help' : 'message to print',
|
|
||||||
'required' : False,
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
|
|
||||||
def main(args, stream):
|
def main(args, stream):
|
||||||
stream.writeline(args.message)
|
stream.writeline(args.message)
|
||||||
|
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
if __name__ == '__main__':
|
execute(main,
|
||||||
execute(main, description, parse_args)
|
description = 'a simple "Hello, World!" program',
|
||||||
|
parse_args = [
|
||||||
|
( ['-m', '--message'], {
|
||||||
|
'dest' : 'message',
|
||||||
|
'default' : 'HELLO WORLD',
|
||||||
|
'help' : 'message to print',
|
||||||
|
'required' : False,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
4
py/lib/.gitignore
vendored
4
py/lib/.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
dist/
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*.so
|
|
@ -1,3 +0,0 @@
|
|||||||
# Python Scwrypts
|
|
||||||
[](https://python.org)
|
|
||||||
<br>
|
|
6
py/lib/__init__.py
Normal file
6
py/lib/__init__.py
Normal file
@ -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
|
1
py/lib/data/__init__.py
Normal file
1
py/lib/data/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
import py.lib.data.converter
|
@ -4,6 +4,9 @@ import yaml
|
|||||||
|
|
||||||
|
|
||||||
def convert(input_stream, input_type, output_stream, 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')
|
||||||
|
|
||||||
data = convert_input(input_stream, input_type)
|
data = convert_input(input_stream, input_type)
|
||||||
write_output(output_stream, output_type, data)
|
write_output(output_stream, output_type, data)
|
||||||
|
|
1
py/lib/fzf/__init__.py
Normal file
1
py/lib/fzf/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from py.lib.fzf.client import fzf, fzf_tail, fzf_head
|
5
py/lib/http/__init__.py
Normal file
5
py/lib/http/__init__.py
Normal file
@ -0,0 +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
|
20
py/lib/http/client.py
Normal file
20
py/lib/http/client.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from requests import request
|
||||||
|
|
||||||
|
|
||||||
|
def get_request_client(base_url, headers=None):
|
||||||
|
if headers is None:
|
||||||
|
headers = {}
|
||||||
|
|
||||||
|
return lambda method, endpoint, **kwargs: request(
|
||||||
|
method = method,
|
||||||
|
url = f'{base_url}/{endpoint}',
|
||||||
|
headers = {
|
||||||
|
**headers,
|
||||||
|
**kwargs.get('headers', {}),
|
||||||
|
},
|
||||||
|
**{
|
||||||
|
key: value
|
||||||
|
for key, value in kwargs.items()
|
||||||
|
if key != 'headers'
|
||||||
|
},
|
||||||
|
)
|
2
py/lib/http/directus/__init__.py
Normal file
2
py/lib/http/directus/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from py.lib.http.directus.client import *
|
||||||
|
from py.lib.http.directus.constant import *
|
56
py/lib/http/directus/client.py
Normal file
56
py/lib/http/directus/client.py
Normal file
@ -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]
|
@ -1,23 +1,3 @@
|
|||||||
'''
|
|
||||||
basic scwrypts.http client for directus
|
|
||||||
|
|
||||||
configured by setting DIRECTUS__BASE_URL and DIRECTUS__API_TOKEN in
|
|
||||||
scwrypts environment
|
|
||||||
'''
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'request',
|
|
||||||
'graphql',
|
|
||||||
'get_collections',
|
|
||||||
'get_fields',
|
|
||||||
'FILTER_OPERATORS',
|
|
||||||
]
|
|
||||||
|
|
||||||
from .client import request
|
|
||||||
from .graphql import graphql
|
|
||||||
from .collections import get_collections
|
|
||||||
from .fields import get_fields
|
|
||||||
|
|
||||||
FILTER_OPERATORS = {
|
FILTER_OPERATORS = {
|
||||||
'_eq',
|
'_eq',
|
||||||
'_neq',
|
'_neq',
|
2
py/lib/http/discord/__init__.py
Normal file
2
py/lib/http/discord/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from py.lib.http.discord.client import *
|
||||||
|
from py.lib.http.discord.send_message import *
|
20
py/lib/http/discord/client.py
Normal file
20
py/lib/http/discord/client.py
Normal file
@ -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)
|
@ -1,5 +1,5 @@
|
|||||||
from scwrypts.env import getenv
|
from py.lib.scwrypts import getenv
|
||||||
from .client import request
|
from py.lib.http.discord import request
|
||||||
|
|
||||||
def send_message(content, channel_id=None, webhook=None, username=None, avatar_url=None, **kwargs):
|
def send_message(content, channel_id=None, webhook=None, username=None, avatar_url=None, **kwargs):
|
||||||
if username is None:
|
if username is None:
|
1
py/lib/http/linear/__init__.py
Normal file
1
py/lib/http/linear/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from py.lib.http.linear.client import *
|
20
py/lib/http/linear/client.py
Normal file
20
py/lib/http/linear/client.py
Normal file
@ -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})
|
@ -1,59 +0,0 @@
|
|||||||
[project]
|
|
||||||
name = 'scwrypts'
|
|
||||||
description = 'scwrypts library and invoker'
|
|
||||||
license = 'GPL-3.0-or-later'
|
|
||||||
|
|
||||||
readme = 'README.md'
|
|
||||||
requires-python = '>=3.10'
|
|
||||||
|
|
||||||
authors = [
|
|
||||||
{ name='yage', email='yage@yage.io' },
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
classifiers = [
|
|
||||||
'Programming Language :: Python :: 3',
|
|
||||||
'Programming Language :: Python :: 3.10',
|
|
||||||
'Programming Language :: Python :: 3.11',
|
|
||||||
'Programming Language :: Python :: 3.12',
|
|
||||||
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
|
|
||||||
]
|
|
||||||
|
|
||||||
dynamic = ['version']
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
'bpython',
|
|
||||||
'pyfzf',
|
|
||||||
'pyyaml',
|
|
||||||
'redis',
|
|
||||||
'twilio',
|
|
||||||
]
|
|
||||||
|
|
||||||
[project.optional-dependencies]
|
|
||||||
dev = [
|
|
||||||
'pylint',
|
|
||||||
]
|
|
||||||
|
|
||||||
test = [
|
|
||||||
'pytest',
|
|
||||||
]
|
|
||||||
|
|
||||||
[project.urls]
|
|
||||||
homepage = 'https://github.com/wrynegade/scwrypts'
|
|
||||||
issues = 'https://github.com/wrynegade/scwrypts/issues'
|
|
||||||
|
|
||||||
[build-system]
|
|
||||||
requires = [
|
|
||||||
'hatchling',
|
|
||||||
'versioningit',
|
|
||||||
]
|
|
||||||
build-backend = 'hatchling.build'
|
|
||||||
|
|
||||||
[tool.hatch.version]
|
|
||||||
source = 'versioningit'
|
|
||||||
|
|
||||||
[tool.hatch.build.targets.wheel]
|
|
||||||
packages = ['./']
|
|
||||||
|
|
||||||
[tool.versioningit.vcs]
|
|
||||||
match = ['v[0-9]*.[0-9]*.[0-9]*']
|
|
1
py/lib/redis/__init__.py
Normal file
1
py/lib/redis/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from py.lib.redis.client import get_client
|
@ -1,6 +1,6 @@
|
|||||||
from redis import StrictRedis
|
from redis import StrictRedis
|
||||||
|
|
||||||
from scwrypts.env import getenv
|
from py.lib.scwrypts import getenv
|
||||||
|
|
||||||
CLIENT = None
|
CLIENT = None
|
||||||
|
|
@ -1,13 +1,6 @@
|
|||||||
'''
|
from py.lib.scwrypts.execute import execute
|
||||||
scwrypts
|
from py.lib.scwrypts.getenv import getenv
|
||||||
|
from py.lib.scwrypts.interactive import interactive
|
||||||
|
from py.lib.scwrypts.run import run
|
||||||
|
|
||||||
python library functions and invoker for scwrypts
|
import py.lib.scwrypts.io
|
||||||
'''
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'scwrypts',
|
|
||||||
'execute',
|
|
||||||
'interactive',
|
|
||||||
]
|
|
||||||
|
|
||||||
from .scwrypts import scwrypts, execute, interactive
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
from .converter import convert
|
|
@ -1,73 +0,0 @@
|
|||||||
from io import StringIO
|
|
||||||
|
|
||||||
from pytest import raises
|
|
||||||
|
|
||||||
from scwrypts.test import generate
|
|
||||||
|
|
||||||
from .converter import convert
|
|
||||||
|
|
||||||
GENERATE_OPTIONS = {
|
|
||||||
'depth': 1,
|
|
||||||
'minimum': -999999,
|
|
||||||
'maximum': 999999,
|
|
||||||
'dict_key_types': {str, int},
|
|
||||||
'csv_columns_minimum': 10,
|
|
||||||
'csv_columns_maximum': 64,
|
|
||||||
'csv_rows_minimum': 10,
|
|
||||||
'csv_rows_maximum': 64,
|
|
||||||
}
|
|
||||||
|
|
||||||
INPUT_TYPES = {'csv', 'json', 'yaml'}
|
|
||||||
OUTPUT_TYPES = {'csv', 'json', 'yaml'}
|
|
||||||
|
|
||||||
|
|
||||||
def test_convert_to_csv():
|
|
||||||
for input_type in INPUT_TYPES:
|
|
||||||
input_stream = generate(input_type, {
|
|
||||||
**GENERATE_OPTIONS,
|
|
||||||
'data_types': {bool,int,float,str},
|
|
||||||
})
|
|
||||||
|
|
||||||
if isinstance(input_stream, str):
|
|
||||||
input_stream = StringIO(input_stream)
|
|
||||||
|
|
||||||
|
|
||||||
convert(input_stream, input_type, StringIO(), 'csv')
|
|
||||||
|
|
||||||
def test_convert_to_json():
|
|
||||||
for input_type in INPUT_TYPES:
|
|
||||||
input_stream = generate(input_type, GENERATE_OPTIONS)
|
|
||||||
|
|
||||||
if isinstance(input_stream, str):
|
|
||||||
input_stream = StringIO(input_stream)
|
|
||||||
|
|
||||||
convert(input_stream, input_type, StringIO(), 'json')
|
|
||||||
|
|
||||||
def test_convert_to_yaml():
|
|
||||||
for input_type in INPUT_TYPES:
|
|
||||||
input_stream = generate(input_type, GENERATE_OPTIONS)
|
|
||||||
|
|
||||||
if isinstance(input_stream, str):
|
|
||||||
input_stream = StringIO(input_stream)
|
|
||||||
|
|
||||||
convert(input_stream, input_type, StringIO(), 'yaml')
|
|
||||||
|
|
||||||
|
|
||||||
def test_convert_deep_json_to_yaml():
|
|
||||||
input_stream = generate('json', {**GENERATE_OPTIONS, 'depth': 4})
|
|
||||||
convert(input_stream, 'json', StringIO(), 'yaml')
|
|
||||||
|
|
||||||
def test_convert_deep_yaml_to_json():
|
|
||||||
input_stream = generate('yaml', {**GENERATE_OPTIONS, 'depth': 4})
|
|
||||||
convert(input_stream, 'yaml', StringIO(), 'json')
|
|
||||||
|
|
||||||
|
|
||||||
def test_convert_output_unsupported():
|
|
||||||
for input_type in list(INPUT_TYPES):
|
|
||||||
with raises(ValueError):
|
|
||||||
convert(StringIO(), input_type, StringIO(), generate(str))
|
|
||||||
|
|
||||||
def test_convert_input_unsupported():
|
|
||||||
for output_type in list(OUTPUT_TYPES):
|
|
||||||
with raises(ValueError):
|
|
||||||
convert(StringIO(), generate(str), StringIO(), output_type)
|
|
16
py/lib/scwrypts/exceptions.py
Normal file
16
py/lib/scwrypts/exceptions.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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} }}')
|
@ -1,12 +1,9 @@
|
|||||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||||
|
|
||||||
from scwrypts.io import get_combined_stream, add_io_arguments
|
from py.lib.scwrypts.io import get_combined_stream, add_io_arguments
|
||||||
|
|
||||||
|
|
||||||
def execute(main, description=None, parse_args=None, allow_input=True, allow_output=True):
|
def execute(main, description=None, parse_args=None, toggle_input=True, toggle_output=True):
|
||||||
'''
|
|
||||||
API to initiate a python-based scwrypt
|
|
||||||
'''
|
|
||||||
if parse_args is None:
|
if parse_args is None:
|
||||||
parse_args = []
|
parse_args = []
|
||||||
|
|
||||||
@ -15,7 +12,7 @@ def execute(main, description=None, parse_args=None, allow_input=True, allow_out
|
|||||||
formatter_class = ArgumentDefaultsHelpFormatter,
|
formatter_class = ArgumentDefaultsHelpFormatter,
|
||||||
)
|
)
|
||||||
|
|
||||||
add_io_arguments(parser, allow_input, allow_output)
|
add_io_arguments(parser, toggle_input, toggle_output)
|
||||||
|
|
||||||
for a in parse_args:
|
for a in parse_args:
|
||||||
parser.add_argument(*a[0], **a[1])
|
parser.add_argument(*a[0], **a[1])
|
@ -1 +0,0 @@
|
|||||||
from .client import fzf, fzf_tail, fzf_head
|
|
@ -1,10 +1,10 @@
|
|||||||
from os import getenv as os_getenv
|
from os import getenv as os_getenv
|
||||||
|
|
||||||
from .scwrypts.exceptions import MissingVariableError
|
from py.lib.scwrypts.exceptions import MissingVariableError
|
||||||
|
|
||||||
|
|
||||||
def getenv(name, required=True):
|
def getenv(name, required=True):
|
||||||
value = os_getenv(f'{name}__override', os_getenv(name))
|
value = os_getenv(name, None)
|
||||||
|
|
||||||
if required and not value:
|
if required and not value:
|
||||||
raise MissingVariableError(name)
|
raise MissingVariableError(name)
|
@ -1 +0,0 @@
|
|||||||
from .client import get_request_client
|
|
@ -1,25 +0,0 @@
|
|||||||
from requests import request
|
|
||||||
|
|
||||||
|
|
||||||
CLIENTS = {}
|
|
||||||
|
|
||||||
def get_request_client(base_url, headers=None):
|
|
||||||
if CLIENTS.get(base_url, None) is None:
|
|
||||||
if headers is None:
|
|
||||||
headers = {}
|
|
||||||
|
|
||||||
CLIENTS[base_url] = lambda method, endpoint, **kwargs: request(
|
|
||||||
method = method,
|
|
||||||
url = f'{base_url}/{endpoint}',
|
|
||||||
headers = {
|
|
||||||
**headers,
|
|
||||||
**kwargs.get('headers', {}),
|
|
||||||
},
|
|
||||||
**{
|
|
||||||
key: value
|
|
||||||
for key, value in kwargs.items()
|
|
||||||
if key != 'headers'
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return CLIENTS[base_url]
|
|
@ -1,40 +0,0 @@
|
|||||||
from types import SimpleNamespace
|
|
||||||
|
|
||||||
from pytest import fixture
|
|
||||||
|
|
||||||
from scwrypts.test import get_generator
|
|
||||||
from scwrypts.test.character_set import uri
|
|
||||||
|
|
||||||
generate = get_generator({
|
|
||||||
'str_length_minimum': 8,
|
|
||||||
'str_length_maximum': 128,
|
|
||||||
'uuid_output_type': str,
|
|
||||||
})
|
|
||||||
|
|
||||||
def get_request_client_sample_data():
|
|
||||||
return {
|
|
||||||
'base_url' : generate(str, {'character_set': uri}),
|
|
||||||
'endpoint' : generate(str, {'character_set': uri}),
|
|
||||||
'method' : generate(str),
|
|
||||||
'response' : generate('requests_Response', {'depth': 4}),
|
|
||||||
'payload' : generate(dict, {
|
|
||||||
'depth': 1,
|
|
||||||
'data_types': { str, 'uuid' },
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
@fixture(name='sample')
|
|
||||||
def fixture_sample():
|
|
||||||
return SimpleNamespace(
|
|
||||||
**get_request_client_sample_data(),
|
|
||||||
|
|
||||||
headers = generate(dict, {
|
|
||||||
'depth': 1,
|
|
||||||
'data_types': { str, 'uuid' },
|
|
||||||
}),
|
|
||||||
|
|
||||||
payload_headers = generate(dict, {
|
|
||||||
'depth': 1,
|
|
||||||
'data_types': { str, 'uuid' },
|
|
||||||
}),
|
|
||||||
)
|
|
@ -1,12 +0,0 @@
|
|||||||
from scwrypts.env import getenv
|
|
||||||
|
|
||||||
from .. import get_request_client
|
|
||||||
|
|
||||||
|
|
||||||
def request(method, endpoint, **kwargs):
|
|
||||||
return get_request_client(
|
|
||||||
base_url = getenv("DIRECTUS__BASE_URL"),
|
|
||||||
headers = {
|
|
||||||
'Authorization': f'bearer {getenv("DIRECTUS__API_TOKEN")}',
|
|
||||||
}
|
|
||||||
)(method, endpoint, **kwargs)
|
|
@ -1,18 +0,0 @@
|
|||||||
from .client import request
|
|
||||||
|
|
||||||
|
|
||||||
COLLECTIONS = None
|
|
||||||
|
|
||||||
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
|
|
@ -1,15 +0,0 @@
|
|||||||
from types import SimpleNamespace
|
|
||||||
|
|
||||||
from pytest import fixture
|
|
||||||
|
|
||||||
from scwrypts.test.character_set import uri
|
|
||||||
from ..conftest import generate, get_request_client_sample_data
|
|
||||||
|
|
||||||
|
|
||||||
@fixture(name='sample')
|
|
||||||
def fixture_sample():
|
|
||||||
return SimpleNamespace(
|
|
||||||
**get_request_client_sample_data(),
|
|
||||||
api_token = generate(str, {'character_set': uri}),
|
|
||||||
query = generate(str),
|
|
||||||
)
|
|
@ -1,16 +0,0 @@
|
|||||||
from .client import request
|
|
||||||
|
|
||||||
|
|
||||||
FIELDS = {}
|
|
||||||
|
|
||||||
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]
|
|
@ -1,9 +0,0 @@
|
|||||||
from .client import request
|
|
||||||
|
|
||||||
|
|
||||||
def graphql(query, system=False):
|
|
||||||
return request(
|
|
||||||
'POST',
|
|
||||||
'graphql' if system is False else 'graphql/system',
|
|
||||||
json={'query': query},
|
|
||||||
)
|
|
@ -1,43 +0,0 @@
|
|||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from pytest import fixture
|
|
||||||
|
|
||||||
from .client import request
|
|
||||||
|
|
||||||
|
|
||||||
def test_directus_request(sample, _response):
|
|
||||||
assert _response == sample.response
|
|
||||||
|
|
||||||
def test_directus_request_client_setup(sample, _response, mock_get_request_client):
|
|
||||||
mock_get_request_client.assert_called_once_with(
|
|
||||||
base_url = sample.base_url,
|
|
||||||
headers = { 'Authorization': f'bearer {sample.api_token}' },
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
@fixture(name='_response')
|
|
||||||
def fixture_response(sample):
|
|
||||||
return request(
|
|
||||||
method = sample.method,
|
|
||||||
endpoint = sample.endpoint,
|
|
||||||
**sample.payload,
|
|
||||||
)
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
@fixture(name='mock_getenv', autouse=True)
|
|
||||||
def fixture_mock_getenv(sample):
|
|
||||||
with patch('scwrypts.http.directus.client.getenv',) as mock:
|
|
||||||
mock.side_effect = lambda name: {
|
|
||||||
'DIRECTUS__BASE_URL': sample.base_url,
|
|
||||||
'DIRECTUS__API_TOKEN': sample.api_token,
|
|
||||||
}[name]
|
|
||||||
yield mock
|
|
||||||
|
|
||||||
@fixture(name='mock_get_request_client', autouse=True)
|
|
||||||
def fixture_mock_get_request_client(sample):
|
|
||||||
with patch('scwrypts.http.directus.client.get_request_client') as mock:
|
|
||||||
mock.return_value = lambda method, endpoint, **kwargs: sample.response
|
|
||||||
yield mock
|
|
@ -1,45 +0,0 @@
|
|||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from pytest import fixture
|
|
||||||
|
|
||||||
from .graphql import graphql
|
|
||||||
|
|
||||||
|
|
||||||
def test_directus_graphql(sample, _response, _mock_request):
|
|
||||||
assert _response == sample.response
|
|
||||||
|
|
||||||
def test_directus_graphql_request_payload(sample, _response, _mock_request):
|
|
||||||
_mock_request.assert_called_once_with(
|
|
||||||
'POST',
|
|
||||||
'graphql',
|
|
||||||
json = {'query': sample.query},
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_directus_graphql_system(sample, _response_system):
|
|
||||||
assert _response_system == sample.response
|
|
||||||
|
|
||||||
def test_directus_graphql_system_request_payload(sample, _response_system, _mock_request):
|
|
||||||
_mock_request.assert_called_once_with(
|
|
||||||
'POST',
|
|
||||||
'graphql/system',
|
|
||||||
json = {'query': sample.query},
|
|
||||||
)
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
@fixture(name='_response')
|
|
||||||
def fixture_response(sample, _mock_request):
|
|
||||||
return graphql(sample.query)
|
|
||||||
|
|
||||||
|
|
||||||
@fixture(name='_response_system')
|
|
||||||
def fixture_response_system(sample, _mock_request):
|
|
||||||
return graphql(sample.query, system=True)
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
@fixture(name='_mock_request')
|
|
||||||
def fixture_mock_request(sample):
|
|
||||||
with patch('scwrypts.http.directus.graphql.request') as mock:
|
|
||||||
mock.return_value = sample.response
|
|
||||||
yield mock
|
|
@ -1,14 +0,0 @@
|
|||||||
'''
|
|
||||||
basic scwrypts.http client for discord
|
|
||||||
|
|
||||||
configured by setting various DISCORD__* options in the
|
|
||||||
scwrypts environment
|
|
||||||
'''
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'request',
|
|
||||||
'send_message',
|
|
||||||
]
|
|
||||||
|
|
||||||
from .client import request
|
|
||||||
from .send_message import send_message
|
|
@ -1,15 +0,0 @@
|
|||||||
from scwrypts.env import getenv
|
|
||||||
|
|
||||||
from .. import get_request_client
|
|
||||||
|
|
||||||
|
|
||||||
def request(method, endpoint, **kwargs):
|
|
||||||
headers = {}
|
|
||||||
|
|
||||||
if (token := getenv("DISCORD__BOT_TOKEN", required = False)) is not None:
|
|
||||||
headers['Authorization'] = f'Bot {token}'
|
|
||||||
|
|
||||||
return get_request_client(
|
|
||||||
base_url = 'https://discord.com/api',
|
|
||||||
headers = headers,
|
|
||||||
)(method, endpoint, **kwargs)
|
|
@ -1,24 +0,0 @@
|
|||||||
from string import ascii_letters, digits
|
|
||||||
from types import SimpleNamespace
|
|
||||||
|
|
||||||
from pytest import fixture
|
|
||||||
|
|
||||||
from scwrypts.test.character_set import uri
|
|
||||||
from ..conftest import generate, get_request_client_sample_data
|
|
||||||
|
|
||||||
@fixture(name='sample')
|
|
||||||
def fixture_sample():
|
|
||||||
return SimpleNamespace(
|
|
||||||
**{
|
|
||||||
**get_request_client_sample_data(),
|
|
||||||
'base_url': 'https://discord.com/api',
|
|
||||||
},
|
|
||||||
bot_token = generate(str, {'character_set': uri}),
|
|
||||||
username = generate(str, {'character_set': ascii_letters + digits}),
|
|
||||||
avatar_url = generate(str, {'character_set': uri}),
|
|
||||||
webhook = generate(str, {'character_set': uri}),
|
|
||||||
channel_id = generate(str, {'character_set': uri}),
|
|
||||||
content_header = generate(str),
|
|
||||||
content_footer = generate(str),
|
|
||||||
content = generate(str),
|
|
||||||
)
|
|
@ -1,54 +0,0 @@
|
|||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from pytest import fixture
|
|
||||||
|
|
||||||
from .client import request
|
|
||||||
|
|
||||||
|
|
||||||
def test_discord_request(sample, _response):
|
|
||||||
assert _response == sample.response
|
|
||||||
|
|
||||||
def test_discord_request_client_setup(sample, mock_get_request_client, _mock_getenv, _response):
|
|
||||||
mock_get_request_client.assert_called_once_with(
|
|
||||||
base_url = sample.base_url,
|
|
||||||
headers = { 'Authorization': f'Bot {sample.bot_token}' },
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_discord_request_client_setup_public(sample, mock_get_request_client, _mock_getenv_optional, _response):
|
|
||||||
mock_get_request_client.assert_called_once_with(
|
|
||||||
base_url = sample.base_url,
|
|
||||||
headers = {},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
@fixture(name='_response')
|
|
||||||
def fixture_response(sample):
|
|
||||||
return request(
|
|
||||||
method = sample.method,
|
|
||||||
endpoint = sample.endpoint,
|
|
||||||
**sample.payload,
|
|
||||||
)
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
@fixture(name='mock_get_request_client', autouse=True)
|
|
||||||
def fixture_mock_get_request_client(sample):
|
|
||||||
with patch('scwrypts.http.discord.client.get_request_client') as mock:
|
|
||||||
mock.return_value = lambda method, endpoint, **kwargs: sample.response
|
|
||||||
yield mock
|
|
||||||
|
|
||||||
@fixture(name='_mock_getenv')
|
|
||||||
def fixture_mock_getenv(sample):
|
|
||||||
with patch('scwrypts.http.discord.client.getenv',) as mock:
|
|
||||||
mock.side_effect = lambda name, **kwargs: {
|
|
||||||
'DISCORD__BOT_TOKEN': sample.bot_token,
|
|
||||||
}[name]
|
|
||||||
yield mock
|
|
||||||
|
|
||||||
@fixture(name='_mock_getenv_optional')
|
|
||||||
def fixture_mock_getenv_optional():
|
|
||||||
with patch('scwrypts.http.discord.client.getenv',) as mock:
|
|
||||||
mock.side_effect = lambda name, **kwargs: None
|
|
||||||
yield mock
|
|
@ -1,91 +0,0 @@
|
|||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from pytest import fixture, raises
|
|
||||||
|
|
||||||
from .send_message import send_message
|
|
||||||
|
|
||||||
|
|
||||||
def test_discord_send_message(sample, mock_request, _mock_getenv):
|
|
||||||
expected = get_default_called_with(sample)
|
|
||||||
|
|
||||||
assert send_message(sample.content) == sample.response
|
|
||||||
mock_request.assert_called_once_with(**expected)
|
|
||||||
|
|
||||||
def test_discord_send_message_without_username(sample, mock_request, _mock_getenv):
|
|
||||||
sample.username = None
|
|
||||||
expected = get_default_called_with(sample)
|
|
||||||
del expected['json']['username']
|
|
||||||
|
|
||||||
assert send_message(sample.content) == sample.response
|
|
||||||
mock_request.assert_called_once_with(**expected)
|
|
||||||
|
|
||||||
def test_discord_send_message_without_avatar_url(sample, mock_request, _mock_getenv):
|
|
||||||
sample.avatar_url = None
|
|
||||||
expected = get_default_called_with(sample)
|
|
||||||
del expected['json']['avatar_url']
|
|
||||||
|
|
||||||
assert send_message(sample.content) == sample.response
|
|
||||||
mock_request.assert_called_once_with(**expected)
|
|
||||||
|
|
||||||
def test_discord_send_message_to_channel_id(sample, mock_request, _mock_getenv):
|
|
||||||
sample.webhook = None
|
|
||||||
expected = get_default_called_with(sample)
|
|
||||||
expected['endpoint'] = f'channels/{sample.channel_id}/messages'
|
|
||||||
|
|
||||||
assert send_message(sample.content) == sample.response
|
|
||||||
mock_request.assert_called_once_with(**expected)
|
|
||||||
|
|
||||||
def test_discord_send_message_without_content_header(sample, mock_request, _mock_getenv):
|
|
||||||
sample.content_header = None
|
|
||||||
expected = get_default_called_with(sample)
|
|
||||||
expected['json']['content'] = f'{sample.content}{sample.content_footer}'
|
|
||||||
|
|
||||||
assert send_message(sample.content) == sample.response
|
|
||||||
mock_request.assert_called_once_with(**expected)
|
|
||||||
|
|
||||||
def test_discord_send_message_without_content_footer(sample, mock_request, _mock_getenv):
|
|
||||||
sample.content_footer = None
|
|
||||||
expected = get_default_called_with(sample)
|
|
||||||
expected['json']['content'] = f'{sample.content_header}{sample.content}'
|
|
||||||
|
|
||||||
assert send_message(sample.content) == sample.response
|
|
||||||
mock_request.assert_called_once_with(**expected)
|
|
||||||
|
|
||||||
def test_discord_send_message_error(sample, mock_request, _mock_getenv):
|
|
||||||
with raises(ValueError):
|
|
||||||
sample.webhook = None
|
|
||||||
sample.channel_id = None
|
|
||||||
|
|
||||||
send_message(sample.content)
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
def get_default_called_with(sample):
|
|
||||||
return {
|
|
||||||
'method': 'POST',
|
|
||||||
'endpoint': f'webhooks/{sample.webhook}',
|
|
||||||
'json': {
|
|
||||||
'content': f'{sample.content_header}{sample.content}{sample.content_footer}',
|
|
||||||
'username': sample.username,
|
|
||||||
'avatar_url': sample.avatar_url,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
@fixture(name='mock_request', autouse=True)
|
|
||||||
def fixture_mock_request(sample):
|
|
||||||
with patch('scwrypts.http.discord.send_message.request') as mock:
|
|
||||||
mock.return_value = sample.response
|
|
||||||
yield mock
|
|
||||||
|
|
||||||
@fixture(name='_mock_getenv')
|
|
||||||
def fixture_mock_getenv(sample):
|
|
||||||
with patch('scwrypts.http.discord.send_message.getenv',) as mock:
|
|
||||||
mock.side_effect = lambda name, **kwargs: {
|
|
||||||
'DISCORD__DEFAULT_USERNAME': sample.username,
|
|
||||||
'DISCORD__DEFAULT_AVATAR_URL': sample.avatar_url,
|
|
||||||
'DISCORD__DEFAULT_WEBHOOK': sample.webhook,
|
|
||||||
'DISCORD__DEFAULT_CHANNEL_ID': sample.channel_id,
|
|
||||||
'DISCORD__CONTENT_HEADER': sample.content_header,
|
|
||||||
'DISCORD__CONTENT_FOOTER': sample.content_footer,
|
|
||||||
}[name]
|
|
||||||
yield mock
|
|
@ -1,14 +0,0 @@
|
|||||||
'''
|
|
||||||
basic scwrypts.http client for linear
|
|
||||||
|
|
||||||
configured by setting the LINEAR__API_TOKEN option in the
|
|
||||||
scwrypts environment
|
|
||||||
'''
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'request',
|
|
||||||
'graphql',
|
|
||||||
]
|
|
||||||
|
|
||||||
from .client import request
|
|
||||||
from .graphql import graphql
|
|
@ -1,12 +0,0 @@
|
|||||||
from scwrypts.env import getenv
|
|
||||||
|
|
||||||
from .. import get_request_client
|
|
||||||
|
|
||||||
|
|
||||||
def request(method, endpoint, **kwargs):
|
|
||||||
return get_request_client(
|
|
||||||
base_url = 'https://api.linear.app',
|
|
||||||
headers = {
|
|
||||||
'Authorization': f'bearer {getenv("LINEAR__API_TOKEN")}',
|
|
||||||
},
|
|
||||||
)(method, endpoint, **kwargs)
|
|
@ -1,18 +0,0 @@
|
|||||||
from string import ascii_letters, digits
|
|
||||||
from types import SimpleNamespace
|
|
||||||
|
|
||||||
from pytest import fixture
|
|
||||||
|
|
||||||
from scwrypts.test.character_set import uri
|
|
||||||
from ..conftest import generate, get_request_client_sample_data
|
|
||||||
|
|
||||||
@fixture(name='sample')
|
|
||||||
def fixture_sample():
|
|
||||||
return SimpleNamespace(
|
|
||||||
**{
|
|
||||||
**get_request_client_sample_data(),
|
|
||||||
'base_url': 'https://api.linear.app',
|
|
||||||
},
|
|
||||||
api_token = generate(str, {'character_set': uri}),
|
|
||||||
query = generate(str),
|
|
||||||
)
|
|
@ -1,5 +0,0 @@
|
|||||||
from .client import request
|
|
||||||
|
|
||||||
|
|
||||||
def graphql(query):
|
|
||||||
return request('POST', 'graphql', json={'query': query})
|
|
@ -1,42 +0,0 @@
|
|||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from pytest import fixture
|
|
||||||
|
|
||||||
from .client import request
|
|
||||||
|
|
||||||
|
|
||||||
def test_discord_request(sample, _response):
|
|
||||||
assert _response == sample.response
|
|
||||||
|
|
||||||
def test_discord_request_client_setup(sample, mock_get_request_client, _response):
|
|
||||||
mock_get_request_client.assert_called_once_with(
|
|
||||||
base_url = sample.base_url,
|
|
||||||
headers = { 'Authorization': f'bearer {sample.api_token}' },
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
@fixture(name='_response')
|
|
||||||
def fixture_response(sample):
|
|
||||||
return request(
|
|
||||||
method = sample.method,
|
|
||||||
endpoint = sample.endpoint,
|
|
||||||
**sample.payload,
|
|
||||||
)
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
@fixture(name='mock_get_request_client', autouse=True)
|
|
||||||
def fixture_mock_get_request_client(sample):
|
|
||||||
with patch('scwrypts.http.linear.client.get_request_client') as mock:
|
|
||||||
mock.return_value = lambda method, endpoint, **kwargs: sample.response
|
|
||||||
yield mock
|
|
||||||
|
|
||||||
@fixture(name='mock_getenv', autouse=True)
|
|
||||||
def fixture_mock_getenv(sample):
|
|
||||||
with patch('scwrypts.http.linear.client.getenv',) as mock:
|
|
||||||
mock.side_effect = lambda name, **kwargs: {
|
|
||||||
'LINEAR__API_TOKEN': sample.api_token,
|
|
||||||
}[name]
|
|
||||||
yield mock
|
|
@ -1,35 +0,0 @@
|
|||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from pytest import fixture
|
|
||||||
|
|
||||||
from .graphql import graphql
|
|
||||||
|
|
||||||
|
|
||||||
def test_directus_graphql(sample, _response, _mock_request):
|
|
||||||
assert _response == sample.response
|
|
||||||
|
|
||||||
def test_directus_graphql_request_payload(sample, _response, _mock_request):
|
|
||||||
_mock_request.assert_called_once_with(
|
|
||||||
'POST',
|
|
||||||
'graphql',
|
|
||||||
json = {'query': sample.query},
|
|
||||||
)
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
@fixture(name='_response')
|
|
||||||
def fixture_response(sample, _mock_request):
|
|
||||||
return graphql(sample.query)
|
|
||||||
|
|
||||||
|
|
||||||
@fixture(name='_response_system')
|
|
||||||
def fixture_response_system(sample, _mock_request):
|
|
||||||
return graphql(sample.query)
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
@fixture(name='_mock_request')
|
|
||||||
def fixture_mock_request(sample):
|
|
||||||
with patch('scwrypts.http.linear.graphql.request') as mock:
|
|
||||||
mock.return_value = sample.response
|
|
||||||
yield mock
|
|
@ -1,55 +0,0 @@
|
|||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from pytest import fixture
|
|
||||||
|
|
||||||
from .client import get_request_client
|
|
||||||
|
|
||||||
|
|
||||||
def test_request_client(sample, _response_basic):
|
|
||||||
assert _response_basic == sample.response
|
|
||||||
|
|
||||||
def test_request_client_forwards_default_headers(sample, mock_request, _response_basic):
|
|
||||||
mock_request.assert_called_once_with(
|
|
||||||
method = sample.method,
|
|
||||||
url = f'{sample.base_url}/{sample.endpoint}',
|
|
||||||
headers = sample.headers,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_get_request_client_payload(sample, _response_payload):
|
|
||||||
assert _response_payload == sample.response
|
|
||||||
|
|
||||||
def test_request_client_forwards_payload_headers(sample, mock_request, _response_payload):
|
|
||||||
assert mock_request.call_args.kwargs['headers'] == sample.headers | sample.payload_headers
|
|
||||||
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
@fixture(name='mock_request', autouse=True)
|
|
||||||
def fixture_mock_request(sample):
|
|
||||||
with patch('scwrypts.http.client.request') as mock:
|
|
||||||
mock.return_value = sample.response
|
|
||||||
yield mock
|
|
||||||
|
|
||||||
@fixture(name='request_client', autouse=True)
|
|
||||||
def fixture_request_client(sample):
|
|
||||||
return get_request_client(sample.base_url, sample.headers)
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
@fixture(name='_response_basic')
|
|
||||||
def fixture_response_basic(sample, request_client):
|
|
||||||
return request_client(
|
|
||||||
method = sample.method,
|
|
||||||
endpoint = sample.endpoint,
|
|
||||||
)
|
|
||||||
|
|
||||||
@fixture(name='_response_payload')
|
|
||||||
def fixture_response_payload(sample, request_client):
|
|
||||||
return request_client(
|
|
||||||
method = sample.method,
|
|
||||||
endpoint = sample.endpoint,
|
|
||||||
**{
|
|
||||||
**sample.payload,
|
|
||||||
'headers': sample.payload_headers,
|
|
||||||
},
|
|
||||||
)
|
|
@ -2,9 +2,6 @@ from bpython import embed
|
|||||||
|
|
||||||
|
|
||||||
def interactive(variable_descriptions):
|
def interactive(variable_descriptions):
|
||||||
'''
|
|
||||||
main() decorator to drop to interactive python environment upon completion
|
|
||||||
'''
|
|
||||||
def outer(function):
|
def outer(function):
|
||||||
|
|
||||||
def inner(*args, **kwargs):
|
def inner(*args, **kwargs):
|
@ -2,47 +2,7 @@ from contextlib import contextmanager
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from sys import stdin, stdout, stderr
|
from sys import stdin, stdout, stderr
|
||||||
|
|
||||||
from scwrypts.env import getenv
|
from py.lib.scwrypts.getenv import getenv
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def get_combined_stream(input_file=None, output_file=None):
|
|
||||||
'''
|
|
||||||
context manager to open an "input_file" and "output_file"
|
|
||||||
|
|
||||||
But the "files" can be pipe-streams, stdin/stdout, or even
|
|
||||||
actual files! Helpful when trying to write CLI scwrypts
|
|
||||||
which would like to accept all kinds of input and output
|
|
||||||
configurations.
|
|
||||||
'''
|
|
||||||
with get_stream(input_file, 'r') as input_stream, get_stream(output_file, 'w+') as output_stream:
|
|
||||||
yield CombinedStream(input_stream, output_stream)
|
|
||||||
|
|
||||||
def add_io_arguments(parser, allow_input=True, allow_output=True):
|
|
||||||
'''
|
|
||||||
slap these puppies onto your argparse.ArgumentParser to
|
|
||||||
allow easy use of the get_combined_stream at the command line
|
|
||||||
'''
|
|
||||||
if allow_input:
|
|
||||||
parser.add_argument(
|
|
||||||
'-i', '--input-file',
|
|
||||||
dest = 'input_file',
|
|
||||||
default = None,
|
|
||||||
help = 'path to input file; omit for stdin',
|
|
||||||
required = False,
|
|
||||||
)
|
|
||||||
|
|
||||||
if allow_output:
|
|
||||||
parser.add_argument(
|
|
||||||
'-o', '--output-file',
|
|
||||||
dest = 'output_file',
|
|
||||||
default = None,
|
|
||||||
help = 'path to output file; omit for stdout',
|
|
||||||
required = False,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
@ -74,6 +34,32 @@ def get_stream(filename=None, mode='r', encoding='utf-8', verbose=False, **kwarg
|
|||||||
stdout.flush()
|
stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def add_io_arguments(parser, toggle_input=True, toggle_output=True):
|
||||||
|
if toggle_input:
|
||||||
|
parser.add_argument(
|
||||||
|
'-i', '--input-file',
|
||||||
|
dest = 'input_file',
|
||||||
|
default = None,
|
||||||
|
help = 'path to input file; omit for stdin',
|
||||||
|
required = False,
|
||||||
|
)
|
||||||
|
|
||||||
|
if toggle_output:
|
||||||
|
parser.add_argument(
|
||||||
|
'-o', '--output-file',
|
||||||
|
dest = 'output_file',
|
||||||
|
default = None,
|
||||||
|
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:
|
class CombinedStream:
|
||||||
def __init__(self, input_stream, output_stream):
|
def __init__(self, input_stream, output_stream):
|
||||||
self.input = input_stream
|
self.input = input_stream
|
@ -1 +0,0 @@
|
|||||||
from .combined_io_stream import get_combined_stream, add_io_arguments
|
|
@ -1 +0,0 @@
|
|||||||
from .client import get_client
|
|
22
py/lib/scwrypts/run.py
Normal file
22
py/lib/scwrypts/run.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from os import getenv
|
||||||
|
from pathlib import Path
|
||||||
|
from subprocess import run as subprocess_run
|
||||||
|
|
||||||
|
|
||||||
|
def run(scwrypt_name, *args):
|
||||||
|
DEPTH = int(getenv('SUBSCWRYPT', '0'))
|
||||||
|
DEPTH += 1
|
||||||
|
|
||||||
|
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(
|
||||||
|
f'SUBSCWRYPT={DEPTH} {SCWRYPTS_EXE} {scwrypt_name} -- {ARGS}',
|
||||||
|
shell=True,
|
||||||
|
executable='/bin/zsh',
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f' {"--"*DEPTH} ({DEPTH}) END SUBSCWRYPT : {Path(scwrypt_name).name}\n')
|
@ -1,23 +0,0 @@
|
|||||||
'''
|
|
||||||
scwrypts meta-configuration
|
|
||||||
|
|
||||||
provides a helpful three ways to run "scwrypts"
|
|
||||||
|
|
||||||
'scwrypts' is an agnostic, top-level executor allowing any scwrypt to be called from python workflows
|
|
||||||
|
|
||||||
'execute' is the default context set-up for python-based scwrypts
|
|
||||||
|
|
||||||
'interactive' is a context set-up for interactive, python-based scwrypts
|
|
||||||
after execution, you are dropped in a bpython shell with all the variables
|
|
||||||
configured during main() execution
|
|
||||||
'''
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'scwrypts',
|
|
||||||
'execute',
|
|
||||||
'interactive',
|
|
||||||
]
|
|
||||||
|
|
||||||
from .scwrypts import scwrypts
|
|
||||||
from .execute import execute
|
|
||||||
from .interactive import interactive
|
|
@ -1,26 +0,0 @@
|
|||||||
from argparse import ArgumentError
|
|
||||||
|
|
||||||
|
|
||||||
class MissingVariableError(EnvironmentError):
|
|
||||||
def init(self, name):
|
|
||||||
super().__init__(f'Missing required environment variable "{name}"')
|
|
||||||
|
|
||||||
|
|
||||||
class MissingFlagAndEnvironmentVariableError(EnvironmentError, ArgumentError):
|
|
||||||
def __init__(self, flags, env_var):
|
|
||||||
super().__init__(f'must provide at least one of : {{ flags: {flags} OR {env_var} }}')
|
|
||||||
|
|
||||||
|
|
||||||
class MissingScwryptsExecutableError(EnvironmentError):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__('scwrypts must be installed and available on your PATH')
|
|
||||||
|
|
||||||
|
|
||||||
class BadScwryptsLookupError(ValueError):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__('must provide name/group/type or scwrypt lookup patterns')
|
|
||||||
|
|
||||||
|
|
||||||
class MissingScwryptsGroupOrTypeError(ValueError):
|
|
||||||
def __init__(self, group, _type):
|
|
||||||
super().__init__(f'missing required group or type (group={group} | type={_type}')
|
|
@ -1,58 +0,0 @@
|
|||||||
from os import getenv
|
|
||||||
from shutil import which
|
|
||||||
from subprocess import run
|
|
||||||
|
|
||||||
from .exceptions import MissingScwryptsExecutableError, BadScwryptsLookupError, MissingScwryptsGroupOrTypeError
|
|
||||||
|
|
||||||
|
|
||||||
def scwrypts(patterns=None, args=None, executable_args=None, name=None, group=None, _type=None):
|
|
||||||
'''
|
|
||||||
top-level scwrypts invoker from python
|
|
||||||
|
|
||||||
patterns str / list pattern-based scwrypt lookup
|
|
||||||
args str / list arguments forwarded to the invoked scwrypt
|
|
||||||
executable_args str / list arguments for the 'scwrypts' executable
|
|
||||||
(str above assumes space-delimited values)
|
|
||||||
|
|
||||||
name str exact scwrypt lookup name (requires group and _type)
|
|
||||||
group str exact scwrypt lookup group
|
|
||||||
_type str exact scwrypt lookup type
|
|
||||||
|
|
||||||
SCWRYPTS_EXECUTABLE configuration variable which defines the full path to scwrypts executable
|
|
||||||
|
|
||||||
see 'scwrypts --help' for more information
|
|
||||||
'''
|
|
||||||
if patterns is None and name is None:
|
|
||||||
raise BadScwryptsLookupError()
|
|
||||||
|
|
||||||
if name is not None and (group is None or _type is None):
|
|
||||||
raise MissingScwryptsGroupOrTypeError(group, _type)
|
|
||||||
|
|
||||||
executable = which(getenv('SCWRYPTS_EXECUTABLE', 'scwrypts'))
|
|
||||||
|
|
||||||
if executable is None:
|
|
||||||
raise MissingScwryptsExecutableError()
|
|
||||||
|
|
||||||
lookup = _parse(patterns) if name is None else f'--name {name} --group {group} --type {_type}'
|
|
||||||
|
|
||||||
depth = getenv('SUBSCWRYPT', '')
|
|
||||||
if depth != '':
|
|
||||||
depth = int(depth) + 1
|
|
||||||
|
|
||||||
return run(
|
|
||||||
f'SUBSCWRYPT={depth} {executable} {_parse(executable_args)} {lookup} -- {_parse(args)}',
|
|
||||||
shell=True,
|
|
||||||
executable='/bin/zsh',
|
|
||||||
check=False,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _parse(string_or_list_args):
|
|
||||||
if string_or_list_args is None:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
if isinstance(string_or_list_args, list):
|
|
||||||
return ' '.join(string_or_list_args)
|
|
||||||
|
|
||||||
return str(string_or_list_args)
|
|
@ -1,184 +0,0 @@
|
|||||||
from random import choice
|
|
||||||
from re import search
|
|
||||||
from string import ascii_letters, digits
|
|
||||||
from types import SimpleNamespace
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from pytest import fixture, raises
|
|
||||||
|
|
||||||
from scwrypts.test import get_generator
|
|
||||||
|
|
||||||
from .exceptions import MissingScwryptsExecutableError, BadScwryptsLookupError, MissingScwryptsGroupOrTypeError
|
|
||||||
from .scwrypts import scwrypts
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
def test_scwrypts(sample, _scwrypts):
|
|
||||||
assert validate_scwrypts_output(sample, _scwrypts)
|
|
||||||
|
|
||||||
def test_scwrypts_finds_system_executable(sample, _scwrypts, mock_which):
|
|
||||||
mock_which.assert_called_once_with(sample.env['SCWRYPTS_EXECUTABLE'])
|
|
||||||
|
|
||||||
def test_scwrypts_uses_configured_executable_path(_scwrypts, mock_getenv):
|
|
||||||
mock_getenv.assert_any_call('SCWRYPTS_EXECUTABLE', 'scwrypts')
|
|
||||||
|
|
||||||
def test_scwrypts_uses_correct_depth(_scwrypts, mock_getenv):
|
|
||||||
mock_getenv.assert_any_call('SUBSCWRYPT', '')
|
|
||||||
|
|
||||||
def test_scwrypts_runs_subprocess(_scwrypts, mock_run):
|
|
||||||
mock_run.assert_called_once()
|
|
||||||
|
|
||||||
##########################################
|
|
||||||
|
|
||||||
def test_scwrypts_omit_optionals(sample, _scwrypts_omit_optionals):
|
|
||||||
assert validate_scwrypts_output(sample, _scwrypts_omit_optionals)
|
|
||||||
|
|
||||||
def test_scwrypts_omit_optionals_finds_system_executable(sample, _scwrypts_omit_optionals, mock_which):
|
|
||||||
mock_which.assert_called_once_with('scwrypts')
|
|
||||||
|
|
||||||
def test_scwrypts_omit_optionals_uses_configured_executable_path(_scwrypts_omit_optionals, mock_getenv):
|
|
||||||
mock_getenv.assert_any_call('SCWRYPTS_EXECUTABLE', 'scwrypts')
|
|
||||||
|
|
||||||
def test_scwrypts_omit_optionals_uses_correct_depth(_scwrypts_omit_optionals, mock_getenv):
|
|
||||||
mock_getenv.assert_any_call('SUBSCWRYPT', '')
|
|
||||||
|
|
||||||
def test_scwrypts_omit_optionals_runs_subprocess(_scwrypts_omit_optionals, mock_run):
|
|
||||||
mock_run.assert_called_once()
|
|
||||||
|
|
||||||
##########################################
|
|
||||||
|
|
||||||
def test_invalid_lookup_missing_patterns_and_name(sample):
|
|
||||||
sample.patterns = None
|
|
||||||
sample.name = None
|
|
||||||
with raises(BadScwryptsLookupError):
|
|
||||||
scwrypts(**get_scwrypts_args(sample))
|
|
||||||
|
|
||||||
def test_invalid_name_lookup_missing_group(sample):
|
|
||||||
sample.group = None
|
|
||||||
with raises(MissingScwryptsGroupOrTypeError):
|
|
||||||
scwrypts(**get_scwrypts_args(sample))
|
|
||||||
|
|
||||||
def test_invalid_name_lookup_missing_type(sample):
|
|
||||||
sample._type = None # pylint: disable=protected-access
|
|
||||||
with raises(MissingScwryptsGroupOrTypeError):
|
|
||||||
scwrypts(**get_scwrypts_args(sample))
|
|
||||||
|
|
||||||
def test_invalid_scwrypts_installation(sample, mock_which):
|
|
||||||
mock_which.return_value = None
|
|
||||||
with raises(MissingScwryptsExecutableError):
|
|
||||||
scwrypts(**get_scwrypts_args(sample))
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
generate = get_generator({
|
|
||||||
'str_length_minimum': 8,
|
|
||||||
'str_length_maximum': 128,
|
|
||||||
'character_set': ascii_letters + digits + '/-_'
|
|
||||||
})
|
|
||||||
|
|
||||||
def _generate_str_or_list_arg():
|
|
||||||
random_arg = generate(list, {'data_types': {str}})
|
|
||||||
return random_arg if choice([str, list]) == list else ' '.join(random_arg)
|
|
||||||
|
|
||||||
@fixture(name='sample')
|
|
||||||
def fixture_sample():
|
|
||||||
sample = SimpleNamespace(
|
|
||||||
patterns = _generate_str_or_list_arg(),
|
|
||||||
args = _generate_str_or_list_arg(),
|
|
||||||
executable_args = _generate_str_or_list_arg(),
|
|
||||||
|
|
||||||
name = generate(str),
|
|
||||||
group = generate(str),
|
|
||||||
_type = generate(str),
|
|
||||||
|
|
||||||
executable = generate(str),
|
|
||||||
|
|
||||||
env = {
|
|
||||||
'SCWRYPTS_EXECUTABLE': generate(str),
|
|
||||||
'SUBSCWRYPT': str(generate(int, {'minimum': 1, 'maximum': 99})),
|
|
||||||
},
|
|
||||||
|
|
||||||
returncode = generate(int),
|
|
||||||
stdout = generate(str),
|
|
||||||
stderr = generate(str),
|
|
||||||
)
|
|
||||||
|
|
||||||
return sample
|
|
||||||
|
|
||||||
def get_scwrypts_args(sample):
|
|
||||||
return {
|
|
||||||
key: getattr(sample, key)
|
|
||||||
for key in [
|
|
||||||
'patterns',
|
|
||||||
'args',
|
|
||||||
'executable_args',
|
|
||||||
'name',
|
|
||||||
'group',
|
|
||||||
'_type',
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
@fixture(name='mock_which', autouse=True)
|
|
||||||
def fixture_mock_which(sample):
|
|
||||||
with patch('scwrypts.scwrypts.scwrypts.which') as mock:
|
|
||||||
mock.return_value = sample.executable
|
|
||||||
yield mock
|
|
||||||
|
|
||||||
@fixture(name='mock_getenv', autouse=True)
|
|
||||||
def fixture_mock_getenv(sample):
|
|
||||||
with patch('scwrypts.scwrypts.scwrypts.getenv') as mock:
|
|
||||||
mock.side_effect = sample.env.get
|
|
||||||
yield mock
|
|
||||||
|
|
||||||
@fixture(name='mock_run', autouse=True)
|
|
||||||
def fixture_mock_run(sample):
|
|
||||||
with patch('scwrypts.scwrypts.scwrypts.run') as mock:
|
|
||||||
mock.side_effect = lambda *args, **_kwargs: SimpleNamespace(
|
|
||||||
args = args,
|
|
||||||
returncode = sample.returncode,
|
|
||||||
stdout = sample.stdout,
|
|
||||||
stderr = sample.stderr,
|
|
||||||
)
|
|
||||||
yield mock
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
@fixture(name='_scwrypts')
|
|
||||||
def fixture_scwrypts(sample):
|
|
||||||
return scwrypts(**get_scwrypts_args(sample))
|
|
||||||
|
|
||||||
@fixture(name='_scwrypts_omit_optionals')
|
|
||||||
def fixture_scwrypts_omit_optionals(sample):
|
|
||||||
sample.args = None
|
|
||||||
sample.executable_args = None
|
|
||||||
|
|
||||||
del sample.env['SCWRYPTS_EXECUTABLE']
|
|
||||||
del sample.env['SUBSCWRYPT']
|
|
||||||
|
|
||||||
return scwrypts(**get_scwrypts_args(sample))
|
|
||||||
|
|
||||||
def validate_scwrypts_output(sample, output):
|
|
||||||
#
|
|
||||||
# I would love to use 'assert _scwrypts == SimpleNamespace(...expected...)'
|
|
||||||
# but the output.args is difficult to recreate without copying all the
|
|
||||||
# processing logic over from the scwrypts function
|
|
||||||
#
|
|
||||||
# opting for a bit of a strange equality test here, checking the args
|
|
||||||
# as closely as possible without copying parsing logic
|
|
||||||
#
|
|
||||||
run_args_reduced_to_a_single_string = len(output.args) == 1
|
|
||||||
run_args_follow_expected_form = search(
|
|
||||||
fr'^SUBSCWRYPT=.* {sample.executable} .*-- .*$',
|
|
||||||
output.args[0],
|
|
||||||
)
|
|
||||||
|
|
||||||
return all([
|
|
||||||
run_args_reduced_to_a_single_string,
|
|
||||||
run_args_follow_expected_form,
|
|
||||||
output.returncode == sample.returncode,
|
|
||||||
output.stdout == sample.stdout,
|
|
||||||
output.stderr == sample.stderr,
|
|
||||||
])
|
|
@ -1,10 +0,0 @@
|
|||||||
'''
|
|
||||||
automated testing utilties, but primarily a random data generator
|
|
||||||
'''
|
|
||||||
__all__ = [
|
|
||||||
'generate',
|
|
||||||
]
|
|
||||||
|
|
||||||
from .generate import generate, get_generator
|
|
||||||
|
|
||||||
from .character_set import *
|
|
@ -1,13 +0,0 @@
|
|||||||
'''
|
|
||||||
string constants typically used for randomly generated data
|
|
||||||
|
|
||||||
the 'string' standard library already contains many character sets,
|
|
||||||
but not these :)
|
|
||||||
'''
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'uri',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
uri = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&\'()*+,;='
|
|
@ -1,10 +0,0 @@
|
|||||||
class GeneratorError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class NoDataTypeError(GeneratorError, ValueError):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__('must provide at least one data type (either "data_type" or "data_types")')
|
|
||||||
|
|
||||||
class BadGeneratorTypeError(GeneratorError, ValueError):
|
|
||||||
def __init__(self, data_type):
|
|
||||||
super().__init__(f'no generator exists for data type "{data_type}"')
|
|
@ -1,387 +0,0 @@
|
|||||||
from csv import writer, QUOTE_NONNUMERIC
|
|
||||||
from io import StringIO
|
|
||||||
from json import dumps, loads
|
|
||||||
from random import randint, uniform, choice
|
|
||||||
from re import sub
|
|
||||||
from string import printable
|
|
||||||
from typing import Hashable, Callable
|
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
from requests import Response, status_codes
|
|
||||||
from yaml import safe_dump
|
|
||||||
|
|
||||||
from .exceptions import NoDataTypeError, BadGeneratorTypeError
|
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_OPTIONS = {
|
|
||||||
'data_types': None,
|
|
||||||
'minimum': 0,
|
|
||||||
'maximum': 64,
|
|
||||||
'depth': 1,
|
|
||||||
'character_set': None,
|
|
||||||
'bool_nullable': False,
|
|
||||||
'str_length': None,
|
|
||||||
'str_length_minimum': 0,
|
|
||||||
'str_length_maximum': 64,
|
|
||||||
'uuid_output_type': 'uuid', # str or 'uuid'
|
|
||||||
'list_length': 8,
|
|
||||||
'set_length': 8,
|
|
||||||
'dict_length': 8,
|
|
||||||
'csv_bool_nullable': True,
|
|
||||||
'csv_columns': None,
|
|
||||||
'csv_columns_minimum': 1,
|
|
||||||
'csv_columns_maximum': 16,
|
|
||||||
'csv_rows': None,
|
|
||||||
'csv_rows_minimum': 2,
|
|
||||||
'csv_rows_maximum': 16,
|
|
||||||
'csv_output_type': 'stringio', # str or 'stringio'
|
|
||||||
'json_initial_type': dict, # typically dict or list
|
|
||||||
'json_bool_nullable': True,
|
|
||||||
'json_output_type': 'stringio', # str or 'stringio'
|
|
||||||
'yaml_initial_type': dict, # typically dict or list
|
|
||||||
'yaml_bool_nullable': True,
|
|
||||||
'yaml_use_default_flow_style': False,
|
|
||||||
'yaml_output_type': 'stringio', # str or 'stringio'
|
|
||||||
'requests_response_status_code': status_codes.codes[200],
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_generator(default_options=None):
|
|
||||||
if default_options is None:
|
|
||||||
default_options = {}
|
|
||||||
|
|
||||||
def generator_function(data_type=None, options_overrides=None):
|
|
||||||
if options_overrides is None:
|
|
||||||
options_overrides = {}
|
|
||||||
|
|
||||||
return generate(
|
|
||||||
data_type = data_type,
|
|
||||||
options = default_options | options_overrides,
|
|
||||||
)
|
|
||||||
|
|
||||||
return generator_function
|
|
||||||
|
|
||||||
def generate(data_type=None, options=None):
|
|
||||||
'''
|
|
||||||
generate random data with the call of a function
|
|
||||||
use data_type to generate a single value
|
|
||||||
|
|
||||||
use options to set generation options (key = type, value = kwargs)
|
|
||||||
|
|
||||||
use options.data_types and omit data_type to generate a random type
|
|
||||||
'''
|
|
||||||
if options is None:
|
|
||||||
options = {}
|
|
||||||
|
|
||||||
options = DEFAULT_OPTIONS | options
|
|
||||||
|
|
||||||
if data_type is None:
|
|
||||||
if options['data_types'] is None or len(options['data_types']) == 0:
|
|
||||||
raise NoDataTypeError()
|
|
||||||
|
|
||||||
return generate(
|
|
||||||
data_type=choice(list(options['data_types'])),
|
|
||||||
options=options,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not isinstance(data_type, str):
|
|
||||||
data_type = data_type.__name__
|
|
||||||
|
|
||||||
if data_type not in Generator.get_supported_data_types():
|
|
||||||
raise BadGeneratorTypeError(data_type)
|
|
||||||
|
|
||||||
return getattr(Generator, f'_{data_type}')(options)
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
SUPPORTED_DATA_TYPES = None
|
|
||||||
|
|
||||||
class Generator:
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_supported_data_types(cls):
|
|
||||||
global SUPPORTED_DATA_TYPES # pylint: disable=global-statement
|
|
||||||
|
|
||||||
if SUPPORTED_DATA_TYPES is None:
|
|
||||||
SUPPORTED_DATA_TYPES = {
|
|
||||||
sub('^_', '', data_type)
|
|
||||||
for data_type, method in Generator.__dict__.items()
|
|
||||||
if isinstance(method, staticmethod)
|
|
||||||
}
|
|
||||||
|
|
||||||
return SUPPORTED_DATA_TYPES
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def filter_data_types(cls, options, filters=None):
|
|
||||||
'''
|
|
||||||
returns an options dict with appropriately filtered data_types
|
|
||||||
|
|
||||||
if data_types are not yet defined, starts with all supported data_types
|
|
||||||
'''
|
|
||||||
if options['data_types'] is None:
|
|
||||||
options['data_types'] = Generator.get_supported_data_types()
|
|
||||||
|
|
||||||
if filters is None or len(filters) == 0:
|
|
||||||
return options
|
|
||||||
|
|
||||||
return {
|
|
||||||
**options,
|
|
||||||
'data_types': set(filter(
|
|
||||||
lambda data_type: all(( f(data_type, options) for f in filters )),
|
|
||||||
options['data_types'],
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
|
|
||||||
class Filters:
|
|
||||||
@staticmethod
|
|
||||||
def hashable(data_type, _options):
|
|
||||||
if isinstance(data_type, Callable):
|
|
||||||
return isinstance(data_type(), Hashable)
|
|
||||||
if not isinstance(data_type, str):
|
|
||||||
data_type = data_type.__name__
|
|
||||||
return data_type in { 'bool', 'int', 'float', 'chr', 'str', 'uuid' }
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def filelike(data_type, _options):
|
|
||||||
return data_type in { 'csv', 'json', 'yaml' }
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def complex(data_type, _options):
|
|
||||||
return data_type in { 'requests_Response' }
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def basic(data_type, options):
|
|
||||||
return all([
|
|
||||||
not Generator.Filters.filelike(data_type, options),
|
|
||||||
not Generator.Filters.complex(data_type, options),
|
|
||||||
])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def pythonset(data_type, _options):
|
|
||||||
if not isinstance(data_type, str):
|
|
||||||
data_type = data_type.__name__
|
|
||||||
return data_type == 'set'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def csvsafe(data_type, options):
|
|
||||||
options['depth'] = max(1, options['depth'])
|
|
||||||
return all([
|
|
||||||
Generator.Filters.basic(data_type, options),
|
|
||||||
not Generator.Filters.pythonset(data_type, options),
|
|
||||||
])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def jsonsafe(data_type, options):
|
|
||||||
return all([
|
|
||||||
Generator.Filters.basic(data_type, options),
|
|
||||||
not Generator.Filters.pythonset(data_type, options),
|
|
||||||
])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def yamlsafe(data_type, options):
|
|
||||||
return all([
|
|
||||||
Generator.Filters.basic(data_type, options),
|
|
||||||
not Generator.Filters.pythonset(data_type, options),
|
|
||||||
])
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_option_with_range(cls, options, option_key, data_type=int):
|
|
||||||
'''
|
|
||||||
typically an integer range, allows both:
|
|
||||||
- setting a fixed configuration (e.g. 'str_length')
|
|
||||||
- allowing a configuration range (e.g. 'str_length_minimum' and 'str_length_maximum')
|
|
||||||
'''
|
|
||||||
fixed = options.get(option_key, None)
|
|
||||||
if fixed is not None:
|
|
||||||
return fixed
|
|
||||||
|
|
||||||
return generate(data_type, {
|
|
||||||
'minimum': options[f'{option_key}_minimum'],
|
|
||||||
'maximum': options[f'{option_key}_maximum'],
|
|
||||||
})
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _bool(options):
|
|
||||||
return choice([True, False, None]) if options['bool_nullable'] else choice([True, False])
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _int(options):
|
|
||||||
return randint(options['minimum'], options['maximum'])
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _float(options):
|
|
||||||
return uniform(options['minimum'], options['maximum'])
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _chr(options):
|
|
||||||
character_set = options['character_set']
|
|
||||||
return choice(character_set) if character_set is not None else chr(randint(0,65536))
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _str(options):
|
|
||||||
return ''.join((
|
|
||||||
generate(chr, options)
|
|
||||||
for _ in range(Generator.get_option_with_range(options, 'str_length'))
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _uuid(options):
|
|
||||||
'''
|
|
||||||
creates a UUID object or a str containing a uuid (v4)
|
|
||||||
'''
|
|
||||||
uuid = uuid4()
|
|
||||||
return str(uuid) if options['uuid_output_type'] == str else uuid
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _list(options):
|
|
||||||
if options['depth'] <= 0:
|
|
||||||
return []
|
|
||||||
|
|
||||||
options['depth'] -= 1
|
|
||||||
options = Generator.filter_data_types(options, [
|
|
||||||
Generator.Filters.basic,
|
|
||||||
])
|
|
||||||
|
|
||||||
return [ generate(None, {**options}) for _ in range(options['list_length']) ]
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _set(options):
|
|
||||||
if options['depth'] <= 0:
|
|
||||||
return set()
|
|
||||||
|
|
||||||
options['depth'] -= 1
|
|
||||||
options = Generator.filter_data_types(options, [
|
|
||||||
Generator.Filters.hashable,
|
|
||||||
])
|
|
||||||
|
|
||||||
return { generate(None, options) for _ in range(options['set_length']) }
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _dict(options):
|
|
||||||
if options['depth'] <= 0:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
options['depth'] -= 1
|
|
||||||
options = Generator.filter_data_types(options, [
|
|
||||||
Generator.Filters.basic,
|
|
||||||
])
|
|
||||||
|
|
||||||
key_options = Generator.filter_data_types(options, [
|
|
||||||
Generator.Filters.hashable,
|
|
||||||
])
|
|
||||||
|
|
||||||
if len(options['data_types']) == 0 or len(key_options['data_types']) == 0:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
return {
|
|
||||||
generate(None, key_options): generate(None, options)
|
|
||||||
for _ in range(options['dict_length'])
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _csv(options):
|
|
||||||
'''
|
|
||||||
creates a StringIO object containing csv data
|
|
||||||
'''
|
|
||||||
if options['character_set'] is None:
|
|
||||||
options['character_set'] = printable
|
|
||||||
|
|
||||||
options['bool_nullable'] = options['csv_bool_nullable']
|
|
||||||
options = Generator.filter_data_types(options, [
|
|
||||||
Generator.Filters.csvsafe,
|
|
||||||
])
|
|
||||||
|
|
||||||
columns = Generator.get_option_with_range(options, 'csv_columns')
|
|
||||||
rows = Generator.get_option_with_range(options, 'csv_rows')
|
|
||||||
|
|
||||||
|
|
||||||
csv = StringIO()
|
|
||||||
csv_writer = writer(csv, quoting=QUOTE_NONNUMERIC)
|
|
||||||
|
|
||||||
options['list_length'] = columns
|
|
||||||
|
|
||||||
[ # pylint: disable=expression-not-assigned
|
|
||||||
csv_writer.writerow(generate(list, options))
|
|
||||||
for _ in range(rows)
|
|
||||||
]
|
|
||||||
|
|
||||||
csv.seek(0)
|
|
||||||
return csv.getvalue() if options['csv_output_type'] == str else csv
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _json(options):
|
|
||||||
'''
|
|
||||||
creates a StringIO object or str containing json data
|
|
||||||
'''
|
|
||||||
|
|
||||||
if options['character_set'] is None:
|
|
||||||
options['character_set'] = printable
|
|
||||||
|
|
||||||
options['bool_nullable'] = options['json_bool_nullable']
|
|
||||||
options['uuid_output_type'] = str
|
|
||||||
options = Generator.filter_data_types(options, [
|
|
||||||
Generator.Filters.jsonsafe,
|
|
||||||
])
|
|
||||||
|
|
||||||
json = dumps(generate(
|
|
||||||
options['json_initial_type'],
|
|
||||||
{**options},
|
|
||||||
))
|
|
||||||
|
|
||||||
return json if options['json_output_type'] == str else StringIO(json)
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _yaml(options):
|
|
||||||
'''
|
|
||||||
creates a StringIO object or str containing yaml data
|
|
||||||
'''
|
|
||||||
if options['character_set'] is None:
|
|
||||||
options['character_set'] = printable
|
|
||||||
|
|
||||||
options['bool_nullable'] = options['yaml_bool_nullable']
|
|
||||||
options['uuid_output_type'] = str
|
|
||||||
options = Generator.filter_data_types(options, [
|
|
||||||
Generator.Filters.yamlsafe,
|
|
||||||
])
|
|
||||||
|
|
||||||
yaml = StringIO()
|
|
||||||
safe_dump(
|
|
||||||
generate(options['yaml_initial_type'], {**options}),
|
|
||||||
yaml,
|
|
||||||
default_flow_style=options['yaml_use_default_flow_style'],
|
|
||||||
)
|
|
||||||
yaml.seek(0)
|
|
||||||
|
|
||||||
return yaml.getvalue() if options['yaml_output_type'] == str else yaml
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _requests_Response(options):
|
|
||||||
'''
|
|
||||||
creates a requests.Response-like object containing json data
|
|
||||||
'''
|
|
||||||
|
|
||||||
options['json_output_type'] = str
|
|
||||||
|
|
||||||
response = Response()
|
|
||||||
response.status_code = options['requests_response_status_code']
|
|
||||||
json = loads(generate('json', options))
|
|
||||||
response.json = lambda: json
|
|
||||||
|
|
||||||
return response
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user