<commit_before>var config = require('./Config');
var express = require('express');
var _ = require('lodash');
var pushAssociations = require('./PushAssociations');
var push = require('./PushController');

var app = express();

// Middleware
app.use(express.compress());
app.use(express.bodyParser());

app.use(express.static(__dirname + '/../public'));

app.post('/*', function (req, res, next) {
    if (req.is('application/json')) {
        next();
    } else {
        res.status(406).send();
    }
})

// Main API
app.post('/subscribe', function (req, res) {
    var deviceInfo = req.body;
    push.subscribe(deviceInfo);

    res.send();
});

app.post('/unsubscribe', function (req, res) {
    var data = req.body;

    if (data.user) {
        push.unsubscribeUser(data.user);
    } else if (data.token) {
        push.unsubscribeDevice(data.token);
    } else {
        return res.status(503).send();
    }

    res.send();
});

app.post('/send', function (req, res) {
    var data = req.body,
        users = data.users,
        androidPayload = data.android,
        iosPayload = data.ios;

    if(androidPayload && iosPayload) {
        target = 'all'
    } else if(iosPayload) {
        target = 'ios'
    } else if (androidPayload) {
        target = 'android';
    } else {
        return res.status(500).send()
    }

    var fetchUsers = users ? pushAssociations.getForUsers : pushAssociations.getAll,
        callback = function (err, pushAssociations) {
            if (err) return;

            if (target !== 'all') {
                // TODO: do it in mongo instead of here ...
                pushAssociations = _.where(pushAssociations, {'type': target});
            }

            push.send(pushAssociations, androidPayload, iosPayload);
        },
        args = users ? [users, callback] : [callback];

    fetchUsers.apply(null, args);

    res.send();
});

// Utils API
app.get('/users/:user/associations', function (req, res) {
    pushAssociations.getForUser(req.params.user, function (err, items) {
        if (!err) {
            res.send({"associations": items});
        } else {
            res.status(503).send();
        }
    });
});

app.get('/users', function (req, res) {
    pushAssociations.getAll(function (err, pushAss) {
        if (!err) {
            var users = _.map(pushAss, 'user');
            res.send({
                "users": users
            });
        } else {
            res.status(503).send()
        }
    });
});

app.delete('/users/:user', function (req, res) {
    pushController.unsubscribeUser(req.params.user);
    res.send('ok');
});

exports.start = function() {
    app.listen(config.get('webPort'));
    console.log('Listening on port ' + config.get('webPort') + "...");
}<commit_msg>Removed duplicate users from /users<commit_after>var config = require('./Config');
var express = require('express');
var _ = require('lodash');
var pushAssociations = require('./PushAssociations');
var push = require('./PushController');

var app = express();

// Middleware
app.use(express.compress());
app.use(express.bodyParser());

app.use(express.static(__dirname + '/../public'));

app.post('/*', function (req, res, next) {
    if (req.is('application/json')) {
        next();
    } else {
        res.status(406).send();
    }
})

// Main API
app.post('/subscribe', function (req, res) {
    var deviceInfo = req.body;
    push.subscribe(deviceInfo);

    res.send();
});

app.post('/unsubscribe', function (req, res) {
    var data = req.body;

    if (data.user) {
        push.unsubscribeUser(data.user);
    } else if (data.token) {
        push.unsubscribeDevice(data.token);
    } else {
        return res.status(503).send();
    }

    res.send();
});

app.post('/send', function (req, res) {
    var data = req.body,
        users = data.users,
        androidPayload = data.android,
        iosPayload = data.ios;

    if(androidPayload && iosPayload) {
        target = 'all'
    } else if(iosPayload) {
        target = 'ios'
    } else if (androidPayload) {
        target = 'android';
    } else {
        return res.status(500).send()
    }

    var fetchUsers = users ? pushAssociations.getForUsers : pushAssociations.getAll,
        callback = function (err, pushAssociations) {
            if (err) return;

            if (target !== 'all') {
                // TODO: do it in mongo instead of here ...
                pushAssociations = _.where(pushAssociations, {'type': target});
            }

            push.send(pushAssociations, androidPayload, iosPayload);
        },
        args = users ? [users, callback] : [callback];

    fetchUsers.apply(null, args);

    res.send();
});

// Utils API
app.get('/users/:user/associations', function (req, res) {
    pushAssociations.getForUser(req.params.user, function (err, items) {
        if (!err) {
            res.send({"associations": items});
        } else {
            res.status(503).send();
        }
    });
});

app.get('/users', function (req, res) {
    pushAssociations.getAll(function (err, pushAss) {
        if (!err) {
            var users = _(pushAss).map('user').unique().value();
            res.send({
                "users": users
            });
        } else {
            res.status(503).send()
        }
    });
});

app.delete('/users/:user', function (req, res) {
    pushController.unsubscribeUser(req.params.user);
    res.send('ok');
});

exports.start = function() {
    app.listen(config.get('webPort'));
    console.log('Listening on port ' + config.get('webPort') + "...");
}
<|endoftext|>


<commit_before>/**
 * Main logic for the other commands live here.
 */
module.exports = {
	events: {
		message: function ( bot, nick, to, text, message ) {
			if ( text.substr( 0, 1 ) === bot.config.irc.controlChar ) {
				var msg = {
					args: text.substr( 1 ).split( ' ' ),
					nick: nick,
					to: to,
					text: text,
					message: message
				};
				msg._cmd = bot.config.irc.controlChar + msg.args[0];
				if ( bot.commands[msg.args[0]] ) {
					try {
						var cmd = bot.commands[msg.args[0]];
						if ( cmd.command ) {
							if ( cmd.disabled ) {
								return;
							}
							if ( cmd.privileged ) {
								if ( bot.config.irc.control !== msg.to ) {
									bot.say( msg.to, msg.nick + ': Unable.' );
									return;
								}
							}
							if ( cmd.usage ) {
								if ( msg.args.length !== cmd.usage.length + 1 ) {
									return bot.say( msg.to, msg.nick + ': Usage: ' + msg._cmd + ' <' + cmd.usage.join( '> < ' ) + '>' );
							}
								msg.args = msg.args.reduce( function ( o, p, k ) {
									o[k] = p;
									return o;
								}, {} );
								for ( var i = 0; i < cmd.usage.length; i++ ) {
									msg.args[cmd.usage[i]] = msg.args[i + 1];
								}
							}
							cmd = cmd.command;
						}
						var output = cmd( bot, msg );
						if ( typeof output === 'string' ) {
							bot.say( msg.to, msg.nick + ': ' + output );
						}
					} catch ( e ) {
						bot.say( bot.config.irc.control,'Error processing `' + msg._cmd + '` in ' + msg.to + ': ' + e );
						console.error( e.message );
						console.error( e.stack );
					}
				} else {
					console.log( 'Unknown command: ' + text );
				}
			}
		}
	}
};

<commit_msg>Fix #42.<commit_after>/**
 * Main logic for the other commands live here.
 */
module.exports = {
	events: {
		message: function ( bot, nick, to, text, message ) {
			if ( text.substr( 0, 1 ) === bot.config.irc.controlChar ) {
				var msg = {
					args: text.substr( 1 ).split( ' ' ),
					nick: nick,
					to: to,
					text: text,
					message: message
				};
				msg._cmd = bot.config.irc.controlChar + msg.args[0];
				if ( bot.commands[msg.args[0]] ) {
					try {
						var cmd = bot.commands[msg.args[0]];
						if ( cmd.command ) {
							if ( cmd.disabled ) {
								return;
							}
							if ( cmd.privileged ) {
								if ( bot.config.irc.control !== msg.to ) {
									// http://www.imdb.com/title/tt0062622/quotes?item=qt0396921
									bot.say( msg.to, 'I\'m sorry, ' + msg.nick + '. I\'m afraid I can\'t do that.' );
									return;
								}
							}
							if ( cmd.usage ) {
								if ( msg.args.length !== cmd.usage.length + 1 ) {
									return bot.say( msg.to, msg.nick + ': Usage: ' + msg._cmd + ' <' + cmd.usage.join( '> < ' ) + '>' );
							}
								msg.args = msg.args.reduce( function ( o, p, k ) {
									o[k] = p;
									return o;
								}, {} );
								for ( var i = 0; i < cmd.usage.length; i++ ) {
									msg.args[cmd.usage[i]] = msg.args[i + 1];
								}
							}
							cmd = cmd.command;
						}
						var output = cmd( bot, msg );
						if ( typeof output === 'string' ) {
							bot.say( msg.to, msg.nick + ': ' + output );
						}
					} catch ( e ) {
						bot.say( bot.config.irc.control,'Error processing `' + msg._cmd + '` in ' + msg.to + ': ' + e );
						console.error( e.message );
						console.error( e.stack );
					}
				} else {
					console.log( 'Unknown command: ' + text );
				}
			}
		}
	}
};

<|endoftext|>


<commit_before>/**
 * Main logic for the other commands live here.
 */
const colors = require('irc').colors

