<commit_before>'use strict';

import path from 'path';
import fs from 'fs-extra';
import glob from 'glob';
import rl from 'readline';
import 'babel-polyfill';
const async = require('async');
const yaml = require('js-yaml');
const temp = require('temp').track();
const pathExists = require('path-exists');
const camelCase = require('camelcase');
const clone = require('clone');

const NEWLINE = '\n';
const ENCODING = 'utf8';
const INSERT_AT_BEGIN = 'begin';
const INSERT_AT_END = 'end';

export default class I18nPatch {
  constructor(src, options) {
    if (!src) {
      throw new Error('src is required');
    }
    this.src = src;
    this.options = options || {};
    this.options.dest = this.options.dest || this.src;
    this.options.config = this.options.config || 'config';
  }

  generate(config, localeConfig) {
    return new Promise((resolve, reject) => {
      try {
        this.setConfigs(config, localeConfig);
      } catch (err) {
        reject(err);
      }
      this.buildPatterns();
      if (this.hasDest()) {
        fs.copySync(this.src, this.options.dest);
      }

      async.waterfall(this.config.translations.map((t) => {
        return (cb) => {
          this.processTranslation(t)
          .catch((err) => cb(err))
          .then(() => cb());
        };
      }), (err) => {
        if (err) {
          reject(err);
        } else {
          resolve();
        }
      });
    });
  }

  hasDest() {
    return this.src !== this.options.dest;
  }

  setConfigs(config, localeConfig) {
    this.config = config || this.readConfigFile('i18n');
    if (localeConfig) {
      this.localeConfig = localeConfig;
    } else {
      if (!this.options.locale) {
        throw new Error('Could not determine locale');
      }
      this.localeConfig = this.readConfigFile(this.options.locale);
    }

    for (let t of this.config.translations) {
      Object.keys(t).forEach((key) => {
        let converted = camelCase(key);
        if (key !== converted) {
          t[camelCase(key)] = t[key];
          delete t[key];
        }
      });
    }
  }

  readConfigFile(name) {
    let configPath = path.join(this.options.config, `${name}.yml`);
    if (pathExists.sync(configPath)) {
      return yaml.load(fs.readFileSync(configPath, ENCODING));
    } else {
      configPath = path.join(this.options.config, `${name}.json`);
      return JSON.parse(fs.readFileSync(configPath));
    }
  }

  buildPatterns() {
    for (let t of this.config.translations) {
      if (t.conditionals) {
        for (let c of t.conditionals) {
          if (!c.insert || !this.hasTranslationKey(c.insert.value)) {
            continue;
          }
          c.insert.resolved = this.localeConfig[c.insert.value];
          if (c.insert.resolved && !c.insert.resolved.endsWith(NEWLINE)) {
            c.insert.resolved += NEWLINE;
          }
        }
      }
      let patterns = [];
      for (let p of t.patterns) {
        let added = false;
        if (p.name) {
          if (t.namedPatterns) {
            let namedPattern;
            for (let np of t.namedPatterns) {
              if (p.name === np.name) {
                namedPattern = np;
                break;
              }
            }
            if (namedPattern) {
              let paramsSet = [];
              if (Array.isArray(p.params)) {
                paramsSet = p.params;
              } else {
                paramsSet = [p.params];
              }
              for (let params of paramsSet) {
                let npPattern = namedPattern.pattern;
                let npReplace = namedPattern.replace;
                let npArgs = clone(namedPattern.args);
                for (let npp of namedPattern.params) {
                  if (params.hasOwnProperty(npp)) {
                    npPattern = npPattern.replace(new RegExp(`{${npp}}`, 'g'), params[npp]);
                    npReplace = npReplace.replace(new RegExp(`{${npp}}`, 'g'), params[npp]);
                    if (npArgs) {
                      for (let a of npArgs) {
                        if (a.replace && typeof a.replace !== 'function' && a.replace.replace) {
                          a.replace = a.replace.replace(new RegExp(`{${npp}}`, 'g'), params[npp]);
                        } else if (a) {
                          a = a.replace(new RegExp(`{${npp}}`, 'g'), params[npp]);
                        }
                      }
                    }
                  }
                }
                let newPattern = {};
                newPattern.pattern = new RegExp(npPattern);
                newPattern.replace = npReplace;
                if (namedPattern.args) {
                  newPattern.args = npArgs;
                }
                patterns.push(newPattern);
                added = true;
              }
            }
          }
        }
        if (!added) {
          patterns.push(p);
        }
      }
      t.patterns = patterns;
      for (let p of t.patterns) {
        this.resolve(p);
        if (p.insert && this.hasTranslationKey(p.insert.value)) {
          p.insert.resolved = this.localeConfig[p.insert.value];
          if (p.insert.resolved && !p.insert.resolved.endsWith(NEWLINE)) {
            p.insert.resolved += NEWLINE;
          }
        }
      }
    }
  }

  processTranslation(t) {
    return new Promise((resolve, reject) => {
      let srcGlob = t.src || '**/*';
      let srcPaths = path.join(this.options.dest, srcGlob);
      glob(srcPaths, null, (err, files) => {
        if (err) {
          reject(err);
          return;
        }
        async.eachLimit(files, 100, (file, cb) => {
          this.processFile(t, file)
          .catch((err) => cb(err))
          .then(() => cb());
        }, (err) => {
          if (err) {
            reject(err);
          } else {
            resolve();
          }
        });
      });
    });
  }

  processFile(t, file) {
    return new Promise((resolve, reject) => {
      this.processFilePerLine(t, file)
      .catch((err) => reject(err))
      .then((file) => {
        if (this.hasPerFilePattern(t)) {
          this.processFilePerFile(t, file)
          .catch((err) => reject(err))
          .then((file) => resolve(file));
        } else {
          resolve(file);
        }
      });
    });
  }

  processFilePerLine(t, file) {
    return new Promise((resolve, reject) => {
      // Per-line processing
      let matched = false;
      let beginBuffer = [];
      let endBuffer = [];
      let error;
      let lr = rl.createInterface({
        input: fs.createReadStream(file)
      });
      let out = temp.createWriteStream()
      .on('error', (err) => {
        reject(err);
      })
      .on('close', () => {
        if (error) {
          // This can be set in 'error' callback of lr
          return;
        }
        if (matched) {
          // TODO Preserve original file stats
          fs.copySync(out.path, file);

          if (1 <= beginBuffer.length) {
            let value = '';
            for (let e of beginBuffer) {
              value += e;
              if (!e.endsWith(NEWLINE)) {
                value += NEWLINE;
              }
            }
            let content = fs.readFileSync(file, ENCODING);
            fs.writeFileSync(file, value + content, ENCODING);
          }
          if (1 <= endBuffer.length) {
            let value = '';
            for (let e of endBuffer) {
              value += e;
              if (!e.endsWith(NEWLINE)) {
                value += NEWLINE;
              }
            }
            let content = fs.readFileSync(file, ENCODING);
            if (!content.endsWith(NEWLINE)) {
              content += NEWLINE;
            }
            fs.writeFileSync(file, content + value, ENCODING);
          }
        }
        resolve(file);
      });
      lr.on('line', (line) => {
        let result = line;
        for (let p of t.patterns) {
          if (!p.resolved) {
            continue;
          }
          let before = result;
          result = this.applyToResolved(result, p, p.pattern);
          if (p.insert && p.insert.resolved) {
            if (p.insert.at === INSERT_AT_BEGIN) {
              if (beginBuffer.indexOf(p.insert.resolved) < 0) {
                beginBuffer.push(p.insert.resolved);
              }
            } else if (p.insert.at === INSERT_AT_END) {
              if (endBuffer.indexOf(p.insert.resolved) < 0) {
                endBuffer.push(p.insert.resolved);
              }
            }
          }
          if (before === result) {
            continue;
          }
          matched = true;
          if (t.conditionals) {
            for (let c of t.conditionals) {
              if (!c.insert || !c.insert.resolved) {
                continue;
              }
              if (c.insert.at === INSERT_AT_BEGIN) {
                if (beginBuffer.indexOf(c.insert.resolved) < 0) {
                  beginBuffer.push(c.insert.resolved);
                }
              } else if (c.insert.at === INSERT_AT_END) {
                if (endBuffer.indexOf(c.insert.resolved) < 0) {
                  endBuffer.push(c.insert.resolved);
                }
              }
            }
          }
        }
        // TODO Preserve original newline if possible
        out.write(`${result}\n`);
      })
      .on('error', (err) => {
        error = err;
        out.end();
        reject(err);
      })
      .on('close', () => {
        out.end();
      });
    });
  }

  processFilePerFile(t, file) {
    return new Promise((resolve, reject) => {
      try {
        for (let p of t.patterns) {
          if (p.pattern || !p.insert || !p.insert.resolved) {
            continue;
          }
          let at = p.insert.at;
          let value = p.insert.resolved;
          if (!value.endsWith(NEWLINE)) {
            value += NEWLINE;
          }
          if (at === INSERT_AT_BEGIN) {
            let content = fs.readFileSync(file, ENCODING);
            fs.writeFileSync(file, value + content, ENCODING);
          } else if (at === INSERT_AT_END) {
            let content = fs.readFileSync(file, ENCODING);
            if (!content.endsWith(NEWLINE)) {
              content += NEWLINE;
            }
            fs.writeFileSync(file, content + value, ENCODING);
          }
        }
        resolve();
      } catch (err) {
        reject(err);
      }
    });
  }

  hasPerFilePattern(t) {
    return !!t.patterns.find(p => p.insert);
  }

  resolve(obj) {
    if (!obj.replace) {
      return;
    }

    obj.resolved = this.resolveTranslationKey(obj.replace);
    if (obj.args) {
      obj.argsResolved = [];
      for (let i = 0; i < obj.args.length; i++) {
        let a = obj.args[i];
        if (a.replace && typeof a.replace !== 'function') {
          this.resolve(a);
          obj.argsResolved.push(a);
        } else if (a) {
          obj.argsResolved.push(this.resolveTranslationKey(a));
        }
      }
    }
  }

