<commit_before>/**
 * dilated: utils.js
 * Copyright (c) 2011-2014, Christopher Jeffrey (MIT License)
 */

/**
 * Utilities
 */

var crypto = require('crypto');

exports.hash = function(str) {
  return crypto
    .createHash('sha256')
    .update(str)
    .digest('hex');
};

exports.escapeHTML = function(html, once) {
  return (html || '')
    .replace(once ? /&(?![^\s;]+;)/g : /&/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/"/g, '"')
    .replace(/'/g, ''');
};

/**
 * Time Functions
 */

exports.date = function(date) {
  if (!date.getUTCFullYear) {
    date = new Date(date);
  }

  date = date
    .toLocaleDateString()
    .split(/,\s+|\s+/);

  date = {
    day: date[0],
    month: date[1],
    date: date[2],
    year: date[3]
  };

  return date.month
    + ' ' + date.date
    + ', ' + date.year;
};

exports.time = function(date) {
  if (!date.getUTCFullYear) {
    date = new Date(date);
  }

  var hours = +date.getHours()
    , minutes = date.getMinutes().toString()
    , meridiem = hours < 12 ? 'am' : 'pm';

  if (hours === 0) hours = 12;
  if (hours > 12) hours -= 12;
  if (minutes.length < 2) minutes = '0' + '' + minutes;
  return hours + ':' + minutes + ' ' + meridiem;
};

exports.datetime = function(date) {
  return exports.date(date)
    + ' ' + exports.time(date);
};

exports.prettyTime = function(time) {
  var date = time.getUTCFullYear ? time : new Date(time)
    , sec = Math.floor(new Date(Date.now() - date) / 1000)
    , days = Math.floor(sec / 86400);

  if (days === 0) {
    if (sec <= 1) {
      return '1 second ago';
    }
    if (sec < 60) {
      return sec + ' seconds ago';
    }
    if (sec < 120) {
      return '1 minute ago';
    }
    if (sec < 3600) {
      return Math.floor(sec / 60)
             + ' minutes ago';
    }
    if (sec < 7200) {
      return '1 hour ago';
    }
    return Math.floor(sec / 3600)
           + ' hours ago';
  }

  if (days < 31) {
    if (days === 1) {
      return 'Yesterday';
    }
    if (days < 14) {
      return days + ' days ago';
    }
    return Math.floor(days / 7)
           + ' weeks ago';
  }

  if (days >= 31) {
    var months = Math.floor(days / 31);
    if (months === 1) {
      return '1 month ago';
    }
    if (months >= 12) {
      var years = Math.floor(months / 12);
      if (years === 1) {
        return '1 year ago';
      }
      return years + ' years ago';
    }
    return months + ' months ago';
  }
};

/**
 * Markdown
 */

exports.markdown = (function() {
  var marked = require('marked');
  return function(text) {
    return marked(text);
  };
})();

/**
 * Pretty print HTML
 */

exports.pretty = (function() {
  var indent = function(num) {
    return Array(num + 1).join('  ');
  };

  var closing = {
    base: true,
    link: true,
    meta: true,
    hr: true,
    br: true,
    wbr: true,
    img: true,
    embed: true,
    param: true,
    source: true,
    track: true,
    area: true,
    col: true,
    input: true,
    keygen: true,
    command: true
  };

  var remove = /<(pre|textarea|title|p|li|a)(?:\s[^>]+)?>[\s\S]+?<\/\1>/g
    , replace = /<!(\d+)%*\/>/g
    , wrap = /([ \t]*)<p>([\s\S]+?)<\/p>/g;

  return function(str) {
    var hash = []
      , out = []
      , cap
      , depth = 0
      , text
      , full
      , tag
      , name;

    // temporarily remove elements before
    // processing, also remove whitespace
    str = str.replace(remove, function(element, name) {
      if (name === 'pre' || name === 'textarea') {
        element = element.replace(/\r?\n/g, '
');
      } else {
        element = element
          .replace(/(<[^\/][^>]*>)\s+|\s+(<\/)/g, '$1$2')
          .replace(/[\r\n]/g, '');
      }
      return '<!' + (hash.push(element) - 1)
                  + (Array(element.length - 3).join('%')) + '/>';
    });

    // indent elements
    str = str
      .replace(/(>)\s+|\s+(<)/g, '$1$2')
      .replace(/[\r\n]/g, '');

    while (cap = /^([^\0]*?)(<([^>]+)>)/.exec(str)) {
      str = str.substring(cap[0].length);
      text = cap[1];
      full = cap[2];
      tag = cap[3];
      name = tag.split(' ')[0];

      if (text) {
        out.push(indent(depth) + text);
      }

      if (name[0] !== '/') {
        out.push(indent(depth) + full);
        if (!closing[name]
            && name[0] !== '!'
            && name[0] !== '?'
            && tag[tag.length-1] !== '/') {
          depth++;
        }
      } else {
        depth--;
        out.push(indent(depth) + full);
      }
    }
    str = out.join('\n');

    // restore the elements to
    // their original locations
    str = str.replace(replace, function($0, $1) {
      return hash[$1];
    });

    // wrap paragraphs
    str = str.replace(wrap, function($0, $1, $2) {
      var indent = $1 + '  '
        , text = indent + $2;

      text = text
        .replace(/[\t\r\n]+/g, '')
        .replace(/(<\/[^>]+>|\/>)(?=\s*<\w)/g, '$1\n' + indent)
        .replace(/(.{75,}?\s+(?![^<]+>))/g, '$1\n' + indent)
        .replace(/([^<>\n]{50,}?)(<[^<]{15,}>)/g, '$1\n' + indent + '$2');

      return $1 + '<p>\n' + text + '\n' + $1 + '</p>';
    });

    return str;
  };
})();

exports.pretty.handle = function(req, res, next) {
  var send = res.send;
  res.send = function(data) {
    res.send = send;
    var type = res.getHeader('Content-Type') || '';
    if ((!type || /html|xml/i.test(type))
        && typeof data === 'string') {
      arguments[0] = data =
        exports.pretty(data);
    }
    return send.apply(res, arguments);
  };
  next();
};

exports.forEach = function(obj, iter, done) {
  var pending = obj.length
    , err;

  function next(e) {
    if (e) err = e;
    if (!--pending) done(err);
  }

  return obj.forEach(function(item, i) {
    return iter(item, next, i);
  });
};

exports.forEachSeries = function(obj, iter, done) {
  var i = 0;
  (function next(err) {
    if (err) return done(err);
    var item = obj[i++];
    if (!item) return done();
    return iter(item, next, i - 1);
  })();
};
<commit_msg>remove html pretty printer.<commit_after>/**
 * dilated: utils.js
 * Copyright (c) 2011-2014, Christopher Jeffrey (MIT License)
 */

/**
 * Utilities
 */

var crypto = require('crypto');

exports.hash = function(str) {
  return crypto
    .createHash('sha256')
    .update(str)
    .digest('hex');
};

exports.escapeHTML = function(html, once) {
  return (html || '')
    .replace(once ? /&(?![^\s;]+;)/g : /&/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/"/g, '"')
    .replace(/'/g, ''');
};

/**
 * Time Functions
 */

exports.date = function(date) {
  if (!date.getUTCFullYear) {
    date = new Date(date);
  }

  date = date
    .toLocaleDateString()
    .split(/,\s+|\s+/);

  date = {
    day: date[0],
    month: date[1],
    date: date[2],
    year: date[3]
  };

  return date.month
    + ' ' + date.date
    + ', ' + date.year;
};

exports.time = function(date) {
  if (!date.getUTCFullYear) {
    date = new Date(date);
  }

  var hours = +date.getHours()
    , minutes = date.getMinutes().toString()
    , meridiem = hours < 12 ? 'am' : 'pm';

  if (hours === 0) hours = 12;
  if (hours > 12) hours -= 12;
  if (minutes.length < 2) minutes = '0' + '' + minutes;
  return hours + ':' + minutes + ' ' + meridiem;
};

exports.datetime = function(date) {
  return exports.date(date)
    + ' ' + exports.time(date);
};

exports.prettyTime = function(time) {
  var date = time.getUTCFullYear ? time : new Date(time)
    , sec = Math.floor(new Date(Date.now() - date) / 1000)
    , days = Math.floor(sec / 86400);

  if (days === 0) {
    if (sec <= 1) {
      return '1 second ago';
    }
    if (sec < 60) {
      return sec + ' seconds ago';
    }
    if (sec < 120) {
      return '1 minute ago';
    }
    if (sec < 3600) {
      return Math.floor(sec / 60)
             + ' minutes ago';
    }
    if (sec < 7200) {
      return '1 hour ago';
    }
    return Math.floor(sec / 3600)
           + ' hours ago';
  }

  if (days < 31) {
    if (days === 1) {
      return 'Yesterday';
    }
    if (days < 14) {
      return days + ' days ago';
    }
    return Math.floor(days / 7)
           + ' weeks ago';
  }

  if (days >= 31) {
    var months = Math.floor(days / 31);
    if (months === 1) {
      return '1 month ago';
    }
    if (months >= 12) {
      var years = Math.floor(months / 12);
      if (years === 1) {
        return '1 year ago';
      }
      return years + ' years ago';
    }
    return months + ' months ago';
  }
};

/**
 * Markdown
 */

exports.markdown = (function() {
  var marked = require('marked');
  return function(text) {
    return marked(text);
  };
})();

/**
 * Async
 */

exports.forEach = function(obj, iter, done) {
  var pending = obj.length
    , err;

  function next(e) {
    if (e) err = e;
    if (!--pending) done(err);
  }

  return obj.forEach(function(item, i) {
    return iter(item, next, i);
  });
};

exports.forEachSeries = function(obj, iter, done) {
  var i = 0;
  (function next(err) {
    if (err) return done(err);
    var item = obj[i++];
    if (!item) return done();
    return iter(item, next, i - 1);
  })();
};
<|endoftext|>


<commit_before>import express from 'express'
let ajaxRouter = express.Router();
import serverSettings from './settings'
import api from 'cezerin-client';
api.init(serverSettings.apiBaseUrl, serverSettings.security.token);

const DEFAULT_CACHE_CONTROL = 'public, max-age=600';
const PRODUCTS_CACHE_CONTROL = 'public, max-age=60';

const fillCartItemWithProductData = (products, cartItem) => {
  const product = products.find(p => p.id === cartItem.product_id);
  if(product) {
    cartItem.image_url = product.images.length > 0 ? product.images[0].url : null;
    cartItem.stock_quantity = product.stock_quantity;
  }
  return cartItem;
}

const fillCartItems = (cartResponse) => {
  let cart = cartResponse.json;
  if(cart && cart.items && cart.items.length > 0) {
    const productIds = cart.items.map(item => item.product_id);
    return api.products.list({ ids: productIds, fields: 'images,enabled,stock_quantity' }).then(({status, json}) => {
      const newCartItem = cart.items.map(cartItem => fillCartItemWithProductData(json.data, cartItem))
      cartResponse.json.items = newCartItem;
      return cartResponse;
    })
  } else {
    return Promise.resolve(cartResponse)
  }
}

ajaxRouter.get('/products', (req, res, next) => {
  const filter = req.query;
  filter.enabled = true;
  api.products.list(filter).then(({status, json}) => {
    res.status(status).header('Cache-Control', PRODUCTS_CACHE_CONTROL).send(json);
  })
})

ajaxRouter.get('/products/:id', (req, res, next) => {
  api.products.retrieve(req.params.id).then(({status, json}) => {
    res.status(status).send(json);
  })
})

ajaxRouter.get('/cart', (req, res, next) => {
  const order_id = req.signedCookies.order_id;
  if (order_id) {
    api.orders.retrieve(order_id).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
      res.status(status).send(json);
    })
  } else {
    res.end();
  }
})

ajaxRouter.post('/cart/items', (req, res, next) => {
  const order_id = req.signedCookies.order_id;
  const item = req.body;
  if (order_id) {
    api.orders.addItem(order_id, item).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
      res.status(status).send(json);
    })
  } else {

    let ip = req.get('x-forwarded-for') || req.ip;

    if(ip && ip.includes(', ')) {
      ip = ip.split(', ')[0];
    }

    if(ip && ip.includes('::ffff:')) {
      ip = ip.replace('::ffff:', '');
    }

    api.orders.create({
      draft: true,
      referrer_url: req.signedCookies.referrer_url,
      landing_url: req.signedCookies.landing_url,
      browser: {
        ip: ip,
        user_agent: req.get('user-agent')
      }
    }).then(({status, json}) => {
      res.cookie('order_id', json.id, serverSettings.cartCookieOptions);
      api.orders.addItem(json.id, item).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
        res.status(status).send(json);
      })
    })
  }
})