const processOutput = (bot, msg, output, customFormatter) => {
  const outputFormatter = customFormatter || ((str) => msg.nick + ': ' + str)
  if (!output) return null
  if (typeof output === 'string') {
    return bot.say(msg.to, outputFormatter(output))
  } else if (output.constructor === Array) {
    output.forEach(line => processOutput(bot, msg, line))
  } else {
    console.error('output is strange type: ' + output)
  }
}
module.exports = {
  events: {
    message: function (bot, nick, to, text, message) {
      let controlChar = bot.config.get('irc.controlChar')
      if (text.substr(0, 1) === controlChar) {
        var msg = {
          body: text.substr(text.indexOf(' ') + 1),
          args: text.substr(1).split(' '),
          nick: nick,
          to: to,
          text: text,
          message: message
        }
        msg._cmd = controlChar + msg.args[0]
        if (bot.commands.hasOwnProperty(msg.args[0])) {
          try {
            var cmd = bot.commands[msg.args[0]]
            if (cmd.command) {
              // stats hook
              if (bot.commandcount) {
                bot.commandcount++
              } else {
                bot.commandcount = 1
              }
              if (cmd.disabled) {
                return
              }
              if (cmd.privileged) {
                if (bot.config.get('irc.control') !== msg.to) {
                  if (bot.config.get('irc.insecure') === true) {
                    // pass
                  } else {
                    // http://www.imdb.com/title/tt0062622/quotes?item=qt0396921
                    return bot.say(msg.to, 'I\'m sorry, ' + msg.nick + '. I\'m afraid I can\'t do that.')
                  }
                }
              }
              if (Array.isArray(cmd.usage)) {
                if (msg.args.length !== cmd.usage.length + 1) {
                  return bot.say(msg.to, msg.nick + ': Usage: ' + msg._cmd + ' <' + cmd.usage.join('> <') + '>')
                }
                msg.args = msg.args.reduce(function (o, p, k) {
                  o[k] = p
                  return o
                }, {})
                for (var i = 0; i < cmd.usage.length; i++) {
                  msg.args[cmd.usage[i]] = msg.args[i + 1]
                }
              }
              cmd = cmd.command
            }
            if (typeof cmd === 'function') {
              return Promise.resolve(cmd(bot, msg))
                .then(output => processOutput(bot, msg, output))
                .catch(output => processOutput(bot, msg, output.toString(), (str) => colors.wrap('red', 'Error: ') + str))
            }
          } catch (e) {
            bot.say(bot.config.get('irc.control'), 'Error processing `' + msg._cmd + '` in ' + msg.to + ': ' + e)
            console.error(e.message)
            console.error(e.stack)
          }
        } else {
          console.log('Unknown command: ' + text)
        }
      }
    }
  }
}
<commit_msg>Output errors to console better<commit_after>/**
 * Main logic for the other commands live here.
 */
const colors = require('irc').colors

const processOutput = (bot, msg, output, customFormatter) => {
  const outputFormatter = customFormatter || ((str) => msg.nick + ': ' + str)
  if (!output) return null
  if (typeof output === 'string') {
    return bot.say(msg.to, outputFormatter(output))
  } else if (output.constructor === Array) {
    output.forEach(line => processOutput(bot, msg, line))
  } else {
    console.error('output is strange type: ' + output)
  }
}
module.exports = {
  events: {
    message: function (bot, nick, to, text, message) {
      let controlChar = bot.config.get('irc.controlChar')
      if (text.substr(0, 1) === controlChar) {
        var msg = {
          body: text.substr(text.indexOf(' ') + 1),
          args: text.substr(1).split(' '),
          nick: nick,
          to: to,
          text: text,
          message: message
        }
        msg._cmd = controlChar + msg.args[0]
        if (bot.commands.hasOwnProperty(msg.args[0])) {
          try {
            var cmd = bot.commands[msg.args[0]]
            if (cmd.command) {
              // stats hook
              if (bot.commandcount) {
                bot.commandcount++
              } else {
                bot.commandcount = 1
              }
              if (cmd.disabled) {
                return
              }
              if (cmd.privileged) {
                if (bot.config.get('irc.control') !== msg.to) {
                  if (bot.config.get('irc.insecure') === true) {
                    // pass
                  } else {
                    // http://www.imdb.com/title/tt0062622/quotes?item=qt0396921
                    return bot.say(msg.to, 'I\'m sorry, ' + msg.nick + '. I\'m afraid I can\'t do that.')
                  }
                }
              }
              if (Array.isArray(cmd.usage)) {
                if (msg.args.length !== cmd.usage.length + 1) {
                  return bot.say(msg.to, msg.nick + ': Usage: ' + msg._cmd + ' <' + cmd.usage.join('> <') + '>')
                }
                msg.args = msg.args.reduce(function (o, p, k) {
                  o[k] = p
                  return o
                }, {})
                for (var i = 0; i < cmd.usage.length; i++) {
                  msg.args[cmd.usage[i]] = msg.args[i + 1]
                }
              }
              cmd = cmd.command
            }
            if (typeof cmd === 'function') {
              return Promise.resolve(cmd(bot, msg))
                .then(output => processOutput(bot, msg, output))
                .catch(e => {
                  console.error(e.message)
                  console.error(e.stack)

                  return processOutput(bot, msg, e.toString(), (str) => colors.wrap('red', 'Error: ') + str)
                })
            }
          } catch (e) {
            bot.say(bot.config.get('irc.control'), 'Error processing `' + msg._cmd + '` in ' + msg.to + ': ' + e)
            console.error(e.message)
            console.error(e.stack)
          }
        } else {
          console.log('Unknown command: ' + text)
        }
      }
    }
  }
}
<|endoftext|>


<commit_before>import extend from 'extend';
import Delta from 'quill-delta';
import {
  Attributor,
  ClassAttributor,
  EmbedBlot,
  Scope,
  StyleAttributor,
} from 'parchment';
import Quill from '../core/quill';
import logger from '../core/logger';
import Module from '../core/module';

import { AlignAttribute, AlignStyle } from '../formats/align';
import { BackgroundStyle } from '../formats/background';
import CodeBlock from '../formats/code';
import { ColorStyle } from '../formats/color';
import { DirectionAttribute, DirectionStyle } from '../formats/direction';
import { FontStyle } from '../formats/font';
import { SizeStyle } from '../formats/size';

const debug = logger('quill:clipboard');

const CLIPBOARD_CONFIG = [
  [Node.TEXT_NODE, matchText],
  [Node.TEXT_NODE, matchNewline],
  ['br', matchBreak],
  [Node.ELEMENT_NODE, matchNewline],
  [Node.ELEMENT_NODE, matchBlot],
  [Node.ELEMENT_NODE, matchAttributor],
  [Node.ELEMENT_NODE, matchStyles],
  ['li', matchIndent],
  ['ol, ul', matchList],
  ['pre', matchCodeBlock],
  ['tr', matchTable],
  ['b', matchAlias.bind(matchAlias, 'bold')],
  ['i', matchAlias.bind(matchAlias, 'italic')],
  ['style', matchIgnore],
];

const ATTRIBUTE_ATTRIBUTORS = [AlignAttribute, DirectionAttribute].reduce(
  (memo, attr) => {
    memo[attr.keyName] = attr;
    return memo;
  },
  {},
);

const STYLE_ATTRIBUTORS = [
  AlignStyle,
  BackgroundStyle,
  ColorStyle,
  DirectionStyle,
  FontStyle,
  SizeStyle,
].reduce((memo, attr) => {
  memo[attr.keyName] = attr;
  return memo;
}, {});

class Clipboard extends Module {
  constructor(quill, options) {
    super(quill, options);
    this.quill.root.addEventListener('copy', this.onCaptureCopy.bind(this));
    this.quill.root.addEventListener('paste', this.onCapturePaste.bind(this));
    this.matchers = [];
    CLIPBOARD_CONFIG.concat(this.options.matchers).forEach(
      ([selector, matcher]) => {
        this.addMatcher(selector, matcher);
      },
    );
  }

  addMatcher(selector, matcher) {
    this.matchers.push([selector, matcher]);
  }

  convert(html) {
    const container = this.quill.root.ownerDocument.createElement('div');
    container.innerHTML = html.replace(/>\r?\n +</g, '><'); // Remove spaces between tags
    const nodeMatches = new WeakMap();
    const [elementMatchers, textMatchers] = this.prepareMatching(
      container,
      nodeMatches,
    );
    let delta = traverse(
      this.quill.scroll,
      container,
      elementMatchers,
      textMatchers,
      nodeMatches,
    );
    // Remove trailing newline
    if (
      deltaEndsWith(delta, '\n') &&
      delta.ops[delta.ops.length - 1].attributes == null
    ) {
      delta = delta.compose(new Delta().retain(delta.length() - 1).delete(1));
    }
    debug.log('convert', container.innerHTML, delta);
    return delta;
  }

  dangerouslyPasteHTML(index, html, source = Quill.sources.API) {
    if (typeof index === 'string') {
      this.quill.setContents(this.convert(index), html);
      this.quill.setSelection(0, Quill.sources.SILENT);
    } else {
      const paste = this.convert(html);
      this.quill.updateContents(
        new Delta().retain(index).concat(paste),
        source,
      );
      this.quill.setSelection(index + paste.length(), Quill.sources.SILENT);
    }
  }

  onCaptureCopy(e) {
    if (e.defaultPrevented) return;
    this.quill.update();
    const [range, native] = this.quill.selection.getRange();
    this.onCopy(e, range, native);
    e.preventDefault();
  }

  onCapturePaste(e) {
    if (e.defaultPrevented || !this.quill.isEnabled()) return;
    const range = this.quill.getSelection(true);
    const files = Array.from(e.clipboardData.files || []);
    if (files.length > 0) {
      this.quill.uploader.upload(range, files);
    } else {
      this.onPaste(e, range);
    }
    e.preventDefault();
  }

  onCopy(e, range, nativeRange) {
    const text = this.quill.getText(range);
    const fragment = nativeRange.native.cloneContents();
    Array.from(fragment.querySelectorAll('select')).forEach(select => {
      select.parentNode.removeChild(select);
    });
    const div = this.quill.root.ownerDocument.createElement('div');
    div.style.whiteSpace = 'pre-wrap';
    div.appendChild(fragment);
    e.clipboardData.setData('text/plain', text);
    e.clipboardData.setData('text/html', div.outerHTML);
  }

  onPaste(e, range) {
    const formats = this.quill.getFormat(this.quill.selection.savedRange.index);
    const html = e.clipboardData.getData('text/html');
    const text = e.clipboardData.getData('text/plain');
    let delta = new Delta().retain(range.index);
    if (formats[CodeBlock.blotName]) {
      delta.insert(text, {
        [CodeBlock.blotName]: formats[CodeBlock.blotName],
      });
    } else if (!html) {
      delta.insert(text);
    } else {
      const pasteDelta = this.convert(html);
      delta = delta.concat(pasteDelta);
    }
    delta.delete(range.length);
    this.quill.updateContents(delta, Quill.sources.USER);
    // range.length contributes to delta.length()
    this.quill.setSelection(
      delta.length() - range.length,
      Quill.sources.SILENT,
    );
    this.quill.scrollIntoView();
  }

  prepareMatching(container, nodeMatches) {
    const elementMatchers = [];
    const textMatchers = [];
    this.matchers.forEach(pair => {
      const [selector, matcher] = pair;
      switch (selector) {
        case Node.TEXT_NODE:
          textMatchers.push(matcher);
          break;
        case Node.ELEMENT_NODE:
          elementMatchers.push(matcher);
          break;
        default:
          Array.from(container.querySelectorAll(selector)).forEach(node => {
            if (nodeMatches.has(node)) {
              const matches = nodeMatches.get(node);
              matches.push(matcher);
            } else {
              nodeMatches.set(node, [matcher]);
            }
          });
          break;
      }
    });
    return [elementMatchers, textMatchers];
  }
}
Clipboard.DEFAULTS = {
  matchers: [],
};

