docs: function parseInputOutput(buildConfig, fileExtensions, runtimeOptions) {
    let outputs, outputIsDirectory;
  
    // Parse output
    if (buildConfig.output) {
      outputs = buildConfig.output;
  
      if (!Array.isArray(outputs)) outputs = [outputs];
  
      // Use compressed if specified
      if (runtimeOptions.compress && 'output_compressed' in buildConfig) {
        let outputsCompressed = buildConfig.output_compressed;
  
        if (!Array.isArray(outputsCompressed)) outputsCompressed = [outputsCompressed];
        if (outputsCompressed.length != outputs.length) {
          throw Error(
            `total number of outputs (${strong(buildConfig.output)}) do not match total number of compressed outputs (${strong(buildConfig.output_compressed)})`
          );
        }
        outputs = outputsCompressed;
        buildConfig.output = buildConfig.output_compressed;
      }
  
      outputIsDirectory = outputs.length == 1 && !path.extname(outputs[0]).length;
    } else {
      buildConfig.outputpaths = [];
    }
  
    // Parse input
    if (buildConfig.input) {
      const allFileExtensions = [];
      const inputsRelative = [];
      let inputs = buildConfig.input;
      let type = '';
      let matchGenerated;
  
      // Gather all extensions to allow for easier filtering
      for (const type in fileExtensions) {
        allFileExtensions.push(...fileExtensions[type]);
      }
  
      if (!Array.isArray(inputs)) inputs = [inputs];
  
      // Handle common/intersection build
      if ((matchGenerated = RE_GENERATED_INPUT.exec(inputs[0]))) {
        type = 'js';
        buildConfig.inputpaths = [dummyFile];
        buildConfig.isGeneratedBuild = true;
        buildConfig.generatedInputPattern = matchGenerated[1];
      } else {
        buildConfig.inputpaths = inputs
          .reduce(
            (inputs, input) => {
              // Expand glob pattern
              if (RE_GLOB.test(input)) {
                inputs = inputs.concat(glob(input, { matchBase: true, nocase: true }));
              } else {
                inputs.push(input);
              }
              return inputs;
            },
            []
          )
          .reduce(
            (inputs, input) => {
              const hasTrailingSlash = RE_TRAILING.test(input);
  
              input = path.resolve(input);
              // Expand directory
              if (!path.extname(input).length) {
                // Batch mode will change output behaviour if dir -> dir
                buildConfig.batch = true;
                inputs = inputs.concat(
                  readdir(input, (resource, stat) => {
                    const isFile = stat.isFile() && !RE_HIDDEN.test(path.basename(resource));
  
                    // Capture relative path for dir -> dir
                    // Like cp command, if trailing slash, use contents
                    if (isFile)
                      inputsRelative.push(path.relative(hasTrailingSlash ? input : path.resolve(input, '..'), resource));
                    return isFile;
                  })
                );
              } else {
                inputs.push(input);
                inputsRelative.push(path.basename(input));
              }
              return inputs;
            },
            []
          )
          .filter((input, idx) => {
            const extension = path.extname(input).slice(1);
            let include = true;
  
            // Include/exclude if grepping
            if (runtimeOptions.grep) {
              include = buildConfig.label == runtimeOptions.grep ||
                match(input, runtimeOptions.grep, { matchBase: true, nocase: true });
              if (runtimeOptions.invert) include = !include;
            }
  
            // Exclude unknown files
            if (!allFileExtensions.includes(extension)) {
              warn(
                `${strong(extension)} is an unknown file type (${strong(input)}). You should install a language pack from npm to enable support. Search npm for "buddy-plugin" to see all available.`
              );
              include = false;
            }
  
            if (!include) inputsRelative.splice(idx, 1);
            return include;
          })
          .map(input => {
            const inputpath = path.resolve(input);
            const inputType = filepathType(inputpath, fileExtensions);
  
            type = !type || type == inputType ? inputType : MIXED_TYPE;
  
            return inputpath;
          });
      }
  
      // Abort if no resolved
      if (!buildConfig.inputpaths.length) {
        buildConfig.input = null;
        buildConfig.inputpaths = null;
        return;
      }
  
      buildConfig.type = type;
  
      if (outputs) {
        if (buildConfig.inputpaths.length != outputs.length && !outputIsDirectory) {
          throw Error(
            `unable to resolve inputs (${strong(buildConfig.input)}) with outputs (${strong(buildConfig[runtimeOptions.compress ? 'output_compressed' : 'output'])})`
          );
        }
  
        buildConfig.outputpaths = buildConfig.inputpaths.map((inputpath, idx) => {
          let outputpath = '';
  
          if (outputIsDirectory) {
            // Preserve relative paths when batching
            outputpath = path.join(path.resolve(outputs[0]), inputsRelative[idx]);
          } else {
            outputpath = path.resolve(outputs[idx]);
          }
  
          const extension = path.extname(outputpath);
          const inputType = filepathType(inputpath, fileExtensions);
  
          // Resolve missing extension
          if (!extension) outputpath += `.${inputType}`;
          if (inputType != 'img' && extension != `.${inputType}`)
            outputpath = outputpath.replace(extension, `.${inputType}`);
  
          if (outputpath === inputpath) {
            throw Error(
              `the input ${strong(buildConfig.input)} is configured to be overwritten by ${strong(buildConfig.output)}. If you're trying to build the contents of a directory, remember to add a trailing slash to ${strong(buildConfig.input)}`
            );
          }
  
          return outputpath;
        });
      }
    }
  }