ajaxRouter.delete('/cart/items/:item_id', (req, res, next) => {
  const order_id = req.signedCookies.order_id;
  const item_id = req.params.item_id;
  if (order_id && item_id) {
    api.orders.deleteItem(order_id, item_id).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
      res.status(status).send(json);
    })
  } else {
    res.end();
  }
})

ajaxRouter.put('/cart/items/:item_id', (req, res, next) => {
  const order_id = req.signedCookies.order_id;
  const item_id = req.params.item_id;
  const item = req.body;
  if (order_id && item_id) {
    api.orders.updateItem(order_id, item_id, item).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
      res.status(status).send(json);
    })
  } else {
    res.end();
  }
})

ajaxRouter.put('/cart/checkout', (req, res, next) => {
  const order_id = req.signedCookies.order_id;
  if (order_id) {
    api.orders.checkout(order_id).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
      res.clearCookie('order_id');
      res.status(status).send(json);
    })
  } else {
    res.end();
  }
});

ajaxRouter.put('/cart', (req, res, next) => {
  const order_id = req.signedCookies.order_id;
  if (order_id) {
    api.orders.update(order_id, req.body).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
      res.status(status).send(json);
    })
  } else {
    res.end();
  }
})

ajaxRouter.put('/cart/shipping_address', (req, res, next) => {
  const order_id = req.signedCookies.order_id;
  if (order_id) {
    api.orders.updateShippingAddress(order_id, req.body).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
      res.status(status).send(json);
    })
  } else {
    res.end();
  }
})

ajaxRouter.put('/cart/billing_address', (req, res, next) => {
  const order_id = req.signedCookies.order_id;
  if (order_id) {
    api.orders.updateBillingAddress(order_id, req.body).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
      res.status(status).send(json);
    })
  } else {
    res.end();
  }
})

ajaxRouter.get('/product_categories', (req, res, next) => {
  api.product_categories.list(req.query).then(({status, json}) => {
    res.status(status).header('Cache-Control', DEFAULT_CACHE_CONTROL).send(json);
  })
})

// ajaxRouter.get('/product_categories/:id', (req, res, next) => {
//   api.product_categories.retrieve(req.params.id).then(({status, json}) => {
//     res.status(status).header('Cache-Control', DEFAULT_CACHE_CONTROL).send(json);
//   })
// })

ajaxRouter.get('/pages/:id', (req, res, next) => {
  api.pages.retrieve(req.params.id).then(({status, json}) => {
    res.status(status).header('Cache-Control', DEFAULT_CACHE_CONTROL).send(json);
  })
})

ajaxRouter.get('/sitemap', (req, res, next) => {
  const filter = req.query;
  filter.enabled = true;
  api.sitemap.retrieve(req.query).then(({status, json}) => {
    res.status(status).header('Cache-Control', DEFAULT_CACHE_CONTROL).send(json);
  })
})

ajaxRouter.get('/payment_methods', (req, res, next) => {
  const filter = {
    order_id: req.signedCookies.order_id
  };
  api.payment_methods.list(filter).then(({status, json}) => {
    res.status(status).send(json);
  })
})

ajaxRouter.get('/shipping_methods', (req, res, next) => {
  const filter = {
    order_id: req.signedCookies.order_id
  };
  api.shipping_methods.list(filter).then(({status, json}) => {
    res.status(status).send(json);
  })
})

// ajaxRouter.get('/countries', (req, res, next) => {
//   api.countries.list().then(({status, json}) => {
//     res.status(status).header('Cache-Control', DEFAULT_CACHE_CONTROL).send(json);
//   })
// })

ajaxRouter.all('*', (req, res, next) => {
  res.status(405).send({'error': 'Method Not Allowed'});
})

ajaxRouter.use(function(err, req, res, next) {
  if (err) {
    res.status(500).send({'error': err});
  }
});

module.exports = ajaxRouter;
<commit_msg>Added cache control to product details<commit_after>import express from 'express'
let ajaxRouter = express.Router();
import serverSettings from './settings'
import api from 'cezerin-client';
api.init(serverSettings.apiBaseUrl, serverSettings.security.token);

const DEFAULT_CACHE_CONTROL = 'public, max-age=600';
const PRODUCTS_CACHE_CONTROL = 'public, max-age=60';
const PRODUCT_DETAILS_CACHE_CONTROL = 'public, max-age=60';

const fillCartItemWithProductData = (products, cartItem) => {
  const product = products.find(p => p.id === cartItem.product_id);
  if(product) {
    cartItem.image_url = product.images.length > 0 ? product.images[0].url : null;
    cartItem.stock_quantity = product.stock_quantity;
  }
  return cartItem;
}

const fillCartItems = (cartResponse) => {
  let cart = cartResponse.json;
  if(cart && cart.items && cart.items.length > 0) {
    const productIds = cart.items.map(item => item.product_id);
    return api.products.list({ ids: productIds, fields: 'images,enabled,stock_quantity' }).then(({status, json}) => {
      const newCartItem = cart.items.map(cartItem => fillCartItemWithProductData(json.data, cartItem))
      cartResponse.json.items = newCartItem;
      return cartResponse;
    })
  } else {
    return Promise.resolve(cartResponse)
  }
}

ajaxRouter.get('/products', (req, res, next) => {
  const filter = req.query;
  filter.enabled = true;
  api.products.list(filter).then(({status, json}) => {
    res.status(status).header('Cache-Control', PRODUCTS_CACHE_CONTROL).send(json);
  })
})

ajaxRouter.get('/products/:id', (req, res, next) => {
  api.products.retrieve(req.params.id).then(({status, json}) => {
    res.status(status).header('Cache-Control', PRODUCT_DETAILS_CACHE_CONTROL).send(json);
  })
})

ajaxRouter.get('/cart', (req, res, next) => {
  const order_id = req.signedCookies.order_id;
  if (order_id) {
    api.orders.retrieve(order_id).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
      res.status(status).send(json);
    })
  } else {
    res.end();
  }
})

ajaxRouter.post('/cart/items', (req, res, next) => {
  const order_id = req.signedCookies.order_id;
  const item = req.body;
  if (order_id) {
    api.orders.addItem(order_id, item).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
      res.status(status).send(json);
    })
  } else {

    let ip = req.get('x-forwarded-for') || req.ip;

    if(ip && ip.includes(', ')) {
      ip = ip.split(', ')[0];
    }

    if(ip && ip.includes('::ffff:')) {
      ip = ip.replace('::ffff:', '');
    }

    api.orders.create({
      draft: true,
      referrer_url: req.signedCookies.referrer_url,
      landing_url: req.signedCookies.landing_url,
      browser: {
        ip: ip,
        user_agent: req.get('user-agent')
      }
    }).then(({status, json}) => {
      res.cookie('order_id', json.id, serverSettings.cartCookieOptions);
      api.orders.addItem(json.id, item).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
        res.status(status).send(json);
      })
    })
  }
})

ajaxRouter.delete('/cart/items/:item_id', (req, res, next) => {
  const order_id = req.signedCookies.order_id;
  const item_id = req.params.item_id;
  if (order_id && item_id) {
    api.orders.deleteItem(order_id, item_id).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
      res.status(status).send(json);
    })
  } else {
    res.end();
  }
})