function applyFormat(delta, format, value) {
  if (typeof format === 'object') {
    return Object.keys(format).reduce((newDelta, key) => {
      return applyFormat(newDelta, key, format[key]);
    }, delta);
  }
  return delta.reduce((newDelta, op) => {
    if (op.attributes && op.attributes[format]) {
      return newDelta.push(op);
    }
    return newDelta.insert(
      op.insert,
      extend({}, { [format]: value }, op.attributes),
    );
  }, new Delta());
}

function deltaEndsWith(delta, text) {
  let endText = '';
  for (
    let i = delta.ops.length - 1;
    i >= 0 && endText.length < text.length;
    --i // eslint-disable-line no-plusplus
  ) {
    const op = delta.ops[i];
    if (typeof op.insert !== 'string') break;
    endText = op.insert + endText;
  }
  return endText.slice(-1 * text.length) === text;
}

function isLine(node) {
  if (node.childNodes.length === 0) return false; // Exclude embed blocks
  return [
    'address',
    'article',
    'blockquote',
    'canvas',
    'dd',
    'div',
    'dl',
    'dt',
    'fieldset',
    'figcaption',
    'figure',
    'footer',
    'form',
    'h1',
    'h2',
    'h3',
    'h4',
    'h5',
    'h6',
    'header',
    'iframe',
    'li',
    'main',
    'nav',
    'ol',
    'output',
    'p',
    'pre',
    'section',
    'table',
    'td',
    'tr',
    'ul',
    'video',
  ].includes(node.tagName.toLowerCase());
}

const preNodes = new WeakMap();
function isPre(node) {
  if (node == null) return false;
  if (!preNodes.has(node)) {
    if (node.tagName === 'PRE') {
      preNodes.set(node, true);
    } else {
      preNodes.set(node, isPre(node.parentNode));
    }
  }
  return preNodes.get(node);
}

function traverse(scroll, node, elementMatchers, textMatchers, nodeMatches) {
  // Post-order
  if (node.nodeType === node.TEXT_NODE) {
    return textMatchers.reduce((delta, matcher) => {
      return matcher(node, delta, scroll);
    }, new Delta());
  } else if (node.nodeType === node.ELEMENT_NODE) {
    return Array.from(node.childNodes || []).reduce((delta, childNode) => {
      let childrenDelta = traverse(
        scroll,
        childNode,
        elementMatchers,
        textMatchers,
        nodeMatches,
      );
      if (childNode.nodeType === node.ELEMENT_NODE) {
        childrenDelta = elementMatchers.reduce((reducedDelta, matcher) => {
          return matcher(childNode, reducedDelta, scroll);
        }, childrenDelta);
        childrenDelta = (nodeMatches.get(childNode) || []).reduce(
          (reducedDelta, matcher) => {
            return matcher(childNode, reducedDelta, scroll);
          },
          childrenDelta,
        );
      }
      return delta.concat(childrenDelta);
    }, new Delta());
  }
  return new Delta();
}

function matchAlias(format, node, delta) {
  return applyFormat(delta, format, true);
}

function matchAttributor(node, delta, scroll) {
  const attributes = Attributor.keys(node);
  const classes = ClassAttributor.keys(node);
  const styles = StyleAttributor.keys(node);
  const formats = {};
  attributes
    .concat(classes)
    .concat(styles)
    .forEach(name => {
      let attr = scroll.query(name, Scope.ATTRIBUTE);
      if (attr != null) {
        formats[attr.attrName] = attr.value(node);
        if (formats[attr.attrName]) return;
      }
      attr = ATTRIBUTE_ATTRIBUTORS[name];
      if (attr != null && (attr.attrName === name || attr.keyName === name)) {
        formats[attr.attrName] = attr.value(node) || undefined;
      }
      attr = STYLE_ATTRIBUTORS[name];
      if (attr != null && (attr.attrName === name || attr.keyName === name)) {
        attr = STYLE_ATTRIBUTORS[name];
        formats[attr.attrName] = attr.value(node) || undefined;
      }
    });
  if (Object.keys(formats).length > 0) {
    return applyFormat(delta, formats);
  }
  return delta;
}

function matchBlot(node, delta, scroll) {
  const match = scroll.query(node);
  if (match == null) return delta;
  if (match.prototype instanceof EmbedBlot) {
    const embed = {};
    const value = match.value(node);
    if (value != null) {
      embed[match.blotName] = value;
      return new Delta().insert(embed, match.formats(node, scroll));
    }
  } else if (typeof match.formats === 'function') {
    return applyFormat(delta, match.blotName, match.formats(node, scroll));
  }
  return delta;
}

function matchBreak(node, delta) {
  if (!deltaEndsWith(delta, '\n')) {
    delta.insert('\n');
  }
  return delta;
}

function matchCodeBlock(node, delta) {
  const language = node.getAttribute('data-language') || true;
  return applyFormat(delta, 'code-block', language);
}

function matchIgnore() {
  return new Delta();
}

function matchIndent(node, delta, scroll) {
  const match = scroll.query(node);
  if (
    match == null ||
    match.blotName !== 'list' ||
    !deltaEndsWith(delta, '\n')
  ) {
    return delta;
  }
  let indent = -1;
  let parent = node.parentNode;
  while (parent != null) {
    if ((scroll.query(parent) || {}).blotName === 'list-container') {
      indent += 1;
    }
    parent = parent.parentNode;
  }
  if (indent <= 0) return delta;
  return delta.compose(
    new Delta().retain(delta.length() - 1).retain(1, { indent }),
  );
}

function matchList(node, delta) {
  const list = node.tagName === 'OL' ? 'ordered' : 'bullet';
  return applyFormat(delta, 'list', list);
}

function matchNewline(node, delta) {
  if (!deltaEndsWith(delta, '\n')) {
    if (
      isLine(node) ||
      (delta.length() > 0 && node.nextSibling && isLine(node.nextSibling))
    ) {
      delta.insert('\n');
    }
  }
  return delta;
}

function matchStyles(node, delta) {
  const formats = {};
  const style = node.style || {};
  if (style.fontStyle === 'italic') {
    formats.italic = true;
  }
  if (
    style.fontWeight.startsWith('bold') ||
    parseInt(style.fontWeight, 10) >= 700
  ) {
    formats.bold = true;
  }
  if (Object.keys(formats).length > 0) {
    delta = applyFormat(delta, formats);
  }
  if (parseFloat(style.textIndent || 0) > 0) {
    // Could be 0.5in
    return new Delta().insert('\t').concat(delta);
  }
  return delta;
}

function matchTable(node, delta) {
  const table =
    node.parentNode.tagName === 'TABLE'
      ? node.parentNode
      : node.parentNode.parentNode;
  const rows = Array.from(table.querySelectorAll('tr'));
  const row = rows.indexOf(node) + 1;
  return applyFormat(delta, 'table', row);
}

function matchText(node, delta) {
  let text = node.data;
  // Word represents empty line with <o:p> </o:p>
  if (node.parentNode.tagName === 'O:P') {
    return delta.insert(text.trim());
  }
  if (text.trim().length === 0 && node.parentNode == null) {
    return delta;
  }
  if (!isPre(node)) {
    const replacer = (collapse, match) => {
      const replaced = match.replace(/[^\u00a0]/g, ''); // \u00a0 is nbsp;
      return replaced.length < 1 && collapse ? ' ' : replaced;
    };
    text = text.replace(/\r\n/g, ' ').replace(/\n/g, ' ');
    text = text.replace(/\s\s+/g, replacer.bind(replacer, true)); // collapse whitespace
    if (
      (node.previousSibling == null && isLine(node.parentNode)) ||
      (node.previousSibling != null && isLine(node.previousSibling))
    ) {
      text = text.replace(/^\s+/, replacer.bind(replacer, false));
    }
    if (
      (node.nextSibling == null && isLine(node.parentNode)) ||
      (node.nextSibling != null && isLine(node.nextSibling))
    ) {
      text = text.replace(/\s+$/, replacer.bind(replacer, false));
    }
  }
  return delta.insert(text);
}

export {
  Clipboard as default,
  matchAttributor,
  matchBlot,
  matchNewline,
  matchText,
};
<commit_msg>avoid pasting code-block: true<commit_after>import extend from 'extend';
import Delta from 'quill-delta';
import {
  Attributor,
  ClassAttributor,
  EmbedBlot,
  Scope,
  StyleAttributor,
} from 'parchment';
import Quill from '../core/quill';
import logger from '../core/logger';
import Module from '../core/module';

import { AlignAttribute, AlignStyle } from '../formats/align';
import { BackgroundStyle } from '../formats/background';
import CodeBlock from '../formats/code';
import { ColorStyle } from '../formats/color';
import { DirectionAttribute, DirectionStyle } from '../formats/direction';
import { FontStyle } from '../formats/font';
import { SizeStyle } from '../formats/size';

const debug = logger('quill:clipboard');

const CLIPBOARD_CONFIG = [
  [Node.TEXT_NODE, matchText],
  [Node.TEXT_NODE, matchNewline],
  ['br', matchBreak],
  [Node.ELEMENT_NODE, matchNewline],
  [Node.ELEMENT_NODE, matchBlot],
  [Node.ELEMENT_NODE, matchAttributor],
  [Node.ELEMENT_NODE, matchStyles],
  ['li', matchIndent],
  ['ol, ul', matchList],
  ['pre', matchCodeBlock],
  ['tr', matchTable],
  ['b', matchAlias.bind(matchAlias, 'bold')],
  ['i', matchAlias.bind(matchAlias, 'italic')],
  ['style', matchIgnore],
];

const ATTRIBUTE_ATTRIBUTORS = [AlignAttribute, DirectionAttribute].reduce(
  (memo, attr) => {
    memo[attr.keyName] = attr;
    return memo;
  },
  {},
);

const STYLE_ATTRIBUTORS = [
  AlignStyle,
  BackgroundStyle,
  ColorStyle,
  DirectionStyle,
  FontStyle,
  SizeStyle,
].reduce((memo, attr) => {
  memo[attr.keyName] = attr;
  return memo;
}, {});

