<commit_before>'use strict';

var tap = require('tap');
var config = require('./util/patternlab-config.json');

// fake pattern lab constructor:
// sets up a fake patternlab object, which is needed by the pattern processing
// apparatus.
function fakePatternLab() {
  var fpl = {
    partials: {},
    patterns: [],
    footer: '',
    header: '',
    listitems: {},
    data: {
      link: {},
    },
    config: config,
    package: {},
  };

  return fpl;
}

var of = require('../src/lib/object_factory');
var Pattern = require('../src/lib/object_factory').Pattern;
var path = require('path');
var pl = fakePatternLab();
var engineLoader = require('../src/lib/pattern_engines');
engineLoader.loadAllEngines(config);

tap.test('test Pattern initializes correctly', function(test) {
  var p = new Pattern('00-atoms/00-global/00-colors.mustache', { d: 123 });
  test.equals(
    p.relPath,
    '00-atoms' + path.sep + '00-global' + path.sep + '00-colors.mustache'
  );
  test.equals(p.name, '00-atoms-00-global-00-colors');
  test.equals(p.subdir, '00-atoms' + path.sep + '00-global');
  test.equals(p.fileName, '00-colors');
  test.equals(p.fileExtension, '.mustache');
  test.equals(p.jsonFileData.d, 123);
  test.equals(p.patternBaseName, 'colors');
  test.equals(p.patternName, 'Colors');
  test.equals(
    p.getPatternLink(pl),
    '00-atoms-00-global-00-colors' +
      path.sep +
      '00-atoms-00-global-00-colors.rendered.html'
  );
  test.equals(p.patternGroup, 'atoms');
  test.equals(p.patternSubGroup, 'global');
  test.equals(p.flatPatternPath, '00-atoms-00-global');
  test.equals(p.patternPartial, 'atoms-colors');
  test.equals(p.template, '');
  test.equals(p.patternPartialCode, '');
  test.equals(p.lineage.length, 0);
  test.equals(p.lineageIndex.length, 0);
  test.equals(p.lineageR.length, 0);
  test.equals(p.lineageRIndex.length, 0);
  test.equals(p.patternState, '');
  test.end();
});

tap.test(
  'test Pattern initializes correctly with pattern in sepatated directory',
  function(test) {
    var p = new Pattern('00-atoms/00-global/00-colors/colors.mustache', {
      d: 123,
    });
    test.equals(
      p.relPath,
      '00-atoms' +
        path.sep +
        '00-global' +
        path.sep +
        '00-colors' +
        path.sep +
        'colors.mustache'
    );
    test.equals(p.name, '00-atoms-00-global-00-colors');
    test.equals(
      p.subdir,
      '00-atoms' + path.sep + '00-global' + path.sep + '00-colors'
    );
    test.equals(p.fileName, 'colors');
    test.equals(p.fileExtension, '.mustache');
    test.equals(p.jsonFileData.d, 123);
    test.equals(p.patternBaseName, 'colors');
    test.equals(p.patternName, 'Colors');
    test.equals(
      p.getPatternLink(pl),
      '00-atoms-00-global-00-colors' +
        path.sep +
        '00-atoms-00-global-00-colors.rendered.html'
    );
    test.equals(p.patternGroup, 'atoms');
    test.equals(p.patternSubGroup, 'global');
    test.equals(p.flatPatternPath, '00-atoms-00-global');
    test.equals(p.patternPartial, 'atoms-colors');
    test.equals(p.template, '');
    test.equals(p.patternPartialCode, '');
    test.equals(p.lineage.length, 0);
    test.equals(p.lineageIndex.length, 0);
    test.equals(p.lineageR.length, 0);
    test.equals(p.lineageRIndex.length, 0);
    test.equals(p.patternState, '');
    test.end();
  }
);

tap.test('test Pattern name for variants correctly initialzed', function(test) {
  var p1 = new Pattern('00-atoms/00-global/00-colors/colors~variant.mustache', {
    d: 123,
  });
  var p2 = new Pattern(
    '00-atoms/00-global/00-colors/colors~variant-minus.json',
    { d: 123 }
  );
  test.equals(p1.name, '00-atoms-00-global-00-colors-variant');
  test.equals(p2.name, '00-atoms-00-global-00-colors-variant-minus');
  test.end();
});

tap.test('test Pattern with one-directory subdir works as expected', function(
  test
) {
  var p = new Pattern('00-atoms/00-colors.mustache', { d: 123 });
  test.equals(p.relPath, '00-atoms' + path.sep + '00-colors.mustache');
  test.equals(p.name, '00-atoms-00-colors');
  test.equals(p.subdir, '00-atoms');
  test.equals(p.fileName, '00-colors');
  test.equals(p.fileExtension, '.mustache');
  test.equals(p.jsonFileData.d, 123);
  test.equals(p.patternBaseName, 'colors');
  test.equals(p.patternName, 'Colors');
  test.equals(
    p.getPatternLink(pl),
    '00-atoms-00-colors' + path.sep + '00-atoms-00-colors.rendered.html'
  );
  test.equals(p.patternGroup, 'atoms');
  test.equals(p.flatPatternPath, '00-atoms');
  test.equals(p.patternPartial, 'atoms-colors');
  test.equals(p.template, '');
  test.equals(p.lineage.length, 0);
  test.equals(p.lineageIndex.length, 0);
  test.equals(p.lineageR.length, 0);
  test.equals(p.lineageRIndex.length, 0);
  test.end();
});

tap.test(
  'test Pattern with no numbers in pattern group works as expected',
  function(test) {
    var p = new Pattern('atoms/colors.mustache', { d: 123 });
    test.equals(p.relPath, 'atoms' + path.sep + 'colors.mustache');
    test.equals(p.name, 'atoms-colors');
    test.equals(p.subdir, 'atoms');
    test.equals(p.fileName, 'colors');
    test.equals(
      p.getPatternLink(pl),
      'atoms-colors' + path.sep + 'atoms-colors.rendered.html'
    );
    test.equals(p.patternGroup, 'atoms');
    test.equals(p.flatPatternPath, 'atoms');
    test.equals(p.patternPartial, 'atoms-colors');
    test.end();
  }
);

tap.test('test Pattern capitalizes patternDisplayName correctly', function(
  test
) {
  var p = new Pattern('00-atoms/00-global/00-colors-alt.mustache', { d: 123 });
  test.equals(p.patternBaseName, 'colors-alt');
  test.equals(p.patternName, 'Colors Alt');
  test.end();
});

tap.test('test Pattern get dir level no sepatated pattern directory', function(
  test
) {
  var p = new Pattern('00-atoms/00-global/00-colors-alt.mustache', { d: 123 });
  console.log(p);
  test.equals(p.getDirLevel(0, { hasDir: false, dirLevel: 2 }), '00-atoms');
  test.equals(p.getDirLevel(1, { hasDir: false, dirLevel: 2 }), '00-global');
  test.equals(p.getDirLevel(3, { hasDir: false, dirLevel: 2 }), '');
  var p = new Pattern('00-atoms/00-colors-alt.mustache', { d: 123 });
  test.equals(p.getDirLevel(0, { hasDir: false, dirLevel: 1 }), '00-atoms');
  test.equals(p.getDirLevel(1, { hasDir: false, dirLevel: 1 }), '00-atoms');
  test.equals(p.getDirLevel(3, { hasDir: false, dirLevel: 1 }), '');
  var p = new Pattern('00-colors-alt.mustache', { d: 123 });
  test.equals(p.getDirLevel(0, { hasDir: false, dirLevel: 0 }), '');
  test.equals(p.getDirLevel(1, { hasDir: false, dirLevel: 0 }), '');
  test.equals(p.getDirLevel(3, { hasDir: false, dirLevel: 0 }), '');
  test.end();
});

tap.test(
  'test Pattern get dir level with sepatated pattern directory',
  function(test) {
    var p = new Pattern(
      '00-atoms/00-global/00-colors-alt/colors-alt.mustache',
      { d: 123 }
    );
    test.equals(p.getDirLevel(0, { hasDir: true, dirLevel: 3 }), '00-atoms');
    test.equals(p.getDirLevel(1, { hasDir: true, dirLevel: 3 }), '00-global');
    test.equals(p.getDirLevel(3, { hasDir: true, dirLevel: 3 }), '');
    var p = new Pattern('00-atoms/00-colors-alt/colors-alt.mustache', {
      d: 123,
    });
    test.equals(p.getDirLevel(0, { hasDir: true, dirLevel: 2 }), '00-atoms');
    test.equals(p.getDirLevel(1, { hasDir: true, dirLevel: 2 }), '00-atoms');
    test.equals(p.getDirLevel(3, { hasDir: true, dirLevel: 2 }), '');
    var p = new Pattern('00-colors-alt/colors-alt.mustache', { d: 123 });
    test.equals(p.getDirLevel(0, { hasDir: true, dirLevel: 1 }), '');
    test.equals(p.getDirLevel(1, { hasDir: true, dirLevel: 1 }), '');
    test.equals(p.getDirLevel(3, { hasDir: true, dirLevel: 1 }), '');
    test.end();
  }
);

tap.test('The forms of Pattern.getPatternLink() work as expected', function(
  test
) {
  var p = new Pattern('00-atoms/00-global/00-colors.hbs');
  test.equals(
    p.getPatternLink(pl),
    '00-atoms-00-global-00-colors' +
      path.sep +
      '00-atoms-00-global-00-colors.rendered.html'
  );
  test.equals(
    p.getPatternLink(pl, 'rendered'),
    '00-atoms-00-global-00-colors' +
      path.sep +
      '00-atoms-00-global-00-colors.rendered.html'
  );
  test.equals(
    p.getPatternLink(pl, 'rawTemplate'),
    '00-atoms-00-global-00-colors' +
      path.sep +
      '00-atoms-00-global-00-colors.hbs'
  );
  test.equals(
    p.getPatternLink(pl, 'markupOnly'),
    '00-atoms-00-global-00-colors' +
      path.sep +
      '00-atoms-00-global-00-colors.markup-only.html'
  );
  test.equals(
    p.getPatternLink(pl, 'custom', '.custom-extension'),
    '00-atoms-00-global-00-colors' +
      path.sep +
      '00-atoms-00-global-00-colors.custom-extension'
  );
  test.end();
});
<commit_msg>Update tests to reflect changes to object_factory<commit_after>'use strict';

var tap = require('tap');
var config = require('./util/patternlab-config.json');

// fake pattern lab constructor:
// sets up a fake patternlab object, which is needed by the pattern processing
// apparatus.
function fakePatternLab() {
  var fpl = {
    partials: {},
    patterns: [],
    footer: '',
    header: '',
    listitems: {},
    data: {
      link: {},
    },
    config: config,
    package: {},
  };

  return fpl;
}

var of = require('../src/lib/object_factory');
var Pattern = require('../src/lib/object_factory').Pattern;
var path = require('path');
var pl = fakePatternLab();
var engineLoader = require('../src/lib/pattern_engines');
engineLoader.loadAllEngines(config);

tap.test('test Pattern initializes correctly', function(test) {
  var p = new Pattern('00-atoms/00-global/00-colors.mustache', { d: 123 });
  test.equals(
    p.relPath,
    '00-atoms' + path.sep + '00-global' + path.sep + '00-colors.mustache'
  );
  test.equals(p.name, '00-atoms-00-global-00-colors');
  test.equals(p.subdir, '00-atoms' + path.sep + '00-global');
  test.equals(p.fileName, '00-colors');
  test.equals(p.fileExtension, '.mustache');
  test.equals(p.jsonFileData.d, 123);
  test.equals(p.patternBaseName, 'colors');
  test.equals(p.patternName, 'Colors');
  test.equals(
    p.getPatternLink(pl),
    '00-atoms-00-global-00-colors' +
      path.sep +
      '00-atoms-00-global-00-colors.rendered.html'
  );
  test.equals(p.patternGroup, 'atoms');
  test.equals(p.patternSubGroup, 'global');
  test.equals(p.flatPatternPath, '00-atoms-00-global');
  test.equals(p.patternPartial, 'atoms-colors');
  test.equals(p.template, '');
  test.equals(p.patternPartialCode, '');
  test.equals(p.lineage.length, 0);
  test.equals(p.lineageIndex.length, 0);
  test.equals(p.lineageR.length, 0);
  test.equals(p.lineageRIndex.length, 0);
  test.equals(p.patternState, '');
  test.end();
});