  applyToResolved(target, obj, pattern) {
    let resolved = obj.resolved;
    if (obj.args) {
      for (let i = 0; i < obj.args.length; i++) {
        let argResolved = obj.argsResolved[i];
        if (argResolved) {
          if (argResolved.replace && typeof argResolved.replace !== 'function') {
            if (argResolved.resolved) {
              resolved = this.applyToArgResolved(resolved, argResolved, `{${i}}`);
            } else {
              resolved = resolved.replace(`{${i}}`, argResolved.replace);
            }
          } else {
            resolved = resolved.replace(`{${i}}`, argResolved);
          }
        } else {
          resolved = resolved.replace(`{${i}}`, obj.args[i]);
        }
      }
    }
    return target.replace(pattern, resolved);
  }

  applyToArgResolved(target, obj, pattern) {
    if (obj.args) {
      for (let i = 0; i < obj.args.length; i++) {
        let argResolved = obj.argsResolved[i];
        if (argResolved) {
          obj.resolved = this.applyToArgResolved(obj.resolved, argResolved, `{${i}}`);
        } else {
          obj.resolved = obj.resolved.replace(`{${i}}`, obj.args[i]);
        }
      }
    }
    return target.replace(pattern, obj.resolved);
  }

  hasTranslationKey(key) {
    return this.localeConfig.hasOwnProperty(key);
  }

  resolveTranslationKey(target) {
    let resolved = false;
    let result = target.replace(/\${([^}]*)}/g, (all, matched) => {
      if (this.hasTranslationKey(matched)) {
        resolved = true;
      }
      return this.localeConfig[matched];
    });
    if (!resolved) {
      result = undefined;
    }
    return result;
  }
}
<commit_msg>Add regexp flags for named-pattern<commit_after>'use strict';

import path from 'path';
import fs from 'fs-extra';
import glob from 'glob';
import rl from 'readline';
import 'babel-polyfill';
const async = require('async');
const yaml = require('js-yaml');
const temp = require('temp').track();
const pathExists = require('path-exists');
const camelCase = require('camelcase');
const clone = require('clone');

const NEWLINE = '\n';
const ENCODING = 'utf8';
const INSERT_AT_BEGIN = 'begin';
const INSERT_AT_END = 'end';

export default class I18nPatch {
  constructor(src, options) {
    if (!src) {
      throw new Error('src is required');
    }
    this.src = src;
    this.options = options || {};
    this.options.dest = this.options.dest || this.src;
    this.options.config = this.options.config || 'config';
  }

  generate(config, localeConfig) {
    return new Promise((resolve, reject) => {
      try {
        this.setConfigs(config, localeConfig);
      } catch (err) {
        reject(err);
      }
      this.buildPatterns();
      if (this.hasDest()) {
        fs.copySync(this.src, this.options.dest);
      }

      async.waterfall(this.config.translations.map((t) => {
        return (cb) => {
          this.processTranslation(t)
          .catch((err) => cb(err))
          .then(() => cb());
        };
      }), (err) => {
        if (err) {
          reject(err);
        } else {
          resolve();
        }
      });
    });
  }

  hasDest() {
    return this.src !== this.options.dest;
  }

  setConfigs(config, localeConfig) {
    this.config = config || this.readConfigFile('i18n');
    if (localeConfig) {
      this.localeConfig = localeConfig;
    } else {
      if (!this.options.locale) {
        throw new Error('Could not determine locale');
      }
      this.localeConfig = this.readConfigFile(this.options.locale);
    }

    for (let t of this.config.translations) {
      Object.keys(t).forEach((key) => {
        let converted = camelCase(key);
        if (key !== converted) {
          t[camelCase(key)] = t[key];
          delete t[key];
        }
      });
    }
  }

  readConfigFile(name) {
    let configPath = path.join(this.options.config, `${name}.yml`);
    if (pathExists.sync(configPath)) {
      return yaml.load(fs.readFileSync(configPath, ENCODING));
    } else {
      configPath = path.join(this.options.config, `${name}.json`);
      return JSON.parse(fs.readFileSync(configPath));
    }
  }

  buildPatterns() {
    for (let t of this.config.translations) {
      if (t.conditionals) {
        for (let c of t.conditionals) {
          if (!c.insert || !this.hasTranslationKey(c.insert.value)) {
            continue;
          }
          c.insert.resolved = this.localeConfig[c.insert.value];
          if (c.insert.resolved && !c.insert.resolved.endsWith(NEWLINE)) {
            c.insert.resolved += NEWLINE;
          }
        }
      }
      let patterns = [];
      for (let p of t.patterns) {
        let added = false;
        if (p.name) {
          if (t.namedPatterns) {
            let namedPattern;
            for (let np of t.namedPatterns) {
              if (p.name === np.name) {
                namedPattern = np;
                break;
              }
            }
            if (namedPattern) {
              let paramsSet = [];
              if (Array.isArray(p.params)) {
                paramsSet = p.params;
              } else {
                paramsSet = [p.params];
              }
              for (let params of paramsSet) {
                let npPattern = namedPattern.pattern;
                let npReplace = namedPattern.replace;
                let npArgs = clone(namedPattern.args);
                let npFlags = namedPattern.flags || 'g';
                for (let npp of namedPattern.params) {
                  if (params.hasOwnProperty(npp)) {
                    npPattern = npPattern.replace(new RegExp(`{${npp}}`, npFlags), params[npp]);
                    npReplace = npReplace.replace(new RegExp(`{${npp}}`, npFlags), params[npp]);
                    if (npArgs) {
                      for (let a of npArgs) {
                        if (a.replace && typeof a.replace !== 'function' && a.replace.replace) {
                          a.replace = a.replace.replace(new RegExp(`{${npp}}`, npFlags), params[npp]);
                        } else if (a) {
                          a = a.replace(new RegExp(`{${npp}}`, npFlags), params[npp]);
                        }
                      }
                    }
                  }
                }
                let newPattern = {};
                newPattern.pattern = new RegExp(npPattern);
                newPattern.replace = npReplace;
                if (namedPattern.args) {
                  newPattern.args = npArgs;
                }
                patterns.push(newPattern);
                added = true;
              }
            }
          }
        }
        if (!added) {
          patterns.push(p);
        }
      }
      t.patterns = patterns;
      for (let p of t.patterns) {
        this.resolve(p);
        if (p.insert && this.hasTranslationKey(p.insert.value)) {
          p.insert.resolved = this.localeConfig[p.insert.value];
          if (p.insert.resolved && !p.insert.resolved.endsWith(NEWLINE)) {
            p.insert.resolved += NEWLINE;
          }
        }
      }
    }
  }

  processTranslation(t) {
    return new Promise((resolve, reject) => {
      let srcGlob = t.src || '**/*';
      let srcPaths = path.join(this.options.dest, srcGlob);
      glob(srcPaths, null, (err, files) => {
        if (err) {
          reject(err);
          return;
        }
        async.eachLimit(files, 100, (file, cb) => {
          this.processFile(t, file)
          .catch((err) => cb(err))
          .then(() => cb());
        }, (err) => {
          if (err) {
            reject(err);
          } else {
            resolve();
          }
        });
      });
    });
  }

  processFile(t, file) {
    return new Promise((resolve, reject) => {
      this.processFilePerLine(t, file)
      .catch((err) => reject(err))
      .then((file) => {
        if (this.hasPerFilePattern(t)) {
          this.processFilePerFile(t, file)
          .catch((err) => reject(err))
          .then((file) => resolve(file));
        } else {
          resolve(file);
        }
      });
    });
  }

  processFilePerLine(t, file) {
    return new Promise((resolve, reject) => {
      // Per-line processing
      let matched = false;
      let beginBuffer = [];
      let endBuffer = [];
      let error;
      let lr = rl.createInterface({
        input: fs.createReadStream(file)
      });
      let out = temp.createWriteStream()
      .on('error', (err) => {
        reject(err);
      })
      .on('close', () => {
        if (error) {
          // This can be set in 'error' callback of lr
          return;
        }
        if (matched) {
          // TODO Preserve original file stats
          fs.copySync(out.path, file);

          if (1 <= beginBuffer.length) {
            let value = '';
            for (let e of beginBuffer) {
              value += e;
              if (!e.endsWith(NEWLINE)) {
                value += NEWLINE;
              }
            }
            let content = fs.readFileSync(file, ENCODING);
            fs.writeFileSync(file, value + content, ENCODING);
          }
          if (1 <= endBuffer.length) {
            let value = '';
            for (let e of endBuffer) {
              value += e;
              if (!e.endsWith(NEWLINE)) {
                value += NEWLINE;
              }
            }
            let content = fs.readFileSync(file, ENCODING);
            if (!content.endsWith(NEWLINE)) {
              content += NEWLINE;
            }
            fs.writeFileSync(file, content + value, ENCODING);
          }
        }
        resolve(file);
      });
      lr.on('line', (line) => {
        let result = line;
        for (let p of t.patterns) {
          if (!p.resolved) {
            continue;
          }
          let before = result;
          result = this.applyToResolved(result, p, p.pattern);
          if (p.insert && p.insert.resolved) {
            if (p.insert.at === INSERT_AT_BEGIN) {
              if (beginBuffer.indexOf(p.insert.resolved) < 0) {
                beginBuffer.push(p.insert.resolved);
              }
            } else if (p.insert.at === INSERT_AT_END) {
              if (endBuffer.indexOf(p.insert.resolved) < 0) {
                endBuffer.push(p.insert.resolved);
              }
            }
          }
          if (before === result) {
            continue;
          }
          matched = true;
          if (t.conditionals) {
            for (let c of t.conditionals) {
              if (!c.insert || !c.insert.resolved) {
                continue;
              }
              if (c.insert.at === INSERT_AT_BEGIN) {
                if (beginBuffer.indexOf(c.insert.resolved) < 0) {
                  beginBuffer.push(c.insert.resolved);
                }
              } else if (c.insert.at === INSERT_AT_END) {
                if (endBuffer.indexOf(c.insert.resolved) < 0) {
                  endBuffer.push(c.insert.resolved);
                }
              }
            }
          }
        }
        // TODO Preserve original newline if possible
        out.write(`${result}\n`);
      })
      .on('error', (err) => {
        error = err;
        out.end();
        reject(err);
      })
      .on('close', () => {
        out.end();
      });
    });
  }

