<commit_before>const fs = require('fs');
const JSZip = require('jszip');
const getProperties = require('./properties');

var schema;
var pages = [];
var errors = {};
var instances = {};
var output = [];

function parseSketch(schemaJson, result) {
  schema = schemaJson;

  let pages = result.pages;
  for (var id in pages) {
    let page = pages[id];

    if (page.name == 'Symbols') {
      continue;
    }

    errors[page.name] = [];

    checkPage(page.name, page.layers);


    const outputJson = JSON.stringify(output, null, 2);

    fs.writeFile("output.json", outputJson, (err) => {
      if (err) {
        return console.log(err);
      }
      else {
        console.log('Property output saved to "output.json"');
      }
    });

    //reportErrors(page.name);
  }

  return result;
}

function checkPage(pageName, artboards) {
  for (var artboard of artboards) {

    var stack = {
      page: pageName,
      layerPath: [],
      counts: {}
    }

    stack = validateSketchObject(artboard, schema.hierarchy, stack);

    for (let pattern in stack.counts) {
      let count = stack.counts[pattern];
      if (count.value != count.expected) {
        console.log('Invalid count for: "' + pattern + '" in artboard: "' + artboard.name + '"');
        console.log('Found: ' + count.value);
        console.log('Expected: ' + count.expected + '\n');
      }
    }
  }
}

/*
 * Takes an object and compares it to an array of possible schemas.
 * When it finds a match, it continues to traverse down the tree.
 * If it doesn't find a match, an error is recorded.
 */
function validateSketchObject(obj, schemas, stack) {
  if (obj.name == 'meta') {
    return;
  }

  // Copy stack for local use
  let localStack = JSON.parse(JSON.stringify(stack));

  // If none of the schema's have a `pattern` key, don't check names
  let checkName = schemas.some((o) => {
    return 'pattern' in o;
  });
  let nameValidated = !checkName;

  // TODO: Add support for schema.pattern to be string OR list

  for (let schema of schemas) {

    // If it passes this check, the obj has the right class
    if (schema.class && obj['_class'] != schema.class) {
      continue;
    }

    // If it passes this check, the obj name matches the pattern
    if (checkName && schema.pattern) {
      let regex = new RegExp(schema.pattern, "g");
      if (regex.test(obj.name)) {
        nameValidated = true;
      } else {
        continue;
      }
    }

    // Need a pattern to track the count
    if (schema.pattern && schema.count) {
      if (stack.counts[schema.pattern]) {
        stack.counts[schema.pattern].value++;
      } else {
        stack.counts[schema.pattern] = {
          expected: schema.count,
          value: 1
        };
      }
    }

    if (schema.output) {
      // Copy stack for use in output
      let objOutput = {
        page: localStack.pageName,
        layerPath: localStack.layerPath,
        name: obj.name,
        properties: getProperties(obj, schema.output)
      }
      output.push(objOutput);
    }

    // Add the object to the stack
    if (obj['_class'] == 'artboard' && !localStack.artboard) {
      localStack['artboard'] = obj.name;
    } else {
      localStack['layerPath'].push(obj.name);
    }

    if (obj.layers && schema.layers) {
      for (let layer of obj.layers) {
        let childStack = validateSketchObject(layer, schema.layers, localStack);

        // Get the updated count values from the recursive call and combine them into one object
        localStack.counts = Object.assign({}, localStack.counts, childStack.counts);
      }
    }
  }

  // TODO: Separate errors by page
  if (!nameValidated) {
    let patterns = [];
    for (let schema of schemas) {
      patterns.push(schema.pattern);
    }
    console.log('Incorrect ' + obj['_class'] + ' name: "' + obj.name +
        '" in artboard: "' + stack.artboard + '"');
    console.log('Expected format(s): "' + patterns.join('", "') + '"\n');
  }

  return localStack;
}

module.exports = parseSketch;<commit_msg>Decompose output writing into it's own function<commit_after>const fs = require('fs');
const JSZip = require('jszip');
const getProperties = require('./properties');

var schema;
var pages = [];
var errors = {};
var instances = {};
var output = [];