tap.test(
  'test Pattern initializes correctly with pattern in sepatated directory',
  function(test) {
    var p = new Pattern('00-atoms/00-global/00-colors/colors.mustache', {
      d: 123,
    });
    test.equals(
      p.relPath,
      '00-atoms' +
        path.sep +
        '00-global' +
        path.sep +
        '00-colors' +
        path.sep +
        'colors.mustache'
    );
    test.equals(p.name, '00-atoms-00-global-00-colors');
    test.equals(
      p.subdir,
      '00-atoms' + path.sep + '00-global' + path.sep + '00-colors'
    );
    test.equals(p.fileName, 'colors');
    test.equals(p.fileExtension, '.mustache');
    test.equals(p.jsonFileData.d, 123);
    test.equals(p.patternBaseName, 'colors');
    test.equals(p.patternName, 'Colors');
    test.equals(
      p.getPatternLink(pl),
      '00-atoms-00-global-00-colors' +
        path.sep +
        '00-atoms-00-global-00-colors.rendered.html'
    );
    test.equals(p.patternGroup, 'atoms');
    test.equals(p.patternSubGroup, 'global');
    test.equals(p.flatPatternPath, '00-atoms-00-global');
    test.equals(p.patternPartial, 'atoms-colors');
    test.equals(p.template, '');
    test.equals(p.patternPartialCode, '');
    test.equals(p.lineage.length, 0);
    test.equals(p.lineageIndex.length, 0);
    test.equals(p.lineageR.length, 0);
    test.equals(p.lineageRIndex.length, 0);
    test.equals(p.patternState, '');
    test.end();
  }
);

tap.test('test Pattern name for variants correctly initialzed', function(test) {
  var p1 = new Pattern('00-atoms/00-global/00-colors/colors~variant.mustache', {
    d: 123,
  });
  var p2 = new Pattern(
    '00-atoms/00-global/00-colors/colors~variant-minus.json',
    { d: 123 }
  );
  test.equals(p1.name, '00-atoms-00-global-00-colors-variant');
  test.equals(p2.name, '00-atoms-00-global-00-colors-variant-minus');
  test.end();
});

tap.test('test Pattern with one-directory subdir works as expected', function(
  test
) {
  var p = new Pattern('00-atoms/00-colors.mustache', { d: 123 });
  test.equals(p.relPath, '00-atoms' + path.sep + '00-colors.mustache');
  test.equals(p.name, '00-atoms-00-colors');
  test.equals(p.subdir, '00-atoms');
  test.equals(p.fileName, '00-colors');
  test.equals(p.fileExtension, '.mustache');
  test.equals(p.jsonFileData.d, 123);
  test.equals(p.patternBaseName, 'colors');
  test.equals(p.patternName, 'Colors');
  test.equals(
    p.getPatternLink(pl),
    '00-atoms-00-colors' + path.sep + '00-atoms-00-colors.rendered.html'
  );
  test.equals(p.patternGroup, 'atoms');
  test.equals(p.flatPatternPath, '00-atoms');
  test.equals(p.patternPartial, 'atoms-colors');
  test.equals(p.template, '');
  test.equals(p.lineage.length, 0);
  test.equals(p.lineageIndex.length, 0);
  test.equals(p.lineageR.length, 0);
  test.equals(p.lineageRIndex.length, 0);
  test.end();
});

tap.test(
  'test Pattern with no numbers in pattern group works as expected',
  function(test) {
    var p = new Pattern('atoms/colors.mustache', { d: 123 });
    test.equals(p.relPath, 'atoms' + path.sep + 'colors.mustache');
    test.equals(p.name, 'atoms-colors');
    test.equals(p.subdir, 'atoms');
    test.equals(p.fileName, 'colors');
    test.equals(
      p.getPatternLink(pl),
      'atoms-colors' + path.sep + 'atoms-colors.rendered.html'
    );
    test.equals(p.patternGroup, 'atoms');
    test.equals(p.flatPatternPath, 'atoms');
    test.equals(p.patternPartial, 'atoms-colors');
    test.end();
  }
);

tap.test('test Pattern capitalizes patternDisplayName correctly', function(
  test
) {
  var p = new Pattern('00-atoms/00-global/00-colors-alt.mustache', { d: 123 });
  test.equals(p.patternBaseName, 'colors-alt');
  test.equals(p.patternName, 'Colors Alt');
  test.end();
});

tap.test('test Pattern get dir level no separated pattern directory', function(
  test
) {
  var p = new Pattern('00-atoms/00-global/00-colors-alt.mustache', { d: 123 });
  console.log(p);
  test.equals(p.getDirLevel(0, { hasDir: false, dirLevel: 2 }), '00-atoms');
  test.equals(p.getDirLevel(1, { hasDir: false, dirLevel: 2 }), '00-global');
  test.equals(p.getDirLevel(3, { hasDir: false, dirLevel: 2 }), '');
  var p = new Pattern('00-atoms/00-colors-alt.mustache', { d: 123 });
  test.equals(p.getDirLevel(0, { hasDir: false, dirLevel: 1 }), '00-atoms');
  test.equals(p.getDirLevel(1, { hasDir: false, dirLevel: 1 }), '00-atoms');
  test.equals(p.getDirLevel(3, { hasDir: false, dirLevel: 1 }), '');
  var p = new Pattern('00-colors-alt.mustache', { d: 123 });
  test.equals(p.getDirLevel(0, { hasDir: false, dirLevel: 0 }), '');
  test.equals(p.getDirLevel(1, { hasDir: false, dirLevel: 0 }), '');
  test.equals(p.getDirLevel(3, { hasDir: false, dirLevel: 0 }), '');
  test.end();
});

tap.test(
  'test Pattern get dir level with separated pattern directory',
  function(test) {
    var p = new Pattern(
      '00-atoms/00-global/00-colors-alt/colors-alt.mustache',
      { d: 123 }
    );
    test.equals(p.getDirLevel(0, { hasDir: true, dirLevel: 3 }), '00-atoms');
    test.equals(p.getDirLevel(1, { hasDir: true, dirLevel: 3 }), '00-global');
    test.equals(
      p.getDirLevel(3, { hasDir: true, dirLevel: 3 }),
      '00-colors-alt'
    );
    var p = new Pattern('00-atoms/00-colors-alt/colors-alt.mustache', {
      d: 123,
    });
    test.equals(p.getDirLevel(0, { hasDir: true, dirLevel: 2 }), '00-atoms');
    test.equals(
      p.getDirLevel(1, { hasDir: true, dirLevel: 2 }),
      '00-colors-alt'
    );
    test.equals(p.getDirLevel(3, { hasDir: true, dirLevel: 2 }), '');
    var p = new Pattern('00-colors-alt/colors-alt.mustache', { d: 123 });
    test.equals(
      p.getDirLevel(0, { hasDir: true, dirLevel: 1 }),
      '00-colors-alt'
    );
    test.equals(
      p.getDirLevel(1, { hasDir: true, dirLevel: 1 }),
      '00-colors-alt'
    );
    test.equals(p.getDirLevel(3, { hasDir: true, dirLevel: 1 }), '');
    test.end();
  }
);

tap.test('The forms of Pattern.getPatternLink() work as expected', function(
  test
) {
  var p = new Pattern('00-atoms/00-global/00-colors.hbs');
  test.equals(
    p.getPatternLink(pl),
    '00-atoms-00-global-00-colors' +
      path.sep +
      '00-atoms-00-global-00-colors.rendered.html'
  );
  test.equals(
    p.getPatternLink(pl, 'rendered'),
    '00-atoms-00-global-00-colors' +
      path.sep +
      '00-atoms-00-global-00-colors.rendered.html'
  );
  test.equals(
    p.getPatternLink(pl, 'rawTemplate'),
    '00-atoms-00-global-00-colors' +
      path.sep +
      '00-atoms-00-global-00-colors.hbs'
  );
  test.equals(
    p.getPatternLink(pl, 'markupOnly'),
    '00-atoms-00-global-00-colors' +
      path.sep +
      '00-atoms-00-global-00-colors.markup-only.html'
  );
  test.equals(
    p.getPatternLink(pl, 'custom', '.custom-extension'),
    '00-atoms-00-global-00-colors' +
      path.sep +
      '00-atoms-00-global-00-colors.custom-extension'
  );
  test.end();
});
<|endoftext|>


<commit_before>'use strict';

var util = require('util');
var chalk = require('chalk');
var conf = require('./lib/conf.js');
var workflow = require('./lib/workflow.js');

module.exports = function (grunt) {

    grunt.registerTask('ec2_setup', 'Sets up port forwarding, installs `rsync`, `node`, and `pm2`, enqueues `ec2_nginx_configure`', function (name) {
        conf.init(grunt);

        if (arguments.length === 0) {
            grunt.fatal([
                'You should provide an instance name.',
                'e.g: ' + chalk.yellow('grunt ec2_setup:name')
            ].join('\n'));
        }

        // TODO rsync user, node user, nginx user?

        var done = this.async();
        var cert = conf('SRV_RSYNC_CERT');
        var latest = conf('SRV_RSYNC_LATEST');
        var versions = conf('SRV_VERSIONS');
        var platform = conf('PM2_PLATFORM');
        var steps = [[
            util.format('echo "configuring up %s instance..."', name)
        ], [ // node.js
            'sudo apt-get install python-software-properties',
            'sudo add-apt-repository ppa:chris-lea/node.js -y',
            'sudo add-apt-repository ppa:chris-lea/nginx-devel -y',
            'sudo apt-get update',
            'sudo apt-get install nodejs -y'
        ],  [ // git
            'sudo apt-get install git -y'
        ],  [ // pm2
            'sudo apt-get install make g++ -y',
            'sudo npm install -g pm2',
            util.format('sudo pm2 startup %s', platform)
        ],  [ // enable forwarding
          'cp /etc/sysctl.conf /tmp/',
          'echo "net.ipv4.ip_forward = 1" >> /tmp/sysctl.conf',
          'sudo cp /tmp/sysctl.conf /etc/',
          'sudo sysctl -p /etc/sysctl.conf'
        ], [ // forward port 80
          forwardPort(80, 8080)
        ], workflow.if_has('SSL_ENABLED', // forward port 443
          forwardPort(443, 8433)
        ), [ // rsync
          util.format('sudo mkdir -p %s', versions),
          util.format('sudo mkdir -p %s', cert),
          util.format('sudo chown ubuntu %s', cert),
          util.format('sudo mkdir -p %s', latest),
          util.format('sudo chown ubuntu %s', latest)
        ], workflow.if_has('SSL_ENABLED', { // send certificates
          rsync: {
            name: 'cert',
            local: conf('SSL_CERTIFICATE_DIRECTORY'),
            remote: conf('SRV_RSYNC_CERT'),
            dest: conf('SRV_CERT'),
            includes: [
              '*/',
              conf('SSL_CERTIFICATE'),
              conf('SSL_CERTIFICATE_KEY')
            ],
            excludes: ['*']
          }
        }), [ // iptables-persistent
          'sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -q iptables-persistent'
        ], workflow.if_has('PANDOC_ENABLED', // install pandoc
          installPandoc()
        )];

        function forwardPort(from, to) {
            return [
                util.format('sudo iptables -A PREROUTING -t nat -i eth0 -p tcp --dport %s -j REDIRECT --to-port %s', from, to),
                util.format('sudo iptables -A INPUT -p tcp -m tcp --sport %s -j ACCEPT', from),
                util.format('sudo iptables -A OUTPUT -p tcp -m tcp --dport %s -j ACCEPT', from),
                'sudo iptables-save'
            ];
        }

        function installPandoc() {
          return [
            'sudo apt-get install haskell-platform',
            'cabal update',
            'cabal install pandoc'
          ];
        }

        workflow(steps, { name: name }, next);

        function next () {
            grunt.log.writeln('Enqueued task for %s configuration.', chalk.cyan('nginx'));
            grunt.task.run('ec2_nginx_configure:' + name);
            done();
        }
    });
};
<commit_msg>assume yes on haskell-platform install<commit_after>'use strict';

