text: var gulp = require('gulp');
  
  var concat = require('gulp-concat');
  var del = require('del');
  var es = require('child_process').execSync;
  var flatten = require('gulp-flatten');
  var fontello = require('gulp-fontello');
  var pump = require('pump');
  var sass = require('gulp-sass')(require('sass'));
  var sassGlob = require('gulp-sass-glob');
  var styleLint = require('gulp-stylelint');
  var spawn = require('child_process').spawn;
  
  var config = require('./gulpfile.config.js');
  
  /**
   * Support functions for Gulp tasks.
   */
  
  function _runInPipenv(command, cb) {
      command.unshift('run');
      command = command.concat(process.argv.splice(3));
      spawn('pipenv', command, { stdio: 'inherit' }).on('exit', function (code) {
          if (code) process.exit(code);
          cb();
      });
  }
  
  /**
   * Deletes local static files.
   *
   * @returns {*}
   */
  function clean() {
      return del([
          '**/static',
          'static'
      ]);
  }
  
  /**
   * Runs coverage operations.
   *
   * @param cb
   */
  function coverage(cb) {
      // Erase any previous coverage results.
      es('pipenv run coverage erase', {stdio: 'inherit'});
  
      // Run tests with coverage.
      spawn(
          'pipenv',
          [
              'run',
              'coverage',
              'run',
              'manage.py',
              'test',
              '--settings=babybuddy.settings.test',
              '--parallel',
              '--exclude-tag',
              'isolate'
          ],
          {
              stdio: 'inherit'
          }
      ).on('exit', function(code) {
          // Run isolated tests with coverage.
          if (code === 0) {
              try {
                  config.testsConfig.isolated.forEach(function (test_name) {
                      es(
                          'pipenv run coverage run manage.py test ' + test_name,
                          {stdio: 'inherit'}
                      );
                  })
              } catch (error) {
                  console.error(error);
                  cb();
                  process.exit(1);
              }
  
              // Combine coverage results.
              es('pipenv run coverage combine', {stdio: 'inherit'});
          }
  
          cb();
          process.exit(code)
      });
  }
  
  /**
   * Builds the documentation site locally.
   *
   * @param cb
   */
  function docsBuild(cb) {
      _runInPipenv(['mkdocs', 'build'], cb);
  }
  
  /**
   * Deploys the documentation site to GitHub Pages.
   *
   * @param cb
   */
  function docsDeploy(cb) {
      _runInPipenv(['mkdocs', 'gh-deploy'], cb);
  }
  
  /**
   * Serves the documentation site, watching for changes.
   *
   * @param cb
   */
  function docsWatch(cb) {
      _runInPipenv(['mkdocs', 'serve'], cb);
  }
  
  /**
   * Builds and copies "extra" static files to configured paths.
   *
   * @param cb
   */
  function extras(cb) {
      pump([
          gulp.src(config.extrasConfig.fonts.files),
          gulp.dest(config.extrasConfig.fonts.dest)
      ], cb);
  
      pump([
          gulp.src(config.extrasConfig.images.files),
          flatten({ subPath: 3 }),
          gulp.dest(config.extrasConfig.images.dest)
      ], cb);
  
      pump([
          gulp.src(config.extrasConfig.logo.files),
          flatten({ subPath: 3 }),
          gulp.dest(config.extrasConfig.logo.dest)
      ], cb);
  
      pump([
          gulp.src(config.extrasConfig.root.files),
          gulp.dest(config.extrasConfig.root.dest)
      ], cb);
  }
  
  /**
   * Runs linting on Python and SASS code.
   *
   * @param cb
   */
  function lint(cb) {
      _runInPipenv(['flake8', '--exclude=.venv,etc,migrations,manage.py,node_modules,settings'], cb);
  
      pump([
          gulp.src(config.watchConfig.stylesGlob),
          styleLint({
              reporters: [
                  { formatter: 'string', console: true }
              ]
          })
      ], cb);
  }
  
  /**
   * Builds and copies JavaScript static files to configured paths.
   *
   * @param cb
   */
  function scripts(cb) {
      pump([
          gulp.src(config.scriptsConfig.vendor),
          concat('vendor.js'),
          gulp.dest(config.scriptsConfig.dest)
      ], cb);
  
      pump([
          gulp.src(config.scriptsConfig.graph),
          concat('graph.js'),
          gulp.dest(config.scriptsConfig.dest)
      ], cb);
  
      pump([
          gulp.src(config.scriptsConfig.app),
          concat('app.js'),
          gulp.dest(config.scriptsConfig.dest)
      ], cb);
  }
  
  /**
   * Builds and copies CSS static files to configured paths.
   *
   * @param cb
   */
  function styles(cb) {
      pump([
          gulp.src(config.stylesConfig.app),
          sassGlob({ignorePaths: config.stylesConfig.ignore}),
          sass().on('error', sass.logError),
          concat('app.css'),
          gulp.dest(config.stylesConfig.dest)
      ], cb);
  }
  
  /**
   * Runs all tests _not_ tagged "isolate".
   *
   * @param cb
   */
  function test(cb) {
      var command = [
          'run',
          'python',
          'manage.py',
          'test',
          '--settings=babybuddy.settings.test',
          '--parallel',
          '--exclude-tag',
          'isolate'
      ];
      command = command.concat(process.argv.splice(3));
      spawn('pipenv', command, { stdio: 'inherit' }).on('exit', function(code) {
          if (code === 0) {
              // Run isolated tests.
              config.testsConfig.isolated.forEach(function(test_name) {
                  try {
                      es('pipenv run python manage.py test ' + test_name, {stdio: 'inherit'});
                  } catch (error) {
                      console.error(error);
                      cb();
                      process.exit(1);
                  }
              })
          }
          cb();
          process.exit(code);
      });
  }
  
  /**
   * Updates glyphs font data from Fontello.
   */
  function updateglyphs(cb) {
      pump([
          gulp.src(config.glyphFontConfig.configFile),
          fontello({ assetsOnly: false }),
          gulp.dest(config.glyphFontConfig.dest)
      ], cb);
  }
  
  /**
   * Watches for changes in configured files.
   */
  function watch() {
      gulp.watch(config.watchConfig.scriptsGlob, scripts);
      gulp.watch(config.watchConfig.stylesGlob, styles);
  }
  
  /**
   * Django management command passthroughs.
   */
  
  gulp.task('collectstatic', function(cb) {
      var command = ['run', 'python', 'manage.py', 'collectstatic'];
  
      /* Use base settings if no settings parameter is supplied. */
      var parameters = process.argv.splice(3);
      var noSettings = true;
      for (var i = 0; i < parameters.length; i++) {
          if (parameters[i].substring(0, 10) === '--settings') {
              noSettings = false;
              break;
          }
      }
      if (noSettings) {
          parameters.push('--settings=babybuddy.settings.base');
      }
  
      command = command.concat(parameters);
      spawn('pipenv', command, { stdio: 'inherit' }).on('exit', cb);
  });
  
  gulp.task('compilemessages', function(cb) {
      _runInPipenv(['python', 'manage.py', 'compilemessages'], cb);
  });
  
  gulp.task('createcachetable', function(cb) {
      _runInPipenv(['python', 'manage.py', 'createcachetable'], cb);
  });
  
  gulp.task('fake', function(cb) {
      _runInPipenv(['python', 'manage.py', 'fake'], cb);
  });
  
  gulp.task('migrate', function(cb) {
      _runInPipenv(['python', 'manage.py', 'migrate'], cb);
  });
  
  gulp.task('makemessages', function(cb) {
      _runInPipenv(['python', 'manage.py', 'makemessages'], cb);
  });
  
  gulp.task('makemigrations', function(cb) {
      _runInPipenv(['python', 'manage.py', 'makemigrations'], cb);
  });
  
  gulp.task('reset', function(cb) {
      _runInPipenv(['python', 'manage.py', 'reset', '--no-input'], cb);
  });
  
  gulp.task('runserver', function(cb) {
      var command = ['run', 'python', 'manage.py', 'runserver'];
  
      /**
       * Process any parameters. Any arguments found here will be removed from
       * the parameters list so other parameters continue to be passed to the
       * command.
       **/
      var parameters = process.argv.splice(2);
      for (var i = 0; i < parameters.length; i++) {
          /* May be included because this is the default gulp command. */
          if (parameters[i] === 'runserver') {
              delete parameters[i];
          }
          /* "--ip" parameter to set the server IP address. */
          else if (parameters[i] === '--ip') {
              command.push(parameters[i+1]);
              delete parameters[i];
              delete parameters[i+1];
              i++;
          }
      }
  
      /* Add parameters to command, removing empty values. */
      command = command.concat(parameters.filter(String));
  
      spawn('pipenv', command, { stdio: 'inherit' }).on('exit', cb);
  });
  
  gulp.task('generateschema', function(cb) {
      _runInPipenv([
          'python',
          'manage.py',
          'generateschema',
          '--title',
          'Baby Buddy API',
          '--file',
          'openapi-schema.yml'
      ], cb);
  });
  
  /**
   * Gulp commands.
   */
  
  gulp.task('clean', clean);
  
  gulp.task('coverage', coverage);
  
  gulp.task('docs:build', docsBuild);
  
  gulp.task('docs:deploy', docsDeploy);
  
  gulp.task('docs:watch', docsWatch);
  
  gulp.task('extras', extras);
  
  gulp.task('lint', lint);
  
  gulp.task('scripts', scripts);
  
  gulp.task('styles', styles);
  
  gulp.task('test', test);
  
  gulp.task('updateglyphs', updateglyphs);
  
  gulp.task('watch', watch);
  
  /**
   * Gulp compound commands.
   */
  
  gulp.task('build', gulp.parallel('extras', 'scripts', 'styles'));
  
  gulp.task('updatestatic', gulp.series('lint', 'clean', 'build', 'collectstatic'));
  
  gulp.task('default', gulp.series('build', gulp.parallel('watch', 'runserver')));
  