<commit_before>/*global cat,cd,cp,echo,env,exec,exit,find,mkdir,mv,pwd,rm,sed,target,test */

var async = require( "async" ),
    path = require( "path" ),
    normalize = function( p ){ return path.normalize( p ); },
    // Make Windows happy, use `node <path>`
    nodeExec = function( p ){ return 'node "' + p + '"'; },

    JSLINT = nodeExec( normalize( "./node_modules/jshint/bin/jshint" ) ),
    html5lint = require( "html5-lint" ),
    CSSLINT = nodeExec( normalize( "./node_modules/csslint/cli.js" ) ),
    RJS = nodeExec( normalize( "./node_modules/requirejs/bin/r.js" ) ),
    LESS = nodeExec( normalize( "./node_modules/less/bin/lessc" ) ),

    DIST_DIR = "dist",

    // Global var for exit code
    passed = true;

require("shelljs/make");

// Get the git repo version info for a given repo root dir
function gitDescribe( repoRoot ) {
  var cwd = pwd();
  cd( repoRoot );
  var version = exec( "git describe",
                      { silent: true } ).output.replace( /\r?\n/m, "" );
  cd( cwd );
  return version;
}

// To supress CSS warnings/errors for a particular line, end the line
// with a comment indicating you want CSS Lint to ignore this line's
// error(s).  Here are some examples:
//
//   -webkit-appearance: button; /* csslint-ignore */
//   -webkit-appearance: button; /*csslint-ignore*/
//   -webkit-appearance: button; /* csslint-ignore: This is being done because of iOS ... */
function checkCSSFile( filename, warnings, errors ) {
  var fileLines = cat( filename ).split( /\r?\n/ ),
    ignoreLines = "",
    // Look for: "blah blah blah /* csslint-ignore */" or
    //           "blah blah /*csslint-ignore: this is my reason*/"
    ignoreRegex = /\/\*\s*csslint-ignore[^*]*\*\/$/,
    // Errors look like: "css/butter.ui.css: line 186, col 3, Error..."
    lineRegex = /\: line (\d+),/;

  echo( "## `" + filename + "`" );

  // Build a map of lines to ignore: "|14||27|" means ignore lines 14 and 27
  for( var i=0; i < fileLines.length; i++ ){
    if( ignoreRegex.test( fileLines[ i ] ) ) {
      ignoreLines += "|" + i + "|";
    }
  }

  // Run CSSLint across the file, check for errors/warnings and ignore if
  // they are ones we know about from above.
  exec(CSSLINT +
    " --warnings=" + warnings +
    " --errors=" + errors +
    " --quiet --format=compact" +
    " " + filename, { silent: true } ).output.split( /\r?\n/ )
    .forEach( function( line ) {
      if( !line ) {
        return;
      }

      // Some warnings don't refer to a line, e.g.
      // "css/butter.ui.css: Warning - Too many floats (10)..."
      var matches = line.match( lineRegex ),
        lineNumber = matches ? matches[ 1 ] : null;

      if( !!lineNumber ) {
        if( ignoreLines.indexOf( "|" + lineNumber + "|" ) === -1 ) {
          echo( line );
          passed = false;
        }
      } else {
        echo( line );
        passed = false;
      }
  });
}

function checkCSS() {
  // see cli.js --list-rules.
  var warnings = [
//    "important",
//    "adjoining-classes",
//    "duplicate-background-images",
//    "qualified-headings",
//    "fallback-colors",
//    "empty-rules",
//    "shorthand",
//    "overqualified-elements",
//    "import",
//    "regex-selectors",
//    "rules-count",
//    "font-sizes",
//    "universal-selector",
//    "unqualified-attributes",
    "zero-units"
  ].join(",");

  var errors = [
    "known-properties",
    "compatible-vendor-prefixes",
    "display-property-grouping",
    "duplicate-properties",
    "errors",
    "gradients",
    "font-faces",
    //"floats",
    "vendor-prefix"
  ].join(",");

  echo( "" );
  echo( "# Linting CSS files" );

  find( "public/" ).filter(function( filename ) {
    return filename.match( /\.css$/ ) && !filename.match( /^public\/test/ ) &&
           !filename.match( /^public\/external/ );
  }).forEach(function( filename ) {
    checkCSSFile( filename, warnings, errors );
  });

}

function checkJS() {
  var dirs = [ "*.js", "lib/", "public/src/", "public/templates/", "routes/", "test/" ];

  echo( "# Linting JS files" );
  dirs.forEach( function( value ) {
    echo( "## `" + value + "`" );
  });

  // Get all js and json files in dirs
  var files = dirs.join( " " );

  passed = !exec( JSLINT + " " + files + " --extra-ext json" ).code && passed;
}

var desc = {
  check: "Lint CSS, HTML, and JS",
  css: "Build LESS files to CSS",
  deploy: "Build Butter suitable for production",
  server: "Run the development server"
};

target.all = function() {
  echo("Please specify a target. Available targets:");
  Object.keys(target).sort().filter(function(t) {
    return t !== "all";
  }).forEach(function(t) {
    echo("  " + t + " - " + desc[t]);
  });
};

function checkHTML( callback ) {
  // Poor-man's HTML Doc vs. Fragment check
  function isHTMLFragment( filename ) {
    return !( /<html[^>]*\>/m ).test( cat( filename ) );
  }

  // List of errors/warnings to ignore, some with a conditional
  // to only ignore when some condition is true.
  var ignoreList = [
    {
      // Don't warn on valid docs
      text: "The document is valid HTML5 + ARIA + SVG 1.1 + MathML 2.0 (subject to the utter previewness of this service)."
    },
    {
      text: "Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.",
      when: isHTMLFragment
    },
    {
      text: "Element “head” is missing a required instance of child element “title”.",
      when: isHTMLFragment
    },
    {
      text: "Bad value “X-UA-Compatible” for attribute “http-equiv” on element “meta”."
    },
    {
      text: "Warning: The character encoding of the document was not declared."
    },
    {
      text: "Attribute “mozallowfullscreen” not allowed on element “iframe” at this point."
    },
    {
      text: "Attribute “webkitallowfullscreen” not allowed on element “iframe” at this point."
    },
    {
      text: "Attribute “allowfullscreen” not allowed on element “iframe” at this point."
    },
    {
      // Let <style> be in fragments.
      text: "Element “style” not allowed as child of element “body” in this context. (Suppressing further errors from this subtree.)",
      when: isHTMLFragment
    },
    {
      // Let <li> be in fragments.
      text: "Element “li” not allowed as child of element “body” in this context. (Suppressing further errors from this subtree.)",
      when: isHTMLFragment
    }
  ];

  echo( "" );
  echo( "# Linting HTML Files" );

  var q = async.queue( function( htmlFile, cb ) {
    html5lint( cat( htmlFile ), function( err, messages ) {
      echo( "## " + htmlFile );
      messages.messages.forEach( function( msg ) {
        var ignored = ignoreList.some( function( ignore ) {
          return ignore.text === msg.message && ( !ignore.when || ( ignore.when && ignore.when( htmlFile ) ) );
        });

        if ( !ignored ) {
          console.log( "Error: %s in %s:%d:%d", msg.message, htmlFile, msg.lastLine, msg.lastColumn );
        }
      });

      cb();
    });
  });

  q.drain = function() {
    callback();
  };

  q.push( find( "public/" ).filter( function( file ) {
    return file.match( /\.html$/ ) && !file.match( /^public\/test/ ) && !file.match( /^public\/external/ );
  }));
}

function lessToCSS( options ){
  var BUTTER_CSS_FILE_COMMENT = "/* THIS FILE WAS GENERATED BY A TOOL, DO NOT EDIT */";

  var compress = !!options.compress,
      lessFile = options.lessFile,
      cssFile = options.cssFile;

  echo( "## `" + lessFile + "` => `" + cssFile + "`" + ( compress ? " with compression" : "" ));

  var args = compress ? " --yui-compress " : " ",
  result = exec(LESS + args + lessFile, {silent:true});

  if( result.code === 0 ){
    var css = BUTTER_CSS_FILE_COMMENT + "\n\n" + result.output;
    css.to( cssFile );
  } else {
    echo( result.output );
    passed = false;
  }
}

function buildCSS(compress) {
  echo( "" );
  echo( "# Compiling CSS Files" );

  [
    "public/css/butter.ui",
    "public/css/transitions",
    "public/css/embed",
    "public/css/embed-shell",
    "public/src/ui/webmakernav/webmakernav",
    "public/templates/assets/css/jquery-ui/jquery.ui.butter",
    "public/templates/assets/plugins/twitter/popcorn.twitter",
    "public/templates/assets/plugins/wikipedia/popcorn.wikipedia",
    "public/templates/basic/style"
  ].forEach(function( file ) {
    lessToCSS({
      lessFile: file + ".less",
      cssFile: file + ".css",
      compress: compress
    });
  });
}

target.check = function() {
  async.series([
    function( callback ) {
      checkJS();
      callback();
    },
    function( callback ) {
      checkCSS();
      callback();
    },
    function( callback ) {
      checkHTML( callback );
    },
    function( callback ) {
      exit( passed ? 0 : 1 );
      callback();
    }
  ]);
};

function stampVersion( version, filename ){
  // Stamp embed.version with supplied version, or git info
  version = version || gitDescribe( "." );
  sed( "-i", /@VERSION@/g, version, filename );
}

target.css = function() {
  buildCSS();
};

function buildJS( version, compress ){
  var doCompress = compress ? "" : "optimize=none";
  var result = "";

  echo( "" );
  echo( "# Optimizing JS Files" );

  echo( "## public/src/butter.js" );
  result = exec(RJS + " -o tools/build-butter.js " + doCompress, {silent: true});
  if (!!result.code) {
    echo(result.output);
  }
  stampVersion( version, "dist/public/src/butter.js" );

  echo( "## public/src/embed.js" );
  result = exec(RJS + " -o tools/build-embed.js " + doCompress, {silent: true});
  if (!!result.code) {
    echo(result.output);
  }
  stampVersion( version, "dist/public/src/embed.js" );

  echo( "## public/src/webmakernav.js" );
  result = exec(RJS + " -o tools/build-webmakernav.js " + doCompress, {silent: true});
  if (!!result.code) {
    echo(result.output);
  }
}

target.server = function() {
  echo("### Serving butter");

  require( "./server.js" );
};