var util = require('util');
var chalk = require('chalk');
var conf = require('./lib/conf.js');
var workflow = require('./lib/workflow.js');

module.exports = function (grunt) {

    grunt.registerTask('ec2_setup', 'Sets up port forwarding, installs `rsync`, `node`, and `pm2`, enqueues `ec2_nginx_configure`', function (name) {
        conf.init(grunt);

        if (arguments.length === 0) {
            grunt.fatal([
                'You should provide an instance name.',
                'e.g: ' + chalk.yellow('grunt ec2_setup:name')
            ].join('\n'));
        }

        // TODO rsync user, node user, nginx user?

        var done = this.async();
        var cert = conf('SRV_RSYNC_CERT');
        var latest = conf('SRV_RSYNC_LATEST');
        var versions = conf('SRV_VERSIONS');
        var platform = conf('PM2_PLATFORM');
        var steps = [[
            util.format('echo "configuring up %s instance..."', name)
        ], [ // node.js
            'sudo apt-get install python-software-properties',
            'sudo add-apt-repository ppa:chris-lea/node.js -y',
            'sudo add-apt-repository ppa:chris-lea/nginx-devel -y',
            'sudo apt-get update',
            'sudo apt-get install nodejs -y'
        ],  [ // git
            'sudo apt-get install git -y'
        ],  [ // pm2
            'sudo apt-get install make g++ -y',
            'sudo npm install -g pm2',
            util.format('sudo pm2 startup %s', platform)
        ],  [ // enable forwarding
          'cp /etc/sysctl.conf /tmp/',
          'echo "net.ipv4.ip_forward = 1" >> /tmp/sysctl.conf',
          'sudo cp /tmp/sysctl.conf /etc/',
          'sudo sysctl -p /etc/sysctl.conf'
        ], [ // forward port 80
          forwardPort(80, 8080)
        ], workflow.if_has('SSL_ENABLED', // forward port 443
          forwardPort(443, 8433)
        ), [ // rsync
          util.format('sudo mkdir -p %s', versions),
          util.format('sudo mkdir -p %s', cert),
          util.format('sudo chown ubuntu %s', cert),
          util.format('sudo mkdir -p %s', latest),
          util.format('sudo chown ubuntu %s', latest)
        ], workflow.if_has('SSL_ENABLED', { // send certificates
          rsync: {
            name: 'cert',
            local: conf('SSL_CERTIFICATE_DIRECTORY'),
            remote: conf('SRV_RSYNC_CERT'),
            dest: conf('SRV_CERT'),
            includes: [
              '*/',
              conf('SSL_CERTIFICATE'),
              conf('SSL_CERTIFICATE_KEY')
            ],
            excludes: ['*']
          }
        }), [ // iptables-persistent
          'sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -q iptables-persistent'
        ], workflow.if_has('PANDOC_ENABLED', // install pandoc
          installPandoc()
        )];

        function forwardPort(from, to) {
            return [
                util.format('sudo iptables -A PREROUTING -t nat -i eth0 -p tcp --dport %s -j REDIRECT --to-port %s', from, to),
                util.format('sudo iptables -A INPUT -p tcp -m tcp --sport %s -j ACCEPT', from),
                util.format('sudo iptables -A OUTPUT -p tcp -m tcp --dport %s -j ACCEPT', from),
                'sudo iptables-save'
            ];
        }

        function installPandoc() {
          return [
            'sudo apt-get install haskell-platform -y',
            'cabal update',
            'cabal install pandoc'
          ];
        }

        workflow(steps, { name: name }, next);

        function next () {
            grunt.log.writeln('Enqueued task for %s configuration.', chalk.cyan('nginx'));
            grunt.task.run('ec2_nginx_configure:' + name);
            done();
        }
    });
};
<|endoftext|>


<commit_before>/*
* Unobtrusive autocomplete
*
* To use it, you just have to include the HTML attribute autocomplete
* with the autocomplete URL as the value
*
*   Example:
*       <input type="text" autocomplete="/url/to/autocomplete">
*
* Optionally, you can use a jQuery selector to specify a field that can
* be updated with the element id whenever you find a matching value
*
*   Example:
*       <input type="text" autocomplete="/url/to/autocomplete" id_element="#id_field">
*/

$(document).ready(function(){
  $('input[data-autocomplete]').each(function(i){
    $(this).autocomplete({
      source: $(this).attr('data-autocomplete'),
      select: function(event, ui) {
        $(this).val(ui.item.value),
        if($(this).attr('id_element')) {
          $($(this).attr('id_element')).val(ui.item.id);
        }
        return false;
      }
    });
  });
});<commit_msg>Fixed a bug that broke the script for FF<commit_after>/*
* Unobtrusive autocomplete
*
* To use it, you just have to include the HTML attribute autocomplete
* with the autocomplete URL as the value
*
*   Example:
*       <input type="text" autocomplete="/url/to/autocomplete">
*
* Optionally, you can use a jQuery selector to specify a field that can
* be updated with the element id whenever you find a matching value
*
*   Example:
*       <input type="text" autocomplete="/url/to/autocomplete" id_element="#id_field">
*/

$(document).ready(function(){
  $('input[data-autocomplete]').each(function(i){
    $(this).autocomplete({
      source: $(this).attr('data-autocomplete'),
      select: function(event, ui) {
        $(this).val(ui.item.value),
        function(){
          if ($(this).attr('id_element')) {
            $($(this).attr('id_element')).val(ui.item.id);
          }
        }
        return false;
      }
    });
  });
});<|endoftext|>


<commit_before>/*
 * grunticon
 * https://github.com/filamentgroup/grunticon
 *
 * Copyright (c) 2012 Scott Jehl, Filament Group, Inc
 * Licensed under the MIT license.
 */

/*global __dirname:true*/
/*global require:true*/

module.exports = function(grunt, undefined) {

	"use strict";

	var path = require('path');
	var os = require('os');

	var fs = require('fs-extra');
	var uglify = require('uglify-js');
	var RSVP = require('rsvp');

	var DirectoryColorfy = require('directory-colorfy');
	var DirectoryEncoder = require('directory-encoder');
	var svgToPng = require('svg-to-png');

	var helper = require(path.join('..', 'lib', 'grunticon-helper'));

	grunt.registerMultiTask('grunticon', 'A mystical CSS icon solution.', function() {
		var done = this.async();

		// get the config
		var config = this.options({
			datasvgcss: "icons.data.svg.css",
			datapngcss: "icons.data.png.css",
			urlpngcss: "icons.fallback.css",
			files: {
				loader: path.join(__dirname, 'grunticon', 'static', 'grunticon.loader.js'),
				banner: path.join(__dirname, 'grunticon', 'static', 'grunticon.loader.banner.js')
			},
			previewhtml: "preview.html",
			loadersnippet: "grunticon.loader.js",
			cssbasepath: path.sep,
			customselectors: {},
			cssprefix: ".icon-",
			defaultWidth: "400px",
			defaultHeight: "300px",
			colors: {},
			pngfolder: "png",
			pngpath: "",
			template: "",
			tmpDir: "grunticon-tmp",
			previewTemplate: path.join(__dirname, "..", "example", "preview.hbs"),
			dynamicColorOnly: true,
			stylesheet: '',
			styles: []
		});

		// just a quick starting message
		grunt.log.writeln("Look, it's a grunticon!");

		var files = this.files.filter(function(file) {
			return file.src[0].match(/png|svg/);
		});
		if (files.length === 0) {
			grunt.log.writeln("Grunticon has no files to read!");
			done();
			return;
		}

		files = files.map(function(file) {
			return file.src[0];
		});

		config.src = this.files[0].orig.cwd;
		config.dest = this.files[0].orig.dest;

		if (!config.dest || config.dest && config.dest === "") {
			grunt.fatal("The destination must be a directory");
		}

		// folder name (within the output folder) for generated png files
		var pngfolder = path.join.apply(null, config.pngfolder.split(path.sep));

		// create the output directory
		grunt.file.mkdir(config.dest);

		// minify the source of the grunticon loader and write that to the output
		// grunt.log.writeln("grunticon now minifying the stylesheet loader source.");
		// var banner = grunt.file.read(config.files.banner);
		// config.min = banner + "\n" + uglify.minify(config.files.loader).code;
		// grunt.file.write(path.join(config.dest, config.loadersnippet), config.min);
		// grunt.log.writeln("grunticon loader file created.");

		var svgToPngOpts = {
			pngfolder: pngfolder,
			defaultWidth: config.defaultWidth,
			defaultHeight: config.defaultHeight
		};

		var o = {
			pngfolder: pngfolder,
			customselectors: config.customselectors,
			template: path.resolve(config.template),
			previewTemplate: path.resolve(config.previewTemplate),
			noencodepng: false,
			prefix: config.cssprefix
		};

		var o2 = {
			pngfolder: pngfolder,
			pngpath: config.pngpath,
			customselectors: config.customselectors,
			template: path.resolve(config.template),
			previewTemplate: path.resolve(config.previewTemplate),
			noencodepng: true,
			prefix: config.cssprefix
		};
		//EBSCO - Parsing less file, parse function found on stackoverflow, question 11827453;

		var lessFilePath = path.join(process.cwd(), path.normalize(config.stylesheet)),
			lessFile, lessVars,
			getLessVars = function(lessStr) {
				var keyVar, lessVars, line, lines, _i, _len;
				lines = lessStr.split('\n');
				lessVars = {};
				for (_i = 0, _len = lines.length; _i < _len; _i++) {
					line = lines[_i];
					if (line.indexOf('@') === 0) {
						keyVar = line.split(';')[0].split(':');
						lessVars[keyVar[0]] = keyVar[1].trim();
					}
				}
				return lessVars;
			};

		if (grunt.file.exists(lessFilePath)) {
			lessFile = fs.readFileSync(lessFilePath).toString();
		}
		if (typeof lessFile === 'string') {
			lessVars = getLessVars(lessFile);

			for (var key in lessVars) {
				for (var index in config.styles) {
					var color = config.styles[index];
					if (key.indexOf(color) > -1) {
						config.colors[color] = lessVars[key];
					}
				}
			}
		}

		grunt.log.writeln("Coloring SVG files");
		// create the tmp directory
		var tmp = path.join(os.tmpDir(), config.tmpDir);
		if (grunt.file.exists(tmp)) {
			fs.removeSync(tmp);
		}
		grunt.file.mkdir(tmp);
		var colorFiles;
		try {
			var dc = new DirectoryColorfy(config.src, tmp, {
				colors: config.colors,
				dynamicColorOnly: config.dynamicColorOnly
			});
			colorFiles = dc.convert();
		} catch (e) {
			grunt.fatal(e);
			done(false);
		}

		//copy non color config files into temp directory
		var transferFiles = this.files.filter(function(f) {
			return !f.src[0].match(/\.colors/);
		});

		transferFiles.forEach(function(f) {
			var filenameArr = f.src[0].split("/"),
				filename = filenameArr[filenameArr.length - 1];
			grunt.file.copy(f.src[0], path.join(tmp, filename));
		});

		grunt.log.writeln("Converting SVG to PNG");
		svgToPng.convert(tmp, config.dest, svgToPngOpts)
			.then(function(result, err) {
				if (err) {
					grunt.fatal(err);
				}

				var svgde = new DirectoryEncoder(tmp, path.join(config.dest, config.datasvgcss), o),
					pngde = new DirectoryEncoder(path.join(config.dest, pngfolder), path.join(config.dest, config.datapngcss), o),
					pngdefall = new DirectoryEncoder(path.join(config.dest, pngfolder), path.join(config.dest, config.urlpngcss), o2);

				grunt.log.writeln("Writing CSS");
				try {
					svgde.encode();
					pngde.encode();
					pngdefall.encode();
				} catch (e) {
					grunt.fatal(e);
					done(false);
				}

				// grunt.log.writeln("Grunticon now creating Preview File");
				// try {
				// 	helper.createPreview(tmp, config);
				// } catch (er) {
				// 	grunt.fatal(er);
				// }

				grunt.log.writeln("Delete Temp Files");
				fs.removeSync(tmp);
				done();
			});

	});
};
<commit_msg>made loader and preview configurable and fixed no stylesheet bug<commit_after>/*
 * grunticon
 * https://github.com/filamentgroup/grunticon
 *
 * Copyright (c) 2012 Scott Jehl, Filament Group, Inc
 * Licensed under the MIT license.
 */