ajaxRouter.put('/cart/items/:item_id', (req, res, next) => {
  const order_id = req.signedCookies.order_id;
  const item_id = req.params.item_id;
  const item = req.body;
  if (order_id && item_id) {
    api.orders.updateItem(order_id, item_id, item).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
      res.status(status).send(json);
    })
  } else {
    res.end();
  }
})

ajaxRouter.put('/cart/checkout', (req, res, next) => {
  const order_id = req.signedCookies.order_id;
  if (order_id) {
    api.orders.checkout(order_id).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
      res.clearCookie('order_id');
      res.status(status).send(json);
    })
  } else {
    res.end();
  }
});

ajaxRouter.put('/cart', (req, res, next) => {
  const order_id = req.signedCookies.order_id;
  if (order_id) {
    api.orders.update(order_id, req.body).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
      res.status(status).send(json);
    })
  } else {
    res.end();
  }
})

ajaxRouter.put('/cart/shipping_address', (req, res, next) => {
  const order_id = req.signedCookies.order_id;
  if (order_id) {
    api.orders.updateShippingAddress(order_id, req.body).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
      res.status(status).send(json);
    })
  } else {
    res.end();
  }
})

ajaxRouter.put('/cart/billing_address', (req, res, next) => {
  const order_id = req.signedCookies.order_id;
  if (order_id) {
    api.orders.updateBillingAddress(order_id, req.body).then(cartResponse => fillCartItems(cartResponse)).then(({status, json}) => {
      res.status(status).send(json);
    })
  } else {
    res.end();
  }
})

ajaxRouter.get('/product_categories', (req, res, next) => {
  api.product_categories.list(req.query).then(({status, json}) => {
    res.status(status).header('Cache-Control', DEFAULT_CACHE_CONTROL).send(json);
  })
})

ajaxRouter.get('/pages/:id', (req, res, next) => {
  api.pages.retrieve(req.params.id).then(({status, json}) => {
    res.status(status).header('Cache-Control', DEFAULT_CACHE_CONTROL).send(json);
  })
})

ajaxRouter.get('/sitemap', (req, res, next) => {
  const filter = req.query;
  filter.enabled = true;
  api.sitemap.retrieve(req.query).then(({status, json}) => {
    res.status(status).header('Cache-Control', DEFAULT_CACHE_CONTROL).send(json);
  })
})

ajaxRouter.get('/payment_methods', (req, res, next) => {
  const filter = {
    order_id: req.signedCookies.order_id
  };
  api.payment_methods.list(filter).then(({status, json}) => {
    res.status(status).send(json);
  })
})

ajaxRouter.get('/shipping_methods', (req, res, next) => {
  const filter = {
    order_id: req.signedCookies.order_id
  };
  api.shipping_methods.list(filter).then(({status, json}) => {
    res.status(status).send(json);
  })
})

ajaxRouter.all('*', (req, res, next) => {
  res.status(405).send({'error': 'Method Not Allowed'});
})

ajaxRouter.use(function(err, req, res, next) {
  if (err) {
    res.status(500).send({'error': err});
  }
});

module.exports = ajaxRouter;
<|endoftext|>


<commit_before>import URIjs from 'urijs';
import uniq from 'lodash/uniq';
import each from 'lodash/each';
import isString from 'lodash/isString';
import isNumber from 'lodash/isNumber';
import once from 'lodash/once';
import reduce from 'lodash/reduce';
import isPlainObject from 'lodash/isPlainObject';
import { compose } from 'redux';

export {
  once,
  uniq,
  each,
  compose,
  reduce,
  isString,
  isNumber,
  isPlainObject
};

export const parseSearchString = (string) =>
  URIjs.parseQuery(string);

export const calcSearchString = (params) =>
  URIjs.buildQuery(params, true);

export const compileStateHash = (name, params) =>
  `${name}?${calcSearchString(params)}`;
<commit_msg>add deepEqual util method<commit_after>import URIjs from 'urijs';
import uniq from 'lodash/uniq';
import isEqual from 'lodash/isEqual';
import each from 'lodash/each';
import isString from 'lodash/isString';
import isNumber from 'lodash/isNumber';
import once from 'lodash/once';
import reduce from 'lodash/reduce';
import isPlainObject from 'lodash/isPlainObject';
import { compose } from 'redux';

export {
  once,
  uniq,
  each,
  isEqual,
  compose,
  reduce,
  isString,
  isNumber,
  isPlainObject
};

export const parseSearchString = (string) =>
  URIjs.parseQuery(string);

export const calcSearchString = (params) =>
  URIjs.buildQuery(params, true);
<|endoftext|>


<commit_before>'use strict';

// 3rd-party modules

var webdriver = require('selenium-webdriver');

// this module

module.exports = function (browser) {
  var driver = new webdriver.Builder()
    .forBrowser(browser)
    // .setFirefoxOptions()
    .build();

  driver.manage().timeouts().implicitlyWait(1e3);

  if (browser !== 'safari') {
    driver.manage().timeouts().pageLoadTimeout(60e3);
    driver.manage().timeouts().setScriptTimeout(60e3);
  }

  return driver;
};
<commit_msg>still setScriptTimeout in Safari, as that is supported<commit_after>'use strict';

// 3rd-party modules

var webdriver = require('selenium-webdriver');

// this module

module.exports = function (browser) {
  var driver = new webdriver.Builder()
    .forBrowser(browser)
    // .setFirefoxOptions()
    .build();

  driver.manage().timeouts().implicitlyWait(1e3);

  if (browser !== 'safari') {
    driver.manage().timeouts().pageLoadTimeout(60e3);
  }
  driver.manage().timeouts().setScriptTimeout(60e3);

  return driver;
};
<|endoftext|>


<commit_before>import EVENTS from './../events.js';
import triggerEvent from './../util/triggerEvent.js';
import getToolForElement from './getToolForElement.js';
import { state } from './../store/index.js';

/**
 * Sets a tool's state to 'active'. Active tools are rendered,
 * respond to user input, and can create new data
 *
 * @export
 * @param {object} element
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
const setToolActiveForElement = function (
  element,
  toolName,
  options,
  isTouchActive
) {
  const tool = getToolForElement(element, toolName);

  if (tool) {
    _setActiveToolWithMatchingMouseButtonMaskToPassive(tool, element, options);
    _setActiveToolWithIsTouchActiveToPassive(
      tool,
      element,
      options,
      isTouchActive
    );

    // TODO: Find active tool w/ active two finger?
    // TODO: Find active tool w/ ...?
  }

  // Resume normal behavior
  setToolModeForElement(
    'active',
    null,
    element,
    toolName,
    options,
    isTouchActive
  );
};

/**
 *
 * @export
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
const setToolActive = function (toolName, options, isTouchActive) {
  state.enabledElements.forEach((element) => {
    setToolActiveForElement(element, toolName, options, isTouchActive);
  });
};

/**
 * Sets a tool's state to 'disabled'. Disabled tools are not rendered,
 * and do not respond to user input
 *
 * @export
 * @param {object} element
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
const setToolDisabledForElement = setToolModeForElement.bind(
  null,
  'disabled',
  null
);

/**
 *
 * @export
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
const setToolDisabled = setToolMode.bind(null, 'disabled', null);

/**
 * Sets a tool's state to 'enabled'. Enabled tools are rendered,
 * but do not respond to user input
 *
 * @export
 * @param {object} element
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
const setToolEnabledForElement = setToolModeForElement.bind(
  null,
  'enabled',
  null
);

/**
 *
 * @export
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
const setToolEnabled = setToolMode.bind(null, 'enabled', null);

/**
 * Sets a tool's state to 'passive'. Passive tools are rendered and respond to user input,
 * but do not create new measurements or annotations.
 *
 * @export
 * @param {object} element
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
const setToolPassiveForElement = setToolModeForElement.bind(
  null,
  'passive',
  EVENTS.TOOL_DEACTIVATED
);

/**
 *
 * @export
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
const setToolPassive = setToolMode.bind(
  null,
  'passive',
  EVENTS.TOOL_DEACTIVATED
);

/**
 * An internal method that helps make sure we change tool state in a consistent
 * way
 *
 * @param {string} mode
 * @param {string} changeEvent
 * @param {object} element
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
function setToolModeForElement (
  mode,
  changeEvent,
  element,
  toolName,
  options,
  isTouchActive
) {
  const tool = getToolForElement(element, toolName);

  if (!tool) {
    console.warn(`Unable to find tool '${toolName}' for enabledElement`);

    return;
  }

  // MouseButtonMask (and isTouchActive) provided as values
  if (typeof options === 'number') {
    options = {
      mouseButtonMask: options
    };

    if (isTouchActive === true || isTouchActive === false) {
      options.isTouchActive = isTouchActive === true;
    }
  }
  // Options is an object, or null/undefined
  else {
    options = options || {};
  }

  // Set mode & options
  tool.mode = mode;
  tool.options = Object.assign({}, tool.options, options);

  // Call tool's hook for this event, if one exists
  if (tool[`${mode}Callback`]) {
    tool[`${mode}Callback`](element, options);
  }

  // Emit event indicating tool state change
  if (changeEvent) {
    const statusChangeEventData = {
      options,
      toolName,
      type: changeEvent
    };

    triggerEvent(element, changeEvent, statusChangeEventData);
  }

  // Trigger Update
  // Todo: don't error out if image hasn't been loaded...
  // Cornerstone.updateImage(element);
}

/**
 * A helper/quick way to set a tool's mode for all canvases
 *
 * @param {string} mode
 * @param {string} changeEvent
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 */
function setToolMode (mode, changeEvent, toolName, options, isTouchActive) {
  state.enabledElements.forEach((element) => {
    setToolModeForElement(
      mode,
      changeEvent,
      element,
      toolName,
      options,
      isTouchActive
    );
  });
}

/**
 *
 *
 * @param {*} element
 * @param {*} options
 * @param {*} isTouchActive
 * @private
 */