  processFilePerFile(t, file) {
    return new Promise((resolve, reject) => {
      try {
        for (let p of t.patterns) {
          if (p.pattern || !p.insert || !p.insert.resolved) {
            continue;
          }
          let at = p.insert.at;
          let value = p.insert.resolved;
          if (!value.endsWith(NEWLINE)) {
            value += NEWLINE;
          }
          if (at === INSERT_AT_BEGIN) {
            let content = fs.readFileSync(file, ENCODING);
            fs.writeFileSync(file, value + content, ENCODING);
          } else if (at === INSERT_AT_END) {
            let content = fs.readFileSync(file, ENCODING);
            if (!content.endsWith(NEWLINE)) {
              content += NEWLINE;
            }
            fs.writeFileSync(file, content + value, ENCODING);
          }
        }
        resolve();
      } catch (err) {
        reject(err);
      }
    });
  }

  hasPerFilePattern(t) {
    return !!t.patterns.find(p => p.insert);
  }

  resolve(obj) {
    if (!obj.replace) {
      return;
    }

    obj.resolved = this.resolveTranslationKey(obj.replace);
    if (obj.args) {
      obj.argsResolved = [];
      for (let i = 0; i < obj.args.length; i++) {
        let a = obj.args[i];
        if (a.replace && typeof a.replace !== 'function') {
          this.resolve(a);
          obj.argsResolved.push(a);
        } else if (a) {
          obj.argsResolved.push(this.resolveTranslationKey(a));
        }
      }
    }
  }

  applyToResolved(target, obj, pattern) {
    let resolved = obj.resolved;
    if (obj.args) {
      for (let i = 0; i < obj.args.length; i++) {
        let argResolved = obj.argsResolved[i];
        if (argResolved) {
          if (argResolved.replace && typeof argResolved.replace !== 'function') {
            if (argResolved.resolved) {
              resolved = this.applyToArgResolved(resolved, argResolved, `{${i}}`);
            } else {
              resolved = resolved.replace(`{${i}}`, argResolved.replace);
            }
          } else {
            resolved = resolved.replace(`{${i}}`, argResolved);
          }
        } else {
          resolved = resolved.replace(`{${i}}`, obj.args[i]);
        }
      }
    }
    return target.replace(pattern, resolved);
  }

  applyToArgResolved(target, obj, pattern) {
    if (obj.args) {
      for (let i = 0; i < obj.args.length; i++) {
        let argResolved = obj.argsResolved[i];
        if (argResolved) {
          obj.resolved = this.applyToArgResolved(obj.resolved, argResolved, `{${i}}`);
        } else {
          obj.resolved = obj.resolved.replace(`{${i}}`, obj.args[i]);
        }
      }
    }
    return target.replace(pattern, obj.resolved);
  }

  hasTranslationKey(key) {
    return this.localeConfig.hasOwnProperty(key);
  }

  resolveTranslationKey(target) {
    let resolved = false;
    let result = target.replace(/\${([^}]*)}/g, (all, matched) => {
      if (this.hasTranslationKey(matched)) {
        resolved = true;
      }
      return this.localeConfig[matched];
    });
    if (!resolved) {
      result = undefined;
    }
    return result;
  }
}
<|endoftext|>


<commit_before>/* globals process */

import path from 'path'
import chokidar from 'chokidar'

import globBase from './lib/glob-base'
import mirror from './lib/mirror'
import trimQuotes from './lib/trim-quotes'
import { watcherCopy, watcherDestroy, watcherError } from './lib/watcher'

const defaults = {
  watch: false,
  delete: true,
  deleteInitial: false,
  depth: Infinity,
}

let count = 0

const syncGlob = (sources, target, options, notify) => {
  if (!Array.isArray(sources)) {
    // eslint-disable-next-line no-param-reassign
    sources = [sources]
  }
  // eslint-disable-next-line no-param-reassign
  sources = sources.map(trimQuotes)
  // eslint-disable-next-line no-param-reassign
  options = {
    ...defaults,
    base: globBase(sources),
    ...options,
  }

  if (typeof options.depth !== 'number' || isNaN(options.depth)) {
    notify('error', 'Expected valid number for option "depth"')
    return false
  }

  if (options.transform) {
    let transformPath = options.transform

    try {
      require.resolve(transformPath)
    } catch (e) {
      transformPath = path.join(process.cwd(), transformPath)
    }

    // eslint-disable-next-line
    options.transform = require(transformPath)
  }

  // Initial mirror
  const mirrored = mirror(sources, target, options, notify, 0)
  let watcher

  if (!mirrored) {
    notify('error', 'Initial mirror failed')
    return false
  }

  if (options.watch) {
    // eslint-disable-next-line no-plusplus
    ++count
    console.log(`+++ Start watching: ${count}`)
    console.log(sources)
    // Watcher to keep in sync from that
    watcher = chokidar.watch(sources, {
      persistent: true,
      depth: options.depth,
      ignoreInitial: true,
    })
      .on('ready', notify.bind(undefined, 'watch', sources))
      .on('add', watcherCopy(target, options, notify))
      .on('addDir', watcherCopy(target, options, notify))
      .on('change', watcherCopy(target, options, notify))
      .on('unlink', watcherDestroy(target, options, notify))
      .on('unlinkDir', watcherDestroy(target, options, notify))
      .on('error', watcherError(options, notify))
      .on('all', (...args) => {
        console.log(`***CHOKIDAR ${count}: ${args.join(', ')}***`)
      })

    process.on('SIGINT', stopWatching)
    process.on('SIGQUIT', stopWatching)
    process.on('SIGTERM', stopWatching)
  }

  return stopWatching

  function stopWatching() {
    if (watcher) {
      watcher.close()
      watcher = null
    }
  }
}

export default syncGlob
<commit_msg>enabled awaitWriteFinish<commit_after>/* globals process */

import path from 'path'
import chokidar from 'chokidar'

import globBase from './lib/glob-base'
import mirror from './lib/mirror'
import trimQuotes from './lib/trim-quotes'
import { watcherCopy, watcherDestroy, watcherError } from './lib/watcher'

const defaults = {
  watch: false,
  delete: true,
  deleteInitial: false,
  depth: Infinity,
}

let count = 0

const syncGlob = (sources, target, options, notify) => {
  if (!Array.isArray(sources)) {
    // eslint-disable-next-line no-param-reassign
    sources = [sources]
  }
  // eslint-disable-next-line no-param-reassign
  sources = sources.map(trimQuotes)
  // eslint-disable-next-line no-param-reassign
  options = {
    ...defaults,
    base: globBase(sources),
    ...options,
  }

  if (typeof options.depth !== 'number' || isNaN(options.depth)) {
    notify('error', 'Expected valid number for option "depth"')
    return false
  }

  if (options.transform) {
    let transformPath = options.transform

    try {
      require.resolve(transformPath)
    } catch (e) {
      transformPath = path.join(process.cwd(), transformPath)
    }

    // eslint-disable-next-line
    options.transform = require(transformPath)
  }

  // Initial mirror
  const mirrored = mirror(sources, target, options, notify, 0)
  let watcher

  if (!mirrored) {
    notify('error', 'Initial mirror failed')
    return false
  }

  if (options.watch) {
    // eslint-disable-next-line no-plusplus
    ++count
    console.log(`+++ Start watching: ${count}`)
    console.log(sources)
    // Watcher to keep in sync from that
    watcher = chokidar.watch(sources, {
      persistent: true,
      depth: options.depth,
      ignoreInitial: true,
      awaitWriteFinish: true,
    })
      .on('ready', notify.bind(undefined, 'watch', sources))
      .on('add', watcherCopy(target, options, notify))
      .on('addDir', watcherCopy(target, options, notify))
      .on('change', watcherCopy(target, options, notify))
      .on('unlink', watcherDestroy(target, options, notify))
      .on('unlinkDir', watcherDestroy(target, options, notify))
      .on('error', watcherError(options, notify))
      .on('all', (...args) => {
        console.log(`***CHOKIDAR ${count}: ${args.join(', ')}***`)
      })

    process.on('SIGINT', stopWatching)
    process.on('SIGQUIT', stopWatching)
    process.on('SIGTERM', stopWatching)
  }

  return stopWatching

  function stopWatching() {
    if (watcher) {
      watcher.close()
      watcher = null
    }
  }
}

export default syncGlob
<|endoftext|>


<commit_before>
// markup mode:
	// found @
		// is email address?
			//keep going
		// not email address?
			// is followed by @?
				// keep going, ignore one @
			// is followed by { ?
				// consume until matching } as JS
			// is followed by keyword?
				// switch to JS block mode
			// is followed by valid identifier?
				// switch to implicit expression mode

// JS block mode:
	// found @
		// is followed by @?
			// keep going, ignore one @
		// not followed by @?
			// switch to markup mode
	// found < ?
		// is followed by / or [a-zA-Z] ?
			// switch to markup mode
		// is not followed by [\/a-zA-Z] ?
			// keep going
	// may need rules for @: here and newlines

// JS implicit expression mode:
	// read single "word" (identifier)
	// A: is the next character a ( or [ ?
		// read matching, and go to A
	// not A: continue
	// is the next character a . ?
		// continue
	// not .:
		// end expression, switch to markup mode
	// is the character after . a valid start char for identifier?
		// read the ., go to start of implicit mode
	// not valid start:
		// end expression without including .
		// switch to markup mode