target.deploy = function(){
  echo("### Making deployable versions of butter, embed, popcorn, etc. in dist/ (use UNMINIFIED=1 for unminified)");

  // To get unminified butter.js, use the UNMINIFIED env variable:
  // $ UNMINIFIED=1 node make deploy
  var compress = env.UNMINIFIED !== "1",
      version = env.VERSION;

  rm( "-fr" , DIST_DIR );
  mkdir( "-p" , DIST_DIR );

  buildCSS( compress );
  buildJS( version, compress );

  // Copy server assets
  cp( "-R", [
    "README.md",
    "lib",
    "package.json",
    "routes",
    "server.js",
    "views"
  ], "dist/");

  // Selectively copy things from public/
  // We don't need .less files, since they're built in buildCSS()
  // We don't need js files from external/ and src/ because they're built into
  // butter with requirejs or copied selectively below.
  find( "public/" ).filter(function( path ) {
    return !path.match( /\.less$/ ) &&
           !path.match( /^public\/external/ ) &&
           !path.match( /^public\/src/ ) &&
           !path.match( /^public\/test/ );
  }).forEach(function( path ) {
    if ( test( "-d", path ) ) {
      mkdir( "-p", "dist/" + path );
    } else if ( test( "-f", path ) ) {
      cp( path, "dist/" + path );
    }
  });

  // Bug #3357 - Add a Popcorn() shim for projects published <v1.0.19
  mkdir( "-p", "dist/public/external/popcorn-js/" );
  cp( "tools/oldprojectshim.js", "dist/public/external/popcorn-js/popcorn.js" );

  // We host our own version of the stamen map tile script, copy that over.
  mkdir( "-p", "dist/public/external/stamen/" );
  cp( "public/external/stamen/tile.stamen-1.2.0.js", "dist/public/external/stamen" );

  // Copy RPM spec files and stamp with version
  var rpmVersion = ( version ? version : gitDescribe( "." ) ).replace( /-/g, "_" );
  cp( "tools/rpmspec/*", DIST_DIR );
  stampVersion( rpmVersion, "dist/butter.spec" );

  // Add a rev.txt file that's web-accessible
  gitDescribe( "." ).to("dist/public/rev.txt");

  // Create a tar archive
  var tarName = "butter-" + rpmVersion + ".tar.bz2";
  exec( "tar -cjf '" + tarName + "' dist" );
  mv( tarName, "dist" );

  // It's important to use the production config
  echo( "" );
  echo( "Run cornfield with `NODE_ENV=production node server.js`" );
};
<commit_msg>No Bug - Re-enable linting of LESS files<commit_after>/*global cat,cd,cp,echo,env,exec,exit,find,mkdir,mv,pwd,rm,sed,target,test */

var async = require( "async" ),
    path = require( "path" ),
    normalize = function( p ){ return path.normalize( p ); },
    // Make Windows happy, use `node <path>`
    nodeExec = function( p ){ return 'node "' + p + '"'; },

    JSLINT = nodeExec( normalize( "./node_modules/jshint/bin/jshint" ) ),
    html5lint = require( "html5-lint" ),
    CSSLINT = nodeExec( normalize( "./node_modules/csslint/cli.js" ) ),
    RJS = nodeExec( normalize( "./node_modules/requirejs/bin/r.js" ) ),
    LESS = nodeExec( normalize( "./node_modules/less/bin/lessc" ) ),

    DIST_DIR = "dist",

    // Global var for exit code
    passed = true;

require("shelljs/make");

// Get the git repo version info for a given repo root dir
function gitDescribe( repoRoot ) {
  var cwd = pwd();
  cd( repoRoot );
  var version = exec( "git describe",
                      { silent: true } ).output.replace( /\r?\n/m, "" );
  cd( cwd );
  return version;
}

// To supress CSS warnings/errors for a particular line, end the line
// with a comment indicating you want CSS Lint to ignore this line's
// error(s).  Here are some examples:
//
//   -webkit-appearance: button; /* csslint-ignore */
//   -webkit-appearance: button; /*csslint-ignore*/
//   -webkit-appearance: button; /* csslint-ignore: This is being done because of iOS ... */
function checkCSSFile( filename, warnings, errors ) {
  var fileLines = cat( filename ).split( /\r?\n/ ),
    ignoreLines = "",
    // Look for: "blah blah blah /* csslint-ignore */" or
    //           "blah blah /*csslint-ignore: this is my reason*/"
    ignoreRegex = /\/\*\s*csslint-ignore[^*]*\*\/$/,
    // Errors look like: "css/butter.ui.css: line 186, col 3, Error..."
    lineRegex = /\: line (\d+),/;

  echo( "## `" + filename + "`" );

  // Build a map of lines to ignore: "|14||27|" means ignore lines 14 and 27
  for( var i=0; i < fileLines.length; i++ ){
    if( ignoreRegex.test( fileLines[ i ] ) ) {
      ignoreLines += "|" + i + "|";
    }
  }

  // Run CSSLint across the file, check for errors/warnings and ignore if
  // they are ones we know about from above.
  exec(CSSLINT +
    " --warnings=" + warnings +
    " --errors=" + errors +
    " --quiet --format=compact" +
    " " + filename, { silent: true } ).output.split( /\r?\n/ )
    .forEach( function( line ) {
      if( !line ) {
        return;
      }

      // Some warnings don't refer to a line, e.g.
      // "css/butter.ui.css: Warning - Too many floats (10)..."
      var matches = line.match( lineRegex ),
        lineNumber = matches ? matches[ 1 ] : null;

      if( !!lineNumber ) {
        if( ignoreLines.indexOf( "|" + lineNumber + "|" ) === -1 ) {
          echo( line );
          passed = false;
        }
      } else {
        echo( line );
        passed = false;
      }
  });
}

function checkCSS() {
  // see cli.js --list-rules.
  var warnings = [
//    "important",
//    "adjoining-classes",
//    "duplicate-background-images",
//    "qualified-headings",
//    "fallback-colors",
//    "empty-rules",
//    "shorthand",
//    "overqualified-elements",
//    "import",
//    "regex-selectors",
//    "rules-count",
//    "font-sizes",
//    "universal-selector",
//    "unqualified-attributes",
    "zero-units"
  ].join(",");

  var errors = [
    "known-properties",
    "compatible-vendor-prefixes",
    "display-property-grouping",
    "duplicate-properties",
    "errors",
    "gradients",
    "font-faces",
    //"floats",
    "vendor-prefix"
  ].join(",");

  echo( "" );
  echo( "# Linting CSS files" );

  find( "public/" ).filter(function( filename ) {
    return filename.match( /\.css$/ ) && !filename.match( /^public\/test/ ) &&
           !filename.match( /^public\/external/ );
  }).forEach(function( filename ) {
    checkCSSFile( filename, warnings, errors );
  });

}

function checkJS() {
  var dirs = [ "*.js", "lib/", "public/src/", "public/templates/", "routes/", "test/" ];

  echo( "# Linting JS files" );
  dirs.forEach( function( value ) {
    echo( "## `" + value + "`" );
  });

  // Get all js and json files in dirs
  var files = dirs.join( " " );

  passed = !exec( JSLINT + " " + files + " --extra-ext json" ).code && passed;
}

var desc = {
  check: "Lint CSS, HTML, and JS",
  css: "Build LESS files to CSS",
  deploy: "Build Butter suitable for production",
  server: "Run the development server"
};

target.all = function() {
  echo("Please specify a target. Available targets:");
  Object.keys(target).sort().filter(function(t) {
    return t !== "all";
  }).forEach(function(t) {
    echo("  " + t + " - " + desc[t]);
  });
};

function checkHTML( callback ) {
  // Poor-man's HTML Doc vs. Fragment check
  function isHTMLFragment( filename ) {
    return !( /<html[^>]*\>/m ).test( cat( filename ) );
  }

  // List of errors/warnings to ignore, some with a conditional
  // to only ignore when some condition is true.
  var ignoreList = [
    {
      // Don't warn on valid docs
      text: "The document is valid HTML5 + ARIA + SVG 1.1 + MathML 2.0 (subject to the utter previewness of this service)."
    },
    {
      text: "Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.",
      when: isHTMLFragment
    },
    {
      text: "Element “head” is missing a required instance of child element “title”.",
      when: isHTMLFragment
    },
    {
      text: "Bad value “X-UA-Compatible” for attribute “http-equiv” on element “meta”."
    },
    {
      text: "Warning: The character encoding of the document was not declared."
    },
    {
      text: "Attribute “mozallowfullscreen” not allowed on element “iframe” at this point."
    },
    {
      text: "Attribute “webkitallowfullscreen” not allowed on element “iframe” at this point."
    },
    {
      text: "Attribute “allowfullscreen” not allowed on element “iframe” at this point."
    },
    {
      // Let <style> be in fragments.
      text: "Element “style” not allowed as child of element “body” in this context. (Suppressing further errors from this subtree.)",
      when: isHTMLFragment
    },
    {
      // Let <li> be in fragments.
      text: "Element “li” not allowed as child of element “body” in this context. (Suppressing further errors from this subtree.)",
      when: isHTMLFragment
    }
  ];

  echo( "" );
  echo( "# Linting HTML Files" );

  var q = async.queue( function( htmlFile, cb ) {
    html5lint( cat( htmlFile ), function( err, messages ) {
      echo( "## " + htmlFile );
      messages.messages.forEach( function( msg ) {
        var ignored = ignoreList.some( function( ignore ) {
          return ignore.text === msg.message && ( !ignore.when || ( ignore.when && ignore.when( htmlFile ) ) );
        });

        if ( !ignored ) {
          console.log( "Error: %s in %s:%d:%d", msg.message, htmlFile, msg.lastLine, msg.lastColumn );
        }
      });

      cb();
    });
  });

  q.drain = function() {
    callback();
  };

  q.push( find( "public/" ).filter( function( file ) {
    return file.match( /\.html$/ ) && !file.match( /^public\/test/ ) && !file.match( /^public\/external/ );
  }));
}

function lessToCSS( options ){
  var BUTTER_CSS_FILE_COMMENT = "/* THIS FILE WAS GENERATED BY A TOOL, DO NOT EDIT */";

  var compress = !!options.compress,
      lessFile = options.lessFile,
      cssFile = options.cssFile;

  echo( "## `" + lessFile + "` => `" + cssFile + "`" + ( compress ? " with compression" : "" ));

  var args = compress ? " --yui-compress " : " ",
  result = exec(LESS + args + lessFile, {silent:true});

  if( result.code === 0 ){
    var css = BUTTER_CSS_FILE_COMMENT + "\n\n" + result.output;
    css.to( cssFile );
  } else {
    echo( result.output );
    passed = false;
  }
}