/*global __dirname:true*/
/*global require:true*/

module.exports = function(grunt, undefined) {

	"use strict";

	var path = require('path');
	var os = require('os');

	var fs = require('fs-extra');
	var uglify = require('uglify-js');
	var RSVP = require('rsvp');

	var DirectoryColorfy = require('directory-colorfy');
	var DirectoryEncoder = require('directory-encoder');
	var svgToPng = require('svg-to-png');

	var helper = require(path.join('..', 'lib', 'grunticon-helper'));

	grunt.registerMultiTask('grunticon', 'A mystical CSS icon solution.', function() {
		var done = this.async();

		// get the config
		var config = this.options({
			datasvgcss: "icons.data.svg.css",
			datapngcss: "icons.data.png.css",
			urlpngcss: "icons.fallback.css",
			files: {
				loader: path.join(__dirname, 'grunticon', 'static', 'grunticon.loader.js'),
				banner: path.join(__dirname, 'grunticon', 'static', 'grunticon.loader.banner.js')
			},
			previewhtml: "preview.html",
			loadersnippet: "grunticon.loader.js",
			cssbasepath: path.sep,
			customselectors: {},
			cssprefix: ".icon-",
			defaultWidth: "400px",
			defaultHeight: "300px",
			colors: {},
			pngfolder: "png",
			pngpath: "",
			template: "",
			tmpDir: "grunticon-tmp",
			previewTemplate: path.join(__dirname, "..", "example", "preview.hbs"),
			dynamicColorOnly: true,
			stylesheet: '',
			styles: [],
			preview: false,
			loader: false,
		});

		// just a quick starting message
		grunt.log.writeln("Look, it's a grunticon!");

		var files = this.files.filter(function(file) {
			return file.src[0].match(/png|svg/);
		});
		if (files.length === 0) {
			grunt.log.writeln("Grunticon has no files to read!");
			done();
			return;
		}

		files = files.map(function(file) {
			return file.src[0];
		});

		config.src = this.files[0].orig.cwd;
		config.dest = this.files[0].orig.dest;

		if (!config.dest || config.dest && config.dest === "") {
			grunt.fatal("The destination must be a directory");
		}

		// folder name (within the output folder) for generated png files
		var pngfolder = path.join.apply(null, config.pngfolder.split(path.sep));

		// create the output directory
		grunt.file.mkdir(config.dest);

		// minify the source of the grunticon loader and write that to the output
		if (config.loader) {
			grunt.log.writeln("grunticon now minifying the stylesheet loader source.");
			var banner = grunt.file.read(config.files.banner);
			config.min = banner + "\n" + uglify.minify(config.files.loader).code;
			grunt.file.write(path.join(config.dest, config.loadersnippet), config.min);
			grunt.log.writeln("grunticon loader file created.");
		}

		var svgToPngOpts = {
			pngfolder: pngfolder,
			defaultWidth: config.defaultWidth,
			defaultHeight: config.defaultHeight
		};

		var o = {
			pngfolder: pngfolder,
			customselectors: config.customselectors,
			template: path.resolve(config.template),
			previewTemplate: path.resolve(config.previewTemplate),
			noencodepng: false,
			prefix: config.cssprefix
		};

		var o2 = {
			pngfolder: pngfolder,
			pngpath: config.pngpath,
			customselectors: config.customselectors,
			template: path.resolve(config.template),
			previewTemplate: path.resolve(config.previewTemplate),
			noencodepng: true,
			prefix: config.cssprefix
		};
		//EBSCO - Parsing less file, parse function found on stackoverflow, question 11827453;

		var lessFilePath = path.join(process.cwd(), path.normalize(config.stylesheet)),
			lessFile, lessVars,
			getLessVars = function(lessStr) {
				var keyVar, lessVars, line, lines, _i, _len;
				lines = lessStr.split('\n');
				lessVars = {};
				for (_i = 0, _len = lines.length; _i < _len; _i++) {
					line = lines[_i];
					if (line.indexOf('@') === 0) {
						keyVar = line.split(';')[0].split(':');
						lessVars[keyVar[0]] = keyVar[1].trim();
					}
				}
				return lessVars;
			};

		if (grunt.file.exists(lessFilePath) && !grunt.file.isDir(lessFilePath)) {
			lessFile = fs.readFileSync(lessFilePath).toString();
		}
		if (typeof lessFile === 'string') {
			lessVars = getLessVars(lessFile);

			for (var key in lessVars) {
				for (var index in config.styles) {
					var color = config.styles[index];
					if (key.indexOf(color) > -1) {
						config.colors[color] = lessVars[key];
					}
				}
			}
		}
		debugger;
		grunt.log.writeln("Coloring SVG files");
		// create the tmp directory
		var tmp = path.join(os.tmpDir(), config.tmpDir);
		if (grunt.file.exists(tmp)) {
			fs.removeSync(tmp);
		}
		grunt.file.mkdir(tmp);
		var colorFiles,
			isEmpty = function(obj) {
				for (var prop in obj) {
					if (obj.hasOwnProperty(prop)) {
						return false;
					}
				}

				return true;
			};

		try {
			var dc = new DirectoryColorfy(config.src, tmp, {
				colors: config.colors,
				dynamicColorOnly: isEmpty(config.colors) ? false : config.dynamicColorOnly
			});
			colorFiles = dc.convert();
		} catch (e) {
			grunt.fatal(e);
			done(false);
		}

		//copy non color config files into temp directory
		var transferFiles = this.files.filter(function(f) {
			return !f.src[0].match(/\.colors/);
		});

		transferFiles.forEach(function(f) {
			var filenameArr = f.src[0].split("/"),
				filename = filenameArr[filenameArr.length - 1];
			grunt.file.copy(f.src[0], path.join(tmp, filename));
		});

		grunt.log.writeln("Converting SVG to PNG");
		svgToPng.convert(tmp, config.dest, svgToPngOpts)
			.then(function(result, err) {
				if (err) {
					grunt.fatal(err);
				}

				var svgde = new DirectoryEncoder(tmp, path.join(config.dest, config.datasvgcss), o),
					pngde = new DirectoryEncoder(path.join(config.dest, pngfolder), path.join(config.dest, config.datapngcss), o),
					pngdefall = new DirectoryEncoder(path.join(config.dest, pngfolder), path.join(config.dest, config.urlpngcss), o2);

				grunt.log.writeln("Writing CSS");
				try {
					svgde.encode();
					pngde.encode();
					pngdefall.encode();
				} catch (e) {
					grunt.fatal(e);
					done(false);
				}
				if (config.preview === true) {
					grunt.log.writeln("Grunticon now creating Preview File");
					try {
						helper.createPreview(tmp, config);
					} catch (er) {
						grunt.fatal(er);
					}
				}

				grunt.log.writeln("Delete Temp Files");
				fs.removeSync(tmp);
				done();
			});

	});
};
<|endoftext|>


<commit_before>const {stream,F,C,N,} = require('../dist/parser-combinator.min');

const st = stream.ofString('12');
const parsing = N.numberLiteral.thenLeft(F.eos).parse(st);

if (parsing.value !== 12){
    throw "Illegal value parsed in prepublish integration test";
}else{
    console.log("=== Prepublish integration test ok ===")
}
<commit_msg>0.3 integration with older javascript<commit_after>//{stream,F,C,N,}
const parsec = require('../dist/parser-combinator.min');
const stream = parsec.stream;
const F = parsec.F;
const N=parsec.N;

const st = stream.ofString('12');
const parsing = N.numberLiteral.thenLeft(F.eos).parse(st);

if (parsing.value !== 12){
    throw "Illegal value parsed in prepublish integration test";
}else{
    console.log("=== Prepublish integration test ok ===")
}
<|endoftext|>