(function(root){

// taken from : http://urazor.com/Scripts/uRazor-classes.js
var TKS = {
    AT:     		/@/,
    PARENSTART:    	/\(/,
    PARENEND:      	/\)/,
    //COLON:      	/:/,
    BRACESTART:    	/\{/,
    BRACEEND:      	/\}/,
    LT:         	/</,
    //GT:         	/>/,
    HARDPARENSTART:	/\[/,
    HARDPARENEND:  	/\]/,
	PERIOD: 		/\./,
	LBBRACEEND: 	/[\n\r]{1,}[^\S\r\n]*\}$/, // newline + optional whitespace + BRACEEND
	TAGOC: 			/\/|[a-zA-Z]/, // tag open or close, minus <
	EMAILCHARS: 	/[a-zA-Z0-9\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~]/,
	IDENTIFIER: /^[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*/, // this could be simplifed to not support unicode
	RESERVED: /^abstract|as|boolean|break|byte|case|catch|char|class|continue|const|debugger|default|delete|do|double|else|enum|export|extends|false|final|finally|float|for|function|goto|if|implements|import|in|instanceof|int|interface|is|long|namespace|native|new|null|package|private|protected|public|return|short|static|super|switch|synchronized|this|throw|throws|transient|true|try|typeof|use|var|void|volatile|while|with/,

	// these are used for template generation, not parsing
    QUOTE:      	/[\"']/gi,
	LINEBREAK:  	/[\n\r]/gi
};

// markup, js block, implicit js expression
var modes = { MKP: "MARKUP", BLK: "BLOCK", EXP: "EXPRESSION" };

function parse(str){

	// current mode
	var mode = modes.MKP, modeStack = [modes.MKP];

	var markupBuffer = '', codeBuffer = '', buffers = [];

	// characters
	var prev, curr, next, i, j;

	var identifier = '', identifierMatch = null, groupLevel = 0;

	var codeNestLevel = 0;

	for(i = 0; i < str.length; i++){
		if(i > 0) prev = str[i-1];
		curr = str[i];
		if(i < str.length - 1) next = str[i+1];

		if(mode === modes.MKP){
			// current character is @
			if(TKS.AT.test(curr) === true){
				if(TKS.EMAILCHARS.test(prev) === true){
					// assume it's an e-mail address and continue
					markupBuffer += curr;
				} else {
					if(TKS.AT.test(next) === true){
						// escaped @. continue, but ignore one @
						markupBuffer += curr;
						i += 1; // skip next @
					} else if(TKS.BRACESTART.test(next) === true){
						// found {, enter block mode
						buffers.push( { type: modes.MKP, value: markupBuffer } );
						markupBuffer = ''; // blank out markup buffer;
						codeBuffer = ''; // blank out just in case
						codeNestLevel += 1;
						mode = modes.BLK;
						continue;
					} else if(TKS.PARENSTART.test(next) === true){

						// consume until next matched ) as mode.EXP, enter markup mode
						buffers.push( { type: modes.MKP, value: markupBuffer } );
						markupBuffer = ''; // blank out markup buffer;
						codeBuffer = ''; // blank out just in case
						j = i+2; // j is index of char after (

						groupLevel = 1;
						while( groupLevel > 0 ){
							j += 1;
							curr = str[j];
							if(j < str.length - 1) next = str[j+1];

							if(TKS.PARENSTART.test(curr) === true) groupLevel += 1;
							if(TKS.PARENEND.test(curr) === true) groupLevel -= 1;

							if(j >= str.length) throw new Error('Syntax Error, unmatched PARENSTART');
						}

						// add ( something something (something something) ) to codeBuffer, without @
						codeBuffer += str.substring(i+1, j+1);

						buffers.push( { type: modes.EXP, value: codeBuffer } );
						markupBuffer = '';
						codeBuffer = ''; // blank out
						mode = modes.MKP;

						i = j; // advance i to current char
						continue;

					} else {
						// test for identifier
						identifier = str.substring(i+1); // everything after @
						identifierMatch = identifier.match( TKS.IDENTIFIER );

						if(identifierMatch === null){
							// stay in content mode?
							markupBuffer += curr;
						} else {
							// found either a reserved word or a valid JS identifier

							if(TKS.RESERVED.test(identifierMatch[0]) === true){
								// found a reserved word, like while
								// switch to JS Block mode
								buffers.push( { type: modes.MKP, value: markupBuffer } );
								markupBuffer = ''; // blank out markup buffer;
								codeBuffer = identifierMatch[0];
								mode = modes.BLK;
								codeNestLevel += 1;
								i += codeBuffer.length; // skip identifier and continue in block mode
								continue;
							} else {
								// we have a valid identifier
								// switch to implicit expression mode: @myvar().dosomethingelse().prop
								buffers.push( { type: modes.MKP, value: markupBuffer } );
								markupBuffer = ''; // blank out markup buffer;
								codeBuffer = ''; // blank out just in case
								mode = modes.EXP;
								continue;
							}
						}
					}
				}
			} else if(TKS.BRACEEND.test(curr) === true) {
				// found } in markup mode,

				identifier = str.substring(0, i+1); // grab from beginning, including }
				identifierMatch = identifier.match(TKS.LBBRACEEND);

				if(identifierMatch == null){
					// just a content }, nothing to worry about
					markupBuffer += curr;
				} else {
					// is a } on a new line, assume it's code related to close a block
					buffers.push( { type: modes.MKP, value: markupBuffer } );
					markupBuffer = ''; // blank out markup buffer;

					codeBuffer = curr; // add } to code buffer
					// switch to block mode
					mode = modes.BLK;

					codeNestLevel -= 1;
					if(codeNestLevel === 0){

						buffers.push( { type: modes.BLK, value: codeBuffer } );
						codeBuffer = '';
						markupBuffer = '';
						mode = modes.MKP;
					}
				}

				continue;

			} else {
				markupBuffer += curr;
				continue;
			}
		}

		if(mode === modes.BLK){

			if(TKS.AT.test(curr) === true){
				// current character is @

				if(TKS.AT.test(next) === true){
					// escaped @, continue, but ignore one @
					codeBuffer += curr;
					i += 1; // skip next @
				} else {

					buffers.push( { type: modes.BLK, value: codeBuffer } );
					codeBuffer = '';
					markupBuffer = '';
					mode = modes.MKP;
					continue;
				}

			} else if(TKS.LT.test(curr) === true){
				// current character is <

				if(TKS.TAGOC.test(next) === true){
					// we have a markup tag
					// switch to markup mode

					buffers.push( { type: modes.BLK, value: codeBuffer } );
					codeBuffer = '';
					markupBuffer = curr;
					mode = modes.MKP;
					continue;
				}

			} else if(TKS.BRACEEND.test(curr) === true) {
				codeBuffer += curr;
				codeNestLevel -= 1;
				if(codeNestLevel === 0){

					buffers.push( { type: modes.BLK, value: codeBuffer } );
					codeBuffer = '';
					markupBuffer = '';
					mode = modes.MKP;
					continue;
				}

			}

			codeBuffer += curr;
			continue;
		}

		if(mode === modes.EXP){
			// test for identifier
			identifier = str.substring(i);
			identifierMatch = identifier.match( TKS.IDENTIFIER );

			if(identifierMatch === null){

				if(TKS.AT.test(curr) === true){
					// must just be standalone @, switch to markup mode
					markupBuffer += curr;

					buffers.push( { type: modes.MPK, value: markupBuffer } );
					markupBuffer = ''; // blank out markup buffer;
					codeBuffer = ''; // blank out just in case
					mode = modes.MKP;
					continue;
				} else {
					// this is probably the end of the expression

					// end of expression, switch to markup mode

					buffers.push( { type: modes.EXP, value: codeBuffer } );
					codeBuffer = '';
					markupBuffer = curr;
					mode = modes.MKP;
					continue;
				}

			} else {
				// found a valid JS identifier

				codeBuffer += identifierMatch[0];
				j = i + identifierMatch[0].length;
				//j = i += identifierMatch[0].length; // set i and j to next char after identifier for next iteration
				next = str[j]; // set next to the actual next char after identifier

				if(TKS.PARENSTART.test(next) === true){
					// found (, consume until next matching

					i = j; // move i forward

					groupLevel = 1;
					while( groupLevel > 0 ){
						j += 1;
						curr = str[j];
						if(j < str.length - 1) next = str[j+1];

						if(TKS.PARENSTART.test(curr) === true) groupLevel += 1;
						if(TKS.PARENEND.test(curr) === true) groupLevel -= 1;

						if(j >= str.length) throw new Error('Syntax Error, unmatched PARENSTART');
					}

					// add ( something something (something something) ) to codeBuffer
					// TODO: may need to substring j+1
					codeBuffer += str.substring(i, j);

					i = j; // advance i to current char
					curr = str[i]; // curr will be equal to )
					if(i < str.length - 1) next = str[i+1];
				} else if(TKS.HARDPARENSTART.test(next) === true){
					// found [, consume until next matching

					i = j; // move i forward

					groupLevel = 1;
					while( groupLevel > 0 ){
						j += 1;
						curr = str[j];
						if(j < str.length - 1) next = str[j+1];

						if(TKS.HARDPARENSTART.test(curr) === true) groupLevel += 1;
						if(TKS.HARDPARENEND.test(curr) === true) groupLevel -= 1;

						if(j >= str.length) throw new Error('Syntax Error, unmatched HARDPARENSTART');
					}

					// add [ something something [something something] ] to codeBuffer
					// TODO: may need to substring j+1
					codeBuffer += str.substring(i, j+1);

					i = j; // advance i to current char
					curr = str[i]; // curr will be equal to ]
					if(i < str.length - 1) next = str[i+1];
				} else if(TKS.PERIOD.test(next) === true){
					// next char is a .

					if( i+2 < str.length && TKS.IDENTIFIER.test( str[i+2] ) ){
						// char after the . is a valid identifier
						// consume ., continue in EXP mode
						codeBuffer += next;
					} else {
						// do not consume . in code, but in markup
						// end EXP block, continue in markup mode

						buffers.push( { type: modes.EXP, value: codeBuffer } );
						markupBuffer = next; // consume . in markup buffer
						codeBuffer = ''; // blank out
						mode = modes.MKP;
						continue;
					}

				}
			}
		}
	}

	buffers.push( { type: modes.MKP, value: markupBuffer } );
	markupBuffer = ''; // blank out markup buffer;
	codeBuffer = ''; // blank out just in case
	mode = modes.MKP;
	return buffers;

}

function generateTemplate(buffers){
	var  i
		,current
		,generated = 'out = "";\n';

	for(i = 0; i < buffers.length; i++){
		current = buffers[i];

		if(current.type === modes.MKP){
			generated += 'out += \'' + current.value.replace(TKS.QUOTE, '\"').replace(TKS.LINEBREAK, '\\n') + '\';\n';
		}

		if(current.type === modes.BLK){
			// nuke new lines, otherwise causes parse error
			generated += current.value.replace(TKS.QUOTE, '\"').replace(TKS.LINEBREAK, '') + '\n';
		}

		if(current.type === modes.EXP){
			generated += 'out += (' + current.value.replace(TKS.QUOTE, '\"').replace(TKS.LINEBREAK, '\\n') + ');\n';
		}
	}

	return new Function("model", "with(model){" + generated + "}\nreturn out;");
}

root.razor = {
	 _parse: parse
	,tpl: generateTemplate
};

})(this);<commit_msg>fixed global leakage<commit_after>
// markup mode:
	// found @
		// is email address?
			//keep going
		// not email address?
			// is followed by @?
				// keep going, ignore one @
			// is followed by { ?
				// consume until matching } as JS
			// is followed by keyword?
				// switch to JS block mode
			// is followed by valid identifier?
				// switch to implicit expression mode

// JS block mode:
	// found @
		// is followed by @?
			// keep going, ignore one @
		// not followed by @?
			// switch to markup mode
	// found < ?
		// is followed by / or [a-zA-Z] ?
			// switch to markup mode
		// is not followed by [\/a-zA-Z] ?
			// keep going
	// may need rules for @: here and newlines

// JS implicit expression mode:
	// read single "word" (identifier)
	// A: is the next character a ( or [ ?
		// read matching, and go to A
	// not A: continue
	// is the next character a . ?
		// continue
	// not .:
		// end expression, switch to markup mode
	// is the character after . a valid start char for identifier?
		// read the ., go to start of implicit mode
	// not valid start:
		// end expression without including .
		// switch to markup mode

(function(root){

var TKS = {
    AT:     		/@/,
    PARENSTART:    	/\(/,
    PARENEND:      	/\)/,
    //COLON:      	/:/,
    BRACESTART:    	/\{/,
    BRACEEND:      	/\}/,
    LT:         	/</,
    //GT:         	/>/,
    HARDPARENSTART:	/\[/,
    HARDPARENEND:  	/\]/,
	PERIOD: 		/\./,
	LBBRACEEND: 	/[\n\r]{1,}[^\S\r\n]*\}$/, // newline + optional whitespace + BRACEEND
	TAGOC: 			/\/|[a-zA-Z]/, // tag open or close, minus <
	EMAILCHARS: 	/[a-zA-Z0-9\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~]/,
	IDENTIFIER: /^[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*/, // this could be simplifed to not support unicode
	RESERVED: /^abstract|as|boolean|break|byte|case|catch|char|class|continue|const|debugger|default|delete|do|double|else|enum|export|extends|false|final|finally|float|for|function|goto|if|implements|import|in|instanceof|int|interface|is|long|namespace|native|new|null|package|private|protected|public|return|short|static|super|switch|synchronized|this|throw|throws|transient|true|try|typeof|use|var|void|volatile|while|with/,

	// these are used for template generation, not parsing
    QUOTE:      	/[\"']/gi,
	LINEBREAK:  	/[\n\r]/gi
};

// markup, js block, implicit js expression
var modes = { MKP: "MARKUP", BLK: "BLOCK", EXP: "EXPRESSION" };

function parse(str){

	// current mode
	var mode = modes.MKP, modeStack = [modes.MKP];

	var markupBuffer = '', codeBuffer = '', buffers = [];

	// characters
	var prev, curr, next, i, j;

	var identifier = '', identifierMatch = null, groupLevel = 0;

	var codeNestLevel = 0;

	for(i = 0; i < str.length; i++){
		if(i > 0) prev = str[i-1];
		curr = str[i];
		if(i < str.length - 1) next = str[i+1];

		if(mode === modes.MKP){
			// current character is @
			if(TKS.AT.test(curr) === true){
				if(TKS.EMAILCHARS.test(prev) === true){
					// assume it's an e-mail address and continue
					markupBuffer += curr;
				} else {
					if(TKS.AT.test(next) === true){
						// escaped @. continue, but ignore one @
						markupBuffer += curr;
						i += 1; // skip next @
					} else if(TKS.BRACESTART.test(next) === true){
						// found {, enter block mode
						buffers.push( { type: modes.MKP, value: markupBuffer } );
						markupBuffer = ''; // blank out markup buffer;
						codeBuffer = ''; // blank out just in case
						codeNestLevel += 1;
						mode = modes.BLK;
						continue;
					} else if(TKS.PARENSTART.test(next) === true){

						// consume until next matched ) as mode.EXP, enter markup mode
						buffers.push( { type: modes.MKP, value: markupBuffer } );
						markupBuffer = ''; // blank out markup buffer;
						codeBuffer = ''; // blank out just in case
						j = i+2; // j is index of char after (

						groupLevel = 1;
						while( groupLevel > 0 ){
							j += 1;
							curr = str[j];
							if(j < str.length - 1) next = str[j+1];

							if(TKS.PARENSTART.test(curr) === true) groupLevel += 1;
							if(TKS.PARENEND.test(curr) === true) groupLevel -= 1;

							if(j >= str.length) throw new Error('Syntax Error, unmatched PARENSTART');
						}

						// add ( something something (something something) ) to codeBuffer, without @
						codeBuffer += str.substring(i+1, j+1);

						buffers.push( { type: modes.EXP, value: codeBuffer } );
						markupBuffer = '';
						codeBuffer = ''; // blank out
						mode = modes.MKP;

						i = j; // advance i to current char
						continue;

					} else {
						// test for identifier
						identifier = str.substring(i+1); // everything after @
						identifierMatch = identifier.match( TKS.IDENTIFIER );

						if(identifierMatch === null){
							// stay in content mode?
							markupBuffer += curr;
						} else {
							// found either a reserved word or a valid JS identifier

							if(TKS.RESERVED.test(identifierMatch[0]) === true){
								// found a reserved word, like while
								// switch to JS Block mode
								buffers.push( { type: modes.MKP, value: markupBuffer } );
								markupBuffer = ''; // blank out markup buffer;
								codeBuffer = identifierMatch[0];
								mode = modes.BLK;
								codeNestLevel += 1;
								i += codeBuffer.length; // skip identifier and continue in block mode
								continue;
							} else {
								// we have a valid identifier
								// switch to implicit expression mode: @myvar().dosomethingelse().prop
								buffers.push( { type: modes.MKP, value: markupBuffer } );
								markupBuffer = ''; // blank out markup buffer;
								codeBuffer = ''; // blank out just in case
								mode = modes.EXP;
								continue;
							}
						}
					}
				}
			} else if(TKS.BRACEEND.test(curr) === true) {
				// found } in markup mode,

				identifier = str.substring(0, i+1); // grab from beginning, including }
				identifierMatch = identifier.match(TKS.LBBRACEEND);

				if(identifierMatch == null){
					// just a content }, nothing to worry about
					markupBuffer += curr;
				} else {
					// is a } on a new line, assume it's code related to close a block
					buffers.push( { type: modes.MKP, value: markupBuffer } );
					markupBuffer = ''; // blank out markup buffer;

					codeBuffer = curr; // add } to code buffer
					// switch to block mode
					mode = modes.BLK;

					codeNestLevel -= 1;
					if(codeNestLevel === 0){

						buffers.push( { type: modes.BLK, value: codeBuffer } );
						codeBuffer = '';
						markupBuffer = '';
						mode = modes.MKP;
					}
				}

				continue;

			} else {
				markupBuffer += curr;
				continue;
			}
		}

		if(mode === modes.BLK){

			if(TKS.AT.test(curr) === true){
				// current character is @

				if(TKS.AT.test(next) === true){
					// escaped @, continue, but ignore one @
					codeBuffer += curr;
					i += 1; // skip next @
				} else {

					buffers.push( { type: modes.BLK, value: codeBuffer } );
					codeBuffer = '';
					markupBuffer = '';
					mode = modes.MKP;
					continue;
				}

			} else if(TKS.LT.test(curr) === true){
				// current character is <

				if(TKS.TAGOC.test(next) === true){
					// we have a markup tag
					// switch to markup mode

					buffers.push( { type: modes.BLK, value: codeBuffer } );
					codeBuffer = '';
					markupBuffer = curr;
					mode = modes.MKP;
					continue;
				}

			} else if(TKS.BRACEEND.test(curr) === true) {
				codeBuffer += curr;
				codeNestLevel -= 1;
				if(codeNestLevel === 0){

					buffers.push( { type: modes.BLK, value: codeBuffer } );
					codeBuffer = '';
					markupBuffer = '';
					mode = modes.MKP;
					continue;
				}

			}

			codeBuffer += curr;
			continue;
		}

		if(mode === modes.EXP){
			// test for identifier
			identifier = str.substring(i);
			identifierMatch = identifier.match( TKS.IDENTIFIER );

			if(identifierMatch === null){

				if(TKS.AT.test(curr) === true){
					// must just be standalone @, switch to markup mode
					markupBuffer += curr;

					buffers.push( { type: modes.MPK, value: markupBuffer } );
					markupBuffer = ''; // blank out markup buffer;
					codeBuffer = ''; // blank out just in case
					mode = modes.MKP;
					continue;
				} else {
					// this is probably the end of the expression

					// end of expression, switch to markup mode

					buffers.push( { type: modes.EXP, value: codeBuffer } );
					codeBuffer = '';
					markupBuffer = curr;
					mode = modes.MKP;
					continue;
				}

			} else {
				// found a valid JS identifier

				codeBuffer += identifierMatch[0];
				j = i + identifierMatch[0].length;
				//j = i += identifierMatch[0].length; // set i and j to next char after identifier for next iteration
				next = str[j]; // set next to the actual next char after identifier

				if(TKS.PARENSTART.test(next) === true){
					// found (, consume until next matching

					i = j; // move i forward

					groupLevel = 1;
					while( groupLevel > 0 ){
						j += 1;
						curr = str[j];
						if(j < str.length - 1) next = str[j+1];

						if(TKS.PARENSTART.test(curr) === true) groupLevel += 1;
						if(TKS.PARENEND.test(curr) === true) groupLevel -= 1;

						if(j >= str.length) throw new Error('Syntax Error, unmatched PARENSTART');
					}

					// add ( something something (something something) ) to codeBuffer
					// TODO: may need to substring j+1
					codeBuffer += str.substring(i, j);

					i = j; // advance i to current char
					curr = str[i]; // curr will be equal to )
					if(i < str.length - 1) next = str[i+1];
				} else if(TKS.HARDPARENSTART.test(next) === true){
					// found [, consume until next matching

					i = j; // move i forward

					groupLevel = 1;
					while( groupLevel > 0 ){
						j += 1;
						curr = str[j];
						if(j < str.length - 1) next = str[j+1];

						if(TKS.HARDPARENSTART.test(curr) === true) groupLevel += 1;
						if(TKS.HARDPARENEND.test(curr) === true) groupLevel -= 1;

						if(j >= str.length) throw new Error('Syntax Error, unmatched HARDPARENSTART');
					}

					// add [ something something [something something] ] to codeBuffer
					// TODO: may need to substring j+1
					codeBuffer += str.substring(i, j+1);

					i = j; // advance i to current char
					curr = str[i]; // curr will be equal to ]
					if(i < str.length - 1) next = str[i+1];
				} else if(TKS.PERIOD.test(next) === true){
					// next char is a .

					if( i+2 < str.length && TKS.IDENTIFIER.test( str[i+2] ) ){
						// char after the . is a valid identifier
						// consume ., continue in EXP mode
						codeBuffer += next;
					} else {
						// do not consume . in code, but in markup
						// end EXP block, continue in markup mode

						buffers.push( { type: modes.EXP, value: codeBuffer } );
						markupBuffer = next; // consume . in markup buffer
						codeBuffer = ''; // blank out
						mode = modes.MKP;
						continue;
					}

				}
			}
		}
	}

	buffers.push( { type: modes.MKP, value: markupBuffer } );
	markupBuffer = ''; // blank out markup buffer;
	codeBuffer = ''; // blank out just in case
	mode = modes.MKP;
	return buffers;

}

function generateTemplate(buffers){
	var  i
		,current
		,generated = 'var out = "";\n';

	for(i = 0; i < buffers.length; i++){
		current = buffers[i];

		if(current.type === modes.MKP){
			generated += 'out += \'' + current.value.replace(TKS.QUOTE, '\"').replace(TKS.LINEBREAK, '\\n') + '\';\n';
		}

		if(current.type === modes.BLK){
			// nuke new lines, otherwise causes parse error
			generated += current.value.replace(TKS.QUOTE, '\"').replace(TKS.LINEBREAK, '') + '\n';
		}

		if(current.type === modes.EXP){
			generated += 'out += (' + current.value.replace(TKS.QUOTE, '\"').replace(TKS.LINEBREAK, '\\n') + ');\n';
		}
	}

	return new Function("model", "with(model){" + generated + "}\nreturn out;");
}

root.razor = {
	 _parse: parse
	,tpl: generateTemplate
};

})(this);<|endoftext|>


<commit_before>const fs = require("fs");
const path = require("path");
const pug = require('pug');
// Compile the source code
const compiledFunction = pug.compileFile('src/template.pug');

// Lists all files in directorys
const walkSync = (dir, filelist = []) => {
    fs.readdirSync(dir).forEach(file => {
        filelist = fs.statSync(path.join(dir, file)).isDirectory() ?
            walkSync(path.join(dir, file), filelist) :
            filelist.concat(path.join(dir, file));

    });
    return filelist;
}

// Lists only directorys
const directoryList = (dir, filelist = []) => {
    fs.readdirSync(dir).forEach((file) => {
        if (fs.statSync(path.join(dir, file)).isDirectory()) {
            filelist.push(path.join(dir, file));
        }
    });
    return filelist;
};

const writeFile = (folders) => {
    console.log(folders);
    const html = compiledFunction({
        folders
    });
    fs.writeFile("index.html", html, (err) => {
        if (err) {
            return console.log(err);
        }

        console.log("The file was saved!");
    });
}

const dir = process.argv[2] || __dirname;
const folders = directoryList(dir);
writeFile(folders);<commit_msg>cleanup<commit_after>const fs = require("fs");
const path = require("path");
const pug = require('pug');
// Compile the source code
const compiledFunction = pug.compileFile('src/template.pug');

// Lists all files in directorys
const walkSync = (dir, filelist = []) => {
    fs.readdirSync(dir).forEach(file => {
        filelist = fs.statSync(path.join(dir, file)).isDirectory() ?
            walkSync(path.join(dir, file), filelist) :
            filelist.concat(path.join(dir, file));

    });
    return filelist;
}

// Lists only directorys
const directoryList = (dir, filelist = []) => {
    fs.readdirSync(dir).forEach((file) => {
        if (fs.statSync(path.join(dir, file)).isDirectory()) {
            filelist.push(path.join(dir, file));
        }
    });
    return filelist;
};

const writeFile = (folders) => {
    const html = compiledFunction({
        folders
    });
    fs.writeFile('index.html', html, (err) => {
        if (err) {
            return console.log(err);
        }
    });
}

const dir = process.argv[2] || __dirname;
const folders = directoryList(dir);
writeFile(folders);<|endoftext|>


<commit_before>'use strict';

/**
 * Serverless CORS Plugin
 */
module.exports = function(S) {
  const _ = require('lodash'),
    path = require('path'),
    Joi = require('joi'),
    Promise = require('bluebird'),
    SError = require(S.getServerlessPath('Error'));

  class ServerlessCors extends S.classes.Plugin {
    static getName() {
      return 'com.joostfarla.' + ServerlessCors.name;
    }

    registerHooks() {
      S.addHook(this.addCorsHeaders.bind(this), {
        action: 'endpointBuildApiGateway',
        event: 'pre'
      });

      S.addHook(this.addPreflightRequests.bind(this), {
        action: 'endpointDeploy',
        event: 'pre'
      });

      return Promise.resolve();
    }

    addCorsHeaders(evt) {
      let policy,
        endpoint = S.getProject().getEndpoint(evt.options.name),
        populatedEndpoint = endpoint.toObjectPopulated({
          stage: evt.options.stage,
          region: evt.options.region
        });

      // Skip preflight requests or when CORS is not enabled
      if (populatedEndpoint.method === 'OPTIONS' || !this._isCorsEnabled(endpoint, evt.options.stage, evt.options.region)) {
        return Promise.resolve(evt);
      }

      try {
        policy = this._getEndpointPolicy(endpoint, evt.options.stage, evt.options.region);
      } catch (err) {
        return Promise.reject(err);
      }

      // Set allow-origin header on all responses
      _.each(populatedEndpoint.responses, function(response) {
        if (!response.responseParameters) {
          response.responseParameters = {};
        }

        response.responseParameters['method.response.header.Access-Control-Allow-Origin'] = '\'' + policy.allowOrigin + '\'';

        if (!_.isUndefined(policy.exposeHeaders)) {
          response.responseParameters['method.response.header.Access-Control-Expose-Headers'] = '\'' + policy.exposeHeaders + '\'';
        }

        // Set allow-credentials header on all GET responses as these will not be preflighted
        if (populatedEndpoint.method === 'GET' && !_.isUndefined(policy.allowCredentials)) {
          response.responseParameters['method.response.header.Access-Control-Allow-Credentials'] = '\'' + policy.allowCredentials + '\'';
        }
      });

      endpoint.fromObject(populatedEndpoint);

      return Promise.resolve(evt);
    }

    addPreflightRequests(evt) {
      const _this = this,
        endpoints = S.getProject().getAllEndpoints(),
        pathParamRegex = /{([^}]+)}/g,
        paths = _.map(
          _.uniqBy(endpoints, 'path'),
          endpoint => endpoint.path
        );

      // Only deploy prefight endpoints when 'all' flag is used.
      if (evt.options.all !== true) {
        return Promise.resolve(evt);
      }

      _.each(paths, function(path) {
        let policy, func, preflightEndpoint, response, pathParam,
          allowMethods = [];

        _.each(_.filter(endpoints, { 'path': path }), function(endpoint) {
          if (!_this._isCorsEnabled(endpoint, evt.options.stage, evt.options.region)) {
            return;
          }

          // @todo handle different configurations within same path
          policy = _this._getEndpointPolicy(endpoint, evt.options.stage, evt.options.region);
          func = endpoint.getFunction();

          allowMethods.push(endpoint.method);
        });

        if (allowMethods.length === 0) {
          return Promise.resolve(evt);
        }

        preflightEndpoint = new S.classes.Endpoint({
          path: path,
          method: 'OPTIONS',
          type: 'MOCK',
          requestParameters: {},
          requestTemplates: {
            'application/json': '{"statusCode": 200}'
          }
        }, func);

        while ((pathParam = pathParamRegex.exec(path)) !== null) {
          preflightEndpoint.requestParameters['integration.request.path.' + pathParam[1]] = 'method.request.path.' + pathParam[1];
        }

        if (preflightEndpoint.responses[400]) {
          delete preflightEndpoint.responses[400];
        }

        response = preflightEndpoint.responses.default;

        response.responseParameters = {
          'method.response.header.Access-Control-Allow-Methods': '\'' + allowMethods + '\'',
          'method.response.header.Access-Control-Allow-Origin': '\'' + policy.allowOrigin + '\''
        };

        if (!_.isUndefined(policy.allowHeaders)) {
          response.responseParameters['method.response.header.Access-Control-Allow-Headers'] = '\'' + policy.allowHeaders + '\'';
        }

        if (!_.isUndefined(policy.allowCredentials)) {
          response.responseParameters['method.response.header.Access-Control-Allow-Credentials'] = '\'' + policy.allowCredentials + '\'';
        }

        if (!_.isUndefined(policy.exposeHeaders)) {
          response.responseParameters['method.response.header.Access-Control-Expose-Headers'] = '\'' + policy.exposeHeaders + '\'';
        }

        if (!_.isUndefined(policy.maxAge)) {
          response.responseParameters['method.response.header.Access-Control-Max-Age'] = '\'' + policy.maxAge + '\'';
        }

        func.setEndpoint(preflightEndpoint);
      });

      return Promise.resolve(evt);
    }

    /**
     * Check CORS is enabled on function
     * - Function must first be populated, in case cors settings are in templates
     */

    _isCorsEnabled(endpoint, stage, region) {
      const options = {
        stage: stage,
        region: region
      };

      return !_.isUndefined(S.getProject().toObjectPopulated(options).custom.cors) ||
        !_.isUndefined(endpoint.getFunction().toObjectPopulated(options).custom.cors);
    }

    /**
     * Get endpoint's CORS policy
     * - Function must first be populated, in case cors settings are in templates
     */

    _getEndpointPolicy(endpoint, stage, region) {
      const options = {
        stage: stage,
        region: region
      };

      const policy = _.merge({},
        S.getProject().toObjectPopulated(options).custom.cors,
        endpoint.getFunction().toObjectPopulated(options).custom.cors
      );

      const schema = Joi.object().keys({
        allowOrigin: Joi.string().required(),
        allowHeaders: Joi.array().min(1).items(Joi.string().regex(/^[\w-]+$/)),
        allowCredentials: Joi.boolean(),
        exposeHeaders: Joi.array().min(1).items(Joi.string().regex(/^[\w-]+$/)),
        maxAge: Joi.number()
      });

      Joi.validate(policy, schema, function(err) {
        if (err) {
          throw new SError(
            err.message,
            SError.errorCodes.INVALID_PROJECT_SERVERLESS
          );
        }
      });

      return policy;
    }
  }

  return ServerlessCors;
}
<commit_msg>Support compatibility with serverless-models-plugin<commit_after>'use strict';

/**
 * Serverless CORS Plugin
 */
module.exports = function(S) {
  const _ = require('lodash'),
    path = require('path'),
    Joi = require('joi'),
    Promise = require('bluebird'),
    SError = require(S.getServerlessPath('Error'));

  class ServerlessCors extends S.classes.Plugin {
    static getName() {
      return 'com.joostfarla.' + ServerlessCors.name;
    }

    registerHooks() {
      S.addHook(this.addCorsHeaders.bind(this), {
        action: 'endpointBuildApiGateway',
        event: 'pre'
      });

      S.addHook(this.addPreflightRequests.bind(this), {
        action: 'endpointDeploy',
        event: 'pre'
      });

      return Promise.resolve();
    }

    addCorsHeaders(evt) {
      let policy,
        endpoint = S.getProject().getEndpoint(evt.options.name),
        populatedEndpoint = endpoint.toObjectPopulated({
          stage: evt.options.stage,
          region: evt.options.region
        });

      // Skip preflight requests or when CORS is not enabled
      if (populatedEndpoint.method === 'OPTIONS' || !this._isCorsEnabled(endpoint, evt.options.stage, evt.options.region)) {
        return Promise.resolve(evt);
      }

      try {
        policy = this._getEndpointPolicy(endpoint, evt.options.stage, evt.options.region);
      } catch (err) {
        return Promise.reject(err);
      }

      // Set allow-origin header on all responses
      _.each(populatedEndpoint.responses, function(response) {
        if (!response.responseParameters) {
          response.responseParameters = {};
        }

        response.responseParameters['method.response.header.Access-Control-Allow-Origin'] = '\'' + policy.allowOrigin + '\'';

        if (!_.isUndefined(policy.exposeHeaders)) {
          response.responseParameters['method.response.header.Access-Control-Expose-Headers'] = '\'' + policy.exposeHeaders + '\'';
        }

        // Set allow-credentials header on all GET responses as these will not be preflighted
        if (populatedEndpoint.method === 'GET' && !_.isUndefined(policy.allowCredentials)) {
          response.responseParameters['method.response.header.Access-Control-Allow-Credentials'] = '\'' + policy.allowCredentials + '\'';
        }
      });

      endpoint.fromObject(populatedEndpoint);

      return Promise.resolve(evt);
    }

    addPreflightRequests(evt) {
      const _this = this,
        endpoints = S.getProject().getAllEndpoints(),
        pathParamRegex = /{([^}]+)}/g,
        paths = _.map(
          _.uniqBy(endpoints, 'path'),
          endpoint => endpoint.path
        );

      // Only deploy prefight endpoints when 'all' flag is used.
      if (evt.options.all !== true) {
        return Promise.resolve(evt);
      }

      _.each(paths, function(path) {
        let policy, func, preflightEndpoint, response, pathParam,
          allowMethods = [];

        _.each(_.filter(endpoints, { 'path': path }), function(endpoint) {
          if (!_this._isCorsEnabled(endpoint, evt.options.stage, evt.options.region)) {
            return;
          }

          // @todo handle different configurations within same path
          policy = _this._getEndpointPolicy(endpoint, evt.options.stage, evt.options.region);
          func = endpoint.getFunction();

          allowMethods.push(endpoint.method);
        });

        if (allowMethods.length === 0) {
          return Promise.resolve(evt);
        }

        preflightEndpoint = new S.classes.Endpoint({
          path: path,
          method: 'OPTIONS',
          type: 'MOCK',
          requestParameters: {},
          requestTemplates: {
            'application/json': '{"statusCode": 200}'
          },
          responses: {
            default: {
              statusCode: "200",
              responseParameters: {},
              responseModels: {},
              responseTemplates: {
                "application/json": ""
              }
            }
          }
        }, func);

        while ((pathParam = pathParamRegex.exec(path)) !== null) {
          preflightEndpoint.requestParameters['integration.request.path.' + pathParam[1]] = 'method.request.path.' + pathParam[1];
        }

        if (preflightEndpoint.responses[400]) {
          delete preflightEndpoint.responses[400];
        }

        response = preflightEndpoint.responses.default;

        response.responseParameters = {
          'method.response.header.Access-Control-Allow-Methods': '\'' + allowMethods + '\'',
          'method.response.header.Access-Control-Allow-Origin': '\'' + policy.allowOrigin + '\''
        };

        if (!_.isUndefined(policy.allowHeaders)) {
          response.responseParameters['method.response.header.Access-Control-Allow-Headers'] = '\'' + policy.allowHeaders + '\'';
        }

        if (!_.isUndefined(policy.allowCredentials)) {
          response.responseParameters['method.response.header.Access-Control-Allow-Credentials'] = '\'' + policy.allowCredentials + '\'';
        }

        if (!_.isUndefined(policy.exposeHeaders)) {
          response.responseParameters['method.response.header.Access-Control-Expose-Headers'] = '\'' + policy.exposeHeaders + '\'';
        }

        if (!_.isUndefined(policy.maxAge)) {
          response.responseParameters['method.response.header.Access-Control-Max-Age'] = '\'' + policy.maxAge + '\'';
        }

        func.setEndpoint(preflightEndpoint);
      });

      return Promise.resolve(evt);
    }

    /**
     * Check CORS is enabled on function
     * - Function must first be populated, in case cors settings are in templates
     */

    _isCorsEnabled(endpoint, stage, region) {
      const options = {
        stage: stage,
        region: region
      };

      return !_.isUndefined(S.getProject().toObjectPopulated(options).custom.cors) ||
        !_.isUndefined(endpoint.getFunction().toObjectPopulated(options).custom.cors);
    }

    /**
     * Get endpoint's CORS policy
     * - Function must first be populated, in case cors settings are in templates
     */

    _getEndpointPolicy(endpoint, stage, region) {
      const options = {
        stage: stage,
        region: region
      };

      const policy = _.merge({},
        S.getProject().toObjectPopulated(options).custom.cors,
        endpoint.getFunction().toObjectPopulated(options).custom.cors
      );

      const schema = Joi.object().keys({
        allowOrigin: Joi.string().required(),
        allowHeaders: Joi.array().min(1).items(Joi.string().regex(/^[\w-]+$/)),
        allowCredentials: Joi.boolean(),
        exposeHeaders: Joi.array().min(1).items(Joi.string().regex(/^[\w-]+$/)),
        maxAge: Joi.number()
      });

      Joi.validate(policy, schema, function(err) {
        if (err) {
          throw new SError(
            err.message,
            SError.errorCodes.INVALID_PROJECT_SERVERLESS
          );
        }
      });

      return policy;
    }
  }

  return ServerlessCors;
}
<|endoftext|>