class Clipboard extends Module {
  constructor(quill, options) {
    super(quill, options);
    this.quill.root.addEventListener('copy', this.onCaptureCopy.bind(this));
    this.quill.root.addEventListener('paste', this.onCapturePaste.bind(this));
    this.matchers = [];
    CLIPBOARD_CONFIG.concat(this.options.matchers).forEach(
      ([selector, matcher]) => {
        this.addMatcher(selector, matcher);
      },
    );
  }

  addMatcher(selector, matcher) {
    this.matchers.push([selector, matcher]);
  }

  convert(html) {
    const container = this.quill.root.ownerDocument.createElement('div');
    container.innerHTML = html.replace(/>\r?\n +</g, '><'); // Remove spaces between tags
    const nodeMatches = new WeakMap();
    const [elementMatchers, textMatchers] = this.prepareMatching(
      container,
      nodeMatches,
    );
    let delta = traverse(
      this.quill.scroll,
      container,
      elementMatchers,
      textMatchers,
      nodeMatches,
    );
    // Remove trailing newline
    if (
      deltaEndsWith(delta, '\n') &&
      delta.ops[delta.ops.length - 1].attributes == null
    ) {
      delta = delta.compose(new Delta().retain(delta.length() - 1).delete(1));
    }
    debug.log('convert', container.innerHTML, delta);
    return delta;
  }

  dangerouslyPasteHTML(index, html, source = Quill.sources.API) {
    if (typeof index === 'string') {
      this.quill.setContents(this.convert(index), html);
      this.quill.setSelection(0, Quill.sources.SILENT);
    } else {
      const paste = this.convert(html);
      this.quill.updateContents(
        new Delta().retain(index).concat(paste),
        source,
      );
      this.quill.setSelection(index + paste.length(), Quill.sources.SILENT);
    }
  }

  onCaptureCopy(e) {
    if (e.defaultPrevented) return;
    this.quill.update();
    const [range, native] = this.quill.selection.getRange();
    this.onCopy(e, range, native);
    e.preventDefault();
  }

  onCapturePaste(e) {
    if (e.defaultPrevented || !this.quill.isEnabled()) return;
    const range = this.quill.getSelection(true);
    const files = Array.from(e.clipboardData.files || []);
    if (files.length > 0) {
      this.quill.uploader.upload(range, files);
    } else {
      this.onPaste(e, range);
    }
    e.preventDefault();
  }

  onCopy(e, range, nativeRange) {
    const text = this.quill.getText(range);
    const fragment = nativeRange.native.cloneContents();
    Array.from(fragment.querySelectorAll('select')).forEach(select => {
      select.parentNode.removeChild(select);
    });
    const div = this.quill.root.ownerDocument.createElement('div');
    div.style.whiteSpace = 'pre-wrap';
    div.appendChild(fragment);
    e.clipboardData.setData('text/plain', text);
    e.clipboardData.setData('text/html', div.outerHTML);
  }

  onPaste(e, range) {
    const formats = this.quill.getFormat(this.quill.selection.savedRange.index);
    const html = e.clipboardData.getData('text/html');
    const text = e.clipboardData.getData('text/plain');
    let delta = new Delta().retain(range.index);
    if (formats[CodeBlock.blotName]) {
      delta.insert(text, {
        [CodeBlock.blotName]: formats[CodeBlock.blotName],
      });
    } else if (!html) {
      delta.insert(text);
    } else {
      const pasteDelta = this.convert(html);
      delta = delta.concat(pasteDelta);
    }
    delta.delete(range.length);
    this.quill.updateContents(delta, Quill.sources.USER);
    // range.length contributes to delta.length()
    this.quill.setSelection(
      delta.length() - range.length,
      Quill.sources.SILENT,
    );
    this.quill.scrollIntoView();
  }

  prepareMatching(container, nodeMatches) {
    const elementMatchers = [];
    const textMatchers = [];
    this.matchers.forEach(pair => {
      const [selector, matcher] = pair;
      switch (selector) {
        case Node.TEXT_NODE:
          textMatchers.push(matcher);
          break;
        case Node.ELEMENT_NODE:
          elementMatchers.push(matcher);
          break;
        default:
          Array.from(container.querySelectorAll(selector)).forEach(node => {
            if (nodeMatches.has(node)) {
              const matches = nodeMatches.get(node);
              matches.push(matcher);
            } else {
              nodeMatches.set(node, [matcher]);
            }
          });
          break;
      }
    });
    return [elementMatchers, textMatchers];
  }
}
Clipboard.DEFAULTS = {
  matchers: [],
};

function applyFormat(delta, format, value) {
  if (typeof format === 'object') {
    return Object.keys(format).reduce((newDelta, key) => {
      return applyFormat(newDelta, key, format[key]);
    }, delta);
  }
  return delta.reduce((newDelta, op) => {
    if (op.attributes && op.attributes[format]) {
      return newDelta.push(op);
    }
    return newDelta.insert(
      op.insert,
      extend({}, { [format]: value }, op.attributes),
    );
  }, new Delta());
}

function deltaEndsWith(delta, text) {
  let endText = '';
  for (
    let i = delta.ops.length - 1;
    i >= 0 && endText.length < text.length;
    --i // eslint-disable-line no-plusplus
  ) {
    const op = delta.ops[i];
    if (typeof op.insert !== 'string') break;
    endText = op.insert + endText;
  }
  return endText.slice(-1 * text.length) === text;
}

function isLine(node) {
  if (node.childNodes.length === 0) return false; // Exclude embed blocks
  return [
    'address',
    'article',
    'blockquote',
    'canvas',
    'dd',
    'div',
    'dl',
    'dt',
    'fieldset',
    'figcaption',
    'figure',
    'footer',
    'form',
    'h1',
    'h2',
    'h3',
    'h4',
    'h5',
    'h6',
    'header',
    'iframe',
    'li',
    'main',
    'nav',
    'ol',
    'output',
    'p',
    'pre',
    'section',
    'table',
    'td',
    'tr',
    'ul',
    'video',
  ].includes(node.tagName.toLowerCase());
}

const preNodes = new WeakMap();
function isPre(node) {
  if (node == null) return false;
  if (!preNodes.has(node)) {
    if (node.tagName === 'PRE') {
      preNodes.set(node, true);
    } else {
      preNodes.set(node, isPre(node.parentNode));
    }
  }
  return preNodes.get(node);
}

function traverse(scroll, node, elementMatchers, textMatchers, nodeMatches) {
  // Post-order
  if (node.nodeType === node.TEXT_NODE) {
    return textMatchers.reduce((delta, matcher) => {
      return matcher(node, delta, scroll);
    }, new Delta());
  } else if (node.nodeType === node.ELEMENT_NODE) {
    return Array.from(node.childNodes || []).reduce((delta, childNode) => {
      let childrenDelta = traverse(
        scroll,
        childNode,
        elementMatchers,
        textMatchers,
        nodeMatches,
      );
      if (childNode.nodeType === node.ELEMENT_NODE) {
        childrenDelta = elementMatchers.reduce((reducedDelta, matcher) => {
          return matcher(childNode, reducedDelta, scroll);
        }, childrenDelta);
        childrenDelta = (nodeMatches.get(childNode) || []).reduce(
          (reducedDelta, matcher) => {
            return matcher(childNode, reducedDelta, scroll);
          },
          childrenDelta,
        );
      }
      return delta.concat(childrenDelta);
    }, new Delta());
  }
  return new Delta();
}

function matchAlias(format, node, delta) {
  return applyFormat(delta, format, true);
}

function matchAttributor(node, delta, scroll) {
  const attributes = Attributor.keys(node);
  const classes = ClassAttributor.keys(node);
  const styles = StyleAttributor.keys(node);
  const formats = {};
  attributes
    .concat(classes)
    .concat(styles)
    .forEach(name => {
      let attr = scroll.query(name, Scope.ATTRIBUTE);
      if (attr != null) {
        formats[attr.attrName] = attr.value(node);
        if (formats[attr.attrName]) return;
      }
      attr = ATTRIBUTE_ATTRIBUTORS[name];
      if (attr != null && (attr.attrName === name || attr.keyName === name)) {
        formats[attr.attrName] = attr.value(node) || undefined;
      }
      attr = STYLE_ATTRIBUTORS[name];
      if (attr != null && (attr.attrName === name || attr.keyName === name)) {
        attr = STYLE_ATTRIBUTORS[name];
        formats[attr.attrName] = attr.value(node) || undefined;
      }
    });
  if (Object.keys(formats).length > 0) {
    return applyFormat(delta, formats);
  }
  return delta;
}

function matchBlot(node, delta, scroll) {
  const match = scroll.query(node);
  if (match == null) return delta;
  if (match.prototype instanceof EmbedBlot) {
    const embed = {};
    const value = match.value(node);
    if (value != null) {
      embed[match.blotName] = value;
      return new Delta().insert(embed, match.formats(node, scroll));
    }
  } else if (typeof match.formats === 'function') {
    return applyFormat(delta, match.blotName, match.formats(node, scroll));
  }
  return delta;
}

function matchBreak(node, delta) {
  if (!deltaEndsWith(delta, '\n')) {
    delta.insert('\n');
  }
  return delta;
}

function matchCodeBlock(node, delta, scroll) {
  const match = scroll.query('code-block');
  const language = match ? match.formats(node, scroll) : true;
  return applyFormat(delta, 'code-block', language);
}

function matchIgnore() {
  return new Delta();
}

function matchIndent(node, delta, scroll) {
  const match = scroll.query(node);
  if (
    match == null ||
    match.blotName !== 'list' ||
    !deltaEndsWith(delta, '\n')
  ) {
    return delta;
  }
  let indent = -1;
  let parent = node.parentNode;
  while (parent != null) {
    if ((scroll.query(parent) || {}).blotName === 'list-container') {
      indent += 1;
    }
    parent = parent.parentNode;
  }
  if (indent <= 0) return delta;
  return delta.compose(
    new Delta().retain(delta.length() - 1).retain(1, { indent }),
  );
}

function matchList(node, delta) {
  const list = node.tagName === 'OL' ? 'ordered' : 'bullet';
  return applyFormat(delta, 'list', list);
}

function matchNewline(node, delta) {
  if (!deltaEndsWith(delta, '\n')) {
    if (
      isLine(node) ||
      (delta.length() > 0 && node.nextSibling && isLine(node.nextSibling))
    ) {
      delta.insert('\n');
    }
  }
  return delta;
}