function _setActiveToolWithMatchingMouseButtonMaskToPassive (
  tool,
  element,
  options
) {
  const mouseButtonMask =
    (typeof options === 'number' ? options : options.mouseButtonMask) ||
    tool.options.mouseButtonMask;
  const hasMouseButtonMask =
    mouseButtonMask !== undefined && mouseButtonMask > 0;
  const activeToolWithMatchingMouseButtonMask = state.tools.find(
    (tool) =>
      tool.element === element &&
      tool.mode === 'active' &&
      tool.options.mouseButtonMask === mouseButtonMask
  );

  if (hasMouseButtonMask && activeToolWithMatchingMouseButtonMask) {
    console.info(
      `Setting tool ${activeToolWithMatchingMouseButtonMask.name} to passive`
    );
    setToolPassiveForElement(
      element,
      activeToolWithMatchingMouseButtonMask.name
    );
  }
}

/**
 *
 *
 * @param {*} element
 * @param {*} options
 * @param {*} isTouchActive
 * @private
 */
function _setActiveToolWithIsTouchActiveToPassive (
  tool,
  element,
  options,
  isTouchActive
) {
  let value;

  if (typeof options === 'number' && typeof isTouchActive === 'boolean') {
    value = isTouchActive === true;
  } else if (
    typeof options === 'object' &&
    typeof options.isTouchActive === 'boolean'
  ) {
    value = options.isTouchActive === true;
  } else {
    value = tool.options.isTouchActive;
  }

  let activeToolWithIsTouchActive;

  if (typeof value === 'boolean' && value === true) {
    activeToolWithIsTouchActive = state.tools.find(
      (tool) =>
        tool.element === element &&
        tool.mode === 'active' &&
        tool.options.isTouchActive === true
    );
  }

  if (activeToolWithIsTouchActive) {
    setToolPassiveForElement(element, activeToolWithIsTouchActive.name);
  }
}

export {
  setToolActive,
  setToolActiveForElement,
  setToolDisabled,
  setToolDisabledForElement,
  setToolEnabled,
  setToolEnabledForElement,
  setToolPassive,
  setToolPassiveForElement
};
<commit_msg>We need to flip touch to false, not the tool's mode to passive for touch conflicts of active tools<commit_after>import EVENTS from './../events.js';
import triggerEvent from './../util/triggerEvent.js';
import getToolForElement from './getToolForElement.js';
import { state } from './../store/index.js';

/**
 * Sets a tool's state to 'active'. Active tools are rendered,
 * respond to user input, and can create new data
 *
 * @export
 * @param {object} element
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
const setToolActiveForElement = function (
  element,
  toolName,
  options,
  isTouchActive
) {
  const tool = getToolForElement(element, toolName);

  if (tool) {
    _setActiveToolWithMatchingMouseButtonMaskToPassive(tool, element, options);
    _setActiveToolWithIsTouchActiveToFalse(
      tool,
      element,
      options,
      isTouchActive
    );

    // TODO: Find active tool w/ active two finger?
    // TODO: Find active tool w/ ...?
  }

  // Resume normal behavior
  setToolModeForElement(
    'active',
    null,
    element,
    toolName,
    options,
    isTouchActive
  );
};

/**
 *
 * @export
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
const setToolActive = function (toolName, options, isTouchActive) {
  state.enabledElements.forEach((element) => {
    setToolActiveForElement(element, toolName, options, isTouchActive);
  });
};

/**
 * Sets a tool's state to 'disabled'. Disabled tools are not rendered,
 * and do not respond to user input
 *
 * @export
 * @param {object} element
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
const setToolDisabledForElement = setToolModeForElement.bind(
  null,
  'disabled',
  null
);

/**
 *
 * @export
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
const setToolDisabled = setToolMode.bind(null, 'disabled', null);

/**
 * Sets a tool's state to 'enabled'. Enabled tools are rendered,
 * but do not respond to user input
 *
 * @export
 * @param {object} element
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
const setToolEnabledForElement = setToolModeForElement.bind(
  null,
  'enabled',
  null
);

/**
 *
 * @export
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
const setToolEnabled = setToolMode.bind(null, 'enabled', null);

/**
 * Sets a tool's state to 'passive'. Passive tools are rendered and respond to user input,
 * but do not create new measurements or annotations.
 *
 * @export
 * @param {object} element
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
const setToolPassiveForElement = setToolModeForElement.bind(
  null,
  'passive',
  EVENTS.TOOL_DEACTIVATED
);

/**
 *
 * @export
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
const setToolPassive = setToolMode.bind(
  null,
  'passive',
  EVENTS.TOOL_DEACTIVATED
);

/**
 * An internal method that helps make sure we change tool state in a consistent
 * way
 *
 * @param {string} mode
 * @param {string} changeEvent
 * @param {object} element
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 * @returns
 */
function setToolModeForElement (
  mode,
  changeEvent,
  element,
  toolName,
  options,
  isTouchActive
) {
  const tool = getToolForElement(element, toolName);

  if (!tool) {
    console.warn(`Unable to find tool '${toolName}' for enabledElement`);

    return;
  }

  // MouseButtonMask (and isTouchActive) provided as values
  if (typeof options === 'number') {
    options = {
      mouseButtonMask: options
    };

    if (isTouchActive === true || isTouchActive === false) {
      options.isTouchActive = isTouchActive === true;
    }
  }
  // Options is an object, or null/undefined
  else {
    options = options || {};
  }

  // Set mode & options
  tool.mode = mode;
  tool.options = Object.assign({}, tool.options, options);

  // Call tool's hook for this event, if one exists
  if (tool[`${mode}Callback`]) {
    tool[`${mode}Callback`](element, options);
  }

  // Emit event indicating tool state change
  if (changeEvent) {
    const statusChangeEventData = {
      options,
      toolName,
      type: changeEvent
    };

    triggerEvent(element, changeEvent, statusChangeEventData);
  }

  // Trigger Update
  // Todo: don't error out if image hasn't been loaded...
  // Cornerstone.updateImage(element);
}

/**
 * A helper/quick way to set a tool's mode for all canvases
 *
 * @param {string} mode
 * @param {string} changeEvent
 * @param {string} toolName
 * @param {(object|number)} options
 * @param {boolean} isTouchActive
 */
function setToolMode (mode, changeEvent, toolName, options, isTouchActive) {
  state.enabledElements.forEach((element) => {
    setToolModeForElement(
      mode,
      changeEvent,
      element,
      toolName,
      options,
      isTouchActive
    );
  });
}

/**
 *
 *
 * @param {*} element
 * @param {*} options
 * @param {*} isTouchActive
 * @private
 */
function _setActiveToolWithMatchingMouseButtonMaskToPassive (
  tool,
  element,
  options
) {
  const mouseButtonMask =
    (typeof options === 'number' ? options : options.mouseButtonMask) ||
    tool.options.mouseButtonMask;
  const hasMouseButtonMask =
    mouseButtonMask !== undefined && mouseButtonMask > 0;
  const activeToolWithMatchingMouseButtonMask = state.tools.find(
    (tool) =>
      tool.element === element &&
      tool.mode === 'active' &&
      tool.options.mouseButtonMask === mouseButtonMask
  );

  if (hasMouseButtonMask && activeToolWithMatchingMouseButtonMask) {
    console.info(
      `Setting tool ${activeToolWithMatchingMouseButtonMask.name} to passive`
    );
    setToolPassiveForElement(
      element,
      activeToolWithMatchingMouseButtonMask.name
    );
  }
}

/**
 *
 *
 * @param {*} element
 * @param {*} options
 * @param {*} isTouchActive
 * @private
 */
function _setActiveToolWithIsTouchActiveToFalse (
  tool,
  element,
  options,
  isTouchActive
) {
  let value;

  if (typeof options === 'number' && typeof isTouchActive === 'boolean') {
    value = isTouchActive === true;
  } else if (
    typeof options === 'object' &&
    typeof options.isTouchActive === 'boolean'
  ) {
    value = options.isTouchActive === true;
  } else {
    value = tool.options.isTouchActive;
  }

  let activeToolWithIsTouchActive;

  if (typeof value === 'boolean' && value === true) {
    activeToolWithIsTouchActive = state.tools.find(
      (tool) =>
        tool.element === element &&
        tool.mode === 'active' &&
        tool.options.isTouchActive === true
    );
  }

  if (activeToolWithIsTouchActive) {
    activeToolWithIsTouchActive.options.isTouchActive = false;
  }
}

export {
  setToolActive,
  setToolActiveForElement,
  setToolDisabled,
  setToolDisabledForElement,
  setToolEnabled,
  setToolEnabledForElement,
  setToolPassive,
  setToolPassiveForElement
};
<|endoftext|>


<commit_before>'use strict';

function bindArguments(args) {
  for (var i = args.length - 1; i >= 0; i--) {
    if (typeof args[i] === 'function') {
      args[i] = global.zone.bind(args[i]);
    }
  }
  return args;
};

function bindArgumentsOnce(args) {
  for (var i = args.length - 1; i >= 0; i--) {
    if (typeof args[i] === 'function') {
      args[i] = global.zone.bindOnce(args[i]);
    }
  }
  return args;
};

function patchPrototype(obj, fnNames) {
  fnNames.forEach(function (name) {
    var delegate = obj[name];
    if (delegate) {
      obj[name] = function () {
        return delegate.apply(this, bindArguments(arguments));
      };
    }
  });
};

function patchProperty(obj, prop) {
  var desc = Object.getOwnPropertyDescriptor(obj, prop) || {
    enumerable: true,
    configurable: true
  };

  // A property descriptor cannot have getter/setter and be writable
  // deleting the writable and value properties avoids this error:
  //
  // TypeError: property descriptors must not specify a value or be writable when a
  // getter or setter has been specified
  delete desc.writable;
  delete desc.value;

  // substr(2) cuz 'onclick' -> 'click', etc
  var eventName = prop.substr(2);
  var _prop = '_' + prop;

  desc.set = function (fn) {
    if (this[_prop]) {
      this.removeEventListener(eventName, this[_prop]);
    }

    if (typeof fn === 'function') {
      this[_prop] = fn;
      this.addEventListener(eventName, fn, false);
    } else {
      this[_prop] = null;
    }
  };

  desc.get = function () {
    return this[_prop];
  };

  Object.defineProperty(obj, prop, desc);
};