<commit_before>'use strict';

/**
 * @module
 * @copyright paywell Team at byteskode <www.byteskode.com>
 * @description redis client factories
 * @since 0.1.0
 * @author lally elias<lallyelias87@gmail.com, lally.elias@byteskode.com>
 * @singleton
 * @public
 */

//dependencies
const redis = require('redis');
const _ = require('lodash');

//defaults settings
const defaults = {
  prefix: 'paywell',
  separator: ':',
  redis: {
    port: 6379,
    host: '127.0.0.1'
  }
};

/**
 * @name defaults
 * @description default redis client connection options
 * @type {Object}
 * @since 0.1.0
 * @public
 */
exports.defaults = _.merge({}, defaults);

/**
 * @function
 * @name createClient
 * @description instantiate new redis client if not exists
 * @return {Object} an instance of redis client
 * @since 0.1.0
 * @public
 */
exports.createClient = function (options) {

  var options = _.merge({}, exports.defaults, options);

  var socket = options.redis.socket;
  var port = !socket ? (options.redis.port || 6379) : null;
  var host = !socket ? (options.redis.host || '127.0.0.1') : null;
  var client =
    redis.createClient(socket || port, host, options.redis.options);

  if (options.redis.auth) {
    client.auth(options.redis.auth);
  }

  if (options.redis.db) {
    client.select(options.redis.db);
  }

  return client;
};