function matchStyles(node, delta) {
  const formats = {};
  const style = node.style || {};
  if (style.fontStyle === 'italic') {
    formats.italic = true;
  }
  if (
    style.fontWeight.startsWith('bold') ||
    parseInt(style.fontWeight, 10) >= 700
  ) {
    formats.bold = true;
  }
  if (Object.keys(formats).length > 0) {
    delta = applyFormat(delta, formats);
  }
  if (parseFloat(style.textIndent || 0) > 0) {
    // Could be 0.5in
    return new Delta().insert('\t').concat(delta);
  }
  return delta;
}

function matchTable(node, delta) {
  const table =
    node.parentNode.tagName === 'TABLE'
      ? node.parentNode
      : node.parentNode.parentNode;
  const rows = Array.from(table.querySelectorAll('tr'));
  const row = rows.indexOf(node) + 1;
  return applyFormat(delta, 'table', row);
}

function matchText(node, delta) {
  let text = node.data;
  // Word represents empty line with <o:p> </o:p>
  if (node.parentNode.tagName === 'O:P') {
    return delta.insert(text.trim());
  }
  if (text.trim().length === 0 && node.parentNode == null) {
    return delta;
  }
  if (!isPre(node)) {
    const replacer = (collapse, match) => {
      const replaced = match.replace(/[^\u00a0]/g, ''); // \u00a0 is nbsp;
      return replaced.length < 1 && collapse ? ' ' : replaced;
    };
    text = text.replace(/\r\n/g, ' ').replace(/\n/g, ' ');
    text = text.replace(/\s\s+/g, replacer.bind(replacer, true)); // collapse whitespace
    if (
      (node.previousSibling == null && isLine(node.parentNode)) ||
      (node.previousSibling != null && isLine(node.previousSibling))
    ) {
      text = text.replace(/^\s+/, replacer.bind(replacer, false));
    }
    if (
      (node.nextSibling == null && isLine(node.parentNode)) ||
      (node.nextSibling != null && isLine(node.nextSibling))
    ) {
      text = text.replace(/\s+$/, replacer.bind(replacer, false));
    }
  }
  return delta.insert(text);
}

export {
  Clipboard as default,
  matchAttributor,
  matchBlot,
  matchNewline,
  matchText,
};
<|endoftext|>


<commit_before>'use strict';

importScripts('/workbox.js');
const workboxSW = new WorkboxSW();

// Static precaching of images
workboxSW.precache([]);

// Register main route for all navigation links to pages
workboxSW.router.registerNavigationRoute('index.html', {
  whitelist: [/./],
  blacklist: [/img\/.*/, /\.(js|css)/]
});

// Cache AMP libraries
workboxSW.router.registerRoute('https://cdn.ampproject.org/(.*)', workboxSW.strategies.staleWhileRevalidate());

// Cache a number of YQL queries, but only for 5 minutes
/*

TODO: Re-enable when workbox fix is deployed..

workboxSW.router.registerRoute(
  'https://query.yahooapis.com/v1/public/(.*)',
  workboxSW.strategies.cacheFirst({
    cacheName: 'feeds',
    cacheExpiration: {
      maxEntries: 20,
      maxAgeSeconds: 5 * 60
    },
    cacheableResponse: {statuses: [ 0, 200 ]}
  })
);

*/

// Cache a number of images
workboxSW.router.registerRoute(
  'https://i.guim.co.uk/img/(.*)',
  workboxSW.strategies.cacheFirst({
    cacheName: 'images',
    cacheExpiration: {
      maxEntries: 10,
      maxAgeSeconds: 7 * 24 * 60 * 60
    },
    cacheableResponse: {statuses: [ 0, 200 ]}
  })
);

// Make sure new versions of the Service Worker activate immediately
self.addEventListener('install', () => {
  self.skipWaiting();
});

self.addEventListener('activate', () => {
  self.clients.claim();
});
<commit_msg>additional caching in sw<commit_after>'use strict';

importScripts('/workbox.js');
const workboxSW = new WorkboxSW({
  clientsClaim: true,
  skipWaiting: true
});

// Static precaching of images
workboxSW.precache([]);

// Register main route for all navigation links to pages
workboxSW.router.registerNavigationRoute('index.html', {
  whitelist: [/./],
  blacklist: [/img\/.*/, /\.(js|css)/]
});

// Cache external libraries and fonts
workboxSW.router.registerRoute('https://cdn.ampproject.org/(.*)', workboxSW.strategies.staleWhileRevalidate());
workboxSW.router.registerRoute('https://cdn.polyfill.io/(.*)', workboxSW.strategies.staleWhileRevalidate());
workboxSW.router.registerRoute('https://pasteup.guim.co.uk/fonts/(.*)', workboxSW.strategies.cacheFirst());

// Cache a number of YQL queries, but only for the offline scenario
workboxSW.router.registerRoute(
  'https://query.yahooapis.com/v1/public/(.*)',
  workboxSW.strategies.networkFirst()
);

// Cache a number of images
workboxSW.router.registerRoute(
  'https://i.guim.co.uk/img/(.*)',
  workboxSW.strategies.cacheFirst({
    cacheName: 'images',
    cacheExpiration: {
      maxEntries: 10,
      maxAgeSeconds: 7 * 24 * 60 * 60
    },
    cacheableResponse: {statuses: [ 0, 200 ]}
  })
);

// Make sure new versions of the Service Worker activate immediately
self.addEventListener('install', () => {
  self.skipWaiting();
});
<|endoftext|>


<commit_before>/**
 * file-util.js
 */
"use strict";
{
  /* api */
  const {URL} = require("url");
  const {
    getType, isString, stringifyPositiveInt, throwErr,
  } = require("./common");
  const fs = require("fs");
  const os = require("os");
  const path = require("path");

  /* constants */
  const CHAR = "utf8";
  const DIR_TMP = os.tmpdir();
  const IS_WIN = os.platform() === "win32";
  const MASK_BIT = 0o111;
  const PERM_FILE = 0o666;
  const PERM_DIR = 0o777;
  const SUBST = "index";

  /**
   * get file name from native file path
   * @param {string} file - file path
   * @param {string} subst - substitute file name
   * @returns {string} - file name
   */
  const getFileNameFromFilePath = (file, subst = SUBST) => {
    const name = isString(file) &&
                   /^([^.]+)(?:\..+)?$/.exec(path.basename(file));
    return name && name[1] || subst;
  };

  /**
   * convert URI to native file path
   * @param {string} uri - URI
   * @returns {Object} - Promise.<?string>, file path
   */
  const convUriToFilePath = async uri => {
    if (!isString(uri)) {
      throw new TypeError(`Expected String but got ${getType(uri)}.`);
    }
    const {protocol, pathname} = new URL(uri);
    let file;
    if (protocol === "file:" && pathname) {
      if (IS_WIN) {
        const arr = pathname.split("/");
        file = arr.reduce((p, c) =>
          p && path.join(p, decodeURIComponent(c)) || decodeURIComponent(c)
        );
      } else {
        file = decodeURIComponent(pathname);
      }
    }
    return file || null;
  };

  /**
   * get stat
   * @param {string} file - file path
   * @returns {Object} - Promise.<Object>, file stat
   */
  const getStat = async file =>
    isString(file) && fs.existsSync(file) && fs.statSync(file) || null;

  /**
   * get stat, sync
   * @param {string} file - file path
   * @returns {Object} - file stat
   */
  const getStatSync = file =>
    isString(file) && fs.existsSync(file) && fs.statSync(file) || null;

  /**
   * the directory is a directory
   * @param {string} dir - directory path
   * @returns {Object} - Promise.<boolean>, result
   */
  const isDir = async dir =>
    getStat(dir).then(stat => stat && stat.isDirectory() || false);

  /**
   * the directory is a directory, sync
   * @param {string} dir - directory path
   * @returns {boolean} - result
   */
  const isDirSync = dir => {
    const stat = getStatSync(dir);
    return stat && stat.isDirectory() || false;
  };

  /**
   * the directory is a subdirectory of a certain directory
   * @param {string} dir - directory path
   * @param {string} baseDir - base directory path
   * @returns {Object} - Promose.<boolean>, result
   */
  const isSubDir = async (dir, baseDir = DIR_TMP) => {
    const arr = await Promise.all([
      isDir(dir),
      isDir(baseDir),
    ]).catch(throwErr);
    return arr.every(i => !!i) && dir.startsWith(baseDir);
  };

  /**
   * the directory is a subdirectory of a certain directory, sync
   * @param {string} dir - directory path
   * @param {string} baseDir - base directory path
   * @returns {boolean} - result
   */
  const isSubDirSync = (dir, baseDir = DIR_TMP) =>
    isDirSync(dir) && isDirSync(baseDir) && dir.startsWith(baseDir);

  /**
   * the file is a file
   * @param {string} file - file path
   * @returns {Object} - Promise.<boolean>, result
   */
  const isFile = async file =>
    getStat(file).then(stat => stat && stat.isFile() || false);

  /**
   * the file is a file, sync
   * @param {string} file - file path
   * @returns {boolean} - result
   */
  const isFileSync = file => {
    const stat = getStatSync(file);
    return stat && stat.isFile() || false;
  };

  /**
   * the file is executable
   * NOTE: On Windows, fs.statSync(file).mode returns 33206 for executable
   * files like `.exe`, which is 100666 in octal.
   * @param {string} file - file path
   * @param {number} mask - mask bit
   * @returns {Object} - Promise.<boolean>, result
   */
  const isExecutable = async (file, mask = MASK_BIT) =>
    getStat(file).then(stat =>
      stat && (
        !!(stat.mode & mask) ||
        IS_WIN && /\.(?:bat|cmd|exe|ps1|wsh)$/i.test(file)
      ) || false
    );

  /**
   * the file is executable, sync
   * NOTE: On Windows, fs.statSync(file).mode returns 33206 for executable
   * files like `.exe`, which is 100666 in octal.
   * @param {string} file - file path
   * @param {number} mask - mask bit
   * @returns {boolean} - result
   */
  const isExecutableSync = (file, mask = MASK_BIT) => {
    const stat = getStatSync(file);
    return stat && (
      !!(stat.mode & mask) || IS_WIN && /\.(?:bat|cmd|exe|ps1|wsh)$/i.test(file)
    ) || false;
  };

  /**
   * get file timestamp
   * @param {string} file - file path
   * @returns {Object} - Promise.<number>, timestamp
   */
  const getFileTimestamp = async file =>
    getStat(file).then(stat => stat && stat.mtime.getTime() || 0);

  /**
   * get file timestamp, sync
   * @param {string} file - file path
   * @returns {number} - timestamp
   */
  const getFileTimestampSync = file => {
    const stat = getStatSync(file);
    return stat && stat.mtime.getTime() || 0;
  };

  /**
   * remove the directory
   * @param {string} dir - directory path
   * @param {string} baseDir - base directory path
   * @returns {void} - Promise.<void>
   */
  const removeDir = async (dir, baseDir = DIR_TMP) => {
    if (await !isSubDir(dir, baseDir)) {
      throw new Error(`${dir} is not a subdirectory of ${baseDir}.`);
    }
    const files = fs.readdirSync(dir);
    const func = [];
    files.length && files.forEach(file => {
      const cur = path.join(dir, file);
      if (fs.lstatSync(cur).isDirectory()) {
        func.push(removeDir(cur, baseDir));
      } else {
        func.push(fs.unlinkSync(cur));
      }
    });
    await Promise.all(func).then(() => fs.rmdirSync(dir)).catch(throwErr);
  };

  /**
   * remove the directory sync
   * @param {string} dir - directory path
   * @param {string} baseDir - base directory path
   * @returns {void}
   */
  const removeDirSync = (dir, baseDir = DIR_TMP) => {
    if (isSubDirSync(dir, baseDir)) {
      const files = fs.readdirSync(dir);
      files.length && files.forEach(file => {
        const cur = path.join(dir, file);
        if (fs.lstatSync(cur).isDirectory()) {
          removeDirSync(cur, baseDir);
        } else {
          fs.unlinkSync(cur);
        }
      });
      fs.rmdirSync(dir);
    }
  };

  /**
   * create a directory
   * @param {Array} arr - directory array
   * @param {string|number} mode - permission
   * @returns {Object} - Promise.<?string>, directory path
   */
  const createDir = async (arr, mode = PERM_DIR) => {
    if (!Array.isArray(arr)) {
      throw new TypeError(`Expected Array but got ${getType(arr)}.`);
    }
    const dir = arr.length && arr.reduce((p, c) => {
      let d;
      p = isString(p) && p || stringifyPositiveInt(p, true);
      if (p) {
        const v = isString(c) && c || stringifyPositiveInt(c, true);
        d = v && path.join(p, v) || p;
        !fs.existsSync(d) && fs.mkdirSync(d, mode);
      }
      return d;
    });
    return isDirSync(dir) && dir || null;
  };

  /**
   * create a file
   * @param {string} file - file path
   * @param {string|Buffer|Uint8Array} value - value to write
   * @param {Object} opt - option
   * @param {string} [opt.encoding] - encoding, note that default is not `null`
   * @param {string} [opt.flag] - flag
   * @param {number|string} [opt.mode] - file permission
   * @returns {Object} - Promise.<?string>, file path
   */
  const createFile = async (
    file, value, opt = {encoding: CHAR, flag: "w", mode: PERM_FILE}
  ) => {
    if (!isString(file)) {
      throw new TypeError(`Expected String but got ${getType(file)}.`);
    }
    if (!isString(value) && !Buffer.isBuffer(value) &&
        !(value instanceof Uint8Array)) {
      throw new TypeError(
        `Expected String, Buffer, Uint8Array but got ${getType(value)}.`
      );
    }
    await fs.writeFileSync(file, value, opt);
    return isFileSync(file) && file || null;
  };

  /**
   * read a file
   * @param {string} file - file path
   * @param {Object} opt - option
   * @param {string} [opt.encoding] - encoding, note that default is not `null`
   * @param {string} [opt.flag] - flag
   * @returns {Object} - Promise.<string|Buffer>, file content
   */
  const readFile = async (file, opt = {encoding: CHAR, flag: "r"}) => {
    if (await !isFile(file)) {
      throw new Error(`${file} is not a file.`);
    }
    const value = await fs.readFileSync(file, opt);
    return value;
  };

  module.exports = {
    convUriToFilePath, createDir, createFile, getFileNameFromFilePath,
    getFileTimestamp, getFileTimestampSync, getStat, getStatSync,
    isDir, isDirSync, isExecutable, isExecutableSync, isFile, isFileSync,
    isSubDir, isSubDirSync, removeDir, removeDirSync, readFile,
  };
}
<commit_msg>Remove async functions<commit_after>/**
 * file-util.js
 */