<commit_before>(function() {
  var root;

  root = typeof exports !== "undefined" && exports !== null ? exports : this;

  root.lessons = {
    1: {
      0: {
        startingText: "Ecrire en Markdon n'est pas difficile !",
        renderedAnswer:
          "
Ecrire en Markdon n'est pas difficile !
"
      },
      1: {
        startingText: "Je veux compléter cette leçon !",
        renderedAnswer:
          "
Je veux compléter cette leçon !
"
      },
      2: {
        startingText:
          '"Bien sûr," chuchota-t-elle. Puis, elle cria : "Tout ce dont j\'ai besoin, c\'est d\'un peu de moxie !"',
        renderedAnswer:
          '
"Bien sûr", chuchota-t-elle. Puis, elle cria : "Tout ce dont j\'ai besoin, c\'est d\'un peu de moxie !"
'
      },
      3: {
        startingText:
          "Si tu penses à toi-même, c'est incroyable, tu auras probablement raison.",
        renderedAnswer:
          "
Si tu penses à toi-même, c'est incroyable, tu auras probablement raison.
"
      }
    },
    2: {
      0: {
        startingText:
          "Titre de niveau 1\nTitre de niveau 2\nTitre de niveau 3\nTitre de niveau 4\nTitre de niveau 5\nTitre de niveau 6",
        renderedAnswer:
          "
Titre de niveau 1
\n
Titre de niveau 2
\n
Titre de niveau 3
\n
Titre de niveau 4
\n
Titre de niveau 5
\n
Titre de niveau 6
"
      },
      1: {
        startingText:
          "Le symbolisme colombien One Hundred Years of Solitude\n\nVoici quelques mots sur le livre _One Hundred Years of Solitude..._.",
        renderedAnswer:
          "
Le symbolisme colombien One Hundred Years of Solitude
\n
Voici quelques mots sur le livre One Hundred Years of Solitude....
"
      }
    },
    3: {
      0: {
        startingText: "Cherchez-le()",
        renderedAnswer: '
Cherchez-le
'
      },
      1: {
        startingText: "Tu vas vraiment, vraiment vouloir voir ça.",
        renderedAnswer:
          '
Tu vas vraiment, vraiment vouloir voir ça.
'
      },
      2: {
        startingText: "Les dernières nouvelles de la BBC",
        renderedAnswer:
          '
Les dernières nouvelles de la BBC
'
      },
      3: {
        startingText:
          "Voulez-vous [voir quelque chose d'amusant][] ?\n\nEh bien, est-ce que j'ai [le site Web pour vous][un autre endroit amusant] !",
        renderedAnswer:
          '
Voulez-vous voir quelque chose d\'amusant ?
\n
Eh bien, est-ce que j\'ai le site Web pour vous !
'
      }
    },
    4: {
      0: {
        startingText:
          "[](https://upload.wikimedia.org/wikipedia/commons/5/56/Tiger.50.jpg)",
        renderedAnswer:
          '

'
      },
      1: {
        startingText:
          "[Chat noir][]\n\n[Chat Orange][Orange]\n\n[Noir]: https://upload.wikimedia.org/wikipedia/commons/a/a3/81_INF_DIV_SSI.jpg",
        renderedAnswer:
          '

\n

'
      }
    },
    5: {
      0: {
        startingText:
          "J'ai lu cette citation intéressante l'autre jour :\n\n\"Ses yeux l'avaient appelé et son âme avait sauté à l'appel. Vivre, errer, tomber, triompher, recréer la vie à partir de la vie !\"",
        renderedAnswer:
          "
J'ai lu cette citation intéressante l'autre jour :
\n
\n
\"Ses yeux l'avaient appelé et son âme avait sauté à l'appel. Vivre, errer, tomber, triompher, recréer la vie à partir de la vie !\"
\n
"
      },
      1: {
        startingText:
          "\n>Il était une fois et une très bonne époque, il y avait une vache maussade qui descendait le long de la route et cette vache maussade qui descendait le long de la route a rencontré un petit garçon de nicens nommé baby tuckoo....\n\nSon père lui a raconté cette histoire : son père l'a regardé à travers un verre : il avait un visage poilu.\n\nIl était bébé Tuckoo. La vache maquée descendit la route où vivait Betty Byrne : elle vendait du citron plat.",
        renderedAnswer:
          "
\n
Il était une fois et une très bonne époque, il y avait une vache maussade qui descendait le long de la route et cette vache maussade qui descendait le long de la route a rencontré un petit garçon de nicens nommé baby tuckoo....
\n
Son père lui a raconté cette histoire : son père l'a regardé à travers un verre : il avait un visage poilu.
\n
Il était bébé Tuckoo. La vache maquée descendit la route où vivait Betty Byrne : elle vendait du citron plat.
\n
"
      },
      2: {
        startingText:
          "Il la quitta rapidement, craignant que son intimité ne se transforme en jibing et souhaitant se mettre à l'écart avant qu'elle n'offre sa vaisselle à un autre touriste d'Angleterre ou à un étudiant de Trinity. La rue Grafton, le long de laquelle il marchait, prolongea ce moment de découragement et de pauvreté. Sur la chaussée en tête de la rue, une dalle a été placée à la mémoire de Wolfe Tone et il se souvient d'avoir été présent avec son père à sa pose. Il se souvient avec amertume de cette scène d'hommage sordide. Il y avait quatre délégués français dans un frein et un, un jeune homme dodu et souriant, tenu, coincé sur un bâton, une carte sur laquelle étaient imprimés les mots : VIVE L'IRLANDE !",
        renderedAnswer:
          "
\n
Il la quitta rapidement, craignant que son intimité ne se transforme en jibing et souhaitant se mettre à l'écart avant qu'elle n'offre sa vaisselle à un autre touriste d'Angleterre ou à un étudiant de Trinity. La rue Grafton, le long de laquelle il marchait, prolongea ce moment de découragement et de pauvreté. Sur la chaussée en tête de la rue, une dalle a été placée à la mémoire de Wolfe Tone et il se souvient d'avoir été présent avec son père à sa pose. Il se souvient avec amertume de cette scène d'hommage sordide. Il y avait quatre délégués français dans un frein et un, un jeune homme dodu et souriant, tenu, coincé sur un bâton, une carte sur laquelle étaient imprimés les mots : VIVE L'IRLANDE!
\n
"
      }
    },
    6: {
      0: {
        startingText: "Farine, fromage, tomates",
        renderedAnswer:
          "
\n
Farine
\n
Fromage
\n
Tomates
\n
"
      },
      1: {
        startingText:
          "Couper le fromage, Trancher les tomates, Frotter les tomates dans la farine",
        renderedAnswer:
          "
\n
Couper le fromage
\n
Trancher les tomates
\n
Frotter les tomates dans la farine
\n
"
      },
      2: {
        startingText:
          "* Azalée (Ericaceae Rhododendron)\n* Chrysanthème (Anthemideae Chrysanthemum)\n* Dalia (Coreopsideae Dahlia)",
        renderedAnswer:
          "
\n
Azalée (Ericaceae Rhododendron)
\n
Chrysanthème (Anthemideae Chrysanthemum)
\n
Dalia (Coreopsideae Dahlia)
\n
"
      },
      3: {
        startingText:
          "* Professeur Tournesol, Professeur, N'a pas de cheveux, Porte souvent du vert\n* Castafiore, Chanteuse d'opéra, A les cheveux blancs, Est peut-être mentalement malade",
        renderedAnswer:
          "
\n
Professeur Tournesol
\n
Professeur
\n
N'a pas de cheveux
\n
Porte souvent du vert
\n
\n
\n
Castafiore
\n
chanteuse d'opéra
\n
A les cheveux blancs
\n
Est peut-être mentalement malade
\n
\n
\n
"
      },
      4: {
        startingText:
          "1. Couper le fromage\n  * Assurez-vous que le fromage est coupé en petits triangles.\n\n2. Couper les tomates en tranches\n * Soyez prudent lorsque vous tenez le couteau.\n  * Pour plus d'aide sur le tranchage des tomates, voir l'essai fondateur de Thomas Jefferson _Tom Ate Those_.",
        renderedAnswer:
          "
\n

Couper le fromage
\n
Assurez-vous que le fromage est coupé en petits triangles.
\n
\n

Couper les tomates en tranches
\n
Soyez prudent lorsque vous tenez le couteau.
\n
Pour plus d'aide sur le tranchage des tomates, voir l'essai fondateur de Thomas Jefferson Tom Ate Those.
\n
\n
"
      }
    },
    7: {
      0: {
        startingText:
          "Nous imaginions les créatures douces et douces où\nIls habitaient dans leur stylo de paille,\nL'un d'entre nous ne s'est pas non plus rendu compte qu'un seul d'entre nous là-bas\nDe douter qu'ils étaient à genoux à l'époque.",
        renderedAnswer:
          "
Nous imaginions les créatures douces et douces où
Ils habitaient dans leur stylo de paille,
L'un d'entre nous ne s'est pas non plus rendu compte qu'un seul d'entre nous là-bas
De douter qu'ils étaient à genoux à l'époque.
"
      },
      1: {
        startingText:
          "1. Casser trois œufs au dessus d'un bol.\n\n Maintenant, vous allez devoir casser les oeufs de telle sorte que vous ne fassiez pas de désastre.\n\n Si vous _faites_ du désordre, utilisez une serviette pour le nettoyer !\n\n2. Verser un litre de lait dans le bol.\n\n En gros, suivez les mêmes conseils que ci-dessus : ne soyez pas désordonné, mais si vous l'êtes, nettoyez-le !",

        renderedAnswer:
          "
\n

Casser trois œufs au dessus d'un bol.
 Maintenant, vous allez devoir casser les oeufs de telle sorte que vous ne fassiez pas de désastre.
 Si vous faites du désordre, utilisez une serviette pour le nettoyer !
\n
\n

Verser un litre de lait dans le bol.
 En gros, suivez les mêmes conseils que ci-dessus : ne soyez pas désordonné, mais si vous l'êtes, nettoyez-le !
\n
\n
"
      }
    }
  };
}.call(this));
<commit_msg>Fix a couple ofissues in french lesson 7<commit_after>(function() {
  var root;

  root = typeof exports !== "undefined" && exports !== null ? exports : this;

  root.lessons = {
    1: {
      0: {
        startingText: "Ecrire en Markdon n'est pas difficile !",
        renderedAnswer:
          "
Ecrire en Markdon n'est pas difficile !
"
      },
      1: {
        startingText: "Je veux compléter cette leçon !",
        renderedAnswer:
          "
Je veux compléter cette leçon !
"
      },
      2: {
        startingText:
          '"Bien sûr," chuchota-t-elle. Puis, elle cria : "Tout ce dont j\'ai besoin, c\'est d\'un peu de moxie !"',
        renderedAnswer:
          '
"Bien sûr", chuchota-t-elle. Puis, elle cria : "Tout ce dont j\'ai besoin, c\'est d\'un peu de moxie !"
'
      },
      3: {
        startingText:
          "Si tu penses à toi-même, c'est incroyable, tu auras probablement raison.",
        renderedAnswer:
          "
Si tu penses à toi-même, c'est incroyable, tu auras probablement raison.
"
      }
    },
    2: {
      0: {
        startingText:
          "Titre de niveau 1\nTitre de niveau 2\nTitre de niveau 3\nTitre de niveau 4\nTitre de niveau 5\nTitre de niveau 6",
        renderedAnswer:
          "
Titre de niveau 1
\n
Titre de niveau 2
\n
Titre de niveau 3
\n
Titre de niveau 4
\n
Titre de niveau 5
\n
Titre de niveau 6
"
      },
      1: {
        startingText:
          "Le symbolisme colombien One Hundred Years of Solitude\n\nVoici quelques mots sur le livre _One Hundred Years of Solitude..._.",
        renderedAnswer:
          "
Le symbolisme colombien One Hundred Years of Solitude
\n
Voici quelques mots sur le livre One Hundred Years of Solitude....
"
      }
    },
    3: {
      0: {
        startingText: "Cherchez-le()",
        renderedAnswer: '
Cherchez-le
'
      },
      1: {
        startingText: "Tu vas vraiment, vraiment vouloir voir ça.",
        renderedAnswer:
          '
Tu vas vraiment, vraiment vouloir voir ça.
'
      },
      2: {
        startingText: "Les dernières nouvelles de la BBC",
        renderedAnswer:
          '
Les dernières nouvelles de la BBC
'
      },
      3: {
        startingText:
          "Voulez-vous [voir quelque chose d'amusant][] ?\n\nEh bien, est-ce que j'ai [le site Web pour vous][un autre endroit amusant] !",
        renderedAnswer:
          '
Voulez-vous voir quelque chose d\'amusant ?
\n
Eh bien, est-ce que j\'ai le site Web pour vous !
'
      }
    },
    4: {
      0: {
        startingText:
          "[](https://upload.wikimedia.org/wikipedia/commons/5/56/Tiger.50.jpg)",
        renderedAnswer:
          '

'
      },
      1: {
        startingText:
          "[Chat noir][]\n\n[Chat Orange][Orange]\n\n[Noir]: https://upload.wikimedia.org/wikipedia/commons/a/a3/81_INF_DIV_SSI.jpg",
        renderedAnswer:
          '

\n

'
      }
    },
    5: {
      0: {
        startingText:
          "J'ai lu cette citation intéressante l'autre jour :\n\n\"Ses yeux l'avaient appelé et son âme avait sauté à l'appel. Vivre, errer, tomber, triompher, recréer la vie à partir de la vie !\"",
        renderedAnswer:
          "
J'ai lu cette citation intéressante l'autre jour :
\n
\n
\"Ses yeux l'avaient appelé et son âme avait sauté à l'appel. Vivre, errer, tomber, triompher, recréer la vie à partir de la vie !\"
\n
"
      },
      1: {
        startingText:
          "\n>Il était une fois et une très bonne époque, il y avait une vache maussade qui descendait le long de la route et cette vache maussade qui descendait le long de la route a rencontré un petit garçon de nicens nommé baby tuckoo....\n\nSon père lui a raconté cette histoire : son père l'a regardé à travers un verre : il avait un visage poilu.\n\nIl était bébé Tuckoo. La vache maquée descendit la route où vivait Betty Byrne : elle vendait du citron plat.",
        renderedAnswer:
          "
\n
Il était une fois et une très bonne époque, il y avait une vache maussade qui descendait le long de la route et cette vache maussade qui descendait le long de la route a rencontré un petit garçon de nicens nommé baby tuckoo....
\n
Son père lui a raconté cette histoire : son père l'a regardé à travers un verre : il avait un visage poilu.
\n
Il était bébé Tuckoo. La vache maquée descendit la route où vivait Betty Byrne : elle vendait du citron plat.
\n
"
      },
      2: {
        startingText:
          "Il la quitta rapidement, craignant que son intimité ne se transforme en jibing et souhaitant se mettre à l'écart avant qu'elle n'offre sa vaisselle à un autre touriste d'Angleterre ou à un étudiant de Trinity. La rue Grafton, le long de laquelle il marchait, prolongea ce moment de découragement et de pauvreté. Sur la chaussée en tête de la rue, une dalle a été placée à la mémoire de Wolfe Tone et il se souvient d'avoir été présent avec son père à sa pose. Il se souvient avec amertume de cette scène d'hommage sordide. Il y avait quatre délégués français dans un frein et un, un jeune homme dodu et souriant, tenu, coincé sur un bâton, une carte sur laquelle étaient imprimés les mots : VIVE L'IRLANDE !",
        renderedAnswer:
          "
\n
Il la quitta rapidement, craignant que son intimité ne se transforme en jibing et souhaitant se mettre à l'écart avant qu'elle n'offre sa vaisselle à un autre touriste d'Angleterre ou à un étudiant de Trinity. La rue Grafton, le long de laquelle il marchait, prolongea ce moment de découragement et de pauvreté. Sur la chaussée en tête de la rue, une dalle a été placée à la mémoire de Wolfe Tone et il se souvient d'avoir été présent avec son père à sa pose. Il se souvient avec amertume de cette scène d'hommage sordide. Il y avait quatre délégués français dans un frein et un, un jeune homme dodu et souriant, tenu, coincé sur un bâton, une carte sur laquelle étaient imprimés les mots : VIVE L'IRLANDE!
\n
"
      }
    },
    6: {
      0: {
        startingText: "Farine, Fromage, Tomates",
        renderedAnswer:
          "
\n
Farine
\n
Fromage
\n
Tomates
\n
"
      },
      1: {
        startingText:
          "Couper le fromage, Trancher les tomates, Frotter les tomates dans la farine",
        renderedAnswer:
          "
\n
Couper le fromage
\n
Trancher les tomates
\n
Frotter les tomates dans la farine
\n
"
      },
      2: {
        startingText:
          "* Azalée (Ericaceae Rhododendron)\n* Chrysanthème (Anthemideae Chrysanthemum)\n* Dalia (Coreopsideae Dahlia)",
        renderedAnswer:
          "
\n
Azalée (Ericaceae Rhododendron)
\n
Chrysanthème (Anthemideae Chrysanthemum)
\n
Dalia (Coreopsideae Dahlia)
\n
"
      },
      3: {
        startingText:
          "* Professeur Tournesol, Professeur, N'a pas de cheveux, Porte souvent du vert\n* Castafiore, Chanteuse d'opéra, A les cheveux blancs, Est peut-être mentalement malade",
        renderedAnswer:
          "
\n
Professeur Tournesol
\n
Professeur
\n
N'a pas de cheveux
\n
Porte souvent du vert
\n
\n
\n
Castafiore
\n
Chanteuse d'opéra
\n
A les cheveux blancs
\n
Est peut-être mentalement malade
\n
\n
\n
"
      },
      4: {
        startingText:
          "1. Couper le fromage\n  * Assurez-vous que le fromage est coupé en petits triangles.\n\n2. Couper les tomates en tranches\n * Soyez prudent lorsque vous tenez le couteau.\n  * Pour plus d'aide sur le tranchage des tomates, voir l'essai fondateur de Thomas Jefferson _Tom Ate Those_.",
        renderedAnswer:
          "
\n

Couper le fromage
\n
Assurez-vous que le fromage est coupé en petits triangles.
\n
\n

Couper les tomates en tranches
\n
Soyez prudent lorsque vous tenez le couteau.
\n
Pour plus d'aide sur le tranchage des tomates, voir l'essai fondateur de Thomas Jefferson Tom Ate Those.
\n
\n
"
      }
    },
    7: {
      0: {
        startingText:
          "Nous imaginions les créatures douces et douces où\nIls habitaient dans leur stylo de paille,\nL'un d'entre nous ne s'est pas non plus rendu compte qu'un seul d'entre nous là-bas\nDe douter qu'ils étaient à genoux à l'époque.",
        renderedAnswer:
          "
Nous imaginions les créatures douces et douces où
Ils habitaient dans leur stylo de paille,
L'un d'entre nous ne s'est pas non plus rendu compte qu'un seul d'entre nous là-bas
De douter qu'ils étaient à genoux à l'époque.
"
      },
      1: {
        startingText:
          "1. Casser trois œufs au dessus d'un bol.\n\n Maintenant, vous allez devoir casser les oeufs de telle sorte que vous ne fassiez pas de désastre.\n\n Si vous _faites_ du désordre, utilisez une serviette pour le nettoyer !\n\n2. Verser un litre de lait dans le bol.\n\n En gros, suivez les mêmes conseils que ci-dessus : ne soyez pas désordonné, mais si vous l'êtes, nettoyez-le !",

        renderedAnswer:
          "
\n

Casser trois œufs au dessus d'un bol.
 Maintenant, vous allez devoir casser les oeufs de telle sorte que vous ne fassiez pas de désastre.
 Si vous faites du désordre, utilisez une serviette pour le nettoyer !
\n
\n

Verser un litre de lait dans le bol.
 En gros, suivez les mêmes conseils que ci-dessus : ne soyez pas désordonné, mais si vous l'êtes, nettoyez-le !
\n
\n
"
      }
    }
  };
}.call(this));
<|endoftext|>