function buildCSS(compress) {
  echo( "" );
  echo( "# Compiling CSS Files" );

  [
    "public/css/butter.ui",
    "public/css/transitions",
    "public/css/embed",
    "public/css/embed-shell",
    "public/templates/assets/css/jquery-ui/jquery.ui.butter",
    "public/templates/assets/plugins/twitter/popcorn.twitter",
    "public/templates/assets/plugins/wikipedia/popcorn.wikipedia",
    "public/templates/basic/style"
  ].forEach(function( file ) {
    lessToCSS({
      lessFile: file + ".less",
      cssFile: file + ".css",
      compress: compress
    });
  });
}

target.check = function() {
  async.series([
    function( callback ) {
      checkJS();
      callback();
    },
    function( callback ) {
      buildCSS();
      checkCSS();
      callback();
    },
    function( callback ) {
      checkHTML( callback );
    },
    function( callback ) {
      exit( passed ? 0 : 1 );
      callback();
    }
  ]);
};

function stampVersion( version, filename ){
  // Stamp embed.version with supplied version, or git info
  version = version || gitDescribe( "." );
  sed( "-i", /@VERSION@/g, version, filename );
}

target.css = function() {
  buildCSS();
};

function buildJS( version, compress ){
  var doCompress = compress ? "" : "optimize=none";
  var result = "";

  echo( "" );
  echo( "# Optimizing JS Files" );

  echo( "## public/src/butter.js" );
  result = exec(RJS + " -o tools/build-butter.js " + doCompress, {silent: true});
  if (!!result.code) {
    echo(result.output);
  }
  stampVersion( version, "dist/public/src/butter.js" );

  echo( "## public/src/embed.js" );
  result = exec(RJS + " -o tools/build-embed.js " + doCompress, {silent: true});
  if (!!result.code) {
    echo(result.output);
  }
  stampVersion( version, "dist/public/src/embed.js" );

  echo( "## public/src/webmakernav.js" );
  result = exec(RJS + " -o tools/build-webmakernav.js " + doCompress, {silent: true});
  if (!!result.code) {
    echo(result.output);
  }
}

target.server = function() {
  echo("### Serving butter");

  require( "./server.js" );
};

target.deploy = function(){
  echo("### Making deployable versions of butter, embed, popcorn, etc. in dist/ (use UNMINIFIED=1 for unminified)");

  // To get unminified butter.js, use the UNMINIFIED env variable:
  // $ UNMINIFIED=1 node make deploy
  var compress = env.UNMINIFIED !== "1",
      version = env.VERSION;

  rm( "-fr" , DIST_DIR );
  mkdir( "-p" , DIST_DIR );

  buildCSS( compress );
  buildJS( version, compress );

  // Copy server assets
  cp( "-R", [
    "README.md",
    "lib",
    "package.json",
    "routes",
    "server.js",
    "views"
  ], "dist/");

  // Selectively copy things from public/
  // We don't need .less files, since they're built in buildCSS()
  // We don't need js files from external/ and src/ because they're built into
  // butter with requirejs or copied selectively below.
  find( "public/" ).filter(function( path ) {
    return !path.match( /\.less$/ ) &&
           !path.match( /^public\/external/ ) &&
           !path.match( /^public\/src/ ) &&
           !path.match( /^public\/test/ );
  }).forEach(function( path ) {
    if ( test( "-d", path ) ) {
      mkdir( "-p", "dist/" + path );
    } else if ( test( "-f", path ) ) {
      cp( path, "dist/" + path );
    }
  });

  // Bug #3357 - Add a Popcorn() shim for projects published <v1.0.19
  mkdir( "-p", "dist/public/external/popcorn-js/" );
  cp( "tools/oldprojectshim.js", "dist/public/external/popcorn-js/popcorn.js" );

  // We host our own version of the stamen map tile script, copy that over.
  mkdir( "-p", "dist/public/external/stamen/" );
  cp( "public/external/stamen/tile.stamen-1.2.0.js", "dist/public/external/stamen" );

  // Copy RPM spec files and stamp with version
  var rpmVersion = ( version ? version : gitDescribe( "." ) ).replace( /-/g, "_" );
  cp( "tools/rpmspec/*", DIST_DIR );
  stampVersion( rpmVersion, "dist/butter.spec" );

  // Add a rev.txt file that's web-accessible
  gitDescribe( "." ).to("dist/public/rev.txt");

  // Create a tar archive
  var tarName = "butter-" + rpmVersion + ".tar.bz2";
  exec( "tar -cjf '" + tarName + "' dist" );
  mv( tarName, "dist" );

  // It's important to use the production config
  echo( "" );
  echo( "Run cornfield with `NODE_ENV=production node server.js`" );
};
<|endoftext|>


<commit_before>/*!
 * fullcalendar-columns v1.7
 * Docs & License: https://github.com/mherrmann/fullcalendar-columns
 * (c) 2015 Michael Herrmann
 */

(function($, moment) {
	var fc = $.fullCalendar;
	var AgendaView = fc.views.agenda.class || fc.views.agenda;
	fc.views.multiColAgenda = AgendaView.extend({
		originalEvents: null,
		fakeEvents: null,
		initialize: function() {
			this.numColumns = this.opt('numColumns');
			this.columnHeaders = this.opt('columnHeaders');
			AgendaView.prototype.initialize.call(this);
			this._monkeyPatchGridRendering();
		},
		renderEvents: function(events) {
			this.originalEvents = {};
			this.fakeEvents = [];
			for (var i = 0; i < events.length; i++) {
				var event = events[i];
				this.originalEvents[event._id] = event;
				this.fakeEvents.push(this._computeFakeEvent(event));
			}
			return AgendaView.prototype.renderEvents.call(
				this, this.fakeEvents
			);
		},
		trigger: function(name, thisObj) {
			var args = Array.prototype.slice.call(arguments);
			if (name == 'eventRender' || name == 'eventAfterRender'
				|| name == 'eventDestroy' || name == 'eventClick'
				|| name == 'eventMouseover' || name == 'eventMouseout')
				args[2] = this.originalEvents[args[2]._id];
			else if (name == 'dayClick' || name == 'dayRightclick') {
				var date = this._computeOriginalEvent({ start: args[2] });
				args[2] = date.start;
				args[2].column = date.column;
			}
			return AgendaView.prototype.trigger.apply(this, args);
		},
		reportEventResize: function(event, location, largeUnit, el, ev) {
			return this._reportEventReschedule(
				'reportEventResize', event, location, largeUnit, el, ev
			);
		},
		reportEventDrop: function(event, location, largeUnit, el, ev) {
			return this._reportEventReschedule(
				'reportEventDrop', event, location, largeUnit, el, ev
			);
		},
		updateEvent: function(event) {
			$.extend(
				this._getFakeEvent(event._id), this._computeFakeEvent(event)
			);
		},
		computeRange: function(date) {
			var result = AgendaView.prototype.computeRange.call(this, date);
			var daysAvailable =
				this._countNonHiddenDaysBetween(result.start, result.end);
			var daysRequired = daysAvailable * this.numColumns;
			result.end = this._addNonHiddenDays(result.start, daysRequired);
			return result;
		},
		_monkeyPatchGridRendering: function() {
			var that = this;
			var origHeadCellHtml = this.timeGrid.headCellHtml;
			this.timeGrid.headCellHtml = function(cell) {
				/*
				 * Make multiple day header cells (each for one column) appear
				 * as one. The easiest way to do this would be to just render
				 * a single header cell with colspan=this.numColumns. However,
				 * this leads to misalignment between the day header cells and
				 * the events table. To get around this, we do render the same
				 * number of  cells as FullCalendar, but only fill the first
				 * one:
				 */
				var cellOrig = that._computeOriginalEvent(cell);
				var $html = $(origHeadCellHtml.call(this, cellOrig));
				var isFirstCellForDay = cellOrig.column == 0;
				var isLastCellForDay = cellOrig.column == that.numColumns - 1;

				var html = '';
				if (isFirstCellForDay) {
					// Make the cell appear centered:
					var posPercent = 100 * that.numColumns;
					html = '
'
					       + $html.html() + '
';
				} else {
					html = '

';
					$html.css('border-left-width', 0);
				}
				if (! isLastCellForDay)
					$html.css('border-right-width', 0);
				if (that.columnHeaders) {
					// Use the prefix 'fce-col-' (as in "FullCalendar
					// extension") for classes pertaining only to
					// fullcalendar-columns:
					html += '
' +
					        that.columnHeaders[cellOrig.column] + '
';
				}
				$html.html(html);
				return $html[0].outerHTML;
			};
			var origGetDayClasses = this.timeGrid.getDayClasses;
			this.timeGrid.getDayClasses = function(date) {
				var dateCol = that._computeOriginalEvent({ start: date });
				return origGetDayClasses.call(this, dateCol.start);
			};
		},
		_computeFakeEvent: function(event) {
			var result = $.extend({}, event);
			var start = this.calendar.moment(event.start);
			if (start >= this.start) {
				var daysDelta =
					moment.duration(event.start - this.start).days();
				var fakeDayOffset = daysDelta * this.numColumns + event.column;
				result.start = this._addNonHiddenDays(
					start.subtract(daysDelta, 'days'), fakeDayOffset
				);
				if ('end' in event) {
					var end = this.calendar.moment(event.end);
					result.end = this._addNonHiddenDays(
						end.subtract(daysDelta, 'days'), fakeDayOffset
					);
				}
			}
			return result;
		},
		_addNonHiddenDays: function(date, deltaDays) {
			var result = this.calendar.moment(date);
			for (var i=0; i < deltaDays; i++)
				result = this.skipHiddenDays(result.add(1, 'day'));
			return result;
		},
		_countNonHiddenDaysBetween: function(date1, date2) {
			for (var result=0; date1.isBefore(date2, 'day'); result++)
				date1 = this._addNonHiddenDays(date1, 1);
			return result;
		},
		_computeOriginalEvent: function(event) {
			var result = $.extend({}, event);
			var start = this.calendar.moment(event.start);
			if (start >= this.start) {
				var fakeDayOffset =
					this._countNonHiddenDaysBetween(this.start, start);
				result.column = fakeDayOffset % this.numColumns;
				var daysDelta = start.diff(this.start, 'days');
				var days = Math.floor(fakeDayOffset / this.numColumns);
				result.start = this._addNonHiddenDays(
					start.subtract(daysDelta, 'days'), days
				);
				if ('end' in event) {
					var end = this.calendar.moment(event.end);
					result.end = this._addNonHiddenDays(
						end.subtract(daysDelta, 'days'), days
					);
				}
			}
			return result;
		},
		_getFakeEvent: function(_id) {
			for (var i=0; i < this.fakeEvents.length; i++)
				if (this.fakeEvents[i]._id == _id)
					return this.fakeEvents[i];
		},
		_reportEventReschedule: function(
			rescheduleType, fakeEvent, location, largeUnit, el, ev
		) {
			fakeEvent.start = location.start.clone();
			fakeEvent.end = location.end.clone();
			var event = this.originalEvents[fakeEvent._id];
			location = this._computeOriginalEvent(location);
			return AgendaView.prototype[rescheduleType].call(
				this, event, location, largeUnit, el, ev
			);
		}
	});
	var origFullCalendar = $.fn.fullCalendar;
	$.fn.fullCalendar = function(options) {
		if (options == 'updateEvent') { // Required by multiColAgenda
			var view = origFullCalendar.call(this, 'getView');
			if (view.updateEvent)
				view.updateEvent(arguments[1]);
		}
		return origFullCalendar.apply(this, arguments);
	};
})(jQuery, moment);<commit_msg>Support for UMD<commit_after>/*!
 * fullcalendar-columns v1.7
 * Docs & License: https://github.com/mherrmann/fullcalendar-columns
 * (c) 2015 Michael Herrmann
 */
