<commit_before>/*
 * regnum2phyx.js: A tool for converting PhyloRegnum database dumps to Phyx files.
 *
 * Synopsis:
 *  regnum2phyx.js [JSON file to process] -o [directory to write files to]
 *
 * Note that existing files in the output directory will be silently overwritten,
 * so this script should usually be run on a new, empty output directory.
 *
 * PhyloRegnum can be accessed at http://app.phyloregnum.org
 */

// We currently produce Phyx files in v0.2.0 format. If this changes, please
// update this here.
const PHYX_CONTEXT_URL = 'http://www.phyloref.org/phyx.js/context/v0.2.0/phyx.json';

// Some constants for nomenclatural codes. We should really export these in Phyx
// (see https://github.com/phyloref/phyx.js/issues/44)
const NAME_IN_UNKNOWN_CODE = 'http://purl.obolibrary.org/obo/NOMEN_0000036';
const ICZN_NAME = 'http://purl.obolibrary.org/obo/NOMEN_0000107';
const ICN_NAME = 'http://purl.obolibrary.org/obo/NOMEN_0000109';
// const ICNP_NAME = 'http://purl.obolibrary.org/obo/NOMEN_0000110';
// const ICTV_NAME = 'http://purl.obolibrary.org/obo/NOMEN_0000111';

// Load necessary modules.
const fs = require('fs');
const path = require('path');
const yargs = require('yargs');
const {
  has, keys, pickBy, isEmpty,
} = require('lodash');

// Helper functions.
function convertAuthorsIntoStrings(authors, lastNameFirst = true) {
  // Given a list of authors with first_name, middle_name and last_name properties,
  // return a list of author names with a single name.
  //
  // We combine author names in two possible ways:
  //  If lastNameFirst is true: $last_name $first_name $middle_name
  //  If lastNameFirst is false: $first_name $middle_name $last_name
  // The middle name will be ignored if it is blank or not present.

  if (lastNameFirst) {
    // Use "last first middle".
    return authors.map(author => (
      `${author.last_name || ''}, ${author.first_name || ''}${
        ((has(author, 'middle_name') && author.middle_name.trim() !== '') ? ` ${author.middle_name}` : '')
      }`.trim()
    )).filter(name => name !== '');
  }

  // Use "first middle last".
  return authors.map(author => (
    `${author.first_name || ''} ${
      ((has(author, 'middle_name') && author.middle_name.trim() !== '') ? `${author.middle_name} ` : '')
    }${author.last_name || ''}`.trim()
  )).filter(name => name !== '');
}

function convertAuthorsIntoBibJSON(authors) {
  // Convert a list of authors in the Regnum dump format into a list of authors
  // in the BibJSON format (http://okfnlabs.org/bibjson/). Names without a
  // last name will be ignored.
  return authors
    .filter(author => author.last_name)
    .map(author => pickBy({ // lodash.pickBy will remove empty keys from the object.
      // We store the author name as first_name middle_name last_name
      name: convertAuthorsIntoStrings([author], false).join(),
      alternate: [
        // We store an alternate author name as last_name, first_name middle_name.
        convertAuthorsIntoStrings([author], true).join(),
      ],
      firstname: (author.first_name || '').trim(),
      lastname: (author.last_name || '').trim(),
      middlename: (author.middle_name || '').trim(),
    }));
}

function convertCitationsToBibJSON(citation) {
  // Convert a citation from its Regnum representation into the
  // BibJSON format (http://okfnlabs.org/bibjson/). We use this rather than
  // CSL-JSON (https://github.com/citation-style-language/schema) because it's
  // easier to set up. We might want to switch over to CSL-JSON
  // eventually (https://github.com/phyloref/clade-ontology/issues/69).

  if (!citation) return [];
  if (Array.isArray(citation)) {
    // If given an array of citation objects, convert each one separately.
    return citation.map(c => convertCitationsToBibJSON(c))
      .reduce((acc, val) => acc.concat(val), []);
  }

  // Do we have a title and a year? If not, skip this entry.
  if (
    citation.title === undefined || citation.title.trim() === ''
    || citation.year === undefined || citation.year.trim() === ''
  ) {
    return [];
  }

  // Assume a default type of 'article' if none was provided.
  let type = citation.citation_type || 'article';

  // Regnum uses 'journal' where BibJSON uses 'article'.
  if (type === 'journal') type = 'article';

  // In BibJSON, identifiers are kept separate from URLs.
  const identifiers = [];
  if (citation.doi) identifiers.push({ type: 'doi', id: citation.doi });

  const urls = [];
  if (citation.url) urls.push({ url: citation.url });

  // lodash.pickBy will remove empty keys from the object.
  const entry = pickBy({
    type,
    title: (citation.title || '').trim(),
    section_title: (citation.section_title || '').trim(),
    year: Number((citation.year || '')),
    edition: (citation.edition || '').trim(),
    authors: convertAuthorsIntoBibJSON(citation.authors),
    editors: convertAuthorsIntoBibJSON(citation.editors || []),
    series_editors: convertAuthorsIntoBibJSON(citation.series_editors || []),
    publisher: (citation.publisher || '').trim(),
    city: (citation.city || '').trim(),
    pages: (citation.pages || '').trim(),
    figure: (citation.figure || '').trim(),
    // keywords: (citation.keyword || '').trim(), -- We really don't need this.
    identifier: identifiers,
    link: urls,
  });

  if (type === 'book' || type === 'book_section') {
    // Add the ISBN to the entry itself.
    if (citation.isbn) {
      identifiers.push({ type: 'isbn', id: citation.isbn });
    }
  } else if (type === 'article') {
    // Identify journal identifiers, such as the
    const journalIdentifiers = [];
    if (citation.isbn) {
      journalIdentifiers.push({ type: 'isbn', id: citation.isbn });
    }
    if (citation.issn) {
      journalIdentifiers.push({ type: 'issn', id: citation.issn });
    }

    // In BibJSON (http://okfnlabs.org/bibjson/), the journal name, volume, number
    // and pages should go into the 'journal' object.
    entry.journal = pickBy({
      name: (citation.journal || '').trim(),
      volume: (citation.volume || '').trim(),
      number: (citation.number || '').trim(),
      identifier: journalIdentifiers,
    });

    // Since we've moved pages and ISBN into journal, we don't also need it in the main entry.
    if (has(entry, 'pages')) delete entry.pages;
  } else {
    process.stderr.write(`Unknown citation type: '${type}', using anyway.`);
  }

  return [entry];
}

// Command line application starts here.

// Read command-line arguments.
const argv = yargs
  .usage('Usage: $0 <JSON file to process> -o <directory to write files to>')
  .demandCommand(1, 1) // Make sure there's one and only one file to convert.
  .option('output-dir', {
    alias: 'o',
    describe: 'Directory to write Phyx files to',
  })
  .demandOption(['o'])
  .option('filenames', {
    describe: 'Choose the type of filenames to generate',
    choices: [
      'label',
      'number',
      'regnum-id',
    ],
  })
  .option('filename-prefix', {
    describe: 'Choose the prefix for the filename being generated',
    string: true,
  })
  .help('h')
  .alias('h', 'help')
  .argv;

// Read the database dump.
const dump = JSON.parse(fs.readFileSync(argv._[0], 'utf8'));

// This dump consists of multiple named phylogenetic clade definitions,
// each of which should be written out to a separate file.
const phyxProduced = {};
let countErrors = 0;

// Loop through all phylorefs in the database dump.
dump.forEach((entry, index) => {
  const phylorefLabel = entry.name.trim();

  // Make sure we don't have multiple phyloreferences with the same label, since
  // we name the file after the phyloreference being produced.
  if (has(phyxProduced, phylorefLabel)) {
    process.stderr.write(`Duplicate phyloreference label '${phylorefLabel}', skipping.`);
    countErrors += 1;
    return;
  }

  // We should never have an actual value for entry.citations.description; if we do,
  // we might be misinterpreting the input and should exit with an error.
  if (has(entry.citations, 'description')) {
    // The Regnum dumps contains citations marked "description" that are empty.
    // Since convertCitationsToBibJSON() removes citations that don't have a title and a
    // year, we can use it to check whether the "description" citation(s) are
    // empty or contain an actual citation. In the latter case, we throw an Error
    // so we fail with an error.
    const descriptionCitations = convertCitationsToBibJSON(entry.citations.description);

    if (descriptionCitations.length > 0) {
      throw new Error(`Citation of type 'description' found in entry: ${
        JSON.stringify(convertCitationsToBibJSON(entry.citations.definitional), null, 4)
      }`);
    }
  }

  // Create an object describing this phyloreference.
  const phylorefTemplate = pickBy({
    regnumId: entry.id,
    label: phylorefLabel,
    'dwc:scientificNameAuthorship': (convertAuthorsIntoStrings(entry.authors)).join(', '),
    'dwc:namePublishedIn': convertCitationsToBibJSON(entry.citations.preexisting),
    'obo:IAO_0000119': // IAO:definition source (http://purl.obolibrary.org/obo/IAO_0000119)
      convertCitationsToBibJSON(entry.citations.definitional),
    cladeDefinition: (entry.definition || '').trim(),
    internalSpecifiers: [],
    externalSpecifiers: [],
  });

  // Do we have any phylogenies to save?
  const primaryPhylogenyCitation = convertCitationsToBibJSON(entry.citations.primary_phylogeny).map(
    phylogeny => pickBy({ primaryPhylogenyCitation: phylogeny })
  );
  const phylogenyCitation = convertCitationsToBibJSON(entry.citations.phylogeny).map(
    phylogeny => pickBy({ phylogenyCitation: phylogeny })
  );
  const phylogenies = primaryPhylogenyCitation.concat(phylogenyCitation).filter(
    phylogeny => !isEmpty(phylogeny)
  );

  // Convert each specifier into one
  (entry.specifiers || []).forEach((specifier) => {
    const kind = specifier.specifier_kind || '';
    let addTo = [];
    if (kind.startsWith('internal')) addTo = phylorefTemplate.internalSpecifiers;
    else if (kind.startsWith('external')) addTo = phylorefTemplate.externalSpecifiers;
    else {
      process.stderr.write(`Unknown specifier type: '${kind}' for phyloreference '${phylorefLabel}'.\n`);
      countErrors += 1;
    }

    // Set up specifier name, authorship and nomenclatural code.
    const specifierName = (specifier.specifier_name || '').trim();
    const specifierAuthors = (specifier.displayAuths || '').trim();
    const specifierCode = (specifier.specifier_code || '').trim();

    let nomenCode = NAME_IN_UNKNOWN_CODE;
    // As of the May 14, 2020 Regnum dump, only ICZN and ICBN
    // are used as pre-existing codes. I'll add ICNP and ICTV
    // as well once we need them.
    switch (specifierCode) {
      case 'ICZN':
        nomenCode = ICZN_NAME;
        break;
      case 'ICBN':
        nomenCode = ICN_NAME;
        break;
      case '':
        nomenCode = NAME_IN_UNKNOWN_CODE;
        break;
      default:
        throw new Error(`Unknown specifier_code: '${specifierCode}'`);
    }

    // Do we have authors? If so, incorporate them into the specifier authors.
    // Otherwise, just use the year.
    let specifierAuthority = (specifier.specifier_year || '').trim();
    if (specifierAuthors.length > 0) {
      specifierAuthority = `${specifierAuthors}, ${(specifier.specifier_year || '').trim()}`;
    }

    // TODO: split name into genus/specifier.

    // Write out a scientificName that includes the specifier name as well as its
    // nomenclatural authority, if present.
    const scname = `${specifierName} ${specifierAuthority}`.trim();
    const specifierTemplate = {
      '@type': 'http://rs.tdwg.org/ontology/voc/TaxonConcept#TaxonConcept',
      hasName: {
        '@type': 'http://rs.tdwg.org/ontology/voc/TaxonName#TaxonName',
        nomenclaturalCode: nomenCode,
        label: scname,
        nameComplete: specifierName,
      },
    };

    addTo.push(specifierTemplate);
  });

  // Prepare a simple Phyx file template.
  const phyxTemplate = pickBy({
    '@context': PHYX_CONTEXT_URL,
    phylogenies,
    phylorefs: [phylorefTemplate],
  });

  // Write out Phyx file for this phyloreference.
  const fileIndex = `${index + 1}`.padStart(6, '0');
  const filePrefix = argv.filenamePrefix || '';
  let phyxFilename;
  if (argv.filenames === 'label') {
    // Use the phyloref label.
    phyxFilename = path.join(argv.outputDir, `${phylorefLabel}.json`);
  } else if (argv.filenames === 'regnum-id') {
    // Use the regnum id.
    if (entry.id) phyxFilename = path.join(argv.outputDir, `REGNUM_${(`${entry.id}`).padStart(6, '0')}.json`);
    else phyxFilename = path.join(argv.outputDir, `${filePrefix}${fileIndex}.json`);
  } else {
    // Default to just the number of the sequence.
    phyxFilename = path.join(argv.outputDir, `${filePrefix}${fileIndex}.json`);
  }
  fs.writeFileSync(phyxFilename, JSON.stringify(phyxTemplate, null, 4));

  // Save for later use if needed.
  phyxProduced[phylorefLabel] = entry;
});

