Refactor .travis.yml into .travis.{yml,sh}
diff --git a/.travis.sh b/.travis.sh
new file mode 100644
index 0000000..b292270
--- /dev/null
+++ b/.travis.sh
@@ -0,0 +1,73 @@
+# shellcheck shell=bash
+
+main() (
+  set -eu -o pipefail
+  set -x
+
+  check-env
+
+  func=$1; shift
+  type "$func" &>/dev/null || {
+    echo 'Invalid travis.sh function'
+    return 1
+  }
+
+  "$func" "$@"
+)
+
+travis:install() {
+  python -m pip.__main__ install cython tox
+
+  git clone --branch="$LIBYAML_VERSION" \
+    https://github.com/yaml/libyaml.git \
+    /tmp/libyaml
+
+  # build libyaml
+  (
+    cd /tmp/libyaml
+    ./bootstrap
+    ./configure
+    make
+    make test-all
+    sudo make install
+  )
+}
+
+travis:before_install:osx() {
+  brew install zlib readline
+  brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/master/Formula/pyenv.rb ||
+    brew upgrade pyenv
+
+  eval "$(pyenv init -)"
+
+  local version_re=${TRAVIS_PYTHON_VERSION//./\\.}
+  if [[ $TRAVIS_PYTHON_VERSION != *dev* ]]; then
+    TRAVIS_PYTHON_VERSION=$(
+      pyenv install --list |
+        grep -E "\s\s$version_re" |
+        grep -vE 'dev|rc' |
+        tail -n 1 |
+        tr -d '[:space:]'
+    )
+    export TRAVIS_PYTHON_VERSION
+  fi
+
+  pyenv install --skip-existing --keep --verbose "$TRAVIS_PYTHON_VERSION" | \
+  tee pyenv-install.log | tail -n 50
+
+  pyenv shell "$TRAVIS_PYTHON_VERSION"
+  python --version
+}
+
+check-env() {
+  if \
+    [[ -z $BASH_VERSION ]] ||
+    [[ $0 == "${BASH_SOURCE[0]}" ]] ||
+    [[ $SHELL != /bin/bash ]]
+  then
+    echo 'Unexpected travis-ci environment'
+    return 1
+  fi
+}
+
+main "$@"
diff --git a/.travis.yml b/.travis.yml
index cd85849..66dd661 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,116 +18,42 @@
   - $HOME/.pre-commit
   - $HOME/Library/Caches/Homebrew
 
-install:
-- python -m pip.__main__ install cython tox
+install: (source ./.travis.sh travis:install)
 
-# build libyaml
-- pushd /tmp
-- set -e
-- git clone https://github.com/yaml/libyaml.git -b "${LIBYAML_VERSION}" libyaml
-- cd libyaml
-- ./bootstrap
-- ./configure
-- make
-- make test-all
-- sudo make install
-- set +e
-- popd
+before_script: sudo ldconfig
 
-before_script:
-- sudo ldconfig
-
-script:
-- python -m tox.__main__
-
-.mixtures:
-- &osx_python
-  if: type IN (api, cron) OR tag IS present
-  os: osx
-  osx_image: xcode9.4
-  language: generic
-  before_install:
-  - brew install zlib readline
-  - brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/master/Formula/pyenv.rb || brew upgrade pyenv
-  - &ensure_pyenv_preloaded eval "$(pyenv init -)"
-  - &select_specific_python |
-      if [[ ! "$TRAVIS_PYTHON_VERSION" =~ "dev" ]]
-      then
-        export TRAVIS_PYTHON_VERSION=$(\
-          pyenv install --list | \
-          grep -E '\s\s'"$TRAVIS_PYTHON_VERSION" | grep -vE 'dev|rc' | \
-          tail -n 1 | tr -d '[:space:]'\
-        )
-      fi
-  - &install_python |
-      pyenv install --skip-existing --keep --verbose "$TRAVIS_PYTHON_VERSION" | \
-      tee pyenv-install.log | tail -n 50
-  - &switch_python pyenv shell "$TRAVIS_PYTHON_VERSION"
-  - &python_version python --version
-  after_failure:
-  - cat pyenv-install.log
-  before_script: []
-  before_cache:
-  - brew --cache
+script: python -m tox.__main__
 
 env:
   global:
     LIBYAML_VERSION: 0.2.2-pre1
     TOXENV: python
+
 jobs:
   fast_finish: true
+
+  .osx: &osx
+    if: type IN (api, cron) OR tag IS present
+    os: osx
+    osx_image: xcode9.4
+    language: generic
+    before_install: (source ./travis.sh travis:before_install:osx)
+    after_failure: cat pyenv-install.log
+    before_script: []
+    before_cache: brew --cache
+
   allow_failures:
-  - os: osx
-    env:
-      TRAVIS_PYTHON_VERSION: '2.6'
-  - os: osx
-    env:
-      TRAVIS_PYTHON_VERSION: '2.7'
-  - os: osx
-    env:
-      TRAVIS_PYTHON_VERSION: &pypy3-osx-version pypy3.6-7.0.0
+  - {os: osx, env: {TRAVIS_PYTHON_VERSION: '2.6'}}
+  - {os: osx, env: {TRAVIS_PYTHON_VERSION: '2.7'}}
+  - {os: osx, env: {TRAVIS_PYTHON_VERSION: pypy3.6-7.0.0}}
+
   include:
   - python: '2.6'
     dist: trusty
-  - <<: *osx_python
-    env:
-      TRAVIS_PYTHON_VERSION: '3.7'
-    python: '3.7'
-  - <<: *osx_python
-    env:
-      TRAVIS_PYTHON_VERSION: '2.7'
-    python: '2.7'
-  - <<: *osx_python
-    env:
-      TRAVIS_PYTHON_VERSION: *pypy3-osx-version
-    python: *pypy3-osx-version
-  - <<: *osx_python
-    env:
-      TRAVIS_PYTHON_VERSION: '3.6'
-    python: '3.6'
-  - <<: *osx_python
-    env:
-      TRAVIS_PYTHON_VERSION: '3.5'
-    python: '3.5'
-  - <<: *osx_python
-    env:
-      TRAVIS_PYTHON_VERSION: '3.4'
-    python: '3.4'
-  - <<: *osx_python
-    env:
-      TRAVIS_PYTHON_VERSION: '2.6'
-    python: '2.6'
-
-
-  # This placeholder can be extended to do actual upload of a dist to
-  # PYPI, it will only appear if the current commit is tagged:
-  - stage: Deploy to PYPI (placeholder)
-    if: tag IS present
-    install: []
-    script: []
-    deploy:
-      provider: pypi
-      skip-cleanup: true
-      user: PLACEHOLDER
-      password:
-        secure: PLACEHOLDER
+  - {<<: *osx, python: '3.7', env: {TRAVIS_PYTHON_VERSION: '3.7'}}
+  - {<<: *osx, python: '2.7', env: {TRAVIS_PYTHON_VERSION: '2.7'}}
+  - {<<: *osx, python: pypy3.6-7.0.0, env: {TRAVIS_PYTHON_VERSION: pypy3.6-7.0.0}}
+  - {<<: *osx, python: '3.6', env: {TRAVIS_PYTHON_VERSION: '3.6'}}
+  - {<<: *osx, python: '3.5', env: {TRAVIS_PYTHON_VERSION: '3.5'}}
+  - {<<: *osx, python: '3.4', env: {TRAVIS_PYTHON_VERSION: '3.4'}}
+  - {<<: *osx, python: '2.6', env: {TRAVIS_PYTHON_VERSION: '2.6'}}