(function(root, factory) {
	if (typeof define === 'function' && define.amd) {
		define([ 'jquery', 'moment' ], factory); // AMD
	} else if (typeof exports === 'object') {
		module.exports = factory(require('jquery'), require('moment')); // Node, CommonJS-like
	} else {
		root.returnExports = factory(jQuery, moment); // Browser globals (root is window)
	}
}(this, function($, moment) {
	var fc = $.fullCalendar;
	var AgendaView = fc.views.agenda.class || fc.views.agenda;
	fc.views.multiColAgenda = AgendaView.extend({
		originalEvents: null,
		fakeEvents: null,
		initialize: function() {
			this.numColumns = this.opt('numColumns');
			this.columnHeaders = this.opt('columnHeaders');
			AgendaView.prototype.initialize.call(this);
			this._monkeyPatchGridRendering();
		},
		renderEvents: function(events) {
			this.originalEvents = {};
			this.fakeEvents = [];
			for (var i = 0; i < events.length; i++) {
				var event = events[i];
				this.originalEvents[event._id] = event;
				this.fakeEvents.push(this._computeFakeEvent(event));
			}
			return AgendaView.prototype.renderEvents.call(
				this, this.fakeEvents
			);
		},
		trigger: function(name, thisObj) {
			var args = Array.prototype.slice.call(arguments);
			if (name == 'eventRender' || name == 'eventAfterRender'
				|| name == 'eventDestroy' || name == 'eventClick'
				|| name == 'eventMouseover' || name == 'eventMouseout')
				args[2] = this.originalEvents[args[2]._id];
			else if (name == 'dayClick' || name == 'dayRightclick') {
				var date = this._computeOriginalEvent({ start: args[2] });
				args[2] = date.start;
				args[2].column = date.column;
			}
			return AgendaView.prototype.trigger.apply(this, args);
		},
		reportEventResize: function(event, location, largeUnit, el, ev) {
			return this._reportEventReschedule(
				'reportEventResize', event, location, largeUnit, el, ev
			);
		},
		reportEventDrop: function(event, location, largeUnit, el, ev) {
			return this._reportEventReschedule(
				'reportEventDrop', event, location, largeUnit, el, ev
			);
		},
		updateEvent: function(event) {
			$.extend(
				this._getFakeEvent(event._id), this._computeFakeEvent(event)
			);
		},
		computeRange: function(date) {
			var result = AgendaView.prototype.computeRange.call(this, date);
			var daysAvailable =
				this._countNonHiddenDaysBetween(result.start, result.end);
			var daysRequired = daysAvailable * this.numColumns;
			result.end = this._addNonHiddenDays(result.start, daysRequired);
			return result;
		},
		_monkeyPatchGridRendering: function() {
			var that = this;
			var origHeadCellHtml = this.timeGrid.headCellHtml;
			this.timeGrid.headCellHtml = function(cell) {
				/*
				 * Make multiple day header cells (each for one column) appear
				 * as one. The easiest way to do this would be to just render
				 * a single header cell with colspan=this.numColumns. However,
				 * this leads to misalignment between the day header cells and
				 * the events table. To get around this, we do render the same
				 * number of  cells as FullCalendar, but only fill the first
				 * one:
				 */
				var cellOrig = that._computeOriginalEvent(cell);
				var $html = $(origHeadCellHtml.call(this, cellOrig));
				var isFirstCellForDay = cellOrig.column == 0;
				var isLastCellForDay = cellOrig.column == that.numColumns - 1;

				var html = '';
				if (isFirstCellForDay) {
					// Make the cell appear centered:
					var posPercent = 100 * that.numColumns;
					html = '
'
					       + $html.html() + '
';
				} else {
					html = '

';
					$html.css('border-left-width', 0);
				}
				if (! isLastCellForDay)
					$html.css('border-right-width', 0);
				if (that.columnHeaders) {
					// Use the prefix 'fce-col-' (as in "FullCalendar
					// extension") for classes pertaining only to
					// fullcalendar-columns:
					html += '
' +
					        that.columnHeaders[cellOrig.column] + '
';
				}
				$html.html(html);
				return $html[0].outerHTML;
			};
			var origGetDayClasses = this.timeGrid.getDayClasses;
			this.timeGrid.getDayClasses = function(date) {
				var dateCol = that._computeOriginalEvent({ start: date });
				return origGetDayClasses.call(this, dateCol.start);
			};
		},
		_computeFakeEvent: function(event) {
			var result = $.extend({}, event);
			var start = this.calendar.moment(event.start);
			if (start >= this.start) {
				var daysDelta =
					moment.duration(event.start - this.start).days();
				var fakeDayOffset = daysDelta * this.numColumns + event.column;
				result.start = this._addNonHiddenDays(
					start.subtract(daysDelta, 'days'), fakeDayOffset
				);
				if ('end' in event) {
					var end = this.calendar.moment(event.end);
					result.end = this._addNonHiddenDays(
						end.subtract(daysDelta, 'days'), fakeDayOffset
					);
				}
			}
			return result;
		},
		_addNonHiddenDays: function(date, deltaDays) {
			var result = this.calendar.moment(date);
			for (var i=0; i < deltaDays; i++)
				result = this.skipHiddenDays(result.add(1, 'day'));
			return result;
		},
		_countNonHiddenDaysBetween: function(date1, date2) {
			for (var result=0; date1.isBefore(date2, 'day'); result++)
				date1 = this._addNonHiddenDays(date1, 1);
			return result;
		},
		_computeOriginalEvent: function(event) {
			var result = $.extend({}, event);
			var start = this.calendar.moment(event.start);
			if (start >= this.start) {
				var fakeDayOffset =
					this._countNonHiddenDaysBetween(this.start, start);
				result.column = fakeDayOffset % this.numColumns;
				var daysDelta = start.diff(this.start, 'days');
				var days = Math.floor(fakeDayOffset / this.numColumns);
				result.start = this._addNonHiddenDays(
					start.subtract(daysDelta, 'days'), days
				);
				if ('end' in event) {
					var end = this.calendar.moment(event.end);
					result.end = this._addNonHiddenDays(
						end.subtract(daysDelta, 'days'), days
					);
				}
			}
			return result;
		},
		_getFakeEvent: function(_id) {
			for (var i=0; i < this.fakeEvents.length; i++)
				if (this.fakeEvents[i]._id == _id)
					return this.fakeEvents[i];
		},
		_reportEventReschedule: function(
			rescheduleType, fakeEvent, location, largeUnit, el, ev
		) {
			fakeEvent.start = location.start.clone();
			fakeEvent.end = location.end.clone();
			var event = this.originalEvents[fakeEvent._id];
			location = this._computeOriginalEvent(location);
			return AgendaView.prototype[rescheduleType].call(
				this, event, location, largeUnit, el, ev
			);
		}
	});
	var origFullCalendar = $.fn.fullCalendar;
	$.fn.fullCalendar = function(options) {
		if (options == 'updateEvent') { // Required by multiColAgenda
			var view = origFullCalendar.call(this, 'getView');
			if (view.updateEvent)
				view.updateEvent(arguments[1]);
		}
		return origFullCalendar.apply(this, arguments);
	};
}));
<|endoftext|>


<commit_before>import chai from 'chai'
import sinon from 'sinon'
import sinonChai from 'sinon-chai'

const expect = chai.expect
chai.use(sinonChai)

import { createRuntime, createApp, getCode } from './prepare'