function patchProperties(obj, properties) {

  (properties || (function () {
      var props = [];
      for (var prop in obj) {
        props.push(prop);
      }
      return props;
    }()).
    filter(function (propertyName) {
      return propertyName.substr(0,2) === 'on';
    })).
    forEach(function (eventName) {
      patchProperty(obj, eventName);
    });
};

function patchEventTargetMethods(obj) {
  var addDelegate = obj.addEventListener;
  obj.addEventListener = function (eventName, fn) {
    fn._bound = fn._bound || {};
    arguments[1] = fn._bound[eventName] = zone.bind(fn);
    return addDelegate.apply(this, arguments);
  };

  var removeDelegate = obj.removeEventListener;
  obj.removeEventListener = function (eventName, fn) {
    if(arguments[1]._bound && arguments[1]._bound[eventName]) {
      var _bound = arguments[1]._bound;
      arguments[1] = _bound[eventName];
      delete _bound[eventName];
    }
    var result = removeDelegate.apply(this, arguments);
    global.zone.dequeueTask(fn);
    return result;
  };
};

// wrap some native API on `window`
function patchClass(className) {
  var OriginalClass = global[className];
  if (!OriginalClass) return;

  global[className] = function () {
    var a = bindArguments(arguments);
    switch (a.length) {
      case 0: this._o = new OriginalClass(); break;
      case 1: this._o = new OriginalClass(a[0]); break;
      case 2: this._o = new OriginalClass(a[0], a[1]); break;
      case 3: this._o = new OriginalClass(a[0], a[1], a[2]); break;
      case 4: this._o = new OriginalClass(a[0], a[1], a[2], a[3]); break;
      default: throw new Error('what are you even doing?');
    }
  };

  var instance = new OriginalClass(className.substr(-16) === 'MutationObserver' ? function () {} : undefined);

  var prop;
  for (prop in instance) {
    (function (prop) {
      if (typeof instance[prop] === 'function') {
        global[className].prototype[prop] = function () {
          return this._o[prop].apply(this._o, arguments);
        };
      } else {
        Object.defineProperty(global[className].prototype, prop, {
          set: function (fn) {
            if (typeof fn === 'function') {
              this._o[prop] = global.zone.bind(fn);
            } else {
              this._o[prop] = fn;
            }
          },
          get: function () {
            return this._o[prop];
          }
        });
      }
    }(prop));
  };
};

module.exports = {
  bindArguments: bindArguments,
  bindArgumentsOnce: bindArgumentsOnce,
  patchPrototype: patchPrototype,
  patchProperty: patchProperty,
  patchProperties: patchProperties,
  patchEventTargetMethods: patchEventTargetMethods,
  patchClass: patchClass
};
<commit_msg>refactor(patchClass): MutationObserver has its own patch<commit_after>'use strict';

function bindArguments(args) {
  for (var i = args.length - 1; i >= 0; i--) {
    if (typeof args[i] === 'function') {
      args[i] = global.zone.bind(args[i]);
    }
  }
  return args;
};

function bindArgumentsOnce(args) {
  for (var i = args.length - 1; i >= 0; i--) {
    if (typeof args[i] === 'function') {
      args[i] = global.zone.bindOnce(args[i]);
    }
  }
  return args;
};

function patchPrototype(obj, fnNames) {
  fnNames.forEach(function (name) {
    var delegate = obj[name];
    if (delegate) {
      obj[name] = function () {
        return delegate.apply(this, bindArguments(arguments));
      };
    }
  });
};

function patchProperty(obj, prop) {
  var desc = Object.getOwnPropertyDescriptor(obj, prop) || {
    enumerable: true,
    configurable: true
  };

  // A property descriptor cannot have getter/setter and be writable
  // deleting the writable and value properties avoids this error:
  //
  // TypeError: property descriptors must not specify a value or be writable when a
  // getter or setter has been specified
  delete desc.writable;
  delete desc.value;

  // substr(2) cuz 'onclick' -> 'click', etc
  var eventName = prop.substr(2);
  var _prop = '_' + prop;

  desc.set = function (fn) {
    if (this[_prop]) {
      this.removeEventListener(eventName, this[_prop]);
    }

    if (typeof fn === 'function') {
      this[_prop] = fn;
      this.addEventListener(eventName, fn, false);
    } else {
      this[_prop] = null;
    }
  };

  desc.get = function () {
    return this[_prop];
  };

  Object.defineProperty(obj, prop, desc);
};

function patchProperties(obj, properties) {

  (properties || (function () {
      var props = [];
      for (var prop in obj) {
        props.push(prop);
      }
      return props;
    }()).
    filter(function (propertyName) {
      return propertyName.substr(0,2) === 'on';
    })).
    forEach(function (eventName) {
      patchProperty(obj, eventName);
    });
};

function patchEventTargetMethods(obj) {
  var addDelegate = obj.addEventListener;
  obj.addEventListener = function (eventName, fn) {
    fn._bound = fn._bound || {};
    arguments[1] = fn._bound[eventName] = zone.bind(fn);
    return addDelegate.apply(this, arguments);
  };

  var removeDelegate = obj.removeEventListener;
  obj.removeEventListener = function (eventName, fn) {
    if(arguments[1]._bound && arguments[1]._bound[eventName]) {
      var _bound = arguments[1]._bound;
      arguments[1] = _bound[eventName];
      delete _bound[eventName];
    }
    var result = removeDelegate.apply(this, arguments);
    global.zone.dequeueTask(fn);
    return result;
  };
};

// wrap some native API on `window`
function patchClass(className) {
  var OriginalClass = global[className];
  if (!OriginalClass) return;

  global[className] = function () {
    var a = bindArguments(arguments);
    switch (a.length) {
      case 0: this._o = new OriginalClass(); break;
      case 1: this._o = new OriginalClass(a[0]); break;
      case 2: this._o = new OriginalClass(a[0], a[1]); break;
      case 3: this._o = new OriginalClass(a[0], a[1], a[2]); break;
      case 4: this._o = new OriginalClass(a[0], a[1], a[2], a[3]); break;
      default: throw new Error('what are you even doing?');
    }
  };

  var instance = new OriginalClass();

  var prop;
  for (prop in instance) {
    (function (prop) {
      if (typeof instance[prop] === 'function') {
        global[className].prototype[prop] = function () {
          return this._o[prop].apply(this._o, arguments);
        };
      } else {
        Object.defineProperty(global[className].prototype, prop, {
          set: function (fn) {
            if (typeof fn === 'function') {
              this._o[prop] = global.zone.bind(fn);
            } else {
              this._o[prop] = fn;
            }
          },
          get: function () {
            return this._o[prop];
          }
        });
      }
    }(prop));
  };
};

module.exports = {
  bindArguments: bindArguments,
  bindArgumentsOnce: bindArgumentsOnce,
  patchPrototype: patchPrototype,
  patchProperty: patchProperty,
  patchProperties: patchProperties,
  patchEventTargetMethods: patchEventTargetMethods,
  patchClass: patchClass
};
<|endoftext|>


<commit_before>import {observable, action, computed} from 'mobx';
import TimerModel from './TimerModel';

const TICK_DELAY = 40;

export default class TimerStore {
    @observable timers = [];

    constructor() {
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
    }

    @action tick() {
        this.timers.forEach(it => it.tick());
    }

    @action enable() {
        this.interval = setInterval(() => this.tick(), TICK_DELAY);
    }

    @action disable() {
        if (this.interval) {
            clearInterval(this.interval);
            this.interval = null;
        }
    }
}<commit_msg>Throttle delay<commit_after>import {observable, action, computed} from 'mobx';
import TimerModel from './TimerModel';

const TICK_DELAY = 100;

export default class TimerStore {
    @observable timers = [];

    constructor() {
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
        this.timers.push(new TimerModel());
    }

    @action tick() {
        this.timers.forEach(it => it.tick());
    }

    @action enable() {
        this.interval = setInterval(() => this.tick(), TICK_DELAY);
    }

    @action disable() {
        if (this.interval) {
            clearInterval(this.interval);
            this.interval = null;
        }
    }
}<|endoftext|>


<commit_before>const fs = require('fs');
const path = require('path');
const parser = require('../build/rpnlang');
const RPNError = require('./RPNError');
const { Evaluation, Lambda, LambdaCall } = require('./Evaluation');
const moduleSymbol = Symbol('rpnModule');

class RPNProgram {
    constructor(text, { log = console.log, debug = false, safe = false } = {}) {
        this.text = text;
        this.log = log;
        this.debug = debug;
        this.safe = safe;

        try {
            this.statements = parser.parse(text);
        } catch (err) {
            if (this.debug) throw err;

            if (err.hash && err.hash.loc) {
                throw new RPNError('Syntax', `Unexpected token ${err.hash.token}`, err.hash.loc);
            }

            if (err.hash) {
                throw new RPNError('Syntax', 'Unrecognized token', {
                    last_line: err.hash.line + 1,
                    last_column: err.message.match(/\n(-*)\^$/)[1].length
                });
            }

            throw new RPNError('Syntax', 'Could not parse', {
                last_line: 0,
                last_column: 0
            });
        }

        this.variables = new Map();
        this.exports = new Map();
        this.imports = new Map();
    }

