From cb5a4e42bffeef72db4910ee77c9842ed2c4156e Mon Sep 17 00:00:00 2001 From: Andrew Miner Date: Fri, 5 Jul 2019 20:39:07 -0600 Subject: [PATCH] Add basic project files for an empty project --- .build_cfg.sh | 3 + .gitignore | 7 + .pip.lock | 20 ++ .python-version | 1 + build.sh | 396 +++++++++++++++++++++++++++++++++++++++ requirements.txt | 13 ++ setup.cfg | 2 + setup.py | 20 ++ src/hooks/pre-commit | 46 +++++ src/pycurses/__init__.py | 3 + 10 files changed, 511 insertions(+) create mode 100644 .build_cfg.sh create mode 100644 .gitignore create mode 100644 .pip.lock create mode 100644 .python-version create mode 100755 build.sh create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100644 setup.py create mode 100755 src/hooks/pre-commit create mode 100644 src/pycurses/__init__.py diff --git a/.build_cfg.sh b/.build_cfg.sh new file mode 100644 index 0000000..3e1b8b3 --- /dev/null +++ b/.build_cfg.sh @@ -0,0 +1,3 @@ +# This file allows you to configure how the build.sh script operates. It will +# be sourced immediately before running a build command. Feel free to leave it +# empty if you don't need any modifications. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c9ddc9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.egg-info/ +*.swo +*.swp +.eggs/ +.env +__pycache__ +usr/ diff --git a/.pip.lock b/.pip.lock new file mode 100644 index 0000000..cfd79ab --- /dev/null +++ b/.pip.lock @@ -0,0 +1,20 @@ +args==0.1.0 +clint==0.5.1 +coverage==4.5.3 +future==0.17.1 +importlib-metadata==0.18 +jedi==0.14.0 +Mako==1.0.7 +mamba==0.9.3 +MarkupSafe==1.1.1 +mock==3.0.5 +parso==0.5.0 +pluggy==0.12.0 +pycodestyle==2.3.1 +pydocstyle==2.1.1 +pyflakes==1.6.0 +python-language-server==0.17.0+1.gc185cf8 +six==1.12.0 +snowballstemmer==1.9.0 +sure==1.4.7 +zipp==0.5.1 diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..424e179 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.6.8 diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..3789829 --- /dev/null +++ b/build.sh @@ -0,0 +1,396 @@ +#!/usr/bin/env bash + +USAGE=$(cat <<-END +USAGE: $0 * + + init: install all development dependencies + + --no-auto-refresh: skip the step of refreshing this build.sh script from + the tip of master in the boom-pylib repository + --nuke: completely remove the ./usr directory before initializing + + edit: launch vim with settings to use the project’s version of python + + lint: run various programs to catch programming & style errors + + test: run automated tests. By default, all tests are run once, and then the + script exits. There are a number of options to change this default. Some + conflict with one another (e.g., --watch and --functional). Whichever is + declared last will take precedence. + + Unit tests are those which run completely within the process space of + the test runner (i.e., no disk or network access). + + Functional tests are those which require no 3rd-party software be + installed or servers set up, but which otherwise do anything else. + + Integration tests have no limits on what they may require. + + --all (optional) turn on all test types + --circle-ci: (optional) only run tests which can pass in CircleCI. + Implies --no-integration. + --format: (optional) either "progress" or "documentation" + --messy: (optional) leave scratch directories behind in ./tmp + --watch: (optional) watch all the source files and repeat the tests + when things change. Implies --no-functional and --no-integration. + --(no-)functional: (optional) turn on/off functional tests + --(no-)unit: (optional) turn on/off unit tests + --(no-)integration: (optional) turn on/off integration tests + --none: (optional) turn off all test types + + clean: remove temporary or one-off data from the ./tmp directory + + python: start a Python REPL console which is sure to use the correct + version of Python pre-initialized with all the correct versions of + this package’s dependencies + + publish: publish this package as an official release on GemFury. This should + only be performed on the master branch, and only after a change has been + merged which updates the package version. + + refresh: refresh this build script and its related helper files. This will + download the latest version from the boom-pylib repo and replace the + files in your current repo. + + upgrade: upgrade any Python dependencies which have new versions available +END +) + +AUTO_REFRESH="YES" +BUILD_CONFIG=".build_cfg.sh" +CIRCLE_CI="NO" +COMMAND="help" +CONFIRM="NO" +ENV_FILE=".env" +FORMAT="progress" +LINT_INCLUDE="src" +LINT_EXCLUDE="--exclude=.ropeproject" +MESSY="NO" +NUKE="NO" +PYTHON=$(which python) +PYTHON_VERSION="3.6" +TEST_FILE=".pylib.testing" +TEST_FUNCTIONAL="YES" +TEST_INTEGRATION="YES" +TEST_RUN=1 +TEST_UNIT="YES" +VIRTUAL_ENV="./usr" +WATCH="NO" + +[[ "$USER" == "" ]] && USER=$(whoami) +VIRTUAL_BIN="$VIRTUAL_ENV/bin" + +# Helper Functions ##################################################################################################### + +function build-native-component { + # do nothing. Packages may override this function if they have a native component to build. + return 0 +} + +function build-python-env { + PYTHON_ENV="MESSY=$MESSY USER=$USER" +} + +function check-python-version { + if ! $PYTHON --version | grep -q $PYTHON_VERSION; then + echo "Please update PYTHON in .env to refer to a Python $PYTHON_VERSION executable." + exit 1 + fi + + return 0 +} + +function die { + printf "$*\n" + echo + exit 1 +} + +function execute-functional-tests { + if [[ "$TEST_FUNCTIONAL" == "YES" ]]; then + printf "\nRunning functional tests...\n" + eval $PYTHON_ENV $VIRTUAL_BIN/mamba run src --format=$FORMAT -t functional + return $? + fi + return 0 +} + +function execute-integration-tests { + if [[ "$TEST_INTEGRATION" == "YES" ]]; then + printf "\nRunning integration tests...\n" + eval $PYTHON_ENV $VIRTUAL_BIN/mamba run src --format=$FORMAT -t integration + return $? + fi + return 0 +} + +function execute-test-run { + echo "Starting test run $TEST_RUN at $(date):" + echo "unit: $TEST_UNIT, functional: $TEST_FUNCTIONAL, integration: $TEST_INTEGRATION" + + build-python-env + build-native-component \ + && execute-unit-tests \ + && execute-functional-tests \ + && execute-integration-tests \ + || return 1 + return 0 +} + +function execute-unit-tests { + if [[ "$TEST_UNIT" == "YES" ]]; then + printf "\nRunning unit tests...\n" + eval $PYTHON_ENV $VIRTUAL_BIN/mamba run src --format=$FORMAT -t unit + return $? + fi + return 0 +} + +function init-custom { + # do nothing. repos with custom initialization can override this function + return 0 +} + +function on-unknown-argument { + die "Unknown argument: $1" +} + +function usage { + die "$*\nTry \"$0 help\" for details" +} + +function watch-source-files { + fswatch -o -r src -e '.*' -i '\.cpp$' -i '\.h$' -i '\.py$' -m poll_monitor +} + +function write-build-config { + [[ -e "$BUILD_CONFIG" ]] && rm "$BUILD_CONFIG" + + echo "# This file allows you to configure how the build.sh script operates. It will" >> $BUILD_CONFIG + echo "# be sourced immediately before running a build command. Feel free to leave it" >> $BUILD_CONFIG + echo "# empty if you don't need any modifications." >> $BUILD_CONFIG +} + +function write-env-file { + [[ -e "$ENV_FILE" ]] && rm "$ENV_FILE" + + echo '# The location of a `python` executable for Python v3.6' >> $ENV_FILE + echo "PYTHON=$PYTHON" >> $ENV_FILE + echo '# The authentication token used for uploading to GemFury' >> $ENV_FILE + echo "GEMFURY_TOKEN=$GEMFURY_TOKEN" >> $ENV_FILE +} + +# Local Commands ####################################################################################################### + +function command-build { + build-native-component +} + +function command-clean { + if [[ "$CONFIRM" == "YES" ]]; then + rm -rf ./tmp + else + echo "Removing ./tmp... (skipped)" + echo "" + fi + + [[ "$CONFIRM" == "YES" ]] || printf "This was a dry run. To actually delete files, add: --confirm\n\n" +} + +function command-edit { + source $VIRTUAL_BIN/activate + export VIRTUAL_ENV + $EDITOR +} + +function command-help { + echo "$USAGE" | more + echo "" + exit +} + +function command-init { + if [[ "$AUTO_REFRESH" == "YES" ]]; then + command-refresh-build || exit 1 + fi + check-python-version || exit 1 + + [[ "$NUKE" == "YES" && -d $VIRTUAL_ENV ]] && rm -rf $VIRTUAL_ENV + $PYTHON -m venv --copies $VIRTUAL_ENV + ./usr/bin/pip install --upgrade pip + + unset PYTHONPATH + export PYTHONPATH + $VIRTUAL_BIN/pip install -r requirements.txt + init-custom + + $VIRTUAL_BIN/pip freeze | grep -v '^-e' > .pip.lock +} + +function command-lint { + $VIRTUAL_BIN/pycodestyle $LINT_INCLUDE $LINT_EXCLUDE + (( RESULT = $RESULT + $? )) + + $VIRTUAL_BIN/pydocstyle $LINT_INCLUDE + (( RESULT = $RESULT + $? )) + + $VIRTUAL_BIN/pyflakes $LINT_INCLUDE + (( RESULT = $RESULT + $? )) + + return $RESULT +} + +function command-publish { + CURRENT_BRANCH=$(git branch | grep '^\*' | awk '{print $2}') + + if [[ "$CURRENT_BRANCH" == "master" ]]; then + [[ "$GEMFURY_TOKEN" == "" ]] && die "Please make sure GEMFURY_TOKEN is defined in $ENV_FILE" + + $VIRTUAL_BIN/python setup.py sdist + PACKAGE="dist/$(ls dist | head -n1)" + + PUBLISHED_VERSION=$( \ + curl -s https://$GEMFURY_TOKEN@pypi.fury.io/boomtechnology/$PACKAGE \ + | grep boom-pylib- \ + | sed "s/.*$PACKAGE-\([0-9.].*\).tar.gz.*/\1/" \ + | tail -n1 + ) + CURRENT_VERSION=$(cat setup.py | grep version= | sed 's/.*version="\(.*\)".*/\1/') + + if [[ "$PUBLISHED_VERSION" != "$CURRENT_VERSION" ]]; then + curl -F package=@$PACKAGE https://$GEMFURY_TOKEN@push.fury.io/boomtechnology \ + || die "\nUploading package failed!" + else + echo "Version $CURRENT_VERSION has already been published, skipping publish..." + fi + + rm -rf dist 2>/dev/null + else + echo "Not on master branch, skipping publish..." + fi +} + +function command-python { + source $VIRTUAL_BIN/activate + eval $PYTHON_ENV $VIRTUAL_BIN/python +} + +function command-refresh-build { + # Install the Git Hooks + pushd .git/hooks >/dev/null + ls ../../src/hooks | while read HOOK_FILE; do + ln -sf "../../src/hooks/$HOOK_FILE" . + done + popd >/dev/null + + if cat .git/config | grep 'url =' | head -n1 | grep -q boom-pylib; then + # We're already in the boom-pylib repo. + return 0 + fi + + # Download the current version of boom-pylib + TMP_REPO=".boom-pylib" + rm -rf $TMP_REPO 2>/dev/null + git clone -q --depth 1 git@github.com:boomtechnology/boom-pylib.git $TMP_REPO + + # Copy over the files we need, replacing existing copies + mkdir -p src + rm -rf src/hooks 2>/dev/null + cp $TMP_REPO/build.sh .build.sh.new + cp -R $TMP_REPO/src/hooks src/hooks + + # Get rid of the boom-pylib repo + rm -rf $TMP_REPO 2>/dev/null + + (sleep 1; mv .build.sh.new build.sh) & +} + +function command-test { + RESULT=0 + + function do-watched-run { + rm -rf ./tmp 2>/dev/null + execute-test-run > $TEST_FILE.out 2>&1 + RESULT=$? + + clear + cat "$TEST_FILE.out" + rm "$TEST_FILE.out" + rm "$TEST_FILE" + printf "\nWaiting for changes...\n" + } + + if [[ "$WATCH" == "YES" ]]; then + clear + execute-test-run + echo "Waiting for changes..." + + rm $TEST_FILE 2> /dev/null + watch-source-files | while read FILE; do + if [[ ! -e $TEST_FILE ]]; then + touch $TEST_FILE + (( TEST_RUN = TEST_RUN + 1 )) + printf "\nChanges detected! Re-running tests...\n" + do-watched-run & + fi + done + else + execute-test-run + RESULT=$? + fi + + [[ "$MESSY" == "NO" ]] && rm -rf ./tmp 2>/dev/null + + return $RESULT +} + +function command-upgrade-pip { + $VIRTUAL_BIN/pip install -r requirements.txt --upgrade + $VIRTUAL_BIN/pip freeze | grep -v '^-e' > .pip.lock +} + +# Process Arguments #################################################################################################### + +[[ -e $ENV_FILE ]] && source $ENV_FILE +[[ -e $BUILD_CONFIG ]] || write-build-config +source $BUILD_CONFIG +write-env-file + +while [[ "$1" != "" ]]; do + case "$1" in + build) COMMAND="build";; + clean) COMMAND="clean";; + edit) COMMAND="edit";; + help|--help|-h|-?) COMMAND="help";; + init) COMMAND="init";; + lint) COMMAND="lint";; + python) COMMAND="python";; + publish) COMMAND="publish";; + refresh) COMMAND="refresh-build";; + test) COMMAND="test";; + upgrade) COMMAND="upgrade-pip";; + + --all) TEST_UNIT="YES"; TEST_FUNCTIONAL="YES"; TEST_INTEGRATION="YES";; + --no-refresh) AUTO_REFRESH="NO";; + --circle-ci) CIRCLE_CI="YES"; TEST_INTEGRATION="NO";; + --confirm) CONFIRM="YES";; + --format) shift; FORMAT="$1";; + --functional) TEST_FUNCTIONAL="YES";; + --integration) TEST_INTEGRATION="YES";; + --messy) MESSY="YES";; + --no-functional) TEST_FUNCTIONAL="NO";; + --no-integration) TEST_INTEGRATION="NO";; + --no-unit) TEST_UNIT="NO";; + --none) TEST_UNIT="NO"; TEST_FUNCTIONAL="NO"; TEST_INTEGRATION="NO";; + --nuke) NUKE="YES";; + --unit) TEST_UNIT="YES";; + --watch) TEST_FUNCTIONAL="NO"; TEST_INTEGRATION="NO"; WATCH="YES";; + + *) on-unknown-argument $1;; + esac + shift +done + +# Script ############################################################################################################### + +"command-$COMMAND" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5d5fd7c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +--index-url https://1hejIc-6NBRw68BmAkuppZVHsgbOfkQPs0@pypi.fury.io/boomtechnology/ +--extra-index-url https://pypi.org/simple + +Mako==1.0.7 +mamba==0.9.3 +pycodestyle==2.3.1 +pydocstyle==2.1.1 +pyflakes==1.6.0 +sure==1.4.7 + +git+ssh://git@github.com/andrewminer/python-language-server#egg=python-language-server + +-e . diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..6224f31 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[pycodestyle] +max-line-length = 120 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e49d9c3 --- /dev/null +++ b/setup.py @@ -0,0 +1,20 @@ +"""Define the pycursesui package.""" +# Taken from the following sample: https://github.com/pypa/sampleproject/blob/master/setup.py + +import setuptools + +setuptools.setup( + name="pycursesui", + version="0.0.1", + description="A python UI framework for command-line applications using curses", + author="Andrew Miner", + author_email="andrewminer@mac.com", + + packages=[ + "pycurses", + ], + package_dir={"": "src"}, + + install_requires=[ + ], +) diff --git a/src/hooks/pre-commit b/src/hooks/pre-commit new file mode 100755 index 0000000..020ca8c --- /dev/null +++ b/src/hooks/pre-commit @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +if git branch | grep -q '^\* master'; then + echo "" + echo "ERROR: Please don't commit directly to master. Instead, create a working branch" + echo " and submit a pull request." + echo "" + exit 1 +fi + +MASTER_VERSION=$(git show master:setup.py | grep version= | sed 's/.*version="\(.*\)".*/\1/') +CURRENT_VERSION=$(cat setup.py | grep version= | sed 's/.*version="\(.*\)".*/\1/') +if [[ "$MASTER_VERSION" == "$CURRENT_VERSION" ]]; then + echo "" + echo "ERROR: It looks like you've forgotten to update the package version for your" + echo " changes. Please update setup.py with a new version number." + echo "" + exit 1 +fi + +echo "Checking for stray fit/fdescription..." +if egrep -rn 'with f(it|description)' src; then + echo "" + echo "ERROR: Please remove fit/fdescription from tests before committing." + echo "" + exit 1 +fi + +echo "Running linters..." +./build.sh lint || exit 1 + +echo "Running tests..." +./build.sh test || exit 1 + +echo "All pre-commit checks passed!" +EXIT_CODE=0 + +if git status | grep -q "not staged"; then + echo "" + echo "WARNING: It looks like you forgot to add some of your changes! If this was" + echo " unintentional, use 'git add -A' to add your missing changes. Otherwise," + echo " use 'git commit -n' to ignore this check." + echo "" + EXIT_CODE=1 +fi + +exit $EXIT_CODE diff --git a/src/pycurses/__init__.py b/src/pycurses/__init__.py new file mode 100644 index 0000000..2caff4b --- /dev/null +++ b/src/pycurses/__init__.py @@ -0,0 +1,3 @@ +"""A python UI framework for command-line applications using curses.""" + +__all__ = []