describe('test input and output', function () {
  let runtime

  before(() => {
    runtime = createRuntime()
    sinon.stub(console, 'info')
    sinon.stub(console, 'warn')
    sinon.stub(console, 'error')
  })

  after(() => {
    console.info.restore()
    console.warn.restore()
    console.error.restore()
  })

  describe('one-time rendering cases', () => {
    let app

    beforeEach(() => {
      app = createApp(runtime)
      callNativeHandler = (...args) => app._target.callNative(...args)
    })

    afterEach(() => {
      callNativeHandler = function () {}
    })

    function checkOutput (app, name) {
      const source = getCode('basic/' + name + '.source.js')
      const output = getCode('basic/' + name + '.output.js')
      const result = eval('(' + output + ')')

      app.$create(source)
      expect(app.getRealRoot()).eql(result)
      app.$destroy()
    }

    it('single case', () => checkOutput(app, 'foo'))
    it('foo2 case', () => checkOutput(app, 'foo2'))
    it('foo3 case', () => checkOutput(app, 'foo3'))
    it('foo4 case', () => checkOutput(app, 'foo4'))
    it('foo5 case', () => checkOutput(app, 'foo5'))
    it('foo6 case', () => checkOutput(app, 'foo6'))
    it('foo7 case', () => checkOutput(app, 'foo7'))
    it('foo8 case', () => checkOutput(app, 'foo8'))
    it('foo9 case', () => checkOutput(app, 'foo9'))

    it('static1 case', () => checkOutput(app, 'static1'))
    it('static2 case', () => checkOutput(app, 'static2'))
    it('static3 case', () => checkOutput(app, 'static3'))
    it('static4 case', () => checkOutput(app, 'static4'))

    it('computed in repeat case', () => checkOutput(app, 'computed-in-repeat'))
    it('backward(register/render) case', () => checkOutput(app, 'backward1'))
    it('backward(define/require) case', () => checkOutput(app, 'backward2'))
    it('append case', () => checkOutput(app, 'append'))
    it('append-root case', () => checkOutput(app, 'append-root'))

    it('if case', () => checkOutput(app, 'if'))
    it('if-repeat case', () => checkOutput(app, 'if-repeat'))
    it('if in root element case', () => checkOutput(app, 'if-root'))
    it('repeat in root element case', () => checkOutput(app, 'repeat-root'))
    it('repeat with index case case', () => checkOutput(app, 'repeat-index'))
    it('repeat with array v case', () => checkOutput(app, 'repeat-array-v'))
    it('repeat with array kv case', () => checkOutput(app, 'repeat-array-kv'))
    it('repeat with array no-kv case', () => checkOutput(app, 'repeat-array-no-kv'))
    it('repeat with array non-obj case', () => checkOutput(app, 'repeat-array-non-obj'))
    it('repeat watch case', () => checkOutput(app, 'repeat-watch'))

    it('reset style case', () => checkOutput(app, 'reset-style'))
    it('dynamic type case', () => checkOutput(app, 'dynamic-type'))
    it('dynamic property case', () => checkOutput(app, 'dynamic-property'))

    it('bind subvm', () => checkOutput(app, 'subvm'))
    it('components options', () => checkOutput(app, 'components'))
    it('change data when created', () => checkOutput(app, 'created'))
    it('change data when ready', () => checkOutput(app, 'ready'))

    it('require 3rd', () => checkOutput(app, 'require'))
    it('a correct transformer version', () => checkOutput(app, 'transformer1'))

    it('promise case', () => checkOutput(app, 'promise'))
  })

  describe('complex cases', function () {
    let app

    beforeEach(() => {
      app = createApp(runtime)
      callNativeHandler = (...args) => app._target.callNative(...args)
    })

    afterEach(() => {
      callNativeHandler = function () {}
    })

    const readSource = name => getCode('complex/' + name + '.source.js')
    const readOutput = name => getCode('complex/' + name + '.output.js')

    it('computed case', () => {
      const name = 'computed'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      app.$refresh({ x: 10 })
      expected.children[0].attr.value = 12
      expected.children[1].attr.value = 12
      expect(app.getRealRoot()).eql(expected)

      app.$refresh({ m: 10 })
      expected.children[0].attr.value = 20
      expected.children[1].attr.value = 20
      expect(app.getRealRoot()).eql(expected)

      app.$destroy()
    })

    it('append-root-event case', () => {
      const name = 'append-root-event'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      const expected = eval('(' + outputCode + ')')
      const actual = app.getRealRoot()
      expect(actual).eql(expected)

      app.$fireEvent(app.doc.body.children[0].ref, 'click', {})
      const actual2 = app.getRealRoot()
      expect(actual2.children[0].attr.value).eql(2)

      app.$destroy()
    })

    it('repeat with array track-by case', () => {
      const name = 'repeat-track-by'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      app.$refresh({
        titlelist: [
          { text: 'Hello World2' },
          { text: 'Hello World1' }
        ]
      })
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      app.$destroy()
    })

    it('if-refresh case', () => {
      const name = 'if-refresh'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      app.$refresh({ showTitle: false })
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      app.$destroy()
    })

    it('if-repeat-refresh case', () => {
      const name = 'if-repeat-refresh'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      app.$refresh({
        titlelist: [
          { showTitle: false, title: 'Hello World1' },
          { showTitle: true, title: 'Hello World2' },
          { showTitle: true, title: 'Hello World3' }
        ]
      })
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      app.$destroy()
    })

    it('click case', () => {
      const name = 'click'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      app.$fireEvent(app.doc.body.children[0].ref, 'click', {})

      app.$destroy()
    })

    it('inline click case', () => {
      const name = 'inline-click'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      app.$fireEvent(app.doc.body.children[0].ref, 'click', {})

      expected.children[0].attr.value = 'Hello World2'
      expect(app.getRealRoot()).eql(expected)

      app.$destroy()
    })

    it('refresh twice', () => {
      const name = 'refresh2'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      expect(app.getRealRoot()).eql({ type: 'container' })

      app.$refresh({ ext: { showbar1: false }})
      app.$refresh({ ext: { showbar1: true }})
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      app.$destroy()
    })

    it('a less wrong transformer version', () => {
      const name = 'transformer2'
      const sourceCode = readSource(name)

      const result = app.$create(sourceCode)
      expect(result).to.be.an.instanceof(Error)
      app.$destroy()
    })

    it('a bigger wrong transformer version', () => {
      const name = 'transformer3'
      const sourceCode = readSource(name)

      const result = app.$create(sourceCode)
      expect(result).to.be.an.instanceof(Error)
      app.$destroy()
    })

    it('input binding', () => {
      const name = 'input-binding'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      app.doc.body.children[0].attr.value = 'abcdefg'
      app.$fireEvent(app.doc.body.children[0].ref, 'change', {}, { attrs: { value: 'abcdefg' }})
      expected.children[0].attr.value = 'abcdefg'
      expected.children.push({ type: 'text', attr: { value: '1 - abcdefg' }})
      expect(app.getRealRoot()).eql(expected)

      app.doc.body.children[0].attr.value = '12345'
      app.$fireEvent(app.doc.body.children[0].ref, 'change', {}, { attrs: { value: '12345' }})
      expected.children[0].attr.value = '12345'
      expected.children.push({ type: 'text', attr: { value: '2 - 12345' }})
      expect(app.getRealRoot()).eql(expected)

      app.$destroy()
    })
  })

  describe('multi page cases', function () {
    let app

    beforeEach(() => {
      app = createApp(runtime)
      callNativeHandler = (...args) => app._target.callNative(...args)
    })

    afterEach(() => {
      callNativeHandler = function () {}
    })

    const readSource = name => getCode('multi/' + name + '.source.js')
    const readOutput = name => getCode('multi/' + name + '.output.js')

    it('clear-module case', () => {
      const app2 = createApp(runtime)

      const nameA = 'clear-moduleA'
      const nameB = 'clear-moduleB'
      const sourceCodeA = readSource(nameA)
      const outputCodeA = readOutput(nameA)
      const sourceCodeB = readSource(nameB)
      const outputCodeB = readOutput(nameB)
      const expectedA = eval('(' + outputCodeA + ')')
      const expectedB = eval('(' + outputCodeB + ')')

      app.$create(sourceCodeA)
      app2.$create(sourceCodeB)

      expect(app2.getRealRoot()).eql(expectedB)

      app2.$destroy()
      app.$fireEvent(app.doc.body.children[0].ref, 'click', {})
      expect(app.getRealRoot()).eql(expectedA)

      app.$destroy()
    })

    it.skip('clear-dep-target case', () => {
      const nameError = 'clear-dep-target-error'
      const nameFine = 'clear-dep-target-fine'
      const sourceCodeError = readSource(nameError)
      const sourceCodeFine = readSource(nameFine)
      const outputCodeFine = readOutput(nameFine)

      // should throw
      expect(() => { app.$create(sourceCodeError) }).to.throw(TypeError)
      app.$destroy()

      // not throw
      app.$create(sourceCodeFine)
      const expected = eval('(' + outputCodeFine + ')')
      expect(app.getRealRoot()).eql(expected)

      app.$destroy()
    })
  })

  describe('timer & callNative signals', function () {
    let app

    const callNativeSpy = sinon.spy()
    function genCallNativeWrapper (count) {
      return (name, tasks, cbId) => {
        callNativeSpy(tasks)
        const length = callNativeSpy.callCount
        if (length > count) {
          return -1
        }
        return length
      }
    }
    beforeEach(() => {
      app = createApp(runtime)
      callNativeHandler = (...args) => app._target.callNative(...args)
    })

    afterEach(() => {
      callNativeSpy.reset()
      callNativeHandler = function () {}
    })

    const readSource = name => getCode('signals/' + name + '.source.js')
    const readOutput = name => getCode('signals/' + name + '.output.js')

    it('use HTML5 timer API', function (done) {
      this.timeout(5000)
      const name = 'timer'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      setTimeout(_ => {
        expect(app.getRealRoot()).eql(expected)
        setTimeout(_ => {
          expected.children[0].attr.value = 'bar'
          expect(app.getRealRoot()).eql(expected)
          app.$destroy()
          done()
        }, 1000)
      }, 1000)
    })

    it('use modal API', function (done) {
      this.timeout(5000)
      const name = 'modal'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      // the test driver will hold the API callback about 1 sec
      setTimeout(_ => {
        expected.children[0].attr.value = 'bar'
        expect(app.getRealRoot()).eql(expected)
        app.$destroy()
        done()
      }, 1500)
    })

    it('signals control', function () {
      this.timeout(15000)

      const name = 'signals'
      const sourceCode = readSource(name)

      function run (calls) {
        callNativeSpy.reset()
        callNativeHandler = genCallNativeWrapper(calls)
        app.$create(sourceCode)
        app.$destroy()
        expect(callNativeSpy.callCount).eql(calls + 2)
      }

      for (let i = 5; i < 60; i++) {
        run(i)
      }
    })

    it('long signals control', function () {
      this.timeout(500000)

      const name = 'signals-long'
      const sourceCode = readSource(name)

      function run (calls) {
        callNativeSpy.reset()
        callNativeHandler = genCallNativeWrapper(calls)
        app.$create(sourceCode)
        app.$destroy()
        expect(callNativeSpy.callCount).eql(calls + 2)
      }

      run(10)
      run(30)
      run(90)
    })
  })
})
<commit_msg>* [jsfm] modify the skipped test case in multi page cases<commit_after>import chai from 'chai'
import sinon from 'sinon'
import sinonChai from 'sinon-chai'

const expect = chai.expect
chai.use(sinonChai)

import { createRuntime, createApp, getCode } from './prepare'