// If there were any errors, report this and exit with a failure code.
if (countErrors > 0) {
  process.stderr.write(`${countErrors} errors occurred while processing database dump.\n`);
  process.exit(1);
} else {
  process.stdout.write(`${keys(phyxProduced).length} Phyx files produced successfully.\n`);
}
<commit_msg>Modified regnum2phyx.js to use labels for filenames by default.<commit_after>/*
 * regnum2phyx.js: A tool for converting PhyloRegnum database dumps to Phyx files.
 *
 * Synopsis:
 *  regnum2phyx.js [JSON file to process] -o [directory to write files to]
 *
 * Note that existing files in the output directory will be silently overwritten,
 * so this script should usually be run on a new, empty output directory.
 *
 * PhyloRegnum can be accessed at http://app.phyloregnum.org
 */

// We currently produce Phyx files in v0.2.0 format. If this changes, please
// update this here.
const PHYX_CONTEXT_URL = 'http://www.phyloref.org/phyx.js/context/v0.2.0/phyx.json';

// Some constants for nomenclatural codes. We should really export these in Phyx
// (see https://github.com/phyloref/phyx.js/issues/44)
const NAME_IN_UNKNOWN_CODE = 'http://purl.obolibrary.org/obo/NOMEN_0000036';
const ICZN_NAME = 'http://purl.obolibrary.org/obo/NOMEN_0000107';
const ICN_NAME = 'http://purl.obolibrary.org/obo/NOMEN_0000109';
// const ICNP_NAME = 'http://purl.obolibrary.org/obo/NOMEN_0000110';
// const ICTV_NAME = 'http://purl.obolibrary.org/obo/NOMEN_0000111';

// Load necessary modules.
const fs = require('fs');
const path = require('path');
const yargs = require('yargs');
const {
  has, keys, pickBy, isEmpty,
} = require('lodash');

// Helper functions.
function convertAuthorsIntoStrings(authors, lastNameFirst = true) {
  // Given a list of authors with first_name, middle_name and last_name properties,
  // return a list of author names with a single name.
  //
  // We combine author names in two possible ways:
  //  If lastNameFirst is true: $last_name $first_name $middle_name
  //  If lastNameFirst is false: $first_name $middle_name $last_name
  // The middle name will be ignored if it is blank or not present.

  if (lastNameFirst) {
    // Use "last first middle".
    return authors.map(author => (
      `${author.last_name || ''}, ${author.first_name || ''}${
        ((has(author, 'middle_name') && author.middle_name.trim() !== '') ? ` ${author.middle_name}` : '')
      }`.trim()
    )).filter(name => name !== '');
  }

  // Use "first middle last".
  return authors.map(author => (
    `${author.first_name || ''} ${
      ((has(author, 'middle_name') && author.middle_name.trim() !== '') ? `${author.middle_name} ` : '')
    }${author.last_name || ''}`.trim()
  )).filter(name => name !== '');
}

function convertAuthorsIntoBibJSON(authors) {
  // Convert a list of authors in the Regnum dump format into a list of authors
  // in the BibJSON format (http://okfnlabs.org/bibjson/). Names without a
  // last name will be ignored.
  return authors
    .filter(author => author.last_name)
    .map(author => pickBy({ // lodash.pickBy will remove empty keys from the object.
      // We store the author name as first_name middle_name last_name
      name: convertAuthorsIntoStrings([author], false).join(),
      alternate: [
        // We store an alternate author name as last_name, first_name middle_name.
        convertAuthorsIntoStrings([author], true).join(),
      ],
      firstname: (author.first_name || '').trim(),
      lastname: (author.last_name || '').trim(),
      middlename: (author.middle_name || '').trim(),
    }));
}

function convertCitationsToBibJSON(citation) {
  // Convert a citation from its Regnum representation into the
  // BibJSON format (http://okfnlabs.org/bibjson/). We use this rather than
  // CSL-JSON (https://github.com/citation-style-language/schema) because it's
  // easier to set up. We might want to switch over to CSL-JSON
  // eventually (https://github.com/phyloref/clade-ontology/issues/69).

  if (!citation) return [];
  if (Array.isArray(citation)) {
    // If given an array of citation objects, convert each one separately.
    return citation.map(c => convertCitationsToBibJSON(c))
      .reduce((acc, val) => acc.concat(val), []);
  }

  // Do we have a title and a year? If not, skip this entry.
  if (
    citation.title === undefined || citation.title.trim() === ''
    || citation.year === undefined || citation.year.trim() === ''
  ) {
    return [];
  }

  // Assume a default type of 'article' if none was provided.
  let type = citation.citation_type || 'article';

  // Regnum uses 'journal' where BibJSON uses 'article'.
  if (type === 'journal') type = 'article';

  // In BibJSON, identifiers are kept separate from URLs.
  const identifiers = [];
  if (citation.doi) identifiers.push({ type: 'doi', id: citation.doi });

  const urls = [];
  if (citation.url) urls.push({ url: citation.url });

  // lodash.pickBy will remove empty keys from the object.
  const entry = pickBy({
    type,
    title: (citation.title || '').trim(),
    section_title: (citation.section_title || '').trim(),
    year: Number((citation.year || '')),
    edition: (citation.edition || '').trim(),
    authors: convertAuthorsIntoBibJSON(citation.authors),
    editors: convertAuthorsIntoBibJSON(citation.editors || []),
    series_editors: convertAuthorsIntoBibJSON(citation.series_editors || []),
    publisher: (citation.publisher || '').trim(),
    city: (citation.city || '').trim(),
    pages: (citation.pages || '').trim(),
    figure: (citation.figure || '').trim(),
    // keywords: (citation.keyword || '').trim(), -- We really don't need this.
    identifier: identifiers,
    link: urls,
  });

  if (type === 'book' || type === 'book_section') {
    // Add the ISBN to the entry itself.
    if (citation.isbn) {
      identifiers.push({ type: 'isbn', id: citation.isbn });
    }
  } else if (type === 'article') {
    // Identify journal identifiers, such as the
    const journalIdentifiers = [];
    if (citation.isbn) {
      journalIdentifiers.push({ type: 'isbn', id: citation.isbn });
    }
    if (citation.issn) {
      journalIdentifiers.push({ type: 'issn', id: citation.issn });
    }

    // In BibJSON (http://okfnlabs.org/bibjson/), the journal name, volume, number
    // and pages should go into the 'journal' object.
    entry.journal = pickBy({
      name: (citation.journal || '').trim(),
      volume: (citation.volume || '').trim(),
      number: (citation.number || '').trim(),
      identifier: journalIdentifiers,
    });

    // Since we've moved pages and ISBN into journal, we don't also need it in the main entry.
    if (has(entry, 'pages')) delete entry.pages;
  } else {
    process.stderr.write(`Unknown citation type: '${type}', using anyway.`);
  }

  return [entry];
}

// Command line application starts here.

// Read command-line arguments.
const argv = yargs
  .usage('Usage: $0 <JSON file to process> -o <directory to write files to>')
  .demandCommand(1, 1) // Make sure there's one and only one file to convert.
  .option('output-dir', {
    alias: 'o',
    describe: 'Directory to write Phyx files to',
  })
  .demandOption(['o'])
  .option('filenames', {
    describe: 'Choose the type of filenames to generate',
    default: 'label',
    choices: [
      'label',
      'number',
      'regnum-id',
    ],
  })
  .option('filename-prefix', {
    describe: 'Choose the prefix for the filename being generated',
    string: true,
  })
  .help('h')
  .alias('h', 'help')
  .argv;

// Read the database dump.
const dump = JSON.parse(fs.readFileSync(argv._[0], 'utf8'));

// This dump consists of multiple named phylogenetic clade definitions,
// each of which should be written out to a separate file.
const phyxProduced = {};
let countErrors = 0;

// Loop through all phylorefs in the database dump.
dump.forEach((entry, index) => {
  const phylorefLabel = entry.name.trim();

  // Make sure we don't have multiple phyloreferences with the same label, since
  // we name the file after the phyloreference being produced.
  if (has(phyxProduced, phylorefLabel)) {
    process.stderr.write(`Duplicate phyloreference label '${phylorefLabel}', skipping.`);
    countErrors += 1;
    return;
  }

  // We should never have an actual value for entry.citations.description; if we do,
  // we might be misinterpreting the input and should exit with an error.
  if (has(entry.citations, 'description')) {
    // The Regnum dumps contains citations marked "description" that are empty.
    // Since convertCitationsToBibJSON() removes citations that don't have a title and a
    // year, we can use it to check whether the "description" citation(s) are
    // empty or contain an actual citation. In the latter case, we throw an Error
    // so we fail with an error.
    const descriptionCitations = convertCitationsToBibJSON(entry.citations.description);

    if (descriptionCitations.length > 0) {
      throw new Error(`Citation of type 'description' found in entry: ${
        JSON.stringify(convertCitationsToBibJSON(entry.citations.definitional), null, 4)
      }`);
    }
  }

  // Create an object describing this phyloreference.
  const phylorefTemplate = pickBy({
    regnumId: entry.id,
    label: phylorefLabel,
    'dwc:scientificNameAuthorship': (convertAuthorsIntoStrings(entry.authors)).join(', '),
    'dwc:namePublishedIn': convertCitationsToBibJSON(entry.citations.preexisting),
    'obo:IAO_0000119': // IAO:definition source (http://purl.obolibrary.org/obo/IAO_0000119)
      convertCitationsToBibJSON(entry.citations.definitional),
    cladeDefinition: (entry.definition || '').trim(),
    internalSpecifiers: [],
    externalSpecifiers: [],
  });

  // Do we have any phylogenies to save?
  const primaryPhylogenyCitation = convertCitationsToBibJSON(entry.citations.primary_phylogeny).map(
    phylogeny => pickBy({ primaryPhylogenyCitation: phylogeny })
  );
  const phylogenyCitation = convertCitationsToBibJSON(entry.citations.phylogeny).map(
    phylogeny => pickBy({ phylogenyCitation: phylogeny })
  );
  const phylogenies = primaryPhylogenyCitation.concat(phylogenyCitation).filter(
    phylogeny => !isEmpty(phylogeny)
  );

  // Convert each specifier into one
  (entry.specifiers || []).forEach((specifier) => {
    const kind = specifier.specifier_kind || '';
    let addTo = [];
    if (kind.startsWith('internal')) addTo = phylorefTemplate.internalSpecifiers;
    else if (kind.startsWith('external')) addTo = phylorefTemplate.externalSpecifiers;
    else {
      process.stderr.write(`Unknown specifier type: '${kind}' for phyloreference '${phylorefLabel}'.\n`);
      countErrors += 1;
    }

    // Set up specifier name, authorship and nomenclatural code.
    const specifierName = (specifier.specifier_name || '').trim();
    const specifierAuthors = (specifier.displayAuths || '').trim();
    const specifierCode = (specifier.specifier_code || '').trim();

    let nomenCode = NAME_IN_UNKNOWN_CODE;
    // As of the May 14, 2020 Regnum dump, only ICZN and ICBN
    // are used as pre-existing codes. I'll add ICNP and ICTV
    // as well once we need them.
    switch (specifierCode) {
      case 'ICZN':
        nomenCode = ICZN_NAME;
        break;
      case 'ICBN':
        nomenCode = ICN_NAME;
        break;
      case '':
        nomenCode = NAME_IN_UNKNOWN_CODE;
        break;
      default:
        throw new Error(`Unknown specifier_code: '${specifierCode}'`);
    }

    // Do we have authors? If so, incorporate them into the specifier authors.
    // Otherwise, just use the year.
    let specifierAuthority = (specifier.specifier_year || '').trim();
    if (specifierAuthors.length > 0) {
      specifierAuthority = `${specifierAuthors}, ${(specifier.specifier_year || '').trim()}`;
    }

    // TODO: split name into genus/specifier.

    // Write out a scientificName that includes the specifier name as well as its
    // nomenclatural authority, if present.
    const scname = `${specifierName} ${specifierAuthority}`.trim();
    const specifierTemplate = {
      '@type': 'http://rs.tdwg.org/ontology/voc/TaxonConcept#TaxonConcept',
      hasName: {
        '@type': 'http://rs.tdwg.org/ontology/voc/TaxonName#TaxonName',
        nomenclaturalCode: nomenCode,
        label: scname,
        nameComplete: specifierName,
      },
    };

    addTo.push(specifierTemplate);
  });

  // Prepare a simple Phyx file template.
  const phyxTemplate = pickBy({
    '@context': PHYX_CONTEXT_URL,
    phylogenies,
    phylorefs: [phylorefTemplate],
  });

  // Write out Phyx file for this phyloreference.
  const fileIndex = `${index + 1}`.padStart(6, '0');
  const filePrefix = argv.filenamePrefix || '';
  let phyxFilename;
  if (argv.filenames === 'label') {
    // Use the phyloref label.
    phyxFilename = path.join(argv.outputDir, `${phylorefLabel}.json`);
  } else if (argv.filenames === 'regnum-id') {
    // Use the regnum id.
    if (entry.id) phyxFilename = path.join(argv.outputDir, `REGNUM_${(`${entry.id}`).padStart(6, '0')}.json`);
    else phyxFilename = path.join(argv.outputDir, `${filePrefix}${fileIndex}.json`);
  } else {
    // Default to just the number of the sequence.
    phyxFilename = path.join(argv.outputDir, `${filePrefix}${fileIndex}.json`);
  }
  fs.writeFileSync(phyxFilename, JSON.stringify(phyxTemplate, null, 4));

  // Save for later use if needed.
  phyxProduced[phylorefLabel] = entry;
});