"use strict";
{
  /* api */
  const {URL} = require("url");
  const {getType, isString, stringifyPositiveInt} = require("./common");
  const fs = require("fs");
  const os = require("os");
  const path = require("path");

  /* constants */
  const CHAR = "utf8";
  const DIR_TMP = os.tmpdir();
  const IS_WIN = os.platform() === "win32";
  const MASK_BIT = 0o111;
  const PERM_FILE = 0o666;
  const PERM_DIR = 0o777;
  const SUBST = "index";

  /**
   * get file name from native file path
   * @param {string} file - file path
   * @param {string} subst - substitute file name
   * @returns {string} - file name
   */
  const getFileNameFromFilePath = (file, subst = SUBST) => {
    const name = isString(file) &&
                   /^([^.]+)(?:\..+)?$/.exec(path.basename(file));
    return name && name[1] || subst;
  };

  /**
   * convert URI to native file path
   * @param {string} uri - URI
   * @returns {?string} - file path
   */
  const convUriToFilePath = uri => {
    if (!isString(uri)) {
      throw new TypeError(`Expected String but got ${getType(uri)}.`);
    }
    const {protocol, pathname} = new URL(uri);
    let file;
    if (protocol === "file:" && pathname) {
      if (IS_WIN) {
        const arr = pathname.split("/");
        file = arr.reduce((p, c) =>
          p && path.join(p, decodeURIComponent(c)) || decodeURIComponent(c)
        );
      } else {
        file = decodeURIComponent(pathname);
      }
    }
    return file || null;
  };

  /**
   * get stat
   * @param {string} file - file path
   * @returns {Object} - file stat
   */
  const getStat = file =>
    isString(file) && fs.existsSync(file) && fs.statSync(file) || null;

  /**
   * the directory is a directory
   * @param {string} dir - directory path
   * @returns {boolean} - result
   */
  const isDir = dir => {
    const stat = getStat(dir);
    return stat && stat.isDirectory() || false;
  };

  /**
   * the directory is a subdirectory of a certain directory
   * @param {string} dir - directory path
   * @param {string} baseDir - base directory path
   * @returns {boolean} - result
   */
  const isSubDir = (dir, baseDir = DIR_TMP) =>
    isDir(dir) && isDir(baseDir) && dir.startsWith(baseDir);

  /**
   * the file is a file
   * @param {string} file - file path
   * @returns {boolean} - result
   */
  const isFile = file => {
    const stat = getStat(file);
    return stat && stat.isFile() || false;
  };

  /**
   * the file is executable
   * NOTE: On Windows, fs.statSync(file).mode returns 33206 for executable
   * files like `.exe`, which is 100666 in octal.
   * @param {string} file - file path
   * @param {number} mask - mask bit
   * @returns {boolean} - result
   */
  const isExecutable = (file, mask = MASK_BIT) => {
    const stat = getStat(file);
    return stat && (
      !!(stat.mode & mask) || IS_WIN && /\.(?:bat|cmd|exe|ps1|wsh)$/i.test(file)
    ) || false;
  };

  /**
   * get file timestamp
   * @param {string} file - file path
   * @returns {number} - timestamp
   */
  const getFileTimestamp = file => {
    const stat = getStat(file);
    return stat && stat.mtime.getTime() || 0;
  };

  /**
   * remove the directory
   * @param {string} dir - directory path
   * @param {string} baseDir - base directory path
   * @returns {void}
   */
  const removeDir = (dir, baseDir = DIR_TMP) => {
    if (!isSubDir(dir, baseDir)) {
      throw new Error(`${dir} is not a subdirectory of ${baseDir}.`);
    }
    const files = fs.readdirSync(dir);
    const func = [];
    files.length && files.forEach(file => {
      const cur = path.join(dir, file);
      if (fs.lstatSync(cur).isDirectory()) {
        func.push(removeDir(cur, baseDir));
      } else {
        func.push(fs.unlinkSync(cur));
      }
    });
    fs.rmdirSync(dir);
  };

  /**
   * create a directory
   * @param {Array} arr - directory array
   * @param {string|number} mode - permission
   * @returns {?string} - directory path
   */
  const createDir = (arr, mode = PERM_DIR) => {
    if (!Array.isArray(arr)) {
      throw new TypeError(`Expected Array but got ${getType(arr)}.`);
    }
    const dir = arr.length && arr.reduce((p, c) => {
      let d;
      p = isString(p) && p || stringifyPositiveInt(p, true);
      if (p) {
        const v = isString(c) && c || stringifyPositiveInt(c, true);
        d = v && path.join(p, v) || p;
        !fs.existsSync(d) && fs.mkdirSync(d, mode);
      }
      return d;
    });
    return isDir(dir) && dir || null;
  };

  /**
   * create a file
   * @param {string} file - file path
   * @param {string|Buffer|Uint8Array} value - value to write
   * @param {Object} opt - option
   * @param {string} [opt.encoding] - encoding, note that default is not `null`
   * @param {string} [opt.flag] - flag
   * @param {number|string} [opt.mode] - file permission
   * @returns {?string} - file path
   */
  const createFile = (
    file, value, opt = {encoding: CHAR, flag: "w", mode: PERM_FILE}
  ) => {
    if (!isString(file)) {
      throw new TypeError(`Expected String but got ${getType(file)}.`);
    }
    if (!isString(value) && !Buffer.isBuffer(value) &&
        !(value instanceof Uint8Array)) {
      throw new TypeError(
        `Expected String, Buffer, Uint8Array but got ${getType(value)}.`
      );
    }
    fs.writeFileSync(file, value, opt);
    return isFile(file) && file || null;
  };

  /**
   * read a file
   * @param {string} file - file path
   * @param {Object} opt - option
   * @param {string} [opt.encoding] - encoding, note that default is not `null`
   * @param {string} [opt.flag] - flag
   * @returns {string|Buffer} - file content
   */
  const readFile = (file, opt = {encoding: CHAR, flag: "r"}) => {
    if (!isFile(file)) {
      throw new Error(`${file} is not a file.`);
    }
    const value = fs.readFileSync(file, opt);
    return value;
  };

  module.exports = {
    convUriToFilePath, createDir, createFile, getFileNameFromFilePath,
    getFileTimestamp, getStat, isDir, isExecutable, isFile, isSubDir,
    removeDir, readFile,
  };
}
<|endoftext|>