describe('test input and output', function () {
  let runtime

  before(() => {
    runtime = createRuntime()
    sinon.stub(console, 'info')
    sinon.stub(console, 'warn')
    sinon.stub(console, 'error')
  })

  after(() => {
    console.info.restore()
    console.warn.restore()
    console.error.restore()
  })

  describe('one-time rendering cases', () => {
    let app

    beforeEach(() => {
      app = createApp(runtime)
      callNativeHandler = (...args) => app._target.callNative(...args)
    })

    afterEach(() => {
      callNativeHandler = function () {}
    })

    function checkOutput (app, name) {
      const source = getCode('basic/' + name + '.source.js')
      const output = getCode('basic/' + name + '.output.js')
      const result = eval('(' + output + ')')

      app.$create(source)
      expect(app.getRealRoot()).eql(result)
      app.$destroy()
    }

    it('single case', () => checkOutput(app, 'foo'))
    it('foo2 case', () => checkOutput(app, 'foo2'))
    it('foo3 case', () => checkOutput(app, 'foo3'))
    it('foo4 case', () => checkOutput(app, 'foo4'))
    it('foo5 case', () => checkOutput(app, 'foo5'))
    it('foo6 case', () => checkOutput(app, 'foo6'))
    it('foo7 case', () => checkOutput(app, 'foo7'))
    it('foo8 case', () => checkOutput(app, 'foo8'))
    it('foo9 case', () => checkOutput(app, 'foo9'))

    it('static1 case', () => checkOutput(app, 'static1'))
    it('static2 case', () => checkOutput(app, 'static2'))
    it('static3 case', () => checkOutput(app, 'static3'))
    it('static4 case', () => checkOutput(app, 'static4'))

    it('computed in repeat case', () => checkOutput(app, 'computed-in-repeat'))
    it('backward(register/render) case', () => checkOutput(app, 'backward1'))
    it('backward(define/require) case', () => checkOutput(app, 'backward2'))
    it('append case', () => checkOutput(app, 'append'))
    it('append-root case', () => checkOutput(app, 'append-root'))

    it('if case', () => checkOutput(app, 'if'))
    it('if-repeat case', () => checkOutput(app, 'if-repeat'))
    it('if in root element case', () => checkOutput(app, 'if-root'))
    it('repeat in root element case', () => checkOutput(app, 'repeat-root'))
    it('repeat with index case case', () => checkOutput(app, 'repeat-index'))
    it('repeat with array v case', () => checkOutput(app, 'repeat-array-v'))
    it('repeat with array kv case', () => checkOutput(app, 'repeat-array-kv'))
    it('repeat with array no-kv case', () => checkOutput(app, 'repeat-array-no-kv'))
    it('repeat with array non-obj case', () => checkOutput(app, 'repeat-array-non-obj'))
    it('repeat watch case', () => checkOutput(app, 'repeat-watch'))

    it('reset style case', () => checkOutput(app, 'reset-style'))
    it('dynamic type case', () => checkOutput(app, 'dynamic-type'))
    it('dynamic property case', () => checkOutput(app, 'dynamic-property'))

    it('bind subvm', () => checkOutput(app, 'subvm'))
    it('components options', () => checkOutput(app, 'components'))
    it('change data when created', () => checkOutput(app, 'created'))
    it('change data when ready', () => checkOutput(app, 'ready'))

    it('require 3rd', () => checkOutput(app, 'require'))
    it('a correct transformer version', () => checkOutput(app, 'transformer1'))

    it('promise case', () => checkOutput(app, 'promise'))
  })

  describe('complex cases', function () {
    let app

    beforeEach(() => {
      app = createApp(runtime)
      callNativeHandler = (...args) => app._target.callNative(...args)
    })

    afterEach(() => {
      callNativeHandler = function () {}
    })

    const readSource = name => getCode('complex/' + name + '.source.js')
    const readOutput = name => getCode('complex/' + name + '.output.js')

    it('computed case', () => {
      const name = 'computed'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      app.$refresh({ x: 10 })
      expected.children[0].attr.value = 12
      expected.children[1].attr.value = 12
      expect(app.getRealRoot()).eql(expected)

      app.$refresh({ m: 10 })
      expected.children[0].attr.value = 20
      expected.children[1].attr.value = 20
      expect(app.getRealRoot()).eql(expected)

      app.$destroy()
    })

    it('append-root-event case', () => {
      const name = 'append-root-event'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      const expected = eval('(' + outputCode + ')')
      const actual = app.getRealRoot()
      expect(actual).eql(expected)

      app.$fireEvent(app.doc.body.children[0].ref, 'click', {})
      const actual2 = app.getRealRoot()
      expect(actual2.children[0].attr.value).eql(2)

      app.$destroy()
    })

    it('repeat with array track-by case', () => {
      const name = 'repeat-track-by'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      app.$refresh({
        titlelist: [
          { text: 'Hello World2' },
          { text: 'Hello World1' }
        ]
      })
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      app.$destroy()
    })

    it('if-refresh case', () => {
      const name = 'if-refresh'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      app.$refresh({ showTitle: false })
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      app.$destroy()
    })

    it('if-repeat-refresh case', () => {
      const name = 'if-repeat-refresh'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      app.$refresh({
        titlelist: [
          { showTitle: false, title: 'Hello World1' },
          { showTitle: true, title: 'Hello World2' },
          { showTitle: true, title: 'Hello World3' }
        ]
      })
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      app.$destroy()
    })

    it('click case', () => {
      const name = 'click'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      app.$fireEvent(app.doc.body.children[0].ref, 'click', {})

      app.$destroy()
    })

    it('inline click case', () => {
      const name = 'inline-click'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      app.$fireEvent(app.doc.body.children[0].ref, 'click', {})

      expected.children[0].attr.value = 'Hello World2'
      expect(app.getRealRoot()).eql(expected)

      app.$destroy()
    })

    it('refresh twice', () => {
      const name = 'refresh2'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      expect(app.getRealRoot()).eql({ type: 'container' })

      app.$refresh({ ext: { showbar1: false }})
      app.$refresh({ ext: { showbar1: true }})
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      app.$destroy()
    })

    it('a less wrong transformer version', () => {
      const name = 'transformer2'
      const sourceCode = readSource(name)

      const result = app.$create(sourceCode)
      expect(result).to.be.an.instanceof(Error)
      app.$destroy()
    })

    it('a bigger wrong transformer version', () => {
      const name = 'transformer3'
      const sourceCode = readSource(name)

      const result = app.$create(sourceCode)
      expect(result).to.be.an.instanceof(Error)
      app.$destroy()
    })

    it('input binding', () => {
      const name = 'input-binding'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      app.doc.body.children[0].attr.value = 'abcdefg'
      app.$fireEvent(app.doc.body.children[0].ref, 'change', {}, { attrs: { value: 'abcdefg' }})
      expected.children[0].attr.value = 'abcdefg'
      expected.children.push({ type: 'text', attr: { value: '1 - abcdefg' }})
      expect(app.getRealRoot()).eql(expected)

      app.doc.body.children[0].attr.value = '12345'
      app.$fireEvent(app.doc.body.children[0].ref, 'change', {}, { attrs: { value: '12345' }})
      expected.children[0].attr.value = '12345'
      expected.children.push({ type: 'text', attr: { value: '2 - 12345' }})
      expect(app.getRealRoot()).eql(expected)

      app.$destroy()
    })
  })

  describe('multi page cases', function () {
    let appA
    let appB

    beforeEach(() => {
      appA = createApp(runtime)
      appB = createApp(runtime)

      callNativeHandler = (id, ...args) => {
        switch (true) {
          case (appA.id === id) : { appA._target.callNative(id, ...args) } break
          case (appB.id === id) : { appB._target.callNative(id, ...args) } break
        }
      }
    })

    afterEach(() => {
      callNativeHandler = function () {}
    })

    const readSource = name => getCode('multi/' + name + '.source.js')
    const readOutput = name => getCode('multi/' + name + '.output.js')

    it('clear-module case', () => {
      const nameA = 'clear-moduleA'
      const nameB = 'clear-moduleB'
      const sourceCodeA = readSource(nameA)
      const sourceCodeB = readSource(nameB)
      const expectedA = eval('(' + readOutput(nameA) + ')')
      const expectedB = eval('(' + readOutput(nameB) + ')')

      appA.$create(sourceCodeA)
      appB.$create(sourceCodeB)

      expect(appB.getRealRoot()).eql(expectedB)

      appB.$destroy()
      appA.$fireEvent(appA.doc.body.children[0].ref, 'click', {})
      expect(appA.getRealRoot()).eql(expectedA)

      appA.$destroy()
    })

    it('clear-dep-target case', () => {
      const nameError = 'clear-dep-target-error'
      const nameFine = 'clear-dep-target-fine'
      const sourceCodeError = readSource(nameError)
      const sourceCodeFine = readSource(nameFine)
      const expectedFine = eval('(' + readOutput(nameFine) + ')')

      // should throw
      expect(() => { appA.$create(sourceCodeError) }).to.throw(TypeError)
      appA.$destroy()

      // not throw
      appB.$create(sourceCodeFine)
      expect(appB.getRealRoot()).eql(expectedFine)

      appB.$destroy()
    })
  })

  describe('timer & callNative signals', function () {
    let app

    const callNativeSpy = sinon.spy()
    function genCallNativeWrapper (count) {
      return (name, tasks, cbId) => {
        callNativeSpy(tasks)
        const length = callNativeSpy.callCount
        if (length > count) {
          return -1
        }
        return length
      }
    }
    beforeEach(() => {
      app = createApp(runtime)
      callNativeHandler = (...args) => app._target.callNative(...args)
    })

    afterEach(() => {
      callNativeSpy.reset()
      callNativeHandler = function () {}
    })

    const readSource = name => getCode('signals/' + name + '.source.js')
    const readOutput = name => getCode('signals/' + name + '.output.js')

    it('use HTML5 timer API', function (done) {
      this.timeout(5000)
      const name = 'timer'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      setTimeout(_ => {
        expect(app.getRealRoot()).eql(expected)
        setTimeout(_ => {
          expected.children[0].attr.value = 'bar'
          expect(app.getRealRoot()).eql(expected)
          app.$destroy()
          done()
        }, 1000)
      }, 1000)
    })

    it('use modal API', function (done) {
      this.timeout(5000)
      const name = 'modal'
      const sourceCode = readSource(name)
      const outputCode = readOutput(name)

      app.$create(sourceCode)
      const expected = eval('(' + outputCode + ')')
      expect(app.getRealRoot()).eql(expected)

      // the test driver will hold the API callback about 1 sec
      setTimeout(_ => {
        expected.children[0].attr.value = 'bar'
        expect(app.getRealRoot()).eql(expected)
        app.$destroy()
        done()
      }, 1500)
    })

    it('signals control', function () {
      this.timeout(15000)

      const name = 'signals'
      const sourceCode = readSource(name)

      function run (calls) {
        callNativeSpy.reset()
        callNativeHandler = genCallNativeWrapper(calls)
        app.$create(sourceCode)
        app.$destroy()
        expect(callNativeSpy.callCount).eql(calls + 2)
      }

      for (let i = 5; i < 60; i++) {
        run(i)
      }
    })

    it('long signals control', function () {
      this.timeout(500000)

      const name = 'signals-long'
      const sourceCode = readSource(name)

      function run (calls) {
        callNativeSpy.reset()
        callNativeHandler = genCallNativeWrapper(calls)
        app.$create(sourceCode)
        app.$destroy()
        expect(callNativeSpy.callCount).eql(calls + 2)
      }

      run(10)
      run(30)
      run(90)
    })
  })
})
<|endoftext|>


<commit_before>var map_stream = require('through2-map');
var _ = require('lodash');
var iso3166 = require('iso3166-1');

var Document = require('pelias-model').Document;

module.exports = {};