// If there were any errors, report this and exit with a failure code.
if (countErrors > 0) {
  process.stderr.write(`${countErrors} errors occurred while processing database dump.\n`);
  process.exit(1);
} else {
  process.stdout.write(`${keys(phyxProduced).length} Phyx files produced successfully.\n`);
}
<|endoftext|>


<commit_before>// @flow
export type Action =
{
	type: 'RECEIVE_WORKS',
	id: number,
	works?: Array<number>
} | {
	type: 'ADD_COLUMN',
	id: number,
	title: string,
	query: {
		type: string,
		q?: string,
		opts: Object
	}
} | {
	type: 'NEXT_PAGE',
	id: number
} | {
	type: 'CLOSE_COLUMN',
	id: number
}
;

export type query = {
	type: string,
	q?: string,
	opts: {
		mode?: string,
		publicity?: 'public' | 'private',
		page: number
	}
}

export function nextPage(id: number): Action {
	return {
		type: 'NEXT_PAGE',
		id
	};
}

export function addColumn(query: query, title: string = ''): Action {
	const id = Date.now();
	return {
		type: 'ADD_COLUMN',
		id,
		title,
		query
	};
}

export function closeColumn(id: number): Action {
	return {
		type: 'CLOSE_COLUMN',
		id
	};
}
<commit_msg>alias<commit_after>// @flow
export type ColumnAction =
{
	type: 'RECEIVE_WORKS',
	id: number,
	works?: Array<number>
} | {
	type: 'ADD_COLUMN',
	id: number,
	title: string,
	query: {
		type: string,
		q?: string,
		opts: Object
	}
} | {
	type: 'NEXT_PAGE',
	id: number
} | {
	type: 'CLOSE_COLUMN',
	id: number
}
;

type Action = ColumnAction;

export type query = {
	type: string,
	q?: string,
	opts: {
		mode?: string,
		publicity?: 'public' | 'private',
		page: number
	}
};

export function nextPage(id: number): Action {
	return {
		type: 'NEXT_PAGE',
		id
	};
}

export function addColumn(query: query, title: string = ''): Action {
	const id = Date.now();
	return {
		type: 'ADD_COLUMN',
		id,
		title,
		query
	};
}

export function closeColumn(id: number): Action {
	return {
		type: 'CLOSE_COLUMN',
		id
	};
}
<|endoftext|>


<commit_before>// @flow
import {ipcRenderer} from 'electron'; // eslint-disable-line import/no-extraneous-dependencies
import {camelizeKeys} from 'humps';
import {normalize} from 'normalizr';
import type {Store, Dispatch} from '../types';
import Schemas from '../schemas';

function format(res: Object) {
	const camelizedJson = camelizeKeys(res);
	return normalize(camelizedJson, Schemas.WORK_ARRAY);
}

export default (store: Store) => {
	const dispatch: Dispatch = store.dispatch;

	function send(id: number, response: Object) {
		dispatch({type: 'SUCCESS_IPC_REQUEST', response});
		dispatch({type: 'RECIEVE_WORKS', id, works: response.result});
	}

	ipcRenderer.on('SUCCESS_LOGINED', () => {
		dispatch({type: 'SUCCESS_LOGINED'});
	});

	ipcRenderer.on('ranking', (ev, data) => {
		const res = data.res.works.map(v => v.work);
		send(data.id, format(res));
	});

	ipcRenderer.on('favoriteWorks', (ev, data) => {
		const res = data.res.response.map(v => v.work);
		send(data.id, format(res));
	});

	ipcRenderer.on('search', (ev, data) => {
		const res = data.res.response;
		send(data.id, format(res));
	});

	ipcRenderer.on('work', (ev, data) => {
		const res = data.response[0];
		const camelizedJson = camelizeKeys(res);
		const normalizedJson = normalize(camelizedJson, Schemas.WORK);
		dispatch({type: 'SUCCESS_IPC_REQUEST', response: normalizedJson});
	});
};
<commit_msg>:beer:<commit_after>// @flow
import {ipcRenderer} from 'electron'; // eslint-disable-line import/no-extraneous-dependencies
import {camelizeKeys} from 'humps';
import {normalize} from 'normalizr';
import type {Store, Dispatch} from '../types';
import Schemas from '../schemas';

function format(res: Object) {
	const camelizedJson = camelizeKeys(res);
	return normalize(camelizedJson, Schemas.WORK_ARRAY);
}

export default (store: Store) => {
	const dispatch: Dispatch = store.dispatch;

	function send(id: number, response: Object) {
		setImmediate(() => {
			dispatch({type: 'SUCCESS_IPC_REQUEST', response});
		});
		setImmediate(() => {
			dispatch({type: 'RECIEVE_WORKS', id, works: response.result});
		});
	}

	ipcRenderer.on('SUCCESS_LOGINED', () => {
		dispatch({type: 'SUCCESS_LOGINED'});
	});

	ipcRenderer.on('ranking', (ev, data) => {
		const res = data.res.works.map(v => v.work);
		send(data.id, format(res));
	});

	ipcRenderer.on('favoriteWorks', (ev, data) => {
		const res = data.res.response.map(v => v.work);
		send(data.id, format(res));
	});

	ipcRenderer.on('search', (ev, data) => {
		const res = data.res.response;
		send(data.id, format(res));
	});

	ipcRenderer.on('work', (ev, data) => {
		const res = data.response[0];
		const camelizedJson = camelizeKeys(res);
		const normalizedJson = normalize(camelizedJson, Schemas.WORK);
		dispatch({type: 'SUCCESS_IPC_REQUEST', response: normalizedJson});
	});
};
<|endoftext|>


<commit_before>'use strict';

var path = require('path');
var autoprefixer = require('autoprefixer');
var WebpackNotifierPlugin = require('webpack-notifier');

module.exports = {
  devtool: 'source-map',
  context: __dirname,
  entry: './app.jsx',
  output: {
    path: __dirname,
    filename: 'bundle.js',
    publicPath: '/assets/'
  },
  plugins: [
    new WebpackNotifierPlugin({alwaysNotify: true})
  ],
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: 'node-modules/',
        loaders: ['babel', __dirname + '/html-pre-tag-loader']
      },
      {
        test: /\.css$/,
        loader: [
          'style-loader',
          'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
          'postcss-loader'
        ].join('!')
      }
    ]
  },
  postcss: [autoprefixer],
  resolve: {
    extensions: ['', '.js', '.jsx']
  },
  stats: false,
  progress: true
};
<commit_msg>i like stats<commit_after>'use strict';

var path = require('path');
var autoprefixer = require('autoprefixer');
var WebpackNotifierPlugin = require('webpack-notifier');

module.exports = {
  devtool: 'source-map',
  context: __dirname,
  entry: './app.jsx',
  output: {
    path: __dirname,
    filename: 'bundle.js',
    publicPath: '/assets/'
  },
  plugins: [
    new WebpackNotifierPlugin({alwaysNotify: true})
  ],
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: 'node-modules/',
        loaders: ['babel', __dirname + '/html-pre-tag-loader']
      },
      {
        test: /\.css$/,
        loader: [
          'style-loader',
          'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
          'postcss-loader'
        ].join('!')
      }
    ]
  },
  postcss: [autoprefixer],
  resolve: {
    extensions: ['', '.js', '.jsx']
  },
  progress: true
};
<|endoftext|>


<commit_before>import fs from 'fs'
import path from 'path'

/**
 * File-based ACLs
 * @param {Object} config
 * @param {String} config.path
 * @returns {{get:get, isLoaded:isLoaded, load: load}}
 */
export default function(config) {
  const file = path.resolve(config.path)
  let list
  let loaded = false

  /**
   * Load ACL file
   * @param {Function} cb
   */
  function load(cb) {
    fs.readFile(file, function(err, data) {
      if (err) {
        return cb(err)
      }

      try {
        list = JSON.parse(data)
        loaded = true
        cb(err, list)
      } catch (e) {
        cb(e)
      }
    })
  }

  /**
   * Load ACL file synchronous
   * @returns Object
   */
  function loadSync() {
    const data = fs.readFileSync(file, 'utf8')
    try {
      list = JSON.parse(data)
      loaded = true

      return list
    } catch(e) {
      throw new Error('Cannot parse ACL data')
    }
  }

  /**
   * Get list
   * @returns {Object}
   */
  function get() {
    return list
  }

  /**
   * Is loaded
   * @returns {boolean}
   */
  function isLoaded() {
    return loaded
  }

  return Object.freeze({
    get,
    isLoaded,
    load,
    loadSync
  })
}
<commit_msg>feat: bypass ACL loading if already loaded<commit_after>import fs from 'fs'
import path from 'path'

/**
 * File-based ACLs
 * @param {Object} config
 * @param {String} config.path
 * @returns {{get:get, isLoaded:isLoaded, load: load}}
 */