/**
 * @function
 * @name pubsub
 * @description instantiate redis pub-sub clients pair if not exists
 * @since 0.1.0
 * @public
 */
exports.pubsub = function () {
  //create publisher if not exists
  if (!exports.publisher) {
    exports.publisher = exports.createClient();
  }

  //create subscriber if not exist
  if (!exports.subscriber) {
    exports.subscriber = exports.createClient();
  }
};

/**
 * @function
 * @name init
 * @description initialize redis client and pubsub
 * @since 0.1.0
 * @public
 */
exports.init = function () {

  //initialize normal client
  if (!exports.client) {
    exports.client = exports.createClient();
  }

  //initialize publisher and subscriber clients
  exports.pubsub();

};

/**
 * @function
 * @name info
 * @description collect redis server health information
 * @param  {Function} done a callback to invoke on success or failure
 * @return {Object}        server details
 * @since 0.1.0
 * @public
 */
exports.info = function (done) {
  //ensure connection
  exports.init();

  exports.client.info(function (error /*, info*/ ) {
    // jshint camelcase:false
    done(error, exports.client.server_info);
    // jshint camelcase:true
  });
};

/**
 * @function
 * @name key
 * @description prepare data storage key
 * @param  {String|String[]} key valid data store key
 * @since 0.1.0
 * @public
 */