module.exports.create = function(hierarchy_finder) {
  return map_stream.obj(function(record) {
    var wofDoc = new Document( 'whosonfirst', record.place_type, record.id );

    if (record.name) {
      wofDoc.setName('default', record.name);
    }
    wofDoc.setCentroid({ lat: record.lat, lon: record.lon });

    // only set population if available
    if (record.population) {
      wofDoc.setPopulation(record.population);
    }

    // only set popularity if available
    if (record.popularity) {
      wofDoc.setPopularity(record.popularity);
    }

    // WOF bbox is defined as:
    // lowerLeft.lon, lowerLeft.lat, upperRight.lon, upperRight.lat
    // so convert to what ES understands
    if (!_.isUndefined(record.bounding_box)) {
      var parsedBoundingBox = record.bounding_box.split(',').map(parseFloat);
      var marshaledBoundingBoxBox = {
        upperLeft: {
          lat: parsedBoundingBox[3],
          lon: parsedBoundingBox[0]
        },
        lowerRight: {
          lat: parsedBoundingBox[1],
          lon: parsedBoundingBox[2]
        }

      };
      wofDoc.setBoundingBox(marshaledBoundingBoxBox);
    }

    // iterate the hierarchy, assigning fields
    hierarchy_finder(record).forEach(function(hierarchy_element) {
      switch (hierarchy_element.place_type) {
        case 'locality':
          wofDoc.addParent('locality', hierarchy_element.name, hierarchy_element.id.toString());
          break;
        case 'borough':
          wofDoc.addParent('borough', hierarchy_element.name, hierarchy_element.id.toString());
          break;
        case 'localadmin':
          wofDoc.addParent('localadmin', hierarchy_element.name, hierarchy_element.id.toString());
          break;
        case 'county':
          wofDoc.addParent('county', hierarchy_element.name, hierarchy_element.id.toString());
          break;
        case 'macrocounty':
          wofDoc.addParent('macrocounty', hierarchy_element.name, hierarchy_element.id.toString());
          break;
        case 'region':
          if (hierarchy_element.abbreviation) {
            wofDoc.addParent('region', hierarchy_element.name, hierarchy_element.id.toString(), hierarchy_element.abbreviation);
          } else {
            wofDoc.addParent('region', hierarchy_element.name, hierarchy_element.id.toString());
          }
          break;
        case 'macroregion':
          wofDoc.addParent('macroregion', hierarchy_element.name, hierarchy_element.id.toString());
          break;
        case 'dependency':
          if (hierarchy_element.abbreviation) {
            wofDoc.addParent('dependency', hierarchy_element.name, hierarchy_element.id.toString(), hierarchy_element.abbreviation);
          } else {
            wofDoc.addParent('dependency', hierarchy_element.name, hierarchy_element.id.toString());
          }
          break;
        case 'country':
          // this is placetype=country, so lookup and set the iso3 from iso2
          if (iso3166.is2(hierarchy_element.iso2)) {
            var iso3 = iso3166.to3(hierarchy_element.iso2);

            wofDoc.setAlpha3(iso3);
            wofDoc.addParent('country', hierarchy_element.name, hierarchy_element.id.toString(), iso3);

          } else {
            wofDoc.addParent('country', hierarchy_element.name, hierarchy_element.id.toString());

          }

          break;
      }
    });

    return wofDoc;

  });

};
<commit_msg>switched to `hasOwnProperty` per convention<commit_after>var map_stream = require('through2-map');
var _ = require('lodash');
var iso3166 = require('iso3166-1');

var Document = require('pelias-model').Document;

module.exports = {};

module.exports.create = function(hierarchy_finder) {
  return map_stream.obj(function(record) {
    var wofDoc = new Document( 'whosonfirst', record.place_type, record.id );

    if (record.name) {
      wofDoc.setName('default', record.name);
    }
    wofDoc.setCentroid({ lat: record.lat, lon: record.lon });

    // only set population if available
    if (record.population) {
      wofDoc.setPopulation(record.population);
    }

    // only set popularity if available
    if (record.popularity) {
      wofDoc.setPopularity(record.popularity);
    }

    // WOF bbox is defined as:
    // lowerLeft.lon, lowerLeft.lat, upperRight.lon, upperRight.lat
    // so convert to what ES understands
    if (!_.isUndefined(record.bounding_box)) {
      var parsedBoundingBox = record.bounding_box.split(',').map(parseFloat);
      var marshaledBoundingBoxBox = {
        upperLeft: {
          lat: parsedBoundingBox[3],
          lon: parsedBoundingBox[0]
        },
        lowerRight: {
          lat: parsedBoundingBox[1],
          lon: parsedBoundingBox[2]
        }

      };
      wofDoc.setBoundingBox(marshaledBoundingBoxBox);
    }

    // iterate the hierarchy, assigning fields
    hierarchy_finder(record).forEach(function(hierarchy_element) {
      switch (hierarchy_element.place_type) {
        case 'locality':
          wofDoc.addParent('locality', hierarchy_element.name, hierarchy_element.id.toString());
          break;
        case 'borough':
          wofDoc.addParent('borough', hierarchy_element.name, hierarchy_element.id.toString());
          break;
        case 'localadmin':
          wofDoc.addParent('localadmin', hierarchy_element.name, hierarchy_element.id.toString());
          break;
        case 'county':
          wofDoc.addParent('county', hierarchy_element.name, hierarchy_element.id.toString());
          break;
        case 'macrocounty':
          wofDoc.addParent('macrocounty', hierarchy_element.name, hierarchy_element.id.toString());
          break;
        case 'region':
          if (hierarchy_element.hasOwnProperty('abbreviation')) {
            wofDoc.addParent('region', hierarchy_element.name, hierarchy_element.id.toString(), hierarchy_element.abbreviation);
          } else {
            wofDoc.addParent('region', hierarchy_element.name, hierarchy_element.id.toString());
          }
          break;
        case 'macroregion':
          wofDoc.addParent('macroregion', hierarchy_element.name, hierarchy_element.id.toString());
          break;
        case 'dependency':
          if (hierarchy_element.hasOwnProperty('abbreviation')) {
            wofDoc.addParent('dependency', hierarchy_element.name, hierarchy_element.id.toString(), hierarchy_element.abbreviation);
          } else {
            wofDoc.addParent('dependency', hierarchy_element.name, hierarchy_element.id.toString());
          }
          break;
        case 'country':
          // this is placetype=country, so lookup and set the iso3 from iso2
          if (iso3166.is2(hierarchy_element.iso2)) {
            var iso3 = iso3166.to3(hierarchy_element.iso2);

            wofDoc.setAlpha3(iso3);
            wofDoc.addParent('country', hierarchy_element.name, hierarchy_element.id.toString(), iso3);

          } else {
            wofDoc.addParent('country', hierarchy_element.name, hierarchy_element.id.toString());

          }

          break;
      }
    });

    return wofDoc;

  });

};
<|endoftext|>


<commit_before>/*
    This file is part of WebBox.

    Copyright 2012 Daniel Alexander Smith
    Copyright 2012 University of Southampton

    WebBox is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    WebBox is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with WebBox.  If not, see <http://www.gnu.org/licenses/>.
*/

/* Author: Daniel A. Smith

*/