export default function(config) {
  const file = path.resolve(config.path)
  let list
  let loaded = false

  /**
   * Load ACL file
   * @param {Function} cb
   */
  function load(cb) {
    if (loaded) {
      return cb()
    }

    fs.readFile(file, function(err, data) {
      if (err) {
        return cb(err)
      }

      try {
        list = JSON.parse(data)
        loaded = true
        cb(err, list)
      } catch (e) {
        cb(e)
      }
    })
  }

  /**
   * Load ACL file synchronous
   * @returns Object
   */
  function loadSync() {
    const data = fs.readFileSync(file, 'utf8')
    try {
      list = JSON.parse(data)
      loaded = true

      return list
    } catch(e) {
      throw new Error('Cannot parse ACL data')
    }
  }

  /**
   * Get list
   * @returns {Object}
   */
  function get() {
    return list
  }

  /**
   * Is loaded
   * @returns {boolean}
   */
  function isLoaded() {
    return loaded
  }

  return Object.freeze({
    get,
    isLoaded,
    load,
    loadSync
  })
}
<|endoftext|>


<commit_before>/*
 * ------------------------------------------
 * 项目Regualar模块基类实现文件
 * @author   sensen(hzzhaoyusen@corp.netease.com)
 * ------------------------------------------
 */
/** @module util/dispatcher/regularModule */
NEJ.define([
    'base/klass',
    'base/event',
    'util/template/tpl',
    'util/dispatcher/module',
],function(_k,_v,_t,_m,_p){
    var _pro;
    /**
     * Regular模块基类对象
     *
     * @class   {_$$RegularModule}
     * @extends {_$$ModuleAbstract}
     */
    _p._$$RegularModule = _k._$klass();
    _pro = _p._$$RegularModule._$extend(_m._$$ModuleAbstract);
    /**
     * 构建Regular组件
     *
     * @protected
     * @method module:util/dispatcher/regularModule._$$RegularModule#__build
     * @return {Void}
     */
    _pro.__build = function(){
        this._$innerModule = new this._$$InnerModule().$inject(this.__body);
        this.__export.parent = this._$innerModule.$refs.view;

        this._$innerModule.__doSendMessage = this.__doSendMessage._$bind(this);
    }
    /**
     * 控件初始化
     *
     * @protected
     * @method module:util/dispatcher/regularModule._$$RegularModule#__init
     * @return {Void}
     */
    _pro.__init = function(){
        this.__super();
        this.__nodeKey = _t._$addNodeTemplate('

');
        this.__body = _t._$getNodeTemplate(this.__nodeKey);

        this.__build();
    }
    /**
     * 显示模块触发事件，子类可重写具体逻辑
     *
     * @protected
     * @method module:util/dispatcher/regularModule._$$RegularModule#__onShow
     * @param  {Object} arg0 - 事件对象
     * @return {Void}
     */
    _pro.__onShow = function(_options){
        // 如果Regular组件被destroy了，就重新构建
        if(this._$innerModule._watchers === null)
            this.__build();

        this.__super(_options);
        if(this._$innerModule.__onShow){
            this._$innerModule.data.$param = _options.param;
            this._$innerModule.data.$params = _options.href.split('?')[1];
            this._$innerModule.__onShow(_options);
            this._$innerModule.$update();
        }
    }
    /**
     * 显示模块触发事件，子类实现具体逻辑
     *
     * @protected
     * @method module:util/dispatcher/regularModule._$$RegularModule#__onRefresh
     * @param  {Object} arg0 - 事件对象
     * @return {Void}
     */
    _pro.__onRefresh = function(_options){
        this.__super(_options);
        if(this._$innerModule.__onRefresh){
            this._$innerModule.data.$param = _options.param;
            this._$innerModule.data.$params = _options.href.split('?')[1];
            this._$innerModule.__onRefresh(_options);
            this._$innerModule.$update();
        }
    }
    /**
     * 接受到消息触发事件，子类实现具体逻辑
     *
     * @abstract
     * @method module:util/dispatcher/regularModule._$$RegularModule#__onMessage
     * @param  {Object} arg0 - 事件对象
     * @return {Void}
     */
    _pro.__onMessage = function(_options){
        this.__super(_options);
        if(this._$innerModule.__onMessage){
            this._$innerModule.__onMessage(_options);
            this._$innerModule.$update();
        }
    }
    /**
     * 模块退出前触发事件，通过阻止输入的事件做退出验证，子类实现具体逻辑
     *
     * @abstract
     * @method module:util/dispatcher/regularModule._$$RegularModule#__onBeforeHide
     * @param  {Object} arg0 - 事件对象
     * @return {Void}
     */
    _pro.__onBeforeHide = function(_options){
        this.__super(_options);
        if(this._$innerModule.__onBeforeHide){
            this._$innerModule.__onBeforeHide(_options);
            this._$innerModule.$update();
        }
    }
    /**
     * 隐藏模块触发事件，子类实现具体逻辑
     *
     * @protected
     * @method module:util/dispatcher/regularModule._$$RegularModule#__onHide
     * @return {Void}
     */
    _pro.__onHide = function(){
        this.__super();
        if(this._$innerModule.__onHide){
            this._$innerModule.__onHide();
            this._$innerModule.$update();
        }
    }
    /**
     * 控件销毁
     *
     * @protected
     * @method module:util/dispatcher/regularModule._$$RegularModule#__destroy
     * @return {Void}
     */
    _pro.__destroy = function(){
        this.__body = _t._$getNodeTemplate(this.__nodeKey);
        this._$innerModule.destroy();
        delete this.__nodeKey;
        this.__super();
    };

    /**
     * 注册RegularModule
     *
     * @public
     * @method module:util/dispatcher/regularModule._$build
     * @param  {String}  arg0 - 模块UMI或者别名
     * @param  {Regular} arg1 - Regular组件模块
     * @return {_$$RegularModule} RegularModule模块
     */
    _p._$regist = function(_umiAlias, _$$InnerModule){
        /**
         * Regular模块对象
         *
         * @class   {_$$OuterModule}
         * @extends {_$$RegularModule}
         */
        var _$$OuterModule = _k._$klass();
        var _pro = _$$OuterModule._$extend(_p._$$RegularModule);

        _pro.__init = function(){
            this.__umiAlias = _umiAlias;
            this._$$InnerModule = _$$InnerModule;
            this.__super();
        }

        _v._$addEvent(document, 'templateready', function(){
            _m._$regist(_umiAlias, _$$OuterModule);
        });

        return _$$OuterModule;
    }

    return _p;
});<commit_msg>remove "," not used<commit_after>/*
 * ------------------------------------------
 * 项目Regualar模块基类实现文件
 * @author   sensen(hzzhaoyusen@corp.netease.com)
 * ------------------------------------------
 */
/** @module util/dispatcher/regularModule */
NEJ.define([
    'base/klass',
    'base/event',
    'util/template/tpl',
    'util/dispatcher/module'
],function(_k,_v,_t,_m,_p){
    var _pro;
    /**
     * Regular模块基类对象
     *
     * @class   {_$$RegularModule}
     * @extends {_$$ModuleAbstract}
     */
    _p._$$RegularModule = _k._$klass();
    _pro = _p._$$RegularModule._$extend(_m._$$ModuleAbstract);
    /**
     * 构建Regular组件
     *
     * @protected
     * @method module:util/dispatcher/regularModule._$$RegularModule#__build
     * @return {Void}
     */
    _pro.__build = function(){
        this._$innerModule = new this._$$InnerModule().$inject(this.__body);
        this.__export.parent = this._$innerModule.$refs.view;

        this._$innerModule.__doSendMessage = this.__doSendMessage._$bind(this);
    }
    /**
     * 控件初始化
     *
     * @protected
     * @method module:util/dispatcher/regularModule._$$RegularModule#__init
     * @return {Void}
     */
    _pro.__init = function(){
        this.__super();
        this.__nodeKey = _t._$addNodeTemplate('

');
        this.__body = _t._$getNodeTemplate(this.__nodeKey);

        this.__build();
    }
    /**
     * 显示模块触发事件，子类可重写具体逻辑
     *
     * @protected
     * @method module:util/dispatcher/regularModule._$$RegularModule#__onShow
     * @param  {Object} arg0 - 事件对象
     * @return {Void}
     */
    _pro.__onShow = function(_options){
        // 如果Regular组件被destroy了，就重新构建
        if(this._$innerModule._watchers === null)
            this.__build();

        this.__super(_options);
        if(this._$innerModule.__onShow){
            this._$innerModule.data.$param = _options.param;
            this._$innerModule.data.$params = _options.href.split('?')[1];
            this._$innerModule.__onShow(_options);
            this._$innerModule.$update();
        }
    }
    /**
     * 显示模块触发事件，子类实现具体逻辑
     *
     * @protected
     * @method module:util/dispatcher/regularModule._$$RegularModule#__onRefresh
     * @param  {Object} arg0 - 事件对象
     * @return {Void}
     */
    _pro.__onRefresh = function(_options){
        this.__super(_options);
        if(this._$innerModule.__onRefresh){
            this._$innerModule.data.$param = _options.param;
            this._$innerModule.data.$params = _options.href.split('?')[1];
            this._$innerModule.__onRefresh(_options);
            this._$innerModule.$update();
        }
    }
    /**
     * 接受到消息触发事件，子类实现具体逻辑
     *
     * @abstract
     * @method module:util/dispatcher/regularModule._$$RegularModule#__onMessage
     * @param  {Object} arg0 - 事件对象
     * @return {Void}
     */
    _pro.__onMessage = function(_options){
        this.__super(_options);
        if(this._$innerModule.__onMessage){
            this._$innerModule.__onMessage(_options);
            this._$innerModule.$update();
        }
    }
    /**
     * 模块退出前触发事件，通过阻止输入的事件做退出验证，子类实现具体逻辑
     *
     * @abstract
     * @method module:util/dispatcher/regularModule._$$RegularModule#__onBeforeHide
     * @param  {Object} arg0 - 事件对象
     * @return {Void}
     */
    _pro.__onBeforeHide = function(_options){
        this.__super(_options);
        if(this._$innerModule.__onBeforeHide){
            this._$innerModule.__onBeforeHide(_options);
            this._$innerModule.$update();
        }
    }
    /**
     * 隐藏模块触发事件，子类实现具体逻辑
     *
     * @protected
     * @method module:util/dispatcher/regularModule._$$RegularModule#__onHide
     * @return {Void}
     */
    _pro.__onHide = function(){
        this.__super();
        if(this._$innerModule.__onHide){
            this._$innerModule.__onHide();
            this._$innerModule.$update();
        }
    }
    /**
     * 控件销毁
     *
     * @protected
     * @method module:util/dispatcher/regularModule._$$RegularModule#__destroy
     * @return {Void}
     */
    _pro.__destroy = function(){
        this.__body = _t._$getNodeTemplate(this.__nodeKey);
        this._$innerModule.destroy();
        delete this.__nodeKey;
        this.__super();
    };

    /**
     * 注册RegularModule
     *
     * @public
     * @method module:util/dispatcher/regularModule._$build
     * @param  {String}  arg0 - 模块UMI或者别名
     * @param  {Regular} arg1 - Regular组件模块
     * @return {_$$RegularModule} RegularModule模块
     */
    _p._$regist = function(_umiAlias, _$$InnerModule){
        /**
         * Regular模块对象
         *
         * @class   {_$$OuterModule}
         * @extends {_$$RegularModule}
         */
        var _$$OuterModule = _k._$klass();
        var _pro = _$$OuterModule._$extend(_p._$$RegularModule);

        _pro.__init = function(){
            this.__umiAlias = _umiAlias;
            this._$$InnerModule = _$$InnerModule;
            this.__super();
        }

        _v._$addEvent(document, 'templateready', function(){
            _m._$regist(_umiAlias, _$$OuterModule);
        });

        return _$$OuterModule;
    }

    return _p;
});<|endoftext|>


<commit_before>var imageExtensions = "jpg|jpeg|gif|png|svg";

console.log("Addon is working correctly, page is " + window.location.href);

function init()
{
	var allAnchors = document.getElementsByTagName("a");

	for(var i = 0; i < allAnchors.length; i++)
	{
		var anchorWithImage = allAnchors[i];

		var matcher = new RegExp(".*\\.(" + imageExtensions + ")$");
		if(matcher.test(anchorWithImage.href))
		{
			anchorWithImage.addEventListener("mouseover", mouseOver);
		}
	}
}

function mouseOver(event)
{
	alert(event.target.href);
}

init();<commit_msg>Improved documentation<commit_after>var imageExtensions = "jpg|jpeg|gif|png|svg";

console.log("On-hover image is working correctly. Page is: " + window.location.href);

/**
 * Called when the script is fully loaded, initializing all anchors to images
 * with the hover script.
 */
function init()
{
	var allAnchors = document.getElementsByTagName("a");

	for(var i = 0; i < allAnchors.length; i++)
	{
		var anchor = allAnchors[i];

		// Finds all anchors that link to images
		var matcher = new RegExp(".*\\.(" + imageExtensions + ")$");
		if(matcher.test(anchor.href))
		{
			anchor.addEventListener("mouseover", mouseOver);
		}
	}
}

/**
 * Called when the mouse hovers over an image link.
 */
function mouseOver(event)
{
	alert(event.target.href);
}

init();<|endoftext|>


<commit_before>'use strict'

import * as CONSTANTS from '../constants/index'

import { MAX_RESULTS_PER_FETCH, MAX_RESULTS_PER_SEARCH, NUMBER_OF_FIRST_RESPONSE_PAGE, RETURN_DELAY_TIME } from '../constants/authors-list'
import { arrayOf, normalize } from 'normalizr'
import { camelizeKeys } from 'humps'
import fetch from 'isomorphic-fetch'
import get from 'lodash/get'
import omit from 'lodash/omit'

import { InternalServerError } from '../custom-error'
import { author as authorSchema } from '../schemas/index'
import { camelizeKeys } from 'humps'
import fetch from 'isomorphic-fetch'
import { formatUrl, urlParasToString } from '../utils/index'
import get from 'lodash/get'

const _ = {
  get,
  omit
}

export function requestSearchAuthors(keywords = '') {
  return {
    type: (keywords === '') ? CONSTANTS.LIST_ALL_AUTHORS_REQUEST : CONSTANTS.SEARCH_AUTHORS_REQUEST,
    keywords: keywords
  }
}

export function failToSearchAuthors(keywords = '', error) {
  return {
    type: (keywords === '') ? CONSTANTS.LIST_ALL_AUTHORS_FAILURE : CONSTANTS.SEARCH_AUTHORS_FAILURE,
    error,
    failedAt: Date.now()
  }
}

export function receiveSearchAuthors(keywords, response) {
  return {
    type: (keywords === '') ? CONSTANTS.LIST_ALL_AUTHORS_SUCCESS : CONSTANTS.SEARCH_AUTHORS_SUCCESS,
    keywords,
    response, // {object} contains entities{object}, result{array}, currentPage{number}, totalPages{number}
    receivedAt: Date.now()
  }
}

export function searchAuthors({ keywords, targetPage, returnDelay }) {
  return (dispatch, getState) => { // eslint-disable-line no-unused-vars
    const searchParas = {
      keywords,
      filters: 'articlesCount>0',
      hitsPerPage: (keywords === '') ? MAX_RESULTS_PER_FETCH : MAX_RESULTS_PER_SEARCH,
      page: targetPage
    }
    // Trans searchParas object to url parameters:
    let urlParasString = urlParasToString(searchParas)
    let url = formatUrl(`searchAuthors?${urlParasString}`)
    dispatch(requestSearchAuthors(keywords))
    // Call our API server to fetch the data
    return fetch(url)
      .then((response) => {
        if (response.status >= 400) {
          throw new InternalServerError('Bad response from API, response:' + JSON.stringify(response))
        }
        return response.json()
      })
      .then((responseObject) => {
        const items = _.get(responseObject, 'hits', {}) // responseObject.hit
        const camelizedJson = camelizeKeys(items)
        const responseItems = normalize(camelizedJson, arrayOf(authorSchema))

        const currentPage = _.get(responseObject, 'page', NUMBER_OF_FIRST_RESPONSE_PAGE - 1)
        const totalPages = _.get(responseObject, 'nbPages', 0)

        const response = {
          ...responseItems,
          currentPage,
          totalPages
        }

        // delay for displaying loading spinner
        function delayDispatch() {
          return new Promise((resolve, reject)=> { // eslint-disable-line no-unused-vars
            setTimeout(() => {
              resolve()
            }, returnDelay)
          })
        }
        if (returnDelay > 0) {
          return delayDispatch().then(()=>{
            return dispatch(receiveSearchAuthors(keywords, response))
          })
        }
        return dispatch(receiveSearchAuthors(keywords, response))
      },
      (error) => {
        return dispatch(failToSearchAuthors(keywords, error))
      })
  }
}

/*
  Algolia set hitsPerPage limit up to 1000 items per search.
  So if number of authors grows over 1000,
  it will need to check hasMore  as case lsat all authors.
*/

export function searchAuthorsIfNeeded(currentKeywords = '') {
  /* --------- list all authors --------- */
  if (currentKeywords === '') {
    return (dispatch, getState) => {
      const currentState = getState()
      const authorsList = _.get(currentState, 'authorsList', {})
      const { isFetching, currentPage, hasMore } = authorsList
      if (currentPage < NUMBER_OF_FIRST_RESPONSE_PAGE) { // Situation 1/3: If no data exists => fetch first page immediately
        return dispatch(searchAuthors({
          keywords: '',
          targetPage: NUMBER_OF_FIRST_RESPONSE_PAGE,
          returnDelay: 0
        }))
      }
      // If current page >= NUMBER_OF_FIRST_RESPONSE_PAGE:
      if (!isFetching && hasMore) { // Situation 2/3: If already have data AND not fetching AND has more => delay && next page
        return dispatch(searchAuthors({
          keywords: '',
          targetPage: currentPage + 1,
          returnDelay: RETURN_DELAY_TIME
        }))
      }
      return Promise.resolve() // Situation 3/3: If already have all data (not has more) OR is fetching => do nothing
    }
  }
  /* --------- searching authors --------- */
  return (dispatch, getState) => {
    const currentState = getState()
    const authorsList = _.get(currentState, 'searchedAuthorsList', {})
    const previousKeywords = _.get(authorsList, 'keywords')
    if ( currentKeywords !== previousKeywords) { // Situation 1/2:If keywords are new => search
      return dispatch(searchAuthors({
        keywords: currentKeywords,
        targetPage: NUMBER_OF_FIRST_RESPONSE_PAGE,
        returnDelay: 0
      }))
    }
    return Promise.resolve() // Situation 2/2:If keywords are the same => do nothing
  }
}
<commit_msg>fix trivial bugs<commit_after>'use strict'

import * as CONSTANTS from '../constants/index'

import { MAX_RESULTS_PER_FETCH, MAX_RESULTS_PER_SEARCH, NUMBER_OF_FIRST_RESPONSE_PAGE, RETURN_DELAY_TIME } from '../constants/authors-list'
import { arrayOf, normalize } from 'normalizr'
import { camelizeKeys } from 'humps'
import fetch from 'isomorphic-fetch'
import get from 'lodash/get'
import omit from 'lodash/omit'
import { InternalServerError } from '../custom-error'
import { author as authorSchema } from '../schemas/index'
import { formatUrl, urlParasToString } from '../utils/index'

const _ = {
  get,
  omit
}

export function requestSearchAuthors(keywords = '') {
  return {
    type: (keywords === '') ? CONSTANTS.LIST_ALL_AUTHORS_REQUEST : CONSTANTS.SEARCH_AUTHORS_REQUEST,
    keywords: keywords
  }
}

export function failToSearchAuthors(keywords = '', error) {
  return {
    type: (keywords === '') ? CONSTANTS.LIST_ALL_AUTHORS_FAILURE : CONSTANTS.SEARCH_AUTHORS_FAILURE,
    error,
    failedAt: Date.now()
  }
}

export function receiveSearchAuthors(keywords, response) {
  return {
    type: (keywords === '') ? CONSTANTS.LIST_ALL_AUTHORS_SUCCESS : CONSTANTS.SEARCH_AUTHORS_SUCCESS,
    keywords,
    response, // {object} contains entities{object}, result{array}, currentPage{number}, totalPages{number}
    receivedAt: Date.now()
  }
}

export function searchAuthors({ keywords, targetPage, returnDelay }) {
  return (dispatch, getState) => { // eslint-disable-line no-unused-vars
    const searchParas = {
      keywords,
      filters: 'articlesCount>0',
      hitsPerPage: (keywords === '') ? MAX_RESULTS_PER_FETCH : MAX_RESULTS_PER_SEARCH,
      page: targetPage
    }
    // Trans searchParas object to url parameters:
    let urlParasString = urlParasToString(searchParas)
    let url = formatUrl(`searchAuthors?${urlParasString}`)
    dispatch(requestSearchAuthors(keywords))
    // Call our API server to fetch the data
    return fetch(url)
      .then((response) => {
        if (response.status >= 400) {
          throw new InternalServerError('Bad response from API, response:' + JSON.stringify(response))
        }
        return response.json()
      })
      .then((responseObject) => {
        const items = _.get(responseObject, 'hits', {}) // responseObject.hit
        const camelizedJson = camelizeKeys(items)
        const responseItems = normalize(camelizedJson, arrayOf(authorSchema))

        const currentPage = _.get(responseObject, 'page', NUMBER_OF_FIRST_RESPONSE_PAGE - 1)
        const totalPages = _.get(responseObject, 'nbPages', 0)

        const response = {
          ...responseItems,
          currentPage,
          totalPages
        }

        // delay for displaying loading spinner
        function delayDispatch() {
          return new Promise((resolve, reject)=> { // eslint-disable-line no-unused-vars
            setTimeout(() => {
              resolve()
            }, returnDelay)
          })
        }
        if (returnDelay > 0) {
          return delayDispatch().then(()=>{
            return dispatch(receiveSearchAuthors(keywords, response))
          })
        }
        return dispatch(receiveSearchAuthors(keywords, response))
      },
      (error) => {
        return dispatch(failToSearchAuthors(keywords, error))
      })
  }
}

/*
  Algolia set hitsPerPage limit up to 1000 items per search.
  So if number of authors grows over 1000,
  it will need to check hasMore  as case lsat all authors.
*/

export function searchAuthorsIfNeeded(currentKeywords = '') {
  /* --------- list all authors --------- */
  if (currentKeywords === '') {
    return (dispatch, getState) => {
      const currentState = getState()
      const authorsList = _.get(currentState, 'authorsList', {})
      const { isFetching, currentPage, hasMore } = authorsList
      if (currentPage < NUMBER_OF_FIRST_RESPONSE_PAGE) { // Situation 1/3: If no data exists => fetch first page immediately
        return dispatch(searchAuthors({
          keywords: '',
          targetPage: NUMBER_OF_FIRST_RESPONSE_PAGE,
          returnDelay: 0
        }))
      }
      // If current page >= NUMBER_OF_FIRST_RESPONSE_PAGE:
      if (!isFetching && hasMore) { // Situation 2/3: If already have data AND not fetching AND has more => delay && next page
        return dispatch(searchAuthors({
          keywords: '',
          targetPage: currentPage + 1,
          returnDelay: RETURN_DELAY_TIME
        }))
      }
      return Promise.resolve() // Situation 3/3: If already have all data (not has more) OR is fetching => do nothing
    }
  }
  /* --------- searching authors --------- */
  return (dispatch, getState) => {
    const currentState = getState()
    const authorsList = _.get(currentState, 'searchedAuthorsList', {})
    const previousKeywords = _.get(authorsList, 'keywords')
    if ( currentKeywords !== previousKeywords) { // Situation 1/2:If keywords are new => search
      return dispatch(searchAuthors({
        keywords: currentKeywords,
        targetPage: NUMBER_OF_FIRST_RESPONSE_PAGE,
        returnDelay: 0
      }))
    }
    return Promise.resolve() // Situation 2/2:If keywords are the same => do nothing
  }
}
<|endoftext|>


<commit_before>import { ActionTypes } from '../config/constants';
import { fetchTickets, dispatchTickets } from '../helpers/cw';
import { ref } from '../config/constants';

export const subscribe = ({ projectId }) => {
  return (dispatch) => {

    dispatch({
      type: ActionTypes.TICKETS_SUBSCRIBE,
    });

    const tickets = ref.child(`tickets/${projectId}`);
    tickets.on('value', snapshot => {
      const nested = snapshot.val();
      const flattened = Object.keys(nested).map(project => nested[project]).reduce((prev, next) => prev.concat(next), []);

      // Sort project ticket by wbsCode
      // ie ['1.1', '1.1.14', '4.2.1.4.6', '4.2.3']
      flattened.sort((a, b) => {
        const aParts = a.wbsCode.split('.');
        const bParts = b.wbsCode.split('.');
        for (let i = 0; i < aParts.length; ++i) {
          if (bParts.length === i || parseInt(aParts[i], 10) > parseInt(bParts[i], 10)) {
            return 1;
          } else if (parseInt(aParts[i], 10) === parseInt(bParts[i], 10)) {
            continue;
          } else {
            return -1;
          }
        }
        return 0;
      });

      dispatch({
        type: ActionTypes.TICKETS_UPDATE,
        payload: {
          projectId,
          flattened,
          nested,
        },
      });
    });
  };
}

export const unsubscribe = ({ projectId }) => {
  return dispatch => {
    const tickets = ref.child(`tickets/${projectId}`);
    tickets.off();

    dispatch({
      type: ActionTypes.TICKETS_REMOVE,
      payload: {
        projectId,
      },
    });
  };
};

export const search = (payload) => {
  return {
    type: ActionTypes.TICKETS_SEARCH,
    payload,
  }
}

export const updateTickets = payload => {
  return (dispatch, getState) => {
    if (!payload.projectId) {
      throw new Error('Missing project id.');
    }

    fetchTickets(payload.projectId).then(tickets => {
      ref.child(`tickets/${payload.projectId}`)
        .set(tickets);

      const { projects } = getState();
      if (!projects.length) {
        console.warn(`Uh oh. Can't update the projects list. Inform the authorities.`);
        return;
      }

      // New project
      // We're assuming the company or name of existing projects will never
      // need to change, which should mostly be correct.
      if (projects.map(project => project.id).indexOf(payload.projectId) === -1) {
        const project = {
          company: tickets[0].company.name,
          id: payload.projectId,
          name: tickets[0].project.name,
        };

        ref.child('projects').set([
          ...projects,
          project,
        ]);
      }
    });
  };
}

export const dispatch = payload => {
  return (dispatch, getState) => {
    dispatch({
      type: ActionTypes.TICKETS_DISPATCH,
      payload,
    });

    dispatchTickets(payload.params).then(response => {
      dispatch({
        type: ActionTypes.TICKETS_DISPATCH_SUCCESS,
        payload: response,
      });
    });
  };
}
<commit_msg>Workaround for missing wbsCode values.<commit_after>import { ActionTypes } from '../config/constants';
import { fetchTickets, dispatchTickets } from '../helpers/cw';
import { ref } from '../config/constants';

export const subscribe = ({ projectId }) => {
  return (dispatch) => {

    dispatch({
      type: ActionTypes.TICKETS_SUBSCRIBE,
    });

    const tickets = ref.child(`tickets/${projectId}`);
    tickets.on('value', snapshot => {
      const nested = snapshot.val();
      const flattened = Object.keys(nested).map(project => nested[project]).reduce((prev, next) => prev.concat(next), []);

      // Sort project ticket by wbsCode
      // ie ['1.1', '1.1.14', '4.2.1.4.6', '4.2.3']
      flattened.sort((a, b) => {
        const aParts = String(a.wbsCode).split('.');
        const bParts = String(b.wbsCode).split('.');
        for (let i = 0; i < aParts.length; ++i) {
          if (bParts.length === i || parseInt(aParts[i], 10) > parseInt(bParts[i], 10)) {
            return 1;
          } else if (parseInt(aParts[i], 10) === parseInt(bParts[i], 10)) {
            continue;
          } else {
            return -1;
          }
        }
        return 0;
      });

      dispatch({
        type: ActionTypes.TICKETS_UPDATE,
        payload: {
          projectId,
          flattened,
          nested,
        },
      });
    });
  };
}

export const unsubscribe = ({ projectId }) => {
  return dispatch => {
    const tickets = ref.child(`tickets/${projectId}`);
    tickets.off();

    dispatch({
      type: ActionTypes.TICKETS_REMOVE,
      payload: {
        projectId,
      },
    });
  };
};

export const search = (payload) => {
  return {
    type: ActionTypes.TICKETS_SEARCH,
    payload,
  }
}

export const updateTickets = payload => {
  return (dispatch, getState) => {
    if (!payload.projectId) {
      throw new Error('Missing project id.');
    }

    fetchTickets(payload.projectId).then(tickets => {
      ref.child(`tickets/${payload.projectId}`)
        .set(tickets);

      const { projects } = getState();
      if (!projects.length) {
        console.warn(`Uh oh. Can't update the projects list. Inform the authorities.`);
        return;
      }

      // New project
      // We're assuming the company or name of existing projects will never
      // need to change, which should mostly be correct.
      if (projects.map(project => project.id).indexOf(payload.projectId) === -1) {
        const project = {
          company: tickets[0].company.name,
          id: payload.projectId,
          name: tickets[0].project.name,
        };

        ref.child('projects').set([
          ...projects,
          project,
        ]);
      }
    });
  };
}

export const dispatch = payload => {
  return (dispatch, getState) => {
    dispatch({
      type: ActionTypes.TICKETS_DISPATCH,
      payload,
    });

    dispatchTickets(payload.params).then(response => {
      dispatch({
        type: ActionTypes.TICKETS_DISPATCH_SUCCESS,
        payload: response,
      });
    });
  };
}
<|endoftext|>


<commit_before>'use strict';

module.exports = function (config) {
  var configuration = {
    basePath: '.',

    frameworks: ['jasmine-ajax', 'jasmine'],

    files: [
      // paths loaded by Karma
      {pattern: 'node_modules/reflect-metadata/Reflect.js', included: true, watched: true},
      {pattern: 'node_modules/systemjs/dist/system.src.js', included: true, watched: false},
      {pattern: 'node_modules/zone.js/dist/zone.js', included: true, watched: true},
      {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false},
      {pattern: 'node_modules/rxjs/**/*.map', included: false, watched: false},
      {pattern: 'node_modules/@angular/**/*.js', included: false, watched: false},
      {pattern: 'node_modules/@angular/**/*.map', included: false, watched: false},
      {pattern: 'node_modules/ng2-alfresco-core/dist/**/*.js', included: false, served: true, watched: false},
      {pattern: 'node_modules/ng2-translate/**/*.js', included: false, served: true, watched: false},

      {pattern: 'karma-test-shim.js', included: true, watched: true},

      // paths loaded via module imports
      {pattern: 'dist/**/*.js', included: false, watched: true},
      {pattern: 'dist/**/*.html', included: true, served: true, watched: true},
      {pattern: 'dist/**/*.css', included: true, served: true, watched: true},

      // paths to support debugging with source maps in dev tools
      {pattern: 'src/**/*.ts', included: false, watched: false},
      {pattern: 'dist/**/*.js.map', included: false, watched: false}
    ],

    // proxied base paths
    proxies: {
      // required for component assets fetched by Angular's compiler
      '/src/': '/base/src/'
    },

    port: 9876,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_DEBUG,

    colors: true,

    autoWatch: true,

    browsers: ['Chrome'],

    customLaunchers: {
      Chrome_travis_ci: {
        base: 'Chrome',
        flags: ['--no-sandbox']
      }
    },

    // Karma plugins loaded
    plugins: [
      'karma-jasmine',
      'karma-coverage',
      'karma-jasmine-ajax',
      'karma-chrome-launcher',
      'karma-mocha-reporter',
      'karma-jasmine-html-reporter'
    ],

    // Coverage reporter generates the coverage
    reporters: ['mocha', 'coverage', 'kjhtml'],

    // Source files that you wanna generate coverage for.
    // Do not include tests or libraries (these files will be instrumented by Istanbul)
    preprocessors: {
      'dist/**/!(*spec).js': ['coverage']
    },

    coverageReporter: {
      dir: 'coverage/',
      subdir: 'report',
      reporters: [
        {type: 'text'},
        {type: 'json', file: 'coverage-final.json'},
        {type: 'html'}
      ]
    }
  }

  if (process.env.TRAVIS) {
    configuration.browsers = ['Chrome_travis_ci'];
  }

  config.set(configuration)
};
<commit_msg>Fix document list tests<commit_after>'use strict';

module.exports = function (config) {
  var configuration = {
    basePath: '.',

    frameworks: [/*'jasmine-ajax',*/ 'jasmine'],

    files: [
      // paths loaded by Karma
      {pattern: 'node_modules/reflect-metadata/Reflect.js', included: true, watched: true},
      {pattern: 'node_modules/systemjs/dist/system.src.js', included: true, watched: false},
      {pattern: 'node_modules/zone.js/dist/zone.js', included: true, watched: true},
      {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false},
      {pattern: 'node_modules/rxjs/**/*.map', included: false, watched: false},
      {pattern: 'node_modules/@angular/**/*.js', included: false, watched: false},
      {pattern: 'node_modules/@angular/**/*.map', included: false, watched: false},
      {pattern: 'node_modules/ng2-alfresco-core/dist/**/*.js', included: false, served: true, watched: false},
      {pattern: 'node_modules/ng2-translate/**/*.js', included: false, served: true, watched: false},

      {pattern: 'karma-test-shim.js', included: true, watched: true},

      // paths loaded via module imports
      {pattern: 'dist/**/*.js', included: false, watched: true},
      {pattern: 'dist/**/*.html', included: true, served: true, watched: true},
      {pattern: 'dist/**/*.css', included: true, served: true, watched: true},

      // paths to support debugging with source maps in dev tools
      {pattern: 'src/**/*.ts', included: false, watched: false},
      {pattern: 'dist/**/*.js.map', included: false, watched: false}
    ],

    // proxied base paths
    proxies: {
      // required for component assets fetched by Angular's compiler
      '/src/': '/base/src/'
    },

    port: 9876,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,

    colors: true,

    autoWatch: true,

    browsers: ['Chrome'],

    customLaunchers: {
      Chrome_travis_ci: {
        base: 'Chrome',
        flags: ['--no-sandbox']
      }
    },

    // Karma plugins loaded
    plugins: [
      'karma-jasmine',
      'karma-coverage',
      //'karma-jasmine-ajax',
      'karma-chrome-launcher',
      'karma-mocha-reporter',
      'karma-jasmine-html-reporter'
    ],

    // Coverage reporter generates the coverage
    reporters: ['mocha', 'coverage', 'kjhtml'],

    // Source files that you wanna generate coverage for.
    // Do not include tests or libraries (these files will be instrumented by Istanbul)
    preprocessors: {
      'dist/**/!(*spec).js': ['coverage']
    },

    coverageReporter: {
      dir: 'coverage/',
      subdir: 'report',
      reporters: [
        {type: 'text'},
        {type: 'json', file: 'coverage-final.json'},
        {type: 'html'}
      ]
    }
  }

  if (process.env.TRAVIS) {
    configuration.browsers = ['Chrome_travis_ci'];
  }

  config.set(configuration)
};
<|endoftext|>


<commit_before>'use strict';

import AbstractAdapter from './AbstractAdapter';

const CMD = 'md5sum';

export default class Md5sum extends AbstractAdapter {

	static get CMD () {
		return CMD;
	}

	formatCmd (filePath) {
        return CMD + ' ' + testFilePath;
    }

    formatResult (string) {
        //example: aaec4956a2be31743c91a0d80ce3ef7b  myFile.txt
        return string.split(' ')[0];
    }

}<commit_msg>fix bad variable name<commit_after>'use strict';

import AbstractAdapter from './AbstractAdapter';

const CMD = 'md5sum';

export default class Md5sum extends AbstractAdapter {

	static get CMD () {
		return CMD;
	}

	formatCmd (filePath) {
        return CMD + ' ' + filePath;
    }

    formatResult (string) {
        //example: aaec4956a2be31743c91a0d80ce3ef7b  myFile.txt
        return string.split(' ')[0];
    }

}
<|endoftext|>


<commit_before>var Fs, os, pwdg;

os = require('os');

pwdg = require('./assets/passwd-groups');

Fs = require('../Fs');

module.exports = {
  os: {
    os: os,
    system: {
      hostname: function() {
        return os.hostname();
      },
      type: function() {
        return os.type();
      },
      platform: function() {
        return os.platform();
      },
      arch: function() {
        return os.arch();
      },
      release: function() {
        return os.release();
      },
      eol: os.EOL,
      uptime: function() {
        return os.uptime();
      },
      loadavg: function() {
        return os.loadavg();
      },
      memory: {
        total: function() {
          return os.totalmem();
        },
        free: function() {
          return os.freemem();
        }
      },
      cpus: function() {
        return os.cpus();
      },
      networkInterfaces: function() {
        return os.networkInterfaces();
      }
    },
    users: {
      all: function(cb) {
        return pwdg.getAllUsers(function(err, users) {
          return cb(err, users);
        });
      },
      get: function(username, cb) {
        return pwdg.getUser(username, function(err, user) {
          return cb(err, user);
        });
      },
      add: function(username, pass, opts, cb) {
        return pwdg.addUser(username, pass, opts, function(err, status) {
          return cb(err, status);
        });
      },
      lock: function(username, opts, cb) {
        return pwdg.lockUser(username, opts, function(err, status) {
          return cb(err, status);
        });
      },
      unlock: function(username, opts, cb) {
        return pwdg.unlockUser(username, opts, function(err, status) {
          return cb(err, status);
        });
      },
      del: function(username, opts, cb) {
        return pwdg.delUser(username, opts, function(err, status) {
          return cb(err, status);
        });
      }
    },
    groups: {
      all: function(cb) {
        return pwdg.getAllGroups(function(err, groups) {
          return cb(err, groups);
        });
      },
      get: function(name, cb) {
        return pwdg.getGroup(name, function(err, group) {
          return cb(err, group);
        });
      },
      add: function(name, opts, cb) {
        return pwdg.addGroup(name, opts, function(err, status) {
          return cb(err, status);
        });
      },
      del: function(name, opts, cb) {
        return pwdg.delGroup(name, opts, function(err, status) {
          return cb(err, status);
        });
      }
    }
  }
};
<commit_msg>(js) use new passwd-groups dependency<commit_after>var Fs, os, pwdg;

os = require('os');

pwdg = require('passwd-groups');

Fs = require('../Fs');

module.exports = {
  os: {
    os: os,
    system: {
      hostname: function() {
        return os.hostname();
      },
      type: function() {
        return os.type();
      },
      platform: function() {
        return os.platform();
      },
      arch: function() {
        return os.arch();
      },
      release: function() {
        return os.release();
      },
      eol: os.EOL,
      uptime: function() {
        return os.uptime();
      },
      loadavg: function() {
        return os.loadavg();
      },
      memory: {
        total: function() {
          return os.totalmem();
        },
        free: function() {
          return os.freemem();
        }
      },
      cpus: function() {
        return os.cpus();
      },
      networkInterfaces: function() {
        return os.networkInterfaces();
      }
    },
    users: {
      all: function(cb) {
        return pwdg.getAllUsers(function(err, users) {
          return cb(err, users);
        });
      },
      get: function(username, cb) {
        return pwdg.getUser(username, function(err, user) {
          return cb(err, user);
        });
      },
      add: function(username, pass, opts, cb) {
        return pwdg.addUser(username, pass, opts, function(err, status) {
          return cb(err, status);
        });
      },
      lock: function(username, opts, cb) {
        return pwdg.lockUser(username, opts, function(err, status) {
          return cb(err, status);
        });
      },
      unlock: function(username, opts, cb) {
        return pwdg.unlockUser(username, opts, function(err, status) {
          return cb(err, status);
        });
      },
      del: function(username, opts, cb) {
        return pwdg.delUser(username, opts, function(err, status) {
          return cb(err, status);
        });
      }
    },
    groups: {
      all: function(cb) {
        return pwdg.getAllGroups(function(err, groups) {
          return cb(err, groups);
        });
      },
      get: function(name, cb) {
        return pwdg.getGroup(name, function(err, group) {
          return cb(err, group);
        });
      },
      add: function(name, opts, cb) {
        return pwdg.addGroup(name, opts, function(err, status) {
          return cb(err, status);
        });
      },
      del: function(name, opts, cb) {
        return pwdg.delGroup(name, opts, function(err, status) {
          return cb(err, status);
        });
      }
    }
  }
};
<|endoftext|>


<commit_before>// Project imports
var MinionPlayer = require('./MinionPlayer');
var MinionSocket = require('./MinionSocket');
var PacketHandler = require('../PacketHandler');

function MinionLoader(gameServer) {
    this.gameServer = gameServer;
}

module.exports = MinionLoader;

MinionLoader.prototype.addBot = function () {
    var s = new MinionSocket(this.gameServer);
    s.playerTracker = new MinionPlayer(this.gameServer, s);
    s.packetHandler = new PacketHandler(this.gameServer, s);

    // Add to client list
    this.gameServer.clients.push(s);

    // Add to world & set name
    s.packetHandler.setNickname(this.gameServer.minionName);
};<commit_msg>Delete MinionLoader.js<commit_after><|endoftext|>


<commit_before>import { DatePicker } from '/client/lib/datepicker';
import Cards from '/models/cards';

Template.cardCustomFieldsPopup.helpers({
  hasCustomField() {
    const card = Utils.getCurrentCard();
    const customFieldId = this._id;
    return card.customFieldIndex(customFieldId) > -1;
  },
});

Template.cardCustomFieldsPopup.events({
  'click .js-select-field'(event) {
    const card = Utils.getCurrentCard();
    const customFieldId = this._id;
    card.toggleCustomField(customFieldId);
    event.preventDefault();
  },
  'click .js-settings'(event) {
    EscapeActions.executeUpTo('detailsPane');
    Sidebar.setView('customFields');
    event.preventDefault();
  },
});

// cardCustomField
const CardCustomField = BlazeComponent.extendComponent({
  getTemplate() {
    return `cardCustomField-${this.data().definition.type}`;
  },

  onCreated() {
    const self = this;
    self.card = Utils.getCurrentCard();
    self.customFieldId = this.data()._id;
  },

  canModifyCard() {
    return (
      Meteor.user() &&
      Meteor.user().isBoardMember() &&
      !Meteor.user().isCommentOnly()
    );
  },
});
CardCustomField.register('cardCustomField');

// cardCustomField-text
(class extends CardCustomField {
  onCreated() {
    super.onCreated();
  }

  events() {
    return [
      {
        'submit .js-card-customfield-text'(event) {
          event.preventDefault();
          const value = this.currentComponent().getValue();
          this.card.setCustomField(this.customFieldId, value);
        },
      },
    ];
  }
}.register('cardCustomField-text'));

// cardCustomField-number
(class extends CardCustomField {
  onCreated() {
    super.onCreated();
  }

  events() {
    return [
      {
        'submit .js-card-customfield-number'(event) {
          event.preventDefault();
          const value = parseInt(this.find('input').value, 10);
          this.card.setCustomField(this.customFieldId, value);
        },
      },
    ];
  }
}.register('cardCustomField-number'));

// cardCustomField-checkbox
(class extends CardCustomField {
  onCreated() {
    super.onCreated();
  }

  toggleItem() {
    this.card.setCustomField(this.customFieldId, !this.data().value);
  }

  events() {
    return [
      {
        'click .js-checklist-item .check-box-container': this.toggleItem,
      },
    ];
  }
}.register('cardCustomField-checkbox'));

// cardCustomField-currency
(class extends CardCustomField {
  onCreated() {
    super.onCreated();

    this.currencyCode = this.data().definition.settings.currencyCode;
  }

  formattedValue() {
    const locale = TAPi18n.getLanguage();

    return new Intl.NumberFormat(locale, {
      style: 'currency',
      currency: this.currencyCode,
    }).format(this.data().value);
  }

  events() {
    return [
      {
        'submit .js-card-customfield-currency'(event) {
          event.preventDefault();
          // To allow input separated by comma, the comma is replaced by a period.
          const value = Number(this.find('input').value.replace(/,/i, '.'), 10);
          this.card.setCustomField(this.customFieldId, value);
        },
      },
    ];
  }
}.register('cardCustomField-currency'));

// cardCustomField-date
(class extends CardCustomField {
  onCreated() {
    super.onCreated();
    const self = this;
    self.date = ReactiveVar();
    self.now = ReactiveVar(moment());
    window.setInterval(() => {
      self.now.set(moment());
    }, 60000);

    self.autorun(() => {
      self.date.set(moment(self.data().value));
    });
  }

  showWeek() {
    return this.date.get().week().toString();
  }

  showDate() {
    // this will start working once mquandalle:moment
    // is updated to at least moment.js 2.10.5
    // until then, the date is displayed in the "L" format
    return this.date.get().calendar(null, {
      sameElse: 'llll',
    });
  }

  showISODate() {
    return this.date.get().toISOString();
  }

  classes() {
    if (
      this.date.get().isBefore(this.now.get(), 'minute') &&
      this.now.get().isBefore(this.data().value)
    ) {
      return 'current';
    }
    return '';
  }

  showTitle() {
    return `${TAPi18n.__('card-start-on')} ${this.date.get().format('LLLL')}`;
  }

  events() {
    return [
      {
        'click .js-edit-date': Popup.open('cardCustomField-date'),
      },
    ];
  }
}.register('cardCustomField-date'));

// cardCustomField-datePopup
(class extends DatePicker {
  onCreated() {
    super.onCreated();
    const self = this;
    self.card = Utils.getCurrentCard();
    self.customFieldId = this.data()._id;
    this.data().value && this.date.set(moment(this.data().value));
  }

  _storeDate(date) {
    this.card.setCustomField(this.customFieldId, date);
  }

  _deleteDate() {
    this.card.setCustomField(this.customFieldId, '');
  }
}.register('cardCustomField-datePopup'));

// cardCustomField-dropdown
(class extends CardCustomField {
  onCreated() {
    super.onCreated();
    this._items = this.data().definition.settings.dropdownItems;
    this.items = this._items.slice(0);
    this.items.unshift({
      _id: '',
      name: TAPi18n.__('custom-field-dropdown-none'),
    });
  }

  selectedItem() {
    const selected = this._items.find(item => {
      return item._id === this.data().value;
    });
    return selected
      ? selected.name
      : TAPi18n.__('custom-field-dropdown-unknown');
  }

  events() {
    return [
      {
        'submit .js-card-customfield-dropdown'(event) {
          event.preventDefault();
          const value = this.find('select').value;
          this.card.setCustomField(this.customFieldId, value);
        },
      },
    ];
  }
}.register('cardCustomField-dropdown'));

// cardCustomField-stringtemplate
(class extends CardCustomField {
  onCreated() {
    super.onCreated();

    this.stringtemplateFormat = this.data().definition.settings.stringtemplateFormat;
    this.stringtemplateSeparator = this.data().definition.settings.stringtemplateSeparator;

    this.stringtemplateItems = new ReactiveVar(this.data().value ?? []);
  }

  formattedValue() {
    return (this.data().value ?? [])
      .filter(value => !!value.trim())
      .map(value => this.stringtemplateFormat.replace(/%\{value\}/gi, value))
      .join(this.stringtemplateSeparator ?? '');
  }

  getItems() {
    return Array.from(this.findAll('input'))
      .map(input => input.value)
      .filter(value => !!value.trim());
  }

  events() {
    return [
      {
        'submit .js-card-customfield-stringtemplate'(event) {
          event.preventDefault();
          const items = this.getItems();
          this.card.setCustomField(this.customFieldId, items);
        },

        'keydown .js-card-customfield-stringtemplate-item'(event) {
          if (event.keyCode === 13) {
            event.preventDefault();

            if (event.metaKey || event.ctrlKey) {
              this.find('button[type=submit]').click();
            } else if (event.target.value.trim()) {
              const inputLast = this.find('input.last');

              let items = this.getItems();

              if (event.target === inputLast) {
                inputLast.value = '';
              } else if (event.target.nextSibling === inputLast) {
                inputLast.focus();
              } else {
                event.target.blur();

                const idx = Array.from(this.findAll('input')).indexOf(
                  event.target,
                );
                items.splice(idx + 1, 0, '');

                Tracker.afterFlush(() => {
                  const element = this.findAll('input')[idx + 1];
                  element.focus();
                  element.value = '';
                });
              }

              this.stringtemplateItems.set(items);
            }
          }
        },

        'blur .js-card-customfield-stringtemplate-item'(event) {
          if (
            !event.target.value.trim() ||
            event.target === this.find('input.last')
          ) {
            const items = this.getItems();
            this.stringtemplateItems.set(items);
            this.find('input.last').value = '';
          }
        },

        'click .js-close-inlined-form'(event) {
          this.stringtemplateItems.set(this.data().value ?? []);
        },
      },
    ];
  }
}.register('cardCustomField-stringtemplate'));
<commit_msg>Custom Field StringTemplates didn't save the last input value on touch devices<commit_after>import { DatePicker } from '/client/lib/datepicker';
import Cards from '/models/cards';

Template.cardCustomFieldsPopup.helpers({
  hasCustomField() {
    const card = Utils.getCurrentCard();
    const customFieldId = this._id;
    return card.customFieldIndex(customFieldId) > -1;
  },
});

Template.cardCustomFieldsPopup.events({
  'click .js-select-field'(event) {
    const card = Utils.getCurrentCard();
    const customFieldId = this._id;
    card.toggleCustomField(customFieldId);
    event.preventDefault();
  },
  'click .js-settings'(event) {
    EscapeActions.executeUpTo('detailsPane');
    Sidebar.setView('customFields');
    event.preventDefault();
  },
});

// cardCustomField
const CardCustomField = BlazeComponent.extendComponent({
  getTemplate() {
    return `cardCustomField-${this.data().definition.type}`;
  },

  onCreated() {
    const self = this;
    self.card = Utils.getCurrentCard();
    self.customFieldId = this.data()._id;
  },

  canModifyCard() {
    return (
      Meteor.user() &&
      Meteor.user().isBoardMember() &&
      !Meteor.user().isCommentOnly()
    );
  },
});
CardCustomField.register('cardCustomField');

// cardCustomField-text
(class extends CardCustomField {
  onCreated() {
    super.onCreated();
  }

  events() {
    return [
      {
        'submit .js-card-customfield-text'(event) {
          event.preventDefault();
          const value = this.currentComponent().getValue();
          this.card.setCustomField(this.customFieldId, value);
        },
      },
    ];
  }
}.register('cardCustomField-text'));

// cardCustomField-number
(class extends CardCustomField {
  onCreated() {
    super.onCreated();
  }

  events() {
    return [
      {
        'submit .js-card-customfield-number'(event) {
          event.preventDefault();
          const value = parseInt(this.find('input').value, 10);
          this.card.setCustomField(this.customFieldId, value);
        },
      },
    ];
  }
}.register('cardCustomField-number'));

// cardCustomField-checkbox
(class extends CardCustomField {
  onCreated() {
    super.onCreated();
  }

  toggleItem() {
    this.card.setCustomField(this.customFieldId, !this.data().value);
  }

  events() {
    return [
      {
        'click .js-checklist-item .check-box-container': this.toggleItem,
      },
    ];
  }
}.register('cardCustomField-checkbox'));

// cardCustomField-currency
(class extends CardCustomField {
  onCreated() {
    super.onCreated();

    this.currencyCode = this.data().definition.settings.currencyCode;
  }

  formattedValue() {
    const locale = TAPi18n.getLanguage();

    return new Intl.NumberFormat(locale, {
      style: 'currency',
      currency: this.currencyCode,
    }).format(this.data().value);
  }

  events() {
    return [
      {
        'submit .js-card-customfield-currency'(event) {
          event.preventDefault();
          // To allow input separated by comma, the comma is replaced by a period.
          const value = Number(this.find('input').value.replace(/,/i, '.'), 10);
          this.card.setCustomField(this.customFieldId, value);
        },
      },
    ];
  }
}.register('cardCustomField-currency'));

// cardCustomField-date
(class extends CardCustomField {
  onCreated() {
    super.onCreated();
    const self = this;
    self.date = ReactiveVar();
    self.now = ReactiveVar(moment());
    window.setInterval(() => {
      self.now.set(moment());
    }, 60000);

    self.autorun(() => {
      self.date.set(moment(self.data().value));
    });
  }

  showWeek() {
    return this.date.get().week().toString();
  }

  showDate() {
    // this will start working once mquandalle:moment
    // is updated to at least moment.js 2.10.5
    // until then, the date is displayed in the "L" format
    return this.date.get().calendar(null, {
      sameElse: 'llll',
    });
  }

  showISODate() {
    return this.date.get().toISOString();
  }

  classes() {
    if (
      this.date.get().isBefore(this.now.get(), 'minute') &&
      this.now.get().isBefore(this.data().value)
    ) {
      return 'current';
    }
    return '';
  }

  showTitle() {
    return `${TAPi18n.__('card-start-on')} ${this.date.get().format('LLLL')}`;
  }

  events() {
    return [
      {
        'click .js-edit-date': Popup.open('cardCustomField-date'),
      },
    ];
  }
}.register('cardCustomField-date'));

// cardCustomField-datePopup
(class extends DatePicker {
  onCreated() {
    super.onCreated();
    const self = this;
    self.card = Utils.getCurrentCard();
    self.customFieldId = this.data()._id;
    this.data().value && this.date.set(moment(this.data().value));
  }

  _storeDate(date) {
    this.card.setCustomField(this.customFieldId, date);
  }

  _deleteDate() {
    this.card.setCustomField(this.customFieldId, '');
  }
}.register('cardCustomField-datePopup'));

// cardCustomField-dropdown
(class extends CardCustomField {
  onCreated() {
    super.onCreated();
    this._items = this.data().definition.settings.dropdownItems;
    this.items = this._items.slice(0);
    this.items.unshift({
      _id: '',
      name: TAPi18n.__('custom-field-dropdown-none'),
    });
  }

  selectedItem() {
    const selected = this._items.find(item => {
      return item._id === this.data().value;
    });
    return selected
      ? selected.name
      : TAPi18n.__('custom-field-dropdown-unknown');
  }

  events() {
    return [
      {
        'submit .js-card-customfield-dropdown'(event) {
          event.preventDefault();
          const value = this.find('select').value;
          this.card.setCustomField(this.customFieldId, value);
        },
      },
    ];
  }
}.register('cardCustomField-dropdown'));

// cardCustomField-stringtemplate
(class extends CardCustomField {
  onCreated() {
    super.onCreated();

    this.stringtemplateFormat = this.data().definition.settings.stringtemplateFormat;
    this.stringtemplateSeparator = this.data().definition.settings.stringtemplateSeparator;

    this.stringtemplateItems = new ReactiveVar(this.data().value ?? []);
  }

  formattedValue() {
    return (this.data().value ?? [])
      .filter(value => !!value.trim())
      .map(value => this.stringtemplateFormat.replace(/%\{value\}/gi, value))
      .join(this.stringtemplateSeparator ?? '');
  }

  getItems() {
    return Array.from(this.findAll('input'))
      .map(input => input.value)
      .filter(value => !!value.trim());
  }

  events() {
    return [
      {
        'submit .js-card-customfield-stringtemplate'(event) {
          event.preventDefault();
          const items = this.stringtemplateItems.get()
          this.card.setCustomField(this.customFieldId, items);
        },

        'keydown .js-card-customfield-stringtemplate-item'(event) {
          if (event.keyCode === 13) {
            event.preventDefault();

            if (event.metaKey || event.ctrlKey) {
              this.find('button[type=submit]').click();
            } else if (event.target.value.trim()) {
              const inputLast = this.find('input.last');

              let items = this.getItems();

              if (event.target === inputLast) {
                inputLast.value = '';
              } else if (event.target.nextSibling === inputLast) {
                inputLast.focus();
              } else {
                event.target.blur();

                const idx = Array.from(this.findAll('input')).indexOf(
                  event.target,
                );
                items.splice(idx + 1, 0, '');

                Tracker.afterFlush(() => {
                  const element = this.findAll('input')[idx + 1];
                  element.focus();
                  element.value = '';
                });
              }

              this.stringtemplateItems.set(items);
            }
          }
        },

        'blur .js-card-customfield-stringtemplate-item'(event) {
          if (
            !event.target.value.trim() ||
            event.target === this.find('input.last')
          ) {
            const items = this.getItems();
            this.stringtemplateItems.set(items);
            this.find('input.last').value = '';
          }
        },

        'click .js-close-inlined-form'(event) {
          this.stringtemplateItems.set(this.data().value ?? []);
        },
      },
    ];
  }
}.register('cardCustomField-stringtemplate'));
<|endoftext|>


<commit_before>import wrapper from '../../constants/wrapper';

const constants = [
  'SEND_SIZE',
  'SCROLL_PARENT_TO_TOP',
  'LTI_NAVIGATE_HOME',
  'LTI_NAVIGATE_NEXT',
  'LTI_NAVIGATE_PREVIOUS',
  'HIDE_LMS_NAVIGATION',
  'SHOW_LMS_NAVIGATION',
  'AVAILABLE_LOCALES',
];

const requests = [];

export const Constants = wrapper(constants, requests);

// TODO: what do any of these do?
export const scrollParentToTop = () => ({ type: Constants.SCROLL_PARENT_TO_TOP });
export const navigateHome = () => ({ type: Constants.LTI_NAVIGATE_HOME });
export const navigateNext = () => ({ type: Constants.LTI_NAVIGATE_NEXT });
export const navigatePrevious = () => ({ type: Constants.LTI_NAVIGATE_PREVIOUS });
export const hideLMSNavigation = () => ({ type: Constants.HIDE_LMS_NAVIGATION });
export const showLMSNavigation = () => ({ type: Constants.SHOW_LMS_NAVIGATION });
export const availableLocales = () => ({ type: Constants.AVAILABLE_LOCALES });
<commit_msg>Restore missing method<commit_after>import wrapper from '../../constants/wrapper';

const constants = [
  'SEND_SIZE',
  'SCROLL_PARENT_TO_TOP',
  'LTI_NAVIGATE_HOME',
  'LTI_NAVIGATE_NEXT',
  'LTI_NAVIGATE_PREVIOUS',
  'HIDE_LMS_NAVIGATION',
  'SHOW_LMS_NAVIGATION',
  'AVAILABLE_LOCALES',
];

const requests = [];

export const Constants = wrapper(constants, requests);

export const sendSize = () => ({ type: Constants.SEND_SIZE });
export const scrollParentToTop = () => ({ type: Constants.SCROLL_PARENT_TO_TOP });
export const navigateHome = () => ({ type: Constants.LTI_NAVIGATE_HOME });
export const navigateNext = () => ({ type: Constants.LTI_NAVIGATE_NEXT });
export const navigatePrevious = () => ({ type: Constants.LTI_NAVIGATE_PREVIOUS });
export const hideLMSNavigation = () => ({ type: Constants.HIDE_LMS_NAVIGATION });
export const showLMSNavigation = () => ({ type: Constants.SHOW_LMS_NAVIGATION });
export const availableLocales = () => ({ type: Constants.AVAILABLE_LOCALES });
<|endoftext|>