function parseSketch(schemaJson, result) {
  schema = schemaJson;

  let pages = result.pages;
  for (var id in pages) {
    let page = pages[id];

    if (page.name == 'Symbols') {
      continue;
    }

    errors[page.name] = [];

    checkPage(page.name, page.layers);

    writeOutput();

    //reportErrors(page.name);
  }

  return result;
}

function checkPage(pageName, artboards) {
  for (var artboard of artboards) {

    var stack = {
      page: pageName,
      layerPath: [],
      counts: {}
    }

    stack = validateSketchObject(artboard, schema.hierarchy, stack);

    for (let pattern in stack.counts) {
      let count = stack.counts[pattern];
      if (count.value != count.expected) {
        console.log('Invalid count for: "' + pattern + '" in artboard: "' + artboard.name + '"');
        console.log('Found: ' + count.value);
        console.log('Expected: ' + count.expected + '\n');
      }
    }
  }
}

/*
 * Takes an object and compares it to an array of possible schemas.
 * When it finds a match, it continues to traverse down the tree.
 * If it doesn't find a match, an error is recorded.
 */
function validateSketchObject(obj, schemas, stack) {
  if (obj.name == 'meta') {
    return;
  }

  // Copy stack for local use
  let localStack = JSON.parse(JSON.stringify(stack));

  // If none of the schema's have a `pattern` key, don't check names
  let checkName = schemas.some((o) => {
    return 'pattern' in o;
  });
  let nameValidated = !checkName;

  // TODO: Add support for schema.pattern to be string OR list

  for (let schema of schemas) {

    // If it passes this check, the obj has the right class
    if (schema.class && obj['_class'] != schema.class) {
      continue;
    }

    // If it passes this check, the obj name matches the pattern
    if (checkName && schema.pattern) {
      let regex = new RegExp(schema.pattern, "g");
      if (regex.test(obj.name)) {
        nameValidated = true;
      } else {
        continue;
      }
    }

    // Need a pattern to track the count
    if (schema.pattern && schema.count) {
      if (stack.counts[schema.pattern]) {
        stack.counts[schema.pattern].value++;
      } else {
        stack.counts[schema.pattern] = {
          expected: schema.count,
          value: 1
        };
      }
    }

    if (schema.output) {
      let objOutput = {
        page: localStack.pageName,
        layerPath: localStack.layerPath,
        name: obj.name,
        properties: getProperties(obj, schema.output)
      }
      output.push(objOutput);
    }

    // Add the object to the stack
    if (obj['_class'] == 'artboard' && !localStack.artboard) {
      localStack['artboard'] = obj.name;
    } else {
      localStack['layerPath'].push(obj.name);
    }

    // If there are nested layers, validate the children
    if (obj.layers && schema.layers) {
      for (let layer of obj.layers) {
        let childStack = validateSketchObject(layer, schema.layers, localStack);

        // Get the updated count values from the recursive call and combine them into one object
        localStack.counts = Object.assign({}, localStack.counts, childStack.counts);
      }
    }
  }

  // TODO: Separate errors by page
  if (!nameValidated) {
    let patterns = [];
    for (let schema of schemas) {
      patterns.push(schema.pattern);
    }
    console.log('Incorrect ' + obj['_class'] + ' name: "' + obj.name +
        '" in artboard: "' + stack.artboard + '"');
    console.log('Expected format(s): "' + patterns.join('", "') + '"\n');
  }

  return localStack;
}

function writeOutput() {
  if (output && !schema.outputLocation) {
    console.log('Output location required in schema to output (i.e. "outputLocation": "./output.json"');
  }

  const outputJson = JSON.stringify(output, null, 2);

  fs.writeFile(schema.outputLocation, outputJson, (err) => {
    if (err) {
      return console.log(err);
    }
    else {
      console.log('Style property output saved to "' + schema.outputLocation + '"');
    }
  });
}

module.exports = parseSketch;
{'fraction_non_alphanumeric': 0.13795594077284218, 'fraction_numerical': 0.00048152160828217165, 'mean_word_length': 2.5218312844425603, 'pattern_counts': {'":': 1, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4182660', 'n_tokens_mistral': 2583, 'n_tokens_neox': 2514, 'n_words': 718}