<commit_before>/*
 * grunt-jsinspect
 *
 * Copyright (c) Stefan Judis and Juga Paazmaya
 * Licensed under the MIT license.
 */

'use strict';

var fs = require('fs');
var strip = require('strip-json-comments');
var Inspector = require('jsinspect/lib/inspector');
var Reporter = require('jsinspect/lib/reporters');

module.exports = function(grunt) {

  grunt.registerMultiTask('jsinspect', 'Grunt task for jsinspect', function() {
    var done = this.async();
    var taskSucceeded = true;
    var nbOfMatches = 0;

    var options = this.options({
      threshold:   30,
      diff:        true,
      identifiers: false,
      failOnMatch: true,
      suppress:    100,
      reporter:    'default',
      configFile:  '.jsinspectrc'
    });

    if (fs.existsSync(options.configFile) && fs.lstatSync(options.configFile).isFile()) {
      var contents = strip(fs.readFileSync(options.configFile, 'utf8'));
      var rc;
      try {
        rc = JSON.parse(contents);
      }
      catch (error) {
        throw new Error('The JSON configuration file (' + options.configFile + ') is not valid.');
      }

      Object.keys(options).forEach(function (key) {
        if (rc.hasOwnProperty(key)) {
          options[key] = rc[key];
        }
      });
    }

    var inspector = new Inspector(this.filesSrc, {
      threshold:   options.threshold,
      diff:        options.diff,
      identifiers: options.identifiers
    });

    if (!Reporter.hasOwnProperty(options.reporter) ||
        typeof Reporter[options.reporter] !== 'function') {
      grunt.log.error('Sorry but the configured reporter "' + options.reporter +
        '" does not exist, thus exiting');
      done(false);

      return;
    }

    var writableStream;
    if (typeof options.outputPath === 'string') {
      // The user wants the output to be written to a file, so pass a writable stream as an option to the reporter.
      writableStream = fs.createWriteStream(options.outputPath, {encoding: 'utf8', flags: 'w'});
    }

    this.reporterType = new Reporter[options.reporter](inspector, {
      writableStream: writableStream,
      diff: options.diff,
      suppress: options.suppress
    });

    if (typeof options.failOnMatch === 'number') {
      // Handle failOnMatch threshold.
      inspector.on('match', function() {
        nbOfMatches++;
        if (nbOfMatches >= options.failOnMatch) {
          taskSucceeded = false;
        }
      });
    } else if (options.failOnMatch === true) {
      // Handle failOnMatch boolean.
      inspector.on('match', function() {
        taskSucceeded = false;
      });
    }

    if (writableStream) {
      writableStream.on('finish', function() {
        done(taskSucceeded);
      });
      writableStream.on('error', function() {
        throw new Error('Can not write to ' + options.outputPath);
      });
      writableStream.on('open', function() {
        inspector.run();
      });
    } else {
      inspector.on('end', function() {
        done(taskSucceeded);
      });
      inspector.run();
    }
  });
};
<commit_msg>Added creation of subdirs for output stream<commit_after>/*
 * grunt-jsinspect
 *
 * Copyright (c) Stefan Judis and Juga Paazmaya
 * Licensed under the MIT license.
 */

'use strict';

var fs = require('fs');
var strip = require('strip-json-comments');
var Inspector = require('jsinspect/lib/inspector');
var Reporter = require('jsinspect/lib/reporters');

module.exports = function(grunt) {

  grunt.registerMultiTask('jsinspect', 'Grunt task for jsinspect', function() {
    var done = this.async();
    var taskSucceeded = true;
    var nbOfMatches = 0;

    var options = this.options({
      threshold:   30,
      diff:        true,
      identifiers: false,
      failOnMatch: true,
      suppress:    100,
      reporter:    'default',
      configFile:  '.jsinspectrc'
    });

    if (fs.existsSync(options.configFile) && fs.lstatSync(options.configFile).isFile()) {
      var contents = strip(fs.readFileSync(options.configFile, 'utf8'));
      var rc;
      try {
        rc = JSON.parse(contents);
      }
      catch (error) {
        throw new Error('The JSON configuration file (' + options.configFile + ') is not valid.');
      }

      Object.keys(options).forEach(function (key) {
        if (rc.hasOwnProperty(key)) {
          options[key] = rc[key];
        }
      });
    }

    var inspector = new Inspector(this.filesSrc, {
      threshold:   options.threshold,
      diff:        options.diff,
      identifiers: options.identifiers
    });

    if (!Reporter.hasOwnProperty(options.reporter) ||
        typeof Reporter[options.reporter] !== 'function') {
      grunt.log.error('Sorry but the configured reporter "' + options.reporter +
        '" does not exist, thus exiting');
      done(false);

      return;
    }

    var writableStream;
    if (typeof options.outputPath === 'string') {
      // The user wants the output to be written to a file, so pass a writable stream as an option to the reporter.
      var path = options.outputPath.split(/[/\\]/g).slice(0, -1).join('/');
      if (path) {
          grunt.file.mkdir(path);
      }
      writableStream = fs.createWriteStream(options.outputPath, {encoding: 'utf8', flags: 'w'});
    }

    this.reporterType = new Reporter[options.reporter](inspector, {
      writableStream: writableStream,
      diff: options.diff,
      suppress: options.suppress
    });

    if (typeof options.failOnMatch === 'number') {
      // Handle failOnMatch threshold.
      inspector.on('match', function() {
        nbOfMatches++;
        if (nbOfMatches >= options.failOnMatch) {
          taskSucceeded = false;
        }
      });
    } else if (options.failOnMatch === true) {
      // Handle failOnMatch boolean.
      inspector.on('match', function() {
        taskSucceeded = false;
      });
    }

    if (writableStream) {
      writableStream.on('finish', function() {
        done(taskSucceeded);
      });
      writableStream.on('error', function() {
        throw new Error('Can not write to ' + options.outputPath);
      });
      writableStream.on('open', function() {
        inspector.run();
      });
    } else {
      inspector.on('end', function() {
        done(taskSucceeded);
      });
      inspector.run();
    }
  });
};
<|endoftext|>