exports.key = function (key) {
  //concatenate key is varargs
  key = [].concat(key);
  key = [exports.defaults.prefix].concat(key);

  //join key using separator
  key = key.join(exports.defaults.separator);

  return key;
};

/**
 * @function
 * @name indexKey
 * @description prepare collection index key
 * @param  {String} collection valid name of the collection
 * @since 0.1.0
 * @public
 */
exports.indexKey = function (collection) {
  collection = [].concat(collection).concat(['search']);
  var indexKey = exports.key(collection);
  return indexKey;
};

/**
 * @function
 * @name reset
 * @description quit and reset redis clients states
 * @since 0.1.0
 * @public
 */
exports.reset = function () {
  //clear subscriptions and listeners
  if (exports.subscriber) {
    exports.subscriber.unsubscribe();
    exports.subscriber.removeAllListeners();
  }

  //quit clients
  exports.client =
    exports.client ? exports.client.quit() : null;

  exports.publisher =
    exports.publisher ? exports.publisher.quit() : null;

  exports.subscriber =
    exports.subscriber ? exports.subscriber.quit() : null;

  //reset clients
  exports.client = null;
  exports.publisher = null;
  exports.subscriber = null;

  //reset settings
  exports.defaults = _.merge({}, defaults);
};

/**
 * @function
 * @name clear
 * @description clear all data saved and their key
 * @param {String|[String]} [keys] key pattern or collection of keys to removed
 * @param {Function} done a callback to invoke on success or failure
 * @since 0.1.0
 * @public
 */