<commit_before>/**
 * file-util.js
 */
"use strict";
{
  /* api */
  const {URL} = require("url");
  const {getType, isString, stringifyPositiveInt} = require("./common");
  const fs = require("fs");
  const os = require("os");
  const path = require("path");

  /* constants */
  const CHAR = "utf8";
  const DIR_TMP = os.tmpdir();
  const IS_WIN = os.platform() === "win32";
  const MASK_BIT = 0o111;
  const PERM_FILE = 0o666;
  const PERM_DIR = 0o777;
  const SUBST = "index";

  /**
   * get file name from native file path
   * @param {string} file - file path
   * @param {string} subst - substitute file name
   * @returns {string} - file name
   */
  const getFileNameFromFilePath = (file, subst = SUBST) => {
    const name = isString(file) &&
                   /^([^.]+)(?:\..+)?$/.exec(path.basename(file));
    return name && name[1] || subst;
  };

  /**
   * convert URI to native file path
   * @param {string} uri - URI
   * @returns {Object} - Promise.<?string>, file path
   */
  const convUriToFilePath = async uri => {
    if (!isString(uri)) {
      throw new TypeError(`Expected String but got ${getType(uri)}.`);
    }
    const {protocol, pathname} = new URL(uri);
    let file;
    if (protocol === "file:" && pathname) {
      if (IS_WIN) {
        const arr = pathname.split("/");
        file = arr.reduce((p, c) =>
          p && path.join(p, decodeURIComponent(c)) || decodeURIComponent(c)
        );
      } else {
        file = decodeURIComponent(pathname);
      }
    }
    return file || null;
  };

  /**
   * get stat
   * @param {string} file - file path
   * @returns {Object} - Promise.<Object>, file stat
   */
  const getStat = async file =>
    isString(file) && fs.existsSync(file) && fs.statSync(file) || null;

  /**
   * get stat, sync
   * @param {string} file - file path
   * @returns {Object} - file stat
   */
  const getStatSync = file =>
    isString(file) && fs.existsSync(file) && fs.statSync(file) || null;

  /**
   * the directory is a directory
   * @param {string} dir - directory path
   * @returns {Object} - Promise.<boolean>, result
   */
  const isDir = async dir =>
    getStat(dir).then(stat => stat && stat.isDirectory() || false);

  /**
   * the directory is a directory, sync
   * @param {string} dir - directory path
   * @returns {boolean} - result
   */
  const isDirSync = dir => {
    const stat = getStatSync(dir);
    return stat && stat.isDirectory() || false;
  };

  /**
   * the directory is a subdirectory of a certain directory
   * @param {string} dir - directory path
   * @param {string} baseDir - base directory path
   * @returns {Object} - Promose.<boolean>, result
   */
  const isSubDir = async (dir, baseDir = DIR_TMP) => {
    const arr = await Promise.all([
      isDir(dir),
      isDir(baseDir),
    ]);
    return arr.every(i => !!i) && dir.startsWith(baseDir);
  };

  /**
   * the directory is a subdirectory of a certain directory, sync
   * @param {string} dir - directory path
   * @param {string} baseDir - base directory path
   * @returns {boolean} - result
   */
  const isSubDirSync = (dir, baseDir = DIR_TMP) =>
    isDirSync(dir) && isDirSync(baseDir) && dir.startsWith(baseDir);

  /**
   * the file is a file
   * @param {string} file - file path
   * @returns {Object} - Promise.<boolean>, result
   */
  const isFile = async file =>
    getStat(file).then(stat => stat && stat.isFile() || false);

  /**
   * the file is a file, sync
   * @param {string} file - file path
   * @returns {boolean} - result
   */
  const isFileSync = file => {
    const stat = getStatSync(file);
    return stat && stat.isFile() || false;
  };

  /**
   * the file is executable
   * NOTE: On Windows, fs.statSync(file).mode returns 33206 for executable
   * files like `.exe`, which is 100666 in octal.
   * @param {string} file - file path
   * @param {number} mask - mask bit
   * @returns {Object} - Promise.<boolean>, result
   */
  const isExecutable = async (file, mask = MASK_BIT) =>
    getStat(file).then(stat =>
      stat && (
        !!(stat.mode & mask) ||
        IS_WIN && /\.(?:bat|cmd|exe|ps1|wsh)$/i.test(file)
      ) || false
    );

  /**
   * the file is executable, sync
   * NOTE: On Windows, fs.statSync(file).mode returns 33206 for executable
   * files like `.exe`, which is 100666 in octal.
   * @param {string} file - file path
   * @param {number} mask - mask bit
   * @returns {boolean} - result
   */
  const isExecutableSync = (file, mask = MASK_BIT) => {
    const stat = getStatSync(file);
    return stat && (
      !!(stat.mode & mask) || IS_WIN && /\.(?:bat|cmd|exe|ps1|wsh)$/i.test(file)
    ) || false;
  };

  /**
   * get file timestamp
   * @param {string} file - file path
   * @returns {Object} - Promise.<number>, timestamp
   */
  const getFileTimestamp = async file =>
    getStat(file).then(stat => stat && stat.mtime.getTime() || 0);

  /**
   * get file timestamp, sync
   * @param {string} file - file path
   * @returns {number} - timestamp
   */
  const getFileTimestampSync = file => {
    const stat = getStatSync(file);
    return stat && stat.mtime.getTime() || 0;
  };

  /**
   * remove the directory
   * @param {string} dir - directory path
   * @param {string} baseDir - base directory path
   * @returns {Object} - Promise.<Function>
   */
  const removeDir = async (dir, baseDir = DIR_TMP) => {
    if (await !isSubDir(dir, baseDir)) {
      throw new Error(`${dir} is not a subdirectory of ${baseDir}.`);
    }
    const files = fs.readdirSync(dir);
    const func = [];
    files.length && files.forEach(file => {
      const cur = path.join(dir, file);
      if (fs.lstatSync(cur).isDirectory()) {
        func.push(removeDir(cur, baseDir));
      } else {
        func.push(fs.unlinkSync(cur));
      }
    });
    await Promise.all(func);
    return fs.rmdirSync(dir);
  };

  /**
   * remove the directory sync
   * @param {string} dir - directory path
   * @param {string} baseDir - base directory path
   * @returns {void}
   */
  const removeDirSync = (dir, baseDir = DIR_TMP) => {
    if (isSubDirSync(dir, baseDir)) {
      const files = fs.readdirSync(dir);
      files.length && files.forEach(file => {
        const cur = path.join(dir, file);
        if (fs.lstatSync(cur).isDirectory()) {
          removeDirSync(cur, baseDir);
        } else {
          fs.unlinkSync(cur);
        }
      });
      fs.rmdirSync(dir);
    }
  };

  /**
   * create a directory
   * @param {Array} arr - directory array
   * @param {string|number} mode - permission
   * @returns {Object} - Promise.<?string>, directory path
   */
  const createDir = async (arr, mode = PERM_DIR) => {
    if (!Array.isArray(arr)) {
      throw new TypeError(`Expected Array but got ${getType(arr)}.`);
    }
    const dir = arr.length && arr.reduce((p, c) => {
      let d;
      p = isString(p) && p || stringifyPositiveInt(p, true);
      if (p) {
        const v = isString(c) && c || stringifyPositiveInt(c, true);
        d = v && path.join(p, v) || p;
        !fs.existsSync(d) && fs.mkdirSync(d, mode);
      }
      return d;
    });
    return isDir(dir) && dir || null;
  };

  /**
   * create a file
   * @param {string} file - file path
   * @param {string|Buffer|Uint8Array} value - value to write
   * @param {Object} opt - option
   * @param {string} [opt.encoding] - encoding, note that default is not `null`
   * @param {string} [opt.flag] - flag
   * @param {number|string} [opt.mode] - file permission
   * @returns {Object} - Promise.<?string>, file path
   */
  const createFile = async (
    file, value, opt = {encoding: CHAR, flag: "w", mode: PERM_FILE}
  ) => {
    if (!isString(file)) {
      throw new TypeError(`Expected String but got ${getType(file)}.`);
    }
    if (!isString(value) && !Buffer.isBuffer(value) &&
        !(value instanceof Uint8Array)) {
      throw new TypeError(
        `Expected String, Buffer, Uint8Array but got ${getType(value)}.`
      );
    }
    await fs.writeFileSync(file, value, opt);
    return isFile(file) && file || null;
  };

  /**
   * read a file
   * @param {string} file - file path
   * @param {Object} opt - option
   * @param {string} [opt.encoding] - encoding, note that default is not `null`
   * @param {string} [opt.flag] - flag
   * @returns {Object} - Promise.<string|Buffer>, file content
   */
  const readFile = async (file, opt = {encoding: CHAR, flag: "r"}) => {
    if (await !isFile(file)) {
      throw new Error(`${file} is not a file.`);
    }
    const value = await fs.readFileSync(file, opt);
    return value;
  };

  module.exports = {
    convUriToFilePath, createDir, createFile, getFileNameFromFilePath,
    getFileTimestamp, getStat, isDir, isExecutable, isFile, isSubDir, removeDir,
    removeDirSync, readFile,
  };
}
<commit_msg>Refactor createFile()<commit_after>/**
 * file-util.js
 */