<commit_before>/*
 * grunt-premailer
 * https://github.com/dwightjack/grunt-premailer
 *
 * Copyright (c) 2013 Marco Solazzi
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {

    var dargs = require('dargs'),
        path = require('path'),
        fs = require('fs'),
        async = require('async'),
        isUtf8 = require('is-utf8'),
        _ = require('lodash');

    grunt.registerMultiTask('premailer', 'Grunt wrapper task for premailer', function() {
        // Merge task-specific and/or target-specific options with these defaults.
        var args = {},
            done = this.async(),
            series = [],
            options = this.options({
                baseUrl: null,
                bundleExec: false,
                queryString: '',
                css: [],
                removeClasses: false,
                removeScripts: false,
                removeComments: false,
                preserveStyles: false,
                lineLength: 65,
                ioException: false,
                verbose: false,
                mode: 'html'
            });

        // Remove bundleExec from arguments
        keys.splice(keys.indexOf('bundleExec'), 1);

        //clean-up falsy options and parse template-like values
        Object.keys(options).forEach(function(key) {
            var val = options[key];

            if (typeof val === 'string') {
                val = grunt.template.process(val);
            }
            if ((typeof val === 'object' && !_.isEmpty(val)) || (typeof val !== 'object' && !! val)) {
                args[key] = val;
            }
        });

        //also manage properly the css option
        if (_.has(args, 'css') && Array.isArray(args.css)) {
            args.css = _(args.css)
                        .map(function (csspath) {
                            return grunt.config.process(csspath);
                        })
                        .reduce(function (paths, csspath) {
                            var expanded = grunt.file.expand({
                                filter: function (src) {
                                    return grunt.file.isFile(src) && (path.extname(src) === '.css');
                                }
                            }, csspath);

                            return paths.concat(expanded);
                        }, [])
                        .valueOf()
                        .join(',');
        }

        args = dargs(args);

        args.unshift(path.join(__dirname, '..', 'lib', 'premailer.rb'));

        // Asynchronously iterate over all specified file groups.
        async.each(this.files, function(f, next) {
            // Concat specified files.
            var srcFile,
                batchArgs,
                premailer;

            grunt.file.write(f.dest,''); // Create empty destination file

            srcFile = f.src.filter(function (f) {
                return grunt.file.isFile(f);
            }).shift();

            if(!srcFile) {
                //skip!
                grunt.log.writeln('Input file not found');
                next(null);
            }
            if (!isUtf8(fs.readFileSync(srcFile))) {
                //skip!
                grunt.log.writeln('Input file must have utf8 encoding');
                next(null);
            }

            // Command to run
            var cmd;
            if (options.bundleExec) {
                cmd = 'bundle';

                args.unshift('exec', 'ruby');
            } else {
                cmd = 'ruby' + (process.platform === 'win32' ? '.exe' : '');
            }

            // Premailer expects absolute paths
            batchArgs = args.concat(['--file-in', path.resolve(srcFile.toString()), '--file-out', path.resolve(f.dest.toString())]);

            premailer = grunt.util.spawn({
                cmd: cmd,
                args: batchArgs
            }, function(err, result, code) {
                if (err) {
                    grunt.fail.fatal(err);
                }

                next(err);
            });

            premailer.stdout.pipe( process.stdout );
            premailer.stderr.pipe( process.stderr );
        }, done);
    });

};
<commit_msg>Defined keys variable and use keys when iterating<commit_after>/*
 * grunt-premailer
 * https://github.com/dwightjack/grunt-premailer
 *
 * Copyright (c) 2013 Marco Solazzi
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {

    var dargs = require('dargs'),
        path = require('path'),
        fs = require('fs'),
        async = require('async'),
        isUtf8 = require('is-utf8'),
        _ = require('lodash');

    grunt.registerMultiTask('premailer', 'Grunt wrapper task for premailer', function() {
        // Merge task-specific and/or target-specific options with these defaults.
        var args = {},
            done = this.async(),
            series = [],
            options = this.options({
                baseUrl: null,
                bundleExec: false,
                queryString: '',
                css: [],
                removeClasses: false,
                removeScripts: false,
                removeComments: false,
                preserveStyles: false,
                lineLength: 65,
                ioException: false,
                verbose: false,
                mode: 'html'
            });

        var keys = Object.keys(options);

        // Remove bundleExec from arguments
        keys.splice(keys.indexOf('bundleExec'), 1);

        //clean-up falsy options and parse template-like values
        keys.forEach(function(key) {
            var val = options[key];

            if (typeof val === 'string') {
                val = grunt.template.process(val);
            }
            if ((typeof val === 'object' && !_.isEmpty(val)) || (typeof val !== 'object' && !! val)) {
                args[key] = val;
            }
        });

        //also manage properly the css option
        if (_.has(args, 'css') && Array.isArray(args.css)) {
            args.css = _(args.css)
                        .map(function (csspath) {
                            return grunt.config.process(csspath);
                        })
                        .reduce(function (paths, csspath) {
                            var expanded = grunt.file.expand({
                                filter: function (src) {
                                    return grunt.file.isFile(src) && (path.extname(src) === '.css');
                                }
                            }, csspath);

                            return paths.concat(expanded);
                        }, [])
                        .valueOf()
                        .join(',');
        }

        args = dargs(args);

        args.unshift(path.join(__dirname, '..', 'lib', 'premailer.rb'));

        // Asynchronously iterate over all specified file groups.
        async.each(this.files, function(f, next) {
            // Concat specified files.
            var srcFile,
                batchArgs,
                premailer;

            grunt.file.write(f.dest,''); // Create empty destination file

            srcFile = f.src.filter(function (f) {
                return grunt.file.isFile(f);
            }).shift();

            if(!srcFile) {
                //skip!
                grunt.log.writeln('Input file not found');
                next(null);
            }
            if (!isUtf8(fs.readFileSync(srcFile))) {
                //skip!
                grunt.log.writeln('Input file must have utf8 encoding');
                next(null);
            }

            // Command to run
            var cmd;
            if (options.bundleExec) {
                cmd = 'bundle';

                args.unshift('exec', 'ruby');
            } else {
                cmd = 'ruby' + (process.platform === 'win32' ? '.exe' : '');
            }

            // Premailer expects absolute paths
            batchArgs = args.concat(['--file-in', path.resolve(srcFile.toString()), '--file-out', path.resolve(f.dest.toString())]);

            premailer = grunt.util.spawn({
                cmd: cmd,
                args: batchArgs
            }, function(err, result, code) {
                if (err) {
                    grunt.fail.fatal(err);
                }

                next(err);
            });

            premailer.stdout.pipe( process.stdout );
            premailer.stderr.pipe( process.stderr );
        }, done);
    });

};
<|endoftext|>


<commit_before>// @flow
/**
 * Part of GDL gdl-frontend.
 * Copyright (C) 2017 GDL
 *
 * See LICENSE
 */

import React, { Fragment } from 'react';
import { Trans } from '@lingui/react';
import NextLink from 'next/link';
import styled from 'react-emotion';
import {
  Menu,
  MenuItem,
  Button,
  Card,
  CardContent,
  Typography,
  Divider,
  Grid
} from '@material-ui/core';
import {
  Edit as EditIcon,
  FileDownload as FileDownloadIcon,
  Translate as TranslateIcon,
  Warning as WarningIcon
} from '@material-ui/icons';

import config from '../../config';
import { fetchBook, fetchSimilarBooks } from '../../fetch';
import type { Book, BookDetails, Context } from '../../types';
import { errorPage } from '../../hocs';
import { Link } from '../../routes';
import BrowseLink from '../../components/BrowseLink';
import Layout from '../../components/Layout';
import Head from '../../components/Head';
import View from '../../elements/View';
import Container from '../../elements/Container';
import BookCover from '../../components/BookCover';
import BookList from '../../components/BookList';
import { hasClaim, claims } from '../../lib/auth/token';
import media from '../../style/media';
import { colors, spacing } from '../../style/theme';
import { BookJsonLd, Metadata } from '../../components/BookDetailsPage';
import ReadingLevelTrans from '../../components/ReadingLevelTrans';

type Props = {
  book: BookDetails,
  similarBooks: Array<Book>,
  userHasEditAccess: boolean,
  userHasEditImageAccess: boolean
};

const CoverWrap = styled('div')`
  ${media.mobile`
    position: absolute;
    top: -120px;
    z-index: 10;
    left: 50%;
    transform: translateX(-50%);
  `} ${media.tablet`
    flex: 0 0 260px;
    margin-right: 20px;
  `};
`;

const EditBookLink = styled('a')`
  color: ${colors.base.white};
  position: absolute;
  top: 0;
  right: 0;
  padding: 5px;
  transition: all 0.3s ease;
  background: rgba(0, 0, 0, 0.5);
  &:hover {
    background: rgba(0, 0, 0, 0.6);
  }
`;

const BORDER_STYLE = `1px solid ${colors.base.grayLight}`;

class BookPage extends React.Component<Props, { anchorEl: ?HTMLElement }> {
  state = {
    anchorEl: null
  };

  static async getInitialProps({ query, req }: Context) {
    const [bookRes, similarRes] = await Promise.all([
      fetchBook(query.id, query.lang),
      fetchSimilarBooks(query.id, query.lang)
    ]);

    if (!bookRes.isOk) {
      return {
        statusCode: bookRes.statusCode
      };
    }

    return {
      book: bookRes.data,
      userHasEditAccess: hasClaim(claims.writeBook, req),
      userHasEditImageAccess: hasClaim(claims.writeImage, req),
      // Don't let similar books crash the page
      similarBooks: similarRes.isOk ? similarRes.data.results : []
    };
  }

  getCrumbs() {
    const { book } = this.props;

    return [
      <BrowseLink
        lang={book.language.code}
        readingLevel={book.readingLevel}
        category={book.category}
      >

          <ReadingLevelTrans readingLevel={book.readingLevel} />

      </BrowseLink>,
      book.title
    ];
  }

  handleDownloadClick = event =>
    this.setState({ anchorEl: event.currentTarget });

  closeDownloadMenu = () => this.setState({ anchorEl: null });