exports.clear = function (keys, done) {

  //normalize arguments
  if (keys && _.isFunction(keys)) {
    done = keys;
    keys = undefined;
  }

  //TODO handle collection of keys passed

  //prepare clear all key regex
  keys = _.compact([exports.defaults.prefix, keys]);
  keys = [keys, '*'].join('');

  //ensure client
  exports.init();

  //clear data
  exports.client.keys(keys, function (error, rows) {
    //back-off in case there is error
    if (error) {
      done(error);
    } else {
      //initiate multi to run all commands atomically
      var _client = exports.client.multi();

      //queue commands
      _.forEach(rows, function (row) {
        _client.del(row);
      });

      //execute commands
      _client.exec(done);
    }
  });
};

//TODO implement process shutdown hook<commit_msg>fix createClient errors<commit_after>'use strict';

/**
 * @module
 * @copyright paywell Team at byteskode <www.byteskode.com>
 * @description redis client factories
 * @since 0.1.0
 * @author lally elias<lallyelias87@gmail.com, lally.elias@byteskode.com>
 * @singleton
 * @public
 */

//dependencies
const redis = require('redis');
const _ = require('lodash');

//defaults settings
const defaults = {
  prefix: 'paywell',
  separator: ':',
  redis: {
    port: 6379,
    host: '127.0.0.1'
  }
};

/**
 * @name defaults
 * @description default redis client connection options
 * @type {Object}
 * @since 0.1.0
 * @public
 */
exports.defaults = _.merge({}, defaults);

/**
 * @function
 * @name createClient
 * @description instantiate new redis client if not exists
 * @return {Object} an instance of redis client
 * @since 0.1.0
 * @public
 */
exports.createClient = function (options) {

  options = _.merge({}, exports.defaults, options);

  const socket = options.redis.socket;
  const port = !socket ? (options.redis.port || 6379) : null;
  const host = !socket ? (options.redis.host || '127.0.0.1') : null;
  const client =
    redis.createClient(socket || port, host, options.redis.options);

  if (options.redis.auth) {
    client.auth(options.redis.auth);
  }

  if (options.redis.db) {
    client.select(options.redis.db);
  }

  return client;
};

/**
 * @function
 * @name pubsub
 * @description instantiate redis pub-sub clients pair if not exists
 * @since 0.1.0
 * @public
 */
exports.pubsub = function () {
  //create publisher if not exists
  if (!exports.publisher) {
    exports.publisher = exports.createClient();
  }

  //create subscriber if not exist
  if (!exports.subscriber) {
    exports.subscriber = exports.createClient();
  }
};

/**
 * @function
 * @name init
 * @description initialize redis client and pubsub
 * @since 0.1.0
 * @public
 */
exports.init = function () {

  //initialize normal client
  if (!exports.client) {
    exports.client = exports.createClient();
  }

  //initialize publisher and subscriber clients
  exports.pubsub();

};

/**
 * @function
 * @name info
 * @description collect redis server health information
 * @param  {Function} done a callback to invoke on success or failure
 * @return {Object}        server details
 * @since 0.1.0
 * @public
 */
exports.info = function (done) {
  //ensure connection
  exports.init();

  exports.client.info(function (error /*, info*/ ) {
    // jshint camelcase:false
    done(error, exports.client.server_info);
    // jshint camelcase:true
  });
};

/**
 * @function
 * @name key
 * @description prepare data storage key
 * @param  {String|String[]} key valid data store key
 * @since 0.1.0
 * @public
 */
exports.key = function (key) {
  //concatenate key is varargs
  key = [].concat(key);
  key = [exports.defaults.prefix].concat(key);

  //join key using separator
  key = key.join(exports.defaults.separator);

  return key;
};

/**
 * @function
 * @name indexKey
 * @description prepare collection index key
 * @param  {String} collection valid name of the collection
 * @since 0.1.0
 * @public
 */
exports.indexKey = function (collection) {
  collection = [].concat(collection).concat(['search']);
  var indexKey = exports.key(collection);
  return indexKey;
};

/**
 * @function
 * @name reset
 * @description quit and reset redis clients states
 * @since 0.1.0
 * @public
 */
exports.reset = function () {
  //clear subscriptions and listeners
  if (exports.subscriber) {
    exports.subscriber.unsubscribe();
    exports.subscriber.removeAllListeners();
  }

  //quit clients
  exports.client =
    exports.client ? exports.client.quit() : null;

  exports.publisher =
    exports.publisher ? exports.publisher.quit() : null;

  exports.subscriber =
    exports.subscriber ? exports.subscriber.quit() : null;

  //reset clients
  exports.client = null;
  exports.publisher = null;
  exports.subscriber = null;

  //reset settings
  exports.defaults = _.merge({}, defaults);
};

/**
 * @function
 * @name clear
 * @description clear all data saved and their key
 * @param {String|[String]} [keys] key pattern or collection of keys to removed
 * @param {Function} done a callback to invoke on success or failure
 * @since 0.1.0
 * @public
 */
exports.clear = function (keys, done) {

  //normalize arguments
  if (keys && _.isFunction(keys)) {
    done = keys;
    keys = undefined;
  }

  //TODO handle collection of keys passed

  //prepare clear all key regex
  keys = _.compact([exports.defaults.prefix, keys]);
  keys = [keys, '*'].join('');

  //ensure client
  exports.init();

  //clear data
  exports.client.keys(keys, function (error, rows) {
    //back-off in case there is error
    if (error) {
      done(error);
    } else {
      //initiate multi to run all commands atomically
      var _client = exports.client.multi();

      //queue commands
      _.forEach(rows, function (row) {
        _client.del(row);
      });

      //execute commands
      _client.exec(done);
    }
  });
};

//TODO implement process shutdown hook<|endoftext|>