"use strict";
{
  /* api */
  const {URL} = require("url");
  const {getType, isString, stringifyPositiveInt} = require("./common");
  const fs = require("fs");
  const os = require("os");
  const path = require("path");

  /* constants */
  const CHAR = "utf8";
  const DIR_TMP = os.tmpdir();
  const IS_WIN = os.platform() === "win32";
  const MASK_BIT = 0o111;
  const PERM_FILE = 0o666;
  const PERM_DIR = 0o777;
  const SUBST = "index";

  /**
   * get file name from native file path
   * @param {string} file - file path
   * @param {string} subst - substitute file name
   * @returns {string} - file name
   */
  const getFileNameFromFilePath = (file, subst = SUBST) => {
    const name = isString(file) &&
                   /^([^.]+)(?:\..+)?$/.exec(path.basename(file));
    return name && name[1] || subst;
  };

  /**
   * convert URI to native file path
   * @param {string} uri - URI
   * @returns {Object} - Promise.<?string>, file path
   */
  const convUriToFilePath = async uri => {
    if (!isString(uri)) {
      throw new TypeError(`Expected String but got ${getType(uri)}.`);
    }
    const {protocol, pathname} = new URL(uri);
    let file;
    if (protocol === "file:" && pathname) {
      if (IS_WIN) {
        const arr = pathname.split("/");
        file = arr.reduce((p, c) =>
          p && path.join(p, decodeURIComponent(c)) || decodeURIComponent(c)
        );
      } else {
        file = decodeURIComponent(pathname);
      }
    }
    return file || null;
  };

  /**
   * get stat
   * @param {string} file - file path
   * @returns {Object} - Promise.<Object>, file stat
   */
  const getStat = async file =>
    isString(file) && fs.existsSync(file) && fs.statSync(file) || null;

  /**
   * get stat, sync
   * @param {string} file - file path
   * @returns {Object} - file stat
   */
  const getStatSync = file =>
    isString(file) && fs.existsSync(file) && fs.statSync(file) || null;

  /**
   * the directory is a directory
   * @param {string} dir - directory path
   * @returns {Object} - Promise.<boolean>, result
   */
  const isDir = async dir =>
    getStat(dir).then(stat => stat && stat.isDirectory() || false);

  /**
   * the directory is a directory, sync
   * @param {string} dir - directory path
   * @returns {boolean} - result
   */
  const isDirSync = dir => {
    const stat = getStatSync(dir);
    return stat && stat.isDirectory() || false;
  };

  /**
   * the directory is a subdirectory of a certain directory
   * @param {string} dir - directory path
   * @param {string} baseDir - base directory path
   * @returns {Object} - Promose.<boolean>, result
   */
  const isSubDir = async (dir, baseDir = DIR_TMP) => {
    const arr = await Promise.all([
      isDir(dir),
      isDir(baseDir),
    ]);
    return arr.every(i => !!i) && dir.startsWith(baseDir);
  };

  /**
   * the directory is a subdirectory of a certain directory, sync
   * @param {string} dir - directory path
   * @param {string} baseDir - base directory path
   * @returns {boolean} - result
   */
  const isSubDirSync = (dir, baseDir = DIR_TMP) =>
    isDirSync(dir) && isDirSync(baseDir) && dir.startsWith(baseDir);

  /**
   * the file is a file
   * @param {string} file - file path
   * @returns {Object} - Promise.<boolean>, result
   */
  const isFile = async file =>
    getStat(file).then(stat => stat && stat.isFile() || false);

  /**
   * the file is a file, sync
   * @param {string} file - file path
   * @returns {boolean} - result
   */
  const isFileSync = file => {
    const stat = getStatSync(file);
    return stat && stat.isFile() || false;
  };

  /**
   * the file is executable
   * NOTE: On Windows, fs.statSync(file).mode returns 33206 for executable
   * files like `.exe`, which is 100666 in octal.
   * @param {string} file - file path
   * @param {number} mask - mask bit
   * @returns {Object} - Promise.<boolean>, result
   */
  const isExecutable = async (file, mask = MASK_BIT) =>
    getStat(file).then(stat =>
      stat && (
        !!(stat.mode & mask) ||
        IS_WIN && /\.(?:bat|cmd|exe|ps1|wsh)$/i.test(file)
      ) || false
    );

  /**
   * the file is executable, sync
   * NOTE: On Windows, fs.statSync(file).mode returns 33206 for executable
   * files like `.exe`, which is 100666 in octal.
   * @param {string} file - file path
   * @param {number} mask - mask bit
   * @returns {boolean} - result
   */
  const isExecutableSync = (file, mask = MASK_BIT) => {
    const stat = getStatSync(file);
    return stat && (
      !!(stat.mode & mask) || IS_WIN && /\.(?:bat|cmd|exe|ps1|wsh)$/i.test(file)
    ) || false;
  };

  /**
   * get file timestamp
   * @param {string} file - file path
   * @returns {Object} - Promise.<number>, timestamp
   */
  const getFileTimestamp = async file =>
    getStat(file).then(stat => stat && stat.mtime.getTime() || 0);

  /**
   * get file timestamp, sync
   * @param {string} file - file path
   * @returns {number} - timestamp
   */
  const getFileTimestampSync = file => {
    const stat = getStatSync(file);
    return stat && stat.mtime.getTime() || 0;
  };

  /**
   * remove the directory
   * @param {string} dir - directory path
   * @param {string} baseDir - base directory path
   * @returns {Object} - Promise.<Function>
   */
  const removeDir = async (dir, baseDir = DIR_TMP) => {
    if (await !isSubDir(dir, baseDir)) {
      throw new Error(`${dir} is not a subdirectory of ${baseDir}.`);
    }
    const files = fs.readdirSync(dir);
    const func = [];
    files.length && files.forEach(file => {
      const cur = path.join(dir, file);
      if (fs.lstatSync(cur).isDirectory()) {
        func.push(removeDir(cur, baseDir));
      } else {
        func.push(fs.unlinkSync(cur));
      }
    });
    await Promise.all(func);
    return fs.rmdirSync(dir);
  };

  /**
   * remove the directory sync
   * @param {string} dir - directory path
   * @param {string} baseDir - base directory path
   * @returns {void}
   */
  const removeDirSync = (dir, baseDir = DIR_TMP) => {
    if (isSubDirSync(dir, baseDir)) {
      const files = fs.readdirSync(dir);
      files.length && files.forEach(file => {
        const cur = path.join(dir, file);
        if (fs.lstatSync(cur).isDirectory()) {
          removeDirSync(cur, baseDir);
        } else {
          fs.unlinkSync(cur);
        }
      });
      fs.rmdirSync(dir);
    }
  };

  /**
   * create a directory
   * @param {Array} arr - directory array
   * @param {string|number} mode - permission
   * @returns {Object} - Promise.<?string>, directory path
   */
  const createDir = async (arr, mode = PERM_DIR) => {
    if (!Array.isArray(arr)) {
      throw new TypeError(`Expected Array but got ${getType(arr)}.`);
    }
    const dir = arr.length && arr.reduce((p, c) => {
      let d;
      p = isString(p) && p || stringifyPositiveInt(p, true);
      if (p) {
        const v = isString(c) && c || stringifyPositiveInt(c, true);
        d = v && path.join(p, v) || p;
        !fs.existsSync(d) && fs.mkdirSync(d, mode);
      }
      return d;
    });
    return isDir(dir) && dir || null;
  };

  /**
   * create a file
   * @param {string} file - file path
   * @param {string|Buffer|Uint8Array} value - value to write
   * @param {Object} opt - option
   * @param {string} [opt.encoding] - encoding, note that default is not `null`
   * @param {string} [opt.flag] - flag
   * @param {number|string} [opt.mode] - file permission
   * @returns {Object} - Promise.<?string>, file path
   */
  const createFile = async (
    file, value, opt = {encoding: CHAR, flag: "w", mode: PERM_FILE}
  ) => {
    let res;
    if (!isString(file)) {
      throw new TypeError(`Expected String but got ${getType(file)}.`);
    }
    if (!isString(value) && !Buffer.isBuffer(value) &&
        !(value instanceof Uint8Array)) {
      throw new TypeError(
        `Expected String, Buffer, Uint8Array but got ${getType(value)}.`
      );
    }
    await fs.writeFileSync(file, value, opt);
    await isFile(file) && (res = file);
    return res || null;
  };

  /**
   * read a file
   * @param {string} file - file path
   * @param {Object} opt - option
   * @param {string} [opt.encoding] - encoding, note that default is not `null`
   * @param {string} [opt.flag] - flag
   * @returns {Object} - Promise.<string|Buffer>, file content
   */
  const readFile = async (file, opt = {encoding: CHAR, flag: "r"}) => {
    if (await !isFile(file)) {
      throw new Error(`${file} is not a file.`);
    }
    const value = await fs.readFileSync(file, opt);
    return value;
  };

  module.exports = {
    convUriToFilePath, createDir, createFile, getFileNameFromFilePath,
    getFileTimestamp, getStat, isDir, isExecutable, isFile, isSubDir, removeDir,
    removeDirSync, readFile,
  };
}
<|endoftext|>


<commit_before>// Invasion Status -- ToonHQ Clone
// Created by @gaarb

// Modules

var request = require('request');

// Variables

var InvasionURL = "https://toontownrewritten.com/api/invasions"; // URL from Toontown Rewritten Data
var InvasionData = []; // Array for Invasion Data

// Router Functions

module.exports = {
  sendInvasionList: function (req, res) {
    grabInvasionList();
    res.send(InvasionData);
  }
};

// Normal Functions

function grabInvasionList() {
  request({
    url: InvasionURL,
    json: true
  }, function (error, response, body) {
    if (!error && response.statusCode == 200) {
      InvasionData = body;
    }
  });
}

console.log('[Invasions] No errors are detected. Continuing...');
<commit_msg>Finished Invaions.<commit_after>// Invasion Status -- ToonHQ Clone
// Created by @gaarb

// Modules

var request = require('request');
var looper = require('infinite-loop');

// Setups

var loop = new looper;

// Variables

var InvasionURL = "https://toontownrewritten.com/api/invasions"; // URL from Toontown Rewritten Data
var InvasionData = []; // Array for Invasion Data

// Router Functions

module.exports = {
  sendInvasionList: function (req, res) {
    res.send(InvasionData);
  }
};

// Normal Functions

function grabInvasionList() {
  request({
    url: InvasionURL,
    json: true
  }, function (error, response, body) {
    if (!error && response.statusCode == 200) {
      InvasionData = body;
    }
  });
}

// Looping functions -- I don't want to depend on the front-end pinging the server incase it breaks, plus others might like to use it.

loop.add(grabInvasionList, []); // Adding the function 'grabInvasionList' to the looper.
loop.setInterval(5000); // Every 5 seconds it'll update the list.

loop.run(); // Finally running the function.

// Finalise

console.log('[Invasions] No errors are detected. Continuing...');
<|endoftext|>