  render() {
    const { similarBooks, book } = this.props;

    return (
      <Fragment>

          <BookJsonLd book={book} />

        <Layout crumbs={this.getCrumbs()} category={book.category}>
          <Container>
            <View flexDirection="row" mt={['135px', spacing.medium]}>
              <CoverWrap>
                <View>
                  <BookCover
                    coverImage={book.coverImage}
                    w={[130, 260]}
                    h={[175, 365]}
                  />
                  {this.props.userHasEditImageAccess && (
                    <NextLink
                      href={{
                        pathname: '/admin/crop',
                        query: {
                          imageUrl: book.coverImage && book.coverImage.url
                        }
                      }}
                      passHref
                    >
                      <EditBookLink title="Edit book image">
                        <EditIcon />
                      </EditBookLink>
                    </NextLink>
                  )}
                </View>
              </CoverWrap>

              {/* All this flexing on => tablet is because we want to push the buttons down in the card*/}
              <Card
                css={[{ width: '100%' }, media.tablet({ display: 'flex' })]}
              >
                <CardContent
                  css={[
                    media.mobile({ paddingTop: '70px' }),
                    media.tablet({
                      display: 'flex',
                      flexDirection: 'column',
                      flex: '1'
                    })
                  ]}
                >
                  <Typography
                    lang={book.language.code}
                    variant="headline"
                    css={media.mobile`text-align: center`}
                  >
                    {book.title}
                  </Typography>

                  <Typography
                    paragraph
                    variant="subheading"
                    css={media.mobile`text-align: center`}
                  >
                    <Trans>from {book.publisher.name}</Trans>
                  </Typography>

                  <Typography
                    lang={book.language.code}
                    paragraph
                    css={[
                      media.mobile`text-align: center`,
                      media.tablet({ flex: 1 })
                    ]}
                  >
                    {book.description}
                  </Typography>

                  <Grid
                    container
                    direction="column"
                    alignItems="center"
                    spacing={16}
                  >
                    {book.bookFormat === 'HTML' && (
                      <Fragment>
                        <Grid item>
                          <Link
                            route="read"
                            passHref
                            params={{ id: book.id, lang: book.language.code }}
                            prefetch
                          >
                            <Button
                              variant="raised"
                              color="primary"
                              size="large"
                            >
                              <Trans>Read book</Trans>
                            </Button>
                          </Link>
                        </Grid>
                        <Grid item>
                          <Button
                            aria-owns={
                              this.state.anchorEl ? 'download-book-menu' : null
                            }
                            aria-haspopup="true"
                            color="primary"
                            onClick={this.handleDownloadClick}
                          >
                            <FileDownloadIcon /> <Trans>Download book</Trans>
                          </Button>
                        </Grid>
                        <Menu
                          id="download-book-menu"
                          onClose={this.closeDownloadMenu}
                          anchorEl={this.state.anchorEl}
                          open={Boolean(this.state.anchorEl)}
                        >
                          {book.downloads.epub && (
                            <MenuItem
                              href={book.downloads.epub}
                              component="a"
                              onClick={this.closeDownloadMenu}
                            >
                              <Trans>E-book (ePUB)</Trans>
                            </MenuItem>
                          )}
                          {book.downloads.pdf && (
                            <MenuItem
                              href={book.downloads.pdf}
                              component="a"
                              onClick={this.closeDownloadMenu}
                            >
                              <Trans>Printable book (PDF)</Trans>
                            </MenuItem>
                          )}
                        </Menu>

                        {this.props.userHasEditAccess && (
                          <NextLink
                            href={{
                              pathname: '/admin/edit',
                              query: { id: book.id, lang: book.language.code }
                            }}
                            passHref
                          >
                            <EditBookLink title="Edit book">
                              <EditIcon />
                            </EditBookLink>
                          </NextLink>
                        )}
                      </Fragment>
                    )}
                    {book.bookFormat === 'PDF' && (
                      <Grid item>
                        <Button
                          href={book.downloads.pdf}
                          color="primary"
                          variant="raised"
                          size="large"
                        >
                          <Trans>Download book</Trans>
                        </Button>
                      </Grid>
                    )}
                  </Grid>
                </CardContent>
              </Card>
            </View>
          </Container>

          <Container mt={spacing.medium}>
            <View ml={[0, 'auto']} w={['auto', 438]}>
              <Metadata book={book} />
              {config.TRANSLATION_PAGES &&
                book.supportsTranslation && (
                  <View borderTop={BORDER_STYLE} mt={spacing.medium}>
                    <Link
                      route="translate"
                      passHref
                      params={{ id: book.id, lang: book.language.code }}
                    >
                      <Button
                        color="primary"
                        css={{ margin: `${spacing.medium} 0` }}
                      >
                        <TranslateIcon /> <Trans>Translate this book</Trans>
                      </Button>
                    </Link>
                  </View>
                )}
              <View
                borderTop={BORDER_STYLE}
                mt={
                  config.TRANSLATION_PAGES && book.supportsTranslation
                    ? 0
                    : spacing.medium
                }
              >
                <Button
                  color="primary"
                  css={{ margin: `${spacing.medium} 0` }}
                  href={config.zendeskUrl}
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  <WarningIcon /> <Trans>Report a problem with this book</Trans>
                </Button>
              </View>
            </View>
          </Container>

          <Container>
            <Divider />
            <View mb={spacing.medium} pt={spacing.medium}>
              {similarBooks.length > 0 && (
                <BookList
                  heading={<Trans>Similar</Trans>}
                  books={similarBooks}
                />
              )}
            </View>
          </Container>
        </Layout>
      </Fragment>
    );
  }
}

export default errorPage(BookPage);
<commit_msg>Change download icon since old one doesn't exist anymore<commit_after>// @flow
/**
 * Part of GDL gdl-frontend.
 * Copyright (C) 2017 GDL
 *
 * See LICENSE
 */

import React, { Fragment } from 'react';
import { Trans } from '@lingui/react';
import NextLink from 'next/link';
import styled from 'react-emotion';
import {
  Menu,
  MenuItem,
  Button,
  Card,
  CardContent,
  Typography,
  Divider,
  Grid
} from '@material-ui/core';
import {
  Edit as EditIcon,
  CloudDownload as CloudDownloadIcon,
  Translate as TranslateIcon,
  Warning as WarningIcon
} from '@material-ui/icons';

import config from '../../config';
import { fetchBook, fetchSimilarBooks } from '../../fetch';
import type { Book, BookDetails, Context } from '../../types';
import { errorPage } from '../../hocs';
import { Link } from '../../routes';
import BrowseLink from '../../components/BrowseLink';
import Layout from '../../components/Layout';
import Head from '../../components/Head';
import View from '../../elements/View';
import Container from '../../elements/Container';
import BookCover from '../../components/BookCover';
import BookList from '../../components/BookList';
import { hasClaim, claims } from '../../lib/auth/token';
import media from '../../style/media';
import { colors, spacing } from '../../style/theme';
import { BookJsonLd, Metadata } from '../../components/BookDetailsPage';
import ReadingLevelTrans from '../../components/ReadingLevelTrans';

type Props = {
  book: BookDetails,
  similarBooks: Array<Book>,
  userHasEditAccess: boolean,
  userHasEditImageAccess: boolean
};

const CoverWrap = styled('div')`
  ${media.mobile`
    position: absolute;
    top: -120px;
    z-index: 10;
    left: 50%;
    transform: translateX(-50%);
  `} ${media.tablet`
    flex: 0 0 260px;
    margin-right: 20px;
  `};
`;

const EditBookLink = styled('a')`
  color: ${colors.base.white};
  position: absolute;
  top: 0;
  right: 0;
  padding: 5px;
  transition: all 0.3s ease;
  background: rgba(0, 0, 0, 0.5);
  &:hover {
    background: rgba(0, 0, 0, 0.6);
  }
`;

const BORDER_STYLE = `1px solid ${colors.base.grayLight}`;

class BookPage extends React.Component<Props, { anchorEl: ?HTMLElement }> {
  state = {
    anchorEl: null
  };

  static async getInitialProps({ query, req }: Context) {
    const [bookRes, similarRes] = await Promise.all([
      fetchBook(query.id, query.lang),
      fetchSimilarBooks(query.id, query.lang)
    ]);

    if (!bookRes.isOk) {
      return {
        statusCode: bookRes.statusCode
      };
    }

    return {
      book: bookRes.data,
      userHasEditAccess: hasClaim(claims.writeBook, req),
      userHasEditImageAccess: hasClaim(claims.writeImage, req),
      // Don't let similar books crash the page
      similarBooks: similarRes.isOk ? similarRes.data.results : []
    };
  }

  getCrumbs() {
    const { book } = this.props;

    return [
      <BrowseLink
        lang={book.language.code}
        readingLevel={book.readingLevel}
        category={book.category}
      >

          <ReadingLevelTrans readingLevel={book.readingLevel} />

      </BrowseLink>,
      book.title
    ];
  }

  handleDownloadClick = event =>
    this.setState({ anchorEl: event.currentTarget });

  closeDownloadMenu = () => this.setState({ anchorEl: null });

  render() {
    const { similarBooks, book } = this.props;

    return (
      <Fragment>

          <BookJsonLd book={book} />

        <Layout crumbs={this.getCrumbs()} category={book.category}>
          <Container>
            <View flexDirection="row" mt={['135px', spacing.medium]}>
              <CoverWrap>
                <View>
                  <BookCover
                    coverImage={book.coverImage}
                    w={[130, 260]}
                    h={[175, 365]}
                  />
                  {this.props.userHasEditImageAccess && (
                    <NextLink
                      href={{
                        pathname: '/admin/crop',
                        query: {
                          imageUrl: book.coverImage && book.coverImage.url
                        }
                      }}
                      passHref
                    >
                      <EditBookLink title="Edit book image">
                        <EditIcon />
                      </EditBookLink>
                    </NextLink>
                  )}
                </View>
              </CoverWrap>

              {/* All this flexing on => tablet is because we want to push the buttons down in the card*/}
              <Card
                css={[{ width: '100%' }, media.tablet({ display: 'flex' })]}
              >
                <CardContent
                  css={[
                    media.mobile({ paddingTop: '70px' }),
                    media.tablet({
                      display: 'flex',
                      flexDirection: 'column',
                      flex: '1'
                    })
                  ]}
                >
                  <Typography
                    lang={book.language.code}
                    variant="headline"
                    css={media.mobile`text-align: center`}
                  >
                    {book.title}
                  </Typography>

                  <Typography
                    paragraph
                    variant="subheading"
                    css={media.mobile`text-align: center`}
                  >
                    <Trans>from {book.publisher.name}</Trans>
                  </Typography>

                  <Typography
                    lang={book.language.code}
                    paragraph
                    css={[
                      media.mobile`text-align: center`,
                      media.tablet({ flex: 1 })
                    ]}
                  >
                    {book.description}
                  </Typography>

                  <Grid
                    container
                    direction="column"
                    alignItems="center"
                    spacing={16}
                  >
                    {book.bookFormat === 'HTML' && (
                      <Fragment>
                        <Grid item>
                          <Link
                            route="read"
                            passHref
                            params={{ id: book.id, lang: book.language.code }}
                            prefetch
                          >
                            <Button
                              variant="raised"
                              color="primary"
                              size="large"
                            >
                              <Trans>Read book</Trans>
                            </Button>
                          </Link>
                        </Grid>
                        <Grid item>
                          <Button
                            aria-owns={
                              this.state.anchorEl ? 'download-book-menu' : null
                            }
                            aria-haspopup="true"
                            color="primary"
                            onClick={this.handleDownloadClick}
                          >
                            <CloudDownloadIcon css={{ marginRight: '10px' }} />
                            <Trans>Download book</Trans>
                          </Button>
                        </Grid>
                        <Menu
                          id="download-book-menu"
                          onClose={this.closeDownloadMenu}
                          anchorEl={this.state.anchorEl}
                          open={Boolean(this.state.anchorEl)}
                        >
                          {book.downloads.epub && (
                            <MenuItem
                              href={book.downloads.epub}
                              component="a"
                              onClick={this.closeDownloadMenu}
                            >
                              <Trans>E-book (ePUB)</Trans>
                            </MenuItem>
                          )}
                          {book.downloads.pdf && (
                            <MenuItem
                              href={book.downloads.pdf}
                              component="a"
                              onClick={this.closeDownloadMenu}
                            >
                              <Trans>Printable book (PDF)</Trans>
                            </MenuItem>
                          )}
                        </Menu>

                        {this.props.userHasEditAccess && (
                          <NextLink
                            href={{
                              pathname: '/admin/edit',
                              query: { id: book.id, lang: book.language.code }
                            }}
                            passHref
                          >
                            <EditBookLink title="Edit book">
                              <EditIcon />
                            </EditBookLink>
                          </NextLink>
                        )}
                      </Fragment>
                    )}
                    {book.bookFormat === 'PDF' && (
                      <Grid item>
                        <Button
                          href={book.downloads.pdf}
                          color="primary"
                          variant="raised"
                          size="large"
                        >
                          <Trans>Download book</Trans>
                        </Button>
                      </Grid>
                    )}
                  </Grid>
                </CardContent>
              </Card>
            </View>
          </Container>

          <Container mt={spacing.medium}>
            <View ml={[0, 'auto']} w={['auto', 438]}>
              <Metadata book={book} />
              {config.TRANSLATION_PAGES &&
                book.supportsTranslation && (
                  <View borderTop={BORDER_STYLE} mt={spacing.medium}>
                    <Link
                      route="translate"
                      passHref
                      params={{ id: book.id, lang: book.language.code }}
                    >
                      <Button
                        color="primary"
                        css={{ margin: `${spacing.medium} 0` }}
                      >
                        <TranslateIcon /> <Trans>Translate this book</Trans>
                      </Button>
                    </Link>
                  </View>
                )}
              <View
                borderTop={BORDER_STYLE}
                mt={
                  config.TRANSLATION_PAGES && book.supportsTranslation
                    ? 0
                    : spacing.medium
                }
              >
                <Button
                  color="primary"
                  css={{ margin: `${spacing.medium} 0` }}
                  href={config.zendeskUrl}
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  <WarningIcon /> <Trans>Report a problem with this book</Trans>
                </Button>
              </View>
            </View>
          </Container>

          <Container>
            <Divider />
            <View mb={spacing.medium} pt={spacing.medium}>
              {similarBooks.length > 0 && (
                <BookList
                  heading={<Trans>Similar</Trans>}
                  books={similarBooks}
                />
              )}
            </View>
          </Container>
        </Layout>
      </Fragment>
    );
  }
}

export default errorPage(BookPage);
<|endoftext|>