    setup() {
        const nan = (num, base = 10) => {
            if (typeof num === 'number') return false;

            let temp = parseInt(num, base);

            for (let i = num.length - 1; i; i--) {
                const res = parseInt(num.slice(0, i), base);
                if (temp === res) return true;
                temp = res;
            }

            return false;
        };

        const namespace = {
            G: 6.67408e-11,
            C: 299792458,
            GR: (1 + Math.sqrt(5)) / 2,
            TAU: Math.PI * 2,
            NAN: NaN,
            TRUE: true,
            FALSE: false,
            UNDEFINED: undefined,
            string: a => String(a),
            number: a => isNaN(a) ? NaN : Number(a),
            int: a => isNaN(a) ? NaN : Math.floor(Number(a)),
            base: (a, b) => nan(a, b) || isNaN(b) ? NaN : parseInt(a, Math.floor(Number(b))),
            nan: a => isNaN(a),
            nanbase: (a, b) => nan(a, b),
            sum: (...args) => args.reduce((t, a) => t + a),
            product: (...args) => args.reduce((t, a) => t * a),
            charcode: (a, b) => String(a).charCodeAt(Number(b)),
            fromcode: a => String.fromCharCode(a),
            random: a => Math.random() * a,
            length: a => a.length,
            type: a => {
                const type = typeof a;
                if (type === 'function') return 'native';
                if (type === 'object') return 'lambda';
                return type;
            },
            read: file => {
                if (this.safe) return undefined;

                try {
                    return fs.readFileSync(file, 'utf-8');
                } catch (err) {
                    return undefined;
                }
            },
            write: (file, data) => {
                if (this.safe) return false;

                try {
                    fs.writeFileSync(file, data, 'utf-8');
                    return true;
                } catch (err) {
                    return false;
                }
            }
        };

        Object.assign(namespace, Object.getOwnPropertyNames(Math).reduce((o, k) => {
            if (k === 'random') return o;
            o[k] = Math[k];
            return o;
        }, {}));

        for (const [key, value] of Object.entries(namespace)) {
            this.variables.set(key, value);
        }
    }

    evaluate(statement) {
        const cases = {
            assign: this.evaluateAssign,
            print: this.evaluatePrint
        };

        const evaluation = new Evaluation(this, statement.expression).evaluate();
        cases[statement.type].call(this, evaluation, statement);
    }

    evaluateAssign(evaluation, statement) {
        if (statement.token === '=') {
            this.variables.set(statement.name, evaluation.stack[0]);
            return;
        }

        if (statement.token === '#') {
            this.variables.set(statement.name, evaluation.stack);
            return;
        }

        if (statement.token === '<<') {
            if (this.safe) {
                throw new RPNError('Module', `Could not import ${filepath}`, statement.pos);
            }

            if (this.imports.has(statement.name)) {
                throw new RPNError('Module', 'Cannot reassign an imported module', statement.pos);
            }

            let filepath = path.resolve(evaluation.stack[0]);

            if (path.extname(filepath) === '.js') {
                const js = require(filepath);

                if (typeof js !== 'object' && !js[moduleSymbol]) {
                    delete require.cache[require.resolve(filepath)];
                    throw new RPNError('Module', `Could not import ${filepath}`, statement.pos);
                }

                this.imports.set(statement.name, js.exports);
                return;
            }

            if (!path.extname(filepath)) {
                filepath += '.rpn';
            }

            let text;

            try {
                text = fs.readFileSync(filepath, 'utf-8');
            } catch (err) {
                throw new RPNError('Module', `Could not import ${filepath}`, statement.pos);
            }

            const program = new RPNProgram(text, {
                log: this.log,
                debug: this.debug,
                safe: this.safe
            });

            program.execute();
            this.imports.set(statement.name, program.exports);
            return;
        }

        if (statement.token === '>>') {
            if (this.exports.has(statement.name)) {
                throw new RPNError('Module', 'Cannot reassign an exported value', statement.pos);
            }

            this.exports.set(statement.name, evaluation.stack[0]);
            return;
        }

        if (statement.token === '#>') {
            if (this.exports.has(statement.name)) {
                throw new RPNError('Module', 'Cannot reassign an exported value', statement.pos);
            }

            this.exports.set(statement.name, evaluation.stack);
        }
    }

    evaluatePrint(evaluation, statement) {
        if (statement.token === '>') {
            const value = this.clean(evaluation.stack[0]);
            this.log(value);
            return;
        }

        if (statement.token === '!') {
            const value = this.clean(evaluation.stack[0]);
            throw new RPNError('', value, statement.pos);
        }
    }

    clean(value) {
        return typeof value === 'object'
            ? { lambda: value.params }
            : typeof value === 'function'
                ? { lambda: value.length || Infinity }
                : value;
    }

    execute() {
        this.setup();
        for (const statement of this.statements) {
            try {
                this.evaluate(statement);
            } catch (err) {
                if (this.debug || err instanceof RPNError) throw err;

                if (err.message === 'Maximum call stack size exceeded') {
                    throw new RPNError('Range', 'Too much recursion', statement.pos);
                }

                if (err.message === 'Reduce of empty array with no initial value') {
                    throw new RPNError('Range', 'Not enough values', statement.pos);
                }

                throw err;
            }
        }
    }

    static makeModule(obj) {
        return {
            _exports: obj,
            exports: new Map(Object.entries(obj)),
            [moduleSymbol]: true
        };
    }
}

RPNProgram.RPNProgram = RPNProgram;
RPNProgram.Evaluation = Evaluation;
RPNProgram.Lambda = Lambda;
RPNProgram.LambdaCall = LambdaCall;
RPNProgram.RPNError = RPNError;
module.exports = RPNProgram;
<commit_msg>Fix import in safe mode<commit_after>const fs = require('fs');
const path = require('path');
const parser = require('../build/rpnlang');
const RPNError = require('./RPNError');
const { Evaluation, Lambda, LambdaCall } = require('./Evaluation');
const moduleSymbol = Symbol('rpnModule');

class RPNProgram {
    constructor(text, { log = console.log, debug = false, safe = false } = {}) {
        this.text = text;
        this.log = log;
        this.debug = debug;
        this.safe = safe;

        try {
            this.statements = parser.parse(text);
        } catch (err) {
            if (this.debug) throw err;

            if (err.hash && err.hash.loc) {
                throw new RPNError('Syntax', `Unexpected token ${err.hash.token}`, err.hash.loc);
            }

            if (err.hash) {
                throw new RPNError('Syntax', 'Unrecognized token', {
                    last_line: err.hash.line + 1,
                    last_column: err.message.match(/\n(-*)\^$/)[1].length
                });
            }

            throw new RPNError('Syntax', 'Could not parse', {
                last_line: 0,
                last_column: 0
            });
        }

        this.variables = new Map();
        this.exports = new Map();
        this.imports = new Map();
    }

    setup() {
        const nan = (num, base = 10) => {
            if (typeof num === 'number') return false;

            let temp = parseInt(num, base);

            for (let i = num.length - 1; i; i--) {
                const res = parseInt(num.slice(0, i), base);
                if (temp === res) return true;
                temp = res;
            }

            return false;
        };

        const namespace = {
            G: 6.67408e-11,
            C: 299792458,
            GR: (1 + Math.sqrt(5)) / 2,
            TAU: Math.PI * 2,
            NAN: NaN,
            TRUE: true,
            FALSE: false,
            UNDEFINED: undefined,
            string: a => String(a),
            number: a => isNaN(a) ? NaN : Number(a),
            int: a => isNaN(a) ? NaN : Math.floor(Number(a)),
            base: (a, b) => nan(a, b) || isNaN(b) ? NaN : parseInt(a, Math.floor(Number(b))),
            nan: a => isNaN(a),
            nanbase: (a, b) => nan(a, b),
            sum: (...args) => args.reduce((t, a) => t + a),
            product: (...args) => args.reduce((t, a) => t * a),
            charcode: (a, b) => String(a).charCodeAt(Number(b)),
            fromcode: a => String.fromCharCode(a),
            random: a => Math.random() * a,
            length: a => a.length,
            type: a => {
                const type = typeof a;
                if (type === 'function') return 'native';
                if (type === 'object') return 'lambda';
                return type;
            },
            read: file => {
                if (this.safe) return undefined;

                try {
                    return fs.readFileSync(file, 'utf-8');
                } catch (err) {
                    return undefined;
                }
            },
            write: (file, data) => {
                if (this.safe) return false;

                try {
                    fs.writeFileSync(file, data, 'utf-8');
                    return true;
                } catch (err) {
                    return false;
                }
            }
        };

        Object.assign(namespace, Object.getOwnPropertyNames(Math).reduce((o, k) => {
            if (k === 'random') return o;
            o[k] = Math[k];
            return o;
        }, {}));

        for (const [key, value] of Object.entries(namespace)) {
            this.variables.set(key, value);
        }
    }

    evaluate(statement) {
        const cases = {
            assign: this.evaluateAssign,
            print: this.evaluatePrint
        };

        const evaluation = new Evaluation(this, statement.expression).evaluate();
        cases[statement.type].call(this, evaluation, statement);
    }

    evaluateAssign(evaluation, statement) {
        if (statement.token === '=') {
            this.variables.set(statement.name, evaluation.stack[0]);
            return;
        }

        if (statement.token === '#') {
            this.variables.set(statement.name, evaluation.stack);
            return;
        }

        if (statement.token === '<<') {
            let filepath = path.resolve(evaluation.stack[0]);

            if (this.safe) {
                throw new RPNError('Module', `Could not import ${filepath}`, statement.pos);
            }

            if (this.imports.has(statement.name)) {
                throw new RPNError('Module', 'Cannot reassign an imported module', statement.pos);
            }

            if (path.extname(filepath) === '.js') {
                const js = require(filepath);

                if (typeof js !== 'object' && !js[moduleSymbol]) {
                    delete require.cache[require.resolve(filepath)];
                    throw new RPNError('Module', `Could not import ${filepath}`, statement.pos);
                }

                this.imports.set(statement.name, js.exports);
                return;
            }

            if (!path.extname(filepath)) {
                filepath += '.rpn';
            }

            let text;

            try {
                text = fs.readFileSync(filepath, 'utf-8');
            } catch (err) {
                throw new RPNError('Module', `Could not import ${filepath}`, statement.pos);
            }

            const program = new RPNProgram(text, {
                log: this.log,
                debug: this.debug,
                safe: this.safe
            });

            program.execute();
            this.imports.set(statement.name, program.exports);
            return;
        }

        if (statement.token === '>>') {
            if (this.exports.has(statement.name)) {
                throw new RPNError('Module', 'Cannot reassign an exported value', statement.pos);
            }

            this.exports.set(statement.name, evaluation.stack[0]);
            return;
        }

        if (statement.token === '#>') {
            if (this.exports.has(statement.name)) {
                throw new RPNError('Module', 'Cannot reassign an exported value', statement.pos);
            }

            this.exports.set(statement.name, evaluation.stack);
        }
    }