(function(){
    /* ObjectStore.js is a Backbone model for storing objects in WebBox. */
    var root = this;

    // The top-level namespace
    var ObjectStore;
    if (typeof exports !== 'undefined'){
        ObjectStore = exports;
    } else {
        ObjectStore = root.ObjectStore = {};
    }

    // ObjectStore.Store represents the store, and returns ObjectStore.Graphs
    var Store = ObjectStore.Store = Backbone.Collection.extend({
        server_url: "http://localhost:8211/webbox/objectstore",
        model: ObjectStore.Graph,
        objectstore_type: "store",
        initialize: function(){
            console.debug("Init store.");

            /*
            this.bind("add", function(model){
                // A graph has been added to the store
            });
            */
        },
        get: function(uri){
            // get a graph of uri
            return new Graph({"_store": this, "@id": uri});
        },
        sync: function(method, model, options){
            switch(method){
                case "create":
                    break;
                case "read":
                    var store = model;
                    return ObjectStore.list_graphs(store);
                case "update":
                    break;
                case "delete":
                    break;
                default:
                    break;
            }
        }
    });

    // ObjectStore.Graph is a named graph, which contains a ObjectStore.GraphCollection of ObjectStore.Objs
    var Graph = ObjectStore.Graph = Backbone.Model.extend({
        idAttribute: "@id", // the URI attribute is '@id' in JSON-LD
        objectstore_type: "graph",
        initialize: function(attributes){
        },
        create: function(object_attrs){
            // add a new object to this graph (ObjectStore.GraphCollection will create an Obj from this)
			if (typeof(object_attrs) == 'string') {  object_attrs = { "@id":object_attrs }; }
            var clone = _.clone(object_attrs);
            clone['_graph'] = this;

            var model = new ObjectStore.Obj(clone);
            this.get("objects").add(model);
            return model;
        },
        sync: function(method, model, options){
            switch(method){
                case "create":
                    break;
                case "read":
                    var graph = model;
                    return ObjectStore.fetch_graph(model, model.get("_store"), model.get("@id"));
                case "update":
                    console.debug("Update graph called on graph:",model);
                    return ObjectStore.update_graph(model);
                case "delete":
                    break;
                default:
                    break;
            }
        }
    });

    // ObjectStore.Obj are individual objects that are part of a ObjectStore.GraphCollection
    var Obj = ObjectStore.Obj = Backbone.Model.extend({
        idAttribute: "@id", // the URI attribute is '@id' in JSON-LD
        objectstore_type: "obj",
        get: function(attr){
            if (attr in this.attributes){
                return this.attributes[attr];
            } else {
                return [];
            }
        },
        sync: function(method, model, options){
			var d = new $.Deferred();
            switch(method){
                case "create":
				    console.log("CREATE ", model.id);
                    break;
                case "read":
				    d.resolve();
                    break;
                case "update":
                    // delegate to the graph
                    console.debug("SAVE -- Update to Obj: ",model.id);
                    return model.attributes._graph.sync("update", model.attributes._graph, options);
                case "delete":
                    break;
                default:
                    break;
            }
			return d.promise();
        },
    });

    // ObjectStore.GraphCollection is the list of ObjectStore.Objs in a ObjectStore.Graph
    var GraphCollection = ObjectStore.GraphCollection = Backbone.Collection.extend({
        model: ObjectStore.Obj,
        objectstore_type: "graphcollection",
    });

    // Functions to communicate with the ObjectStore server
    ObjectStore.list_graphs = function(store, callback){
        // return a list of named graphs (each of type ObjectStore.Graph) to populate a GraphCollection
        return $.ajax({
            url: store.server_url,
            data: {},
            dataType: "json",
            type: "GET",
            success: function(data){

                var graph_uris = data;
                var graphs = [];
                $.each(graph_uris, function(){
                    var graph_uri = this;
                    var graph = new ObjectStore.Graph({"@id": graph_uri, "_store": store});
                    graphs.push(graph);
                });

                store.reset(graphs);
            }
        });
    };

    ObjectStore.update_graph = function(graph){
        var graph_objs = [ // object to PUT to the server
        ]

        $.each(graph.get("objects").models, function(){
            var obj = this;
            var uri = obj.id;

            var out_obj = {};
            $.each(obj.attributes, function(pred, vals){
                if (pred[0] == "_"){
                    return;
                }
                if (pred[0] == "@"){ // don't expand @id etc.
                    out_obj[pred] = vals;
                    return;
                }

                var obj_vals = [];

                if (!(vals instanceof Array)){
                    vals = [vals];
                }
                $.each(vals, function(){
                    var val = this;
                    if (typeof val === "string" || val instanceof String){
                        obj_vals.push({"@value": val});
                    } else if (val instanceof ObjectStore.Obj) {
                        obj_vals.push({"@id": val.id });
                    } else {
                        obj_vals.push(val); // val is a fully expanded string, e.g. {"@value": "foo", "@language": "en" ... }
                    }
                });

                out_obj[pred] = obj_vals;
            });
            graph_objs.push(out_obj);
        });

        var store = graph.attributes._store;
        var url = store.server_url+"?graph="+escape(graph.id)+"&version="+escape(graph._version);

        console.debug("Sending PUT.");

        return $.ajax({
            url: url,
            processData: false,
            contentType: "application/json",
            dataType: "json",
            data: JSON.stringify(graph_objs),
            type: "PUT",
            success: function(data){
                // TODO check that it worked
                graph._version = data["@version"];
            }
        });
    };

    ObjectStore.fetch_graph = function(graph, store, uri){
        // return a list of models (each of type ObjectStore.Object) to populate a GraphCollection
        return $.ajax({
            url: store.server_url,
            data: {"graph": uri},
            dataType: "json",
            type: "GET",
            success: function(data){
                var graph_collection = new ObjectStore.GraphCollection();
                graph_collection._store = store;

                graph.set("objects", graph_collection);

                var version = 0; // new version FIXME check
                $.each(data, function(uri, obj){
                    if (uri == "@version"){
                        version = obj;
                    }
                    if (uri[0] == "@"){
                        // ignore @graph etc.
                        return;
                    }

                    var obj_model = {"@id": uri, "_graph": graph};

                    $.each(obj, function(key, vals){
                        var obj_vals = [];
                        $.each(vals, function(){
                            var val = this;
                            if ("@id" in val){
                                var val_obj = graph.create({"@id": val["@id"]});
                                obj_vals.push(val_obj);
                            } else if ("@value" in val){
                                // if the string has no language or datatype, compress to just a string.
                                if ("@language" in val && val["@language"] == "" && "@type" in val && val["@type"] == ""){
                                    obj_vals.push(val["@value"]);
                                } else {
                                    obj_vals.push(val);
                                }
                            }
                        });
                        obj_model[key] = obj_vals;
                    });

                    graph.create(obj_model);
                });
                graph_collection._version = version;
                graph._version = version;
            }
        });
    };

}).call(this);
<commit_msg>Fix tabs<commit_after>/*
    This file is part of WebBox.

    Copyright 2012 Daniel Alexander Smith
    Copyright 2012 University of Southampton

    WebBox is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    WebBox is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with WebBox.  If not, see <http://www.gnu.org/licenses/>.
*/

/* Author: Daniel A. Smith

*/

(function(){
    /* ObjectStore.js is a Backbone model for storing objects in WebBox. */
    var root = this;

    // The top-level namespace
    var ObjectStore;
    if (typeof exports !== 'undefined'){
        ObjectStore = exports;
    } else {
        ObjectStore = root.ObjectStore = {};
    }

    // ObjectStore.Store represents the store, and returns ObjectStore.Graphs
    var Store = ObjectStore.Store = Backbone.Collection.extend({
        server_url: "http://localhost:8211/webbox/objectstore",
        model: ObjectStore.Graph,
        objectstore_type: "store",
        initialize: function(){
            console.debug("Init store.");

            /*
            this.bind("add", function(model){
                // A graph has been added to the store
            });
            */
        },
        get: function(uri){
            // get a graph of uri
            return new Graph({"_store": this, "@id": uri});
        },
        sync: function(method, model, options){
            switch(method){
                case "create":
                    break;
                case "read":
                    var store = model;
                    return ObjectStore.list_graphs(store);
                case "update":
                    break;
                case "delete":
                    break;
                default:
                    break;
            }
        }
    });

    // ObjectStore.Graph is a named graph, which contains a ObjectStore.GraphCollection of ObjectStore.Objs
    var Graph = ObjectStore.Graph = Backbone.Model.extend({
        idAttribute: "@id", // the URI attribute is '@id' in JSON-LD
        objectstore_type: "graph",
        initialize: function(attributes){
        },
        create: function(object_attrs){
            // add a new object to this graph (ObjectStore.GraphCollection will create an Obj from this)
            if (typeof(object_attrs) == 'string') {  object_attrs = { "@id":object_attrs }; }
            var clone = _.clone(object_attrs);
            clone['_graph'] = this;

            var model = new ObjectStore.Obj(clone);
            this.get("objects").add(model);
            return model;
        },
        sync: function(method, model, options){
            switch(method){
                case "create":
                    break;
                case "read":
                    var graph = model;
                    return ObjectStore.fetch_graph(model, model.get("_store"), model.get("@id"));
                case "update":
                    console.debug("Update graph called on graph:",model);
                    return ObjectStore.update_graph(model);
                case "delete":
                    break;
                default:
                    break;
            }
        }
    });

    // ObjectStore.Obj are individual objects that are part of a ObjectStore.GraphCollection
    var Obj = ObjectStore.Obj = Backbone.Model.extend({
        idAttribute: "@id", // the URI attribute is '@id' in JSON-LD
        objectstore_type: "obj",
        get: function(attr){
            if (attr in this.attributes){
                return this.attributes[attr];
            } else {
                return [];
            }
        },
        sync: function(method, model, options){
            var d = new $.Deferred();
            switch(method){
                case "create":
                    console.log("CREATE ", model.id);
                    break;
                case "read":
                    d.resolve();
                    break;
                case "update":
                    // delegate to the graph
                    console.debug("SAVE -- Update to Obj: ",model.id);
                    return model.attributes._graph.sync("update", model.attributes._graph, options);
                case "delete":
                    break;
                default:
                    break;
            }
            return d.promise();
        },
    });

    // ObjectStore.GraphCollection is the list of ObjectStore.Objs in a ObjectStore.Graph
    var GraphCollection = ObjectStore.GraphCollection = Backbone.Collection.extend({
        model: ObjectStore.Obj,
        objectstore_type: "graphcollection",
    });

    // Functions to communicate with the ObjectStore server
    ObjectStore.list_graphs = function(store, callback){
        // return a list of named graphs (each of type ObjectStore.Graph) to populate a GraphCollection
        return $.ajax({
            url: store.server_url,
            data: {},
            dataType: "json",
            type: "GET",
            success: function(data){

                var graph_uris = data;
                var graphs = [];
                $.each(graph_uris, function(){
                    var graph_uri = this;
                    var graph = new ObjectStore.Graph({"@id": graph_uri, "_store": store});
                    graphs.push(graph);
                });

                store.reset(graphs);
            }
        });
    };

    ObjectStore.update_graph = function(graph){
        var graph_objs = [ // object to PUT to the server
        ]

        $.each(graph.get("objects").models, function(){
            var obj = this;
            var uri = obj.id;

            var out_obj = {};
            $.each(obj.attributes, function(pred, vals){
                if (pred[0] == "_"){
                    return;
                }
                if (pred[0] == "@"){ // don't expand @id etc.
                    out_obj[pred] = vals;
                    return;
                }

                var obj_vals = [];

                if (!(vals instanceof Array)){
                    vals = [vals];
                }
                $.each(vals, function(){
                    var val = this;
                    if (typeof val === "string" || val instanceof String){
                        obj_vals.push({"@value": val});
                    } else if (val instanceof ObjectStore.Obj) {
                        obj_vals.push({"@id": val.id });
                    } else {
                        obj_vals.push(val); // val is a fully expanded string, e.g. {"@value": "foo", "@language": "en" ... }
                    }
                });

                out_obj[pred] = obj_vals;
            });
            graph_objs.push(out_obj);
        });

        var store = graph.attributes._store;
        var url = store.server_url+"?graph="+escape(graph.id)+"&version="+escape(graph._version);

        console.debug("Sending PUT.");

        return $.ajax({
            url: url,
            processData: false,
            contentType: "application/json",
            dataType: "json",
            data: JSON.stringify(graph_objs),
            type: "PUT",
            success: function(data){
                // TODO check that it worked
                graph._version = data["@version"];
            }
        });
    };

    ObjectStore.fetch_graph = function(graph, store, uri){
        // return a list of models (each of type ObjectStore.Object) to populate a GraphCollection
        return $.ajax({
            url: store.server_url,
            data: {"graph": uri},
            dataType: "json",
            type: "GET",
            success: function(data){
                var graph_collection = new ObjectStore.GraphCollection();
                graph_collection._store = store;

                graph.set("objects", graph_collection);

                var version = 0; // new version FIXME check
                $.each(data, function(uri, obj){
                    if (uri == "@version"){
                        version = obj;
                    }
                    if (uri[0] == "@"){
                        // ignore @graph etc.
                        return;
                    }

                    var obj_model = {"@id": uri, "_graph": graph};

                    $.each(obj, function(key, vals){
                        var obj_vals = [];
                        $.each(vals, function(){
                            var val = this;
                            if ("@id" in val){
                                var val_obj = graph.create({"@id": val["@id"]});
                                obj_vals.push(val_obj);
                            } else if ("@value" in val){
                                // if the string has no language or datatype, compress to just a string.
                                if ("@language" in val && val["@language"] == "" && "@type" in val && val["@type"] == ""){
                                    obj_vals.push(val["@value"]);
                                } else {
                                    obj_vals.push(val);
                                }
                            }
                        });
                        obj_model[key] = obj_vals;
                    });

                    graph.create(obj_model);
                });
                graph_collection._version = version;
                graph._version = version;
            }
        });
    };

}).call(this);
<|endoftext|>