    evaluatePrint(evaluation, statement) {
        if (statement.token === '>') {
            const value = this.clean(evaluation.stack[0]);
            this.log(value);
            return;
        }

        if (statement.token === '!') {
            const value = this.clean(evaluation.stack[0]);
            throw new RPNError('', value, statement.pos);
        }
    }

    clean(value) {
        return typeof value === 'object'
            ? { lambda: value.params }
            : typeof value === 'function'
                ? { lambda: value.length || Infinity }
                : value;
    }

    execute() {
        this.setup();
        for (const statement of this.statements) {
            try {
                this.evaluate(statement);
            } catch (err) {
                if (this.debug || err instanceof RPNError) throw err;

                if (err.message === 'Maximum call stack size exceeded') {
                    throw new RPNError('Range', 'Too much recursion', statement.pos);
                }

                if (err.message === 'Reduce of empty array with no initial value') {
                    throw new RPNError('Range', 'Not enough values', statement.pos);
                }

                throw err;
            }
        }
    }

    static makeModule(obj) {
        return {
            _exports: obj,
            exports: new Map(Object.entries(obj)),
            [moduleSymbol]: true
        };
    }
}

RPNProgram.RPNProgram = RPNProgram;
RPNProgram.Evaluation = Evaluation;
RPNProgram.Lambda = Lambda;
RPNProgram.LambdaCall = LambdaCall;
RPNProgram.RPNError = RPNError;
module.exports = RPNProgram;
<|endoftext|>


<commit_before>const Path = require('path')
const {Point, Range} = require('atom')

function getAdjacentPane (basePane, which) {
  const parent = basePane.getParent()
  if (parent && parent.getChildren) {
    const children = parent.getChildren()
    const index = children.indexOf(basePane) + (which === 'next' ? +1 : -1)
    const pane = children[index]
    if (pane && pane.constructor.name === 'Pane') {
      // Don't return PaneAxis, just Pane
      return pane
    }
  }
}

function getNextAdjacentPaneForPane (basePane) {
  return getAdjacentPane(basePane, 'next')
}

function getPreviousAdjacentPaneForPane (basePane) {
  return getAdjacentPane(basePane, 'previous')
}

function splitPane (basePane, {split}) {
  const paneWasActive = basePane.isActive()
  if (!['right', 'down'].includes(split)) {
    throw new Error('split must either of `right` or `down`')
  }
  const pane = split === 'right' ? basePane.splitRight() : basePane.splitDown()
  // Atom doesn't allow 'split' pane without activating it.
  // So re-activte basePane if it originally was active.
  if (paneWasActive && !basePane.isActive()) basePane.activate()
  return pane
}

function saveEditorState (editor) {
  const oldScrollTop = editor.element.getScrollTop()
  const oldCursorPosition = editor.getCursorBufferPosition()
  const oldFoldStartRows = editor.displayLayer.foldsMarkerLayer.findMarkers({}).map(m => m.getStartPosition().row)

  return ({activatePane = true} = {}) => {
    const pane = paneForItem(editor)
    if (!pane) return

    if (activatePane) pane.activate()
    pane.activateItem(editor)

    if (!editor.getCursorBufferPosition().isEqual(oldCursorPosition)) {
      editor.setCursorBufferPosition(oldCursorPosition)
    }
    for (const row of oldFoldStartRows.reverse()) {
      if (!editor.isFoldedAtBufferRow(row)) {
        editor.foldBufferRow(row)
      }
    }
    editor.element.setScrollTop(oldScrollTop)
  }
}

function requireFrom (pack, path) {
  const packPath = atom.packages.resolvePackagePath(pack)
  return require(`${packPath}/lib/${path}`)
}

function limitNumber (number, {max, min} = {}) {
  if (max != null) number = Math.min(number, max)
  if (min != null) number = Math.max(number, min)
  return number
}

function getCurrentWord (editor) {
  const selection = editor.getLastSelection()
  if (!selection.isEmpty()) {
    return selection.getText()
  } else {
    const point = selection.cursor.getBufferPosition()
    selection.selectWord()
    const text = selection.getText()
    selection.cursor.setBufferPosition(point)
    return text
  }
}

function isActiveEditor (editor) {
  return editor === atom.workspace.getActiveTextEditor()
}

function getValidIndexForList (list, index) {
  const length = list.length
  if (length === 0) return -1

  index = index % length
  return index >= 0 ? index : length + index
}

// Respect goalColumn when moving cursor.
function setBufferRow (cursor, row) {
  if (cursor.goalColumn == null) {
    cursor.goalColumn = cursor.getBufferColumn()
  }
  cursor.setBufferPosition([row, cursor.goalColumn])
}

function isTextEditor (item) {
  return atom.workspace.isTextEditor(item)
}

function paneForItem (item) {
  return atom.workspace.paneForItem(item)
}

function isNarrowEditor (editor) {
  return isTextEditor(editor) && editor.element.classList.contains('narrow-editor')
}

function getVisibleEditors () {
  return atom.workspace
    .getPanes()
    .map(pane => pane.getActiveEditor())
    .filter(editor => editor)
}

function getFirstCharacterPositionForBufferRow (editor, row) {
  let point
  editor.scanInBufferRange(/\S/, editor.bufferRangeForBufferRow(row), event => {
    point = event.range.start
  })
  return point || new Point(row, 0)
}

function cloneRegExp (regExp) {
  return new RegExp(regExp.source, regExp.flags)
}

// Utils used in Ui
// =========================

// item utils
// -------------------------
function getPrefixedTextLengthInfo (item) {
  const lineHeaderLength = item._lineHeader ? item._lineHeader.length : 0
  const truncationIndicatorLength = item._truncationIndicator ? item._truncationIndicator.length : 0
  const totalLength = lineHeaderLength + truncationIndicatorLength

  return {lineHeaderLength, truncationIndicatorLength, totalLength}
}

function compareByPoint (a, b) {
  return a.point.compare(b.point)
}

function toMB (num) {
  return Math.floor(num / (1024 * 1024))
}

const ignoreSubject = ['refresh']
function startMeasureMemory (subject, simple = true) {
  if (ignoreSubject.includes(subject)) {
    return () => {}
  }
  console.time(subject)

  const v8 = require('v8')
  const before = v8.getHeapStatistics()
  console.time(subject)
  return function () {
    const after = v8.getHeapStatistics()
    const diff = {}
    for (let key of Object.keys(before)) {
      diff[key] = after[key] - before[key]
    }

    console.info(`= ${subject}`)
    if (simple) {
      console.timeEnd(subject)
      // console.log("diff.used_heap_size", toMB(diff.used_heap_size))
    } else {
      const table = [before, after, diff]
      for (let result of table) {
        for (const key in result) {
          const value = result[key]
          result[key] = toMB(value)
        }
      }
      console.timeEnd(subject)
      console.table(table)
    }
  }
}

// If editor was given, return first project path which editor's path is contained.
function getProjectPaths (editor) {
  if (!editor) return atom.project.getPaths()

  const filePath = editor.getPath()
  if (filePath) {
    const dir = atom.project.getDirectories().find(dir => dir.contains(filePath))
    if (dir) return [dir.getPath()]
  }
  atom.notifications.addInfo('This file is not belonging to any project', {dismissable: true})
}

function suppressEvent (event) {
  if (event != null) {
    event.preventDefault()
    event.stopPropagation()
  }
}

function relativizeFilePath (filePath) {
  const [projectPath, relativeFilePath] = atom.project.relativizePath(filePath)
  return Path.join(Path.basename(projectPath), relativeFilePath)
}

function getMemoizedRelativizeFilePath () {
  const cache = {}
  return filePath => (cache[filePath] = cache[filePath] || relativizeFilePath(filePath))
}

function isExcludeFilter (text, negateByEndingExclamation) {
  return (text.length > 1 && text.startsWith('!')) || (negateByEndingExclamation && text.endsWith('!'))
}

function scanItemsForBuffer (buffer, regex) {
  const items = []
  const filePath = buffer.getPath()
  regex = cloneRegExp(regex)

  const lines = buffer.getLines()
  for (const row = 0; row < lines.length; row++) {
    let match
    const text = lines[row]
    regex.lastIndex = 0
    while ((match = regex.exec(text))) {
      const point = new Point(row, match.index)
      const range = new Range(point, [row, regex.lastIndex])
      items.push({text, point, range, filePath})
      // Avoid infinite loop in zero length match when regex is /^/
      if (!match[0]) break
    }
  }
  return items
}

function scanItemsForFilePath (filePath, regex) {
  return atom.workspace.open(filePath, {activateItem: false}).then(editor => {
    return scanItemsForBuffer(editor.buffer, regex)
  })
}

function parsePromptLine (promptLineText, {useFirstQueryAsSearchTerm, negateByEndingExclamation}) {
  let searchTerm, match

  const includeFilters = []
  const excludeFilters = []

  const regex = /\S+/g

  if (useFirstQueryAsSearchTerm) {
    regex.exec(promptLineText)
    searchTerm = new Range([0, 0], [0, regex.lastIndex])
  }

  while ((match = regex.exec(promptLineText))) {
    const range = new Range([0, match.index], [0, regex.lastIndex])
    const text = promptLineText.slice(match.index, regex.lastIndex)
    if (isExcludeFilter(text, negateByEndingExclamation)) {
      excludeFilters.push(range)
    } else {
      includeFilters.push(range)
    }
  }

  return {searchTerm, includeFilters, excludeFilters}
}

module.exports = {
  getAdjacentPane,
  getNextAdjacentPaneForPane,
  getPreviousAdjacentPaneForPane,
  splitPane,
  saveEditorState,
  requireFrom,
  limitNumber,
  getCurrentWord,
  isActiveEditor,
  getValidIndexForList,
  setBufferRow,

  isTextEditor,
  isNarrowEditor,
  paneForItem,
  getVisibleEditors,
  getFirstCharacterPositionForBufferRow,
  cloneRegExp,

  getPrefixedTextLengthInfo,
  compareByPoint,
  getProjectPaths,
  suppressEvent,
  startMeasureMemory,
  relativizeFilePath,
  getMemoizedRelativizeFilePath,
  isExcludeFilter,
  scanItemsForBuffer,
  scanItemsForFilePath,
  parsePromptLine
}
<commit_msg>revert mistake<commit_after>const Path = require('path')
const {Point, Range} = require('atom')

function getAdjacentPane (basePane, which) {
  const parent = basePane.getParent()
  if (parent && parent.getChildren) {
    const children = parent.getChildren()
    const index = children.indexOf(basePane) + (which === 'next' ? +1 : -1)
    const pane = children[index]
    if (pane && pane.constructor.name === 'Pane') {
      // Don't return PaneAxis, just Pane
      return pane
    }
  }
}

function getNextAdjacentPaneForPane (basePane) {
  return getAdjacentPane(basePane, 'next')
}

function getPreviousAdjacentPaneForPane (basePane) {
  return getAdjacentPane(basePane, 'previous')
}

function splitPane (basePane, {split}) {
  const paneWasActive = basePane.isActive()
  if (!['right', 'down'].includes(split)) {
    throw new Error('split must either of `right` or `down`')
  }
  const pane = split === 'right' ? basePane.splitRight() : basePane.splitDown()
  // Atom doesn't allow 'split' pane without activating it.
  // So re-activte basePane if it originally was active.
  if (paneWasActive && !basePane.isActive()) basePane.activate()
  return pane
}

function saveEditorState (editor) {
  const oldScrollTop = editor.element.getScrollTop()
  const oldCursorPosition = editor.getCursorBufferPosition()
  const oldFoldStartRows = editor.displayLayer.foldsMarkerLayer.findMarkers({}).map(m => m.getStartPosition().row)

  return ({activatePane = true} = {}) => {
    const pane = paneForItem(editor)
    if (!pane) return

    if (activatePane) pane.activate()
    pane.activateItem(editor)

    if (!editor.getCursorBufferPosition().isEqual(oldCursorPosition)) {
      editor.setCursorBufferPosition(oldCursorPosition)
    }
    for (const row of oldFoldStartRows.reverse()) {
      if (!editor.isFoldedAtBufferRow(row)) {
        editor.foldBufferRow(row)
      }
    }
    editor.element.setScrollTop(oldScrollTop)
  }
}

function requireFrom (pack, path) {
  const packPath = atom.packages.resolvePackagePath(pack)
  return require(`${packPath}/lib/${path}`)
}

function limitNumber (number, {max, min} = {}) {
  if (max != null) number = Math.min(number, max)
  if (min != null) number = Math.max(number, min)
  return number
}

function getCurrentWord (editor) {
  const selection = editor.getLastSelection()
  if (!selection.isEmpty()) {
    return selection.getText()
  } else {
    const point = selection.cursor.getBufferPosition()
    selection.selectWord()
    const text = selection.getText()
    selection.cursor.setBufferPosition(point)
    return text
  }
}

function isActiveEditor (editor) {
  return editor === atom.workspace.getActiveTextEditor()
}

function getValidIndexForList (list, index) {
  const length = list.length
  if (length === 0) return -1

  index = index % length
  return index >= 0 ? index : length + index
}

// Respect goalColumn when moving cursor.
function setBufferRow (cursor, row) {
  if (cursor.goalColumn == null) {
    cursor.goalColumn = cursor.getBufferColumn()
  }
  cursor.setBufferPosition([row, cursor.goalColumn])
}

function isTextEditor (item) {
  return atom.workspace.isTextEditor(item)
}

function paneForItem (item) {
  return atom.workspace.paneForItem(item)
}

function isNarrowEditor (editor) {
  return isTextEditor(editor) && editor.element.classList.contains('narrow-editor')
}

function getVisibleEditors () {
  return atom.workspace
    .getPanes()
    .map(pane => pane.getActiveEditor())
    .filter(editor => editor)
}

function getFirstCharacterPositionForBufferRow (editor, row) {
  let point
  editor.scanInBufferRange(/\S/, editor.bufferRangeForBufferRow(row), event => {
    point = event.range.start
  })
  return point || new Point(row, 0)
}

function cloneRegExp (regExp) {
  return new RegExp(regExp.source, regExp.flags)
}

// Utils used in Ui
// =========================

// item utils
// -------------------------
function getPrefixedTextLengthInfo (item) {
  const lineHeaderLength = item._lineHeader ? item._lineHeader.length : 0
  const truncationIndicatorLength = item._truncationIndicator ? item._truncationIndicator.length : 0
  const totalLength = lineHeaderLength + truncationIndicatorLength

  return {lineHeaderLength, truncationIndicatorLength, totalLength}
}

function compareByPoint (a, b) {
  return a.point.compare(b.point)
}

function toMB (num) {
  return Math.floor(num / (1024 * 1024))
}

const ignoreSubject = ['refresh']
function startMeasureMemory (subject, simple = true) {
  if (ignoreSubject.includes(subject)) {
    return () => {}
  }
  console.time(subject)

  const v8 = require('v8')
  const before = v8.getHeapStatistics()
  console.time(subject)
  return function () {
    const after = v8.getHeapStatistics()
    const diff = {}
    for (let key of Object.keys(before)) {
      diff[key] = after[key] - before[key]
    }

    console.info(`= ${subject}`)
    if (simple) {
      console.timeEnd(subject)
      // console.log("diff.used_heap_size", toMB(diff.used_heap_size))
    } else {
      const table = [before, after, diff]
      for (let result of table) {
        for (const key in result) {
          const value = result[key]
          result[key] = toMB(value)
        }
      }
      console.timeEnd(subject)
      console.table(table)
    }
  }
}

// If editor was given, return first project path which editor's path is contained.
function getProjectPaths (editor) {
  if (!editor) return atom.project.getPaths()

  const filePath = editor.getPath()
  if (filePath) {
    const dir = atom.project.getDirectories().find(dir => dir.contains(filePath))
    if (dir) return [dir.getPath()]
  }
  atom.notifications.addInfo('This file is not belonging to any project', {dismissable: true})
}

function suppressEvent (event) {
  if (event != null) {
    event.preventDefault()
    event.stopPropagation()
  }
}

function relativizeFilePath (filePath) {
  const [projectPath, relativeFilePath] = atom.project.relativizePath(filePath)
  return Path.join(Path.basename(projectPath), relativeFilePath)
}

function getMemoizedRelativizeFilePath () {
  const cache = {}
  return filePath => (cache[filePath] = cache[filePath] || relativizeFilePath(filePath))
}

function isExcludeFilter (text, negateByEndingExclamation) {
  return (text.length > 1 && text.startsWith('!')) || (negateByEndingExclamation && text.endsWith('!'))
}

function scanItemsForBuffer (buffer, regex) {
  const items = []
  const filePath = buffer.getPath()
  regex = cloneRegExp(regex)

  const lines = buffer.getLines()
  for (let row = 0; row < lines.length; row++) {
    let match
    const text = lines[row]
    regex.lastIndex = 0
    while ((match = regex.exec(text))) {
      const point = new Point(row, match.index)
      const range = new Range(point, [row, regex.lastIndex])
      items.push({text, point, range, filePath})
      // Avoid infinite loop in zero length match when regex is /^/
      if (!match[0]) break
    }
  }
  return items
}

function scanItemsForFilePath (filePath, regex) {
  return atom.workspace.open(filePath, {activateItem: false}).then(editor => {
    return scanItemsForBuffer(editor.buffer, regex)
  })
}

function parsePromptLine (promptLineText, {useFirstQueryAsSearchTerm, negateByEndingExclamation}) {
  let searchTerm, match

  const includeFilters = []
  const excludeFilters = []

  const regex = /\S+/g

  if (useFirstQueryAsSearchTerm) {
    regex.exec(promptLineText)
    searchTerm = new Range([0, 0], [0, regex.lastIndex])
  }

  while ((match = regex.exec(promptLineText))) {
    const range = new Range([0, match.index], [0, regex.lastIndex])
    const text = promptLineText.slice(match.index, regex.lastIndex)
    if (isExcludeFilter(text, negateByEndingExclamation)) {
      excludeFilters.push(range)
    } else {
      includeFilters.push(range)
    }
  }

  return {searchTerm, includeFilters, excludeFilters}
}

module.exports = {
  getAdjacentPane,
  getNextAdjacentPaneForPane,
  getPreviousAdjacentPaneForPane,
  splitPane,
  saveEditorState,
  requireFrom,
  limitNumber,
  getCurrentWord,
  isActiveEditor,
  getValidIndexForList,
  setBufferRow,

  isTextEditor,
  isNarrowEditor,
  paneForItem,
  getVisibleEditors,
  getFirstCharacterPositionForBufferRow,
  cloneRegExp,

  getPrefixedTextLengthInfo,
  compareByPoint,
  getProjectPaths,
  suppressEvent,
  startMeasureMemory,
  relativizeFilePath,
  getMemoizedRelativizeFilePath,
  isExcludeFilter,
  scanItemsForBuffer,
  scanItemsForFilePath,
  parsePromptLine
}
<|endoftext|>