Skip to content

Commit

Permalink
rev0 - calendars, localizations, plus module, and added basic tests
Browse files Browse the repository at this point in the history
  • Loading branch information
alexcjohnson committed Oct 31, 2016
1 parent 405f673 commit 578aa4e
Show file tree
Hide file tree
Showing 350 changed files with 27,109 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
language: node_js
node_js:
- "4"
before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,19 @@
# world-calendars
Node module for converting between various calendars

This project takes [kbwood/calendars](https://github.com/kbwood/calendars) and transforms them from a jQuery plugin to a node module. Many thanks to Keith Wood and all of the contributors to the original project!

kbwood/calendars was originally pulled at version 2.0.2 in October 2016.

The initial implementation converts all built-in calendars and localizations, including the `plus` module, but not the `validation` or `picker` functionality. Also includes basic test functionality.

Typical usage for converting from one date system to another goes through Julian days:
```
var calendars = require('world-calendars');
var gregorian = calendars.instance();
var nepali = calendars.instance('nepali');
// gives nepali date 2073-07-15
var nepaliHalloween = nepali.fromJD(gregorian.newDate(2016, 10, 31).toJD());
```
242 changes: 242 additions & 0 deletions bin/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
var fs = require('fs');

function toRegExp(s) { return new RegExp(s); }

/*
* Config options
*/
var config = JSON.parse(fs.readFileSync('./config.json', 'utf8')),

// string: dir of source files, relative to 'bin'
srcDir = config.srcDir,

// string: dir to put built files into, relative to 'bin'
distDir = config.distDir,

// array[string]: subdirectories to create in dist
subdirectories = config.subdirectories,

// newline character to use in build
newLine = config.newLine,

/*
* array[{
* from: string in source file
* to: string in built file
* setup: array[string]: setup lines in built file, if `from` is found
* include: string(regexp) files to include in this translation item
* exclude: string(regexp) files to exclude from this translation item
* }]
*/
translate = config.translate,

// name of index file, within dist directory
indexFile = config.indexFile,

// array[string(regexp)]: files in source directory to include
include = config.include.map(toRegExp),

// array[string(regexp)]: files in source directory to exclude
exclude = config.exclude.map(toRegExp),

/*
* special translation to remove jQuery IIFE: {
* start: string(regexp): beginning of IIFE
* end: string(regexp): end of IIFE
* }
* will de-indent all text inside the IIFE, and error if any code is found outside the IIFE
* but comments outside the IIFE will be included in the built files.
*/
unwrap = {
start: toRegExp(config.unwrap.start),
end: toRegExp(config.unwrap.end)
},

/*
* array giving the order to process files and how to translate their names
* [{
* match: string(regexp): pattern for this group of files. Within one group
* we order lexicographically after removing the .js extension
* out: regexp substitution expression to give the output file name
* export: optional bool: whether this is the module.exports.
* }]
*/
outputOrder = config.outputOrder;


var indexOrder = [];

translate.forEach(function(ti) {
if(ti.include) ti.include = toRegExp(ti.include);
if(ti.exclude) ti.exclude = toRegExp(ti.exclude);
});

outputOrder.forEach(function(v) {
v.match = toRegExp(v.match);
indexOrder.push(v.match);
});



function unwrapped(lines, fn) {
// take off the IIFE wrap around everything
var unwrapStart,
unwrapEnd;

for(i = 0; i < lines.length; i++) {
if(lines[i].match(unwrap.start)) {
if(unwrapStart !== undefined) {
throw new Error('multiple unwrap starts found in ' + fn);
}
unwrapStart = i;
}
if(lines[i].match(unwrap.end)) {
if(unwrapEnd !== undefined) {
throw new Error('multiple unwrap ends found in ' + fn);
}
unwrapEnd = i;
}
}
if(unwrapEnd <= unwrapStart) {
throw new Error('unwrap end found before start in ' + fn);
}
if(unwrapStart === undefined || unwrapEnd === undefined) {
throw new Error('wrapping not found in ' + fn);
}

var header = lines.slice(0, unwrapStart),
body = lines.slice(unwrapStart + 1, unwrapEnd).map(function(v, i) {
if(v && v.substr(0, 4) !== ' ') {
throw new Error('IIFE body is not all indented in ' + fn + ' body line ' + String(i));
}
return v.substr(4);
}),
footer = lines.slice(unwrapEnd + 1);
if(notJustComments(header) || notJustComments(footer)) {
throw new Error('code found in header or footer in ' + fn);
}

return {header: header, body: body, footer: footer};
}

function notJustComments(lines) {
var linestr = lines
// take out single-line comments
.filter(function(line) { return !line.match(/^\s\/\//); })
.join('');

// take out /* ... */ comments
var nonComment = linestr.replace(/\/\*([^\*]|[\*]+[^\/])+\*\//g, '');

return (nonComment.trim() !== '');
}

function translateBody(body, fnOut) {
var setup = [];

for(var i = 0; i < translate.length; i++) {
var ti = translate[i];

if(ti.include && !fnOut.match(ti.include)) continue;
if(ti.exclude && fnOut.match(ti.exclude)) continue;

if(body.indexOf(ti.to) !== -1) {
throw new Error('to text "' + ti.to + '" found in file ' + fnOut);
}

var bodySplit = body.split(ti.from),
found = bodySplit.length > 1;

if(found) {
body = bodySplit.join(ti.to);
for(var j = 0; j < ti.setup.length; j++) {
if(setup.indexOf(ti.setup[j]) === -1) {
setup.push(ti.setup[j]);
}
}
}
}

if(body.indexOf('$') !== -1) {
console.log('"$" is still present in ' + fnOut);
}

return setup.join(newLine) + newLine + newLine + body;
}

function srcSort(fn1, fn2) {
var o1 = srcOrder(fn1),
o2 = srcOrder(fn2),
n1 = fn1.replace(/[\.]js$/, ''),
n2 = fn2.replace(/[\.]js$/, '');

if(o1 < o2) return -1;
else if(o1 > o2) return 1;
// within a category: order lexicographically after stripping extension
else return (n1 < n2) ? -1 : 1;
}

function srcOrder(fn) {
for(var i = 0; i < indexOrder.length; i++) {
if(fn.match(indexOrder[i])) return i;
}
throw new Error('no sort order found for file ' + fn);
}

function srcInclude(fn) {
var i,
match = false;
for(i = 0; i < include.length; i++) {
if(fn.match(include[i])) {
match = true;
break;
}
}
if(!match) return false;
for(i = 0; i < exclude.length; i++) {
if(fn.match(exclude[i])) {
return false;
}
}
return true;
}

subdirectories.forEach(function(subdir) {
fs.mkdirSync(distDir + subdir);
});

var srcFiles = fs.readdirSync(srcDir).filter(srcInclude).sort(srcSort);

var headComment = fs.readFileSync('./head-comment.js', 'utf8');

var indexLines = [];

srcFiles.forEach(function(fn) {
var fstr = fs.readFileSync(srcDir + fn, 'utf8');

var fnMap = outputOrder[srcOrder(fn)],
fnOut = fn.replace(fnMap.match, fnMap.out);

// tabs to spaces
fstr = fstr.replace(/\t/g, ' ');
var lines = fstr.split(/\r?\n/);

// generate the built source file
var parts = unwrapped(lines, fn),
header = parts.header.join(newLine),
body = translateBody(parts.body.join(newLine), fnOut),
footer = parts.footer.join(newLine),
outStr = [headComment, header, body, footer].join(newLine);

// write this file and include it in the index in the right place
fs.writeFileSync(distDir + fnOut + '.js', outStr);

if(fnMap.export) {
indexLines.push("module.exports = require('./" + fnOut + "');");
}
else {
indexLines.push("require('./" + fnOut + "');")
}
});

fs.writeFileSync(distDir + indexFile, headComment + newLine + indexLines.join(newLine));
88 changes: 88 additions & 0 deletions bin/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"srcDir": "../jquery-src/",
"distDir": "../dist/",
"indexFile": "index.js",
"include": ["^jquery[\\.]calendars.*[\\.]js$"],
"exclude": [
"[\\.]min[\\.]js$",
"picker",
"validation",
"plugin",
"[\\.]all[\\.]js$",
"[\\.]lang[\\.]js$"
],
"subdirectories": ["calendars", "regional"],
"outputOrder": [
{
"match": "^(jquery[\\.]calendars[\\.]js)$",
"out": "main",
"export": true
},
{
"match": "^(jquery[\\.]calendars[\\.]plus[\\.]js)$",
"out": "plus"
},
{
"match": "^(jquery[\\.]calendars[\\.]([^\\.]+)[\\.]js)$",
"out": "calendars/$2"
},
{
"match": "^(jquery[\\.]calendars-([^\\.]+)[\\.]js)$",
"out": "regional/$2"
}
],
"unwrap": {
"start": "^\\s*\\(\\s*function\\s*\\(\\s*\\$\\s*\\)\\s*\\{\\s*(\\/\\/.*)?$",
"end": "^\\s*}\\s*\\)\\s*\\(\\s*jQuery\\s*\\);\\s*(\\/\\/.*)?$"
},
"newLine": "\n",
"translate": [
{
"from": "$.calendars.calendars.gregorian",
"to": "_gregorian",
"setup": [
"var main = require('../main');",
"var _gregorian = main.calendars.gregorian;"
],
"exclude": "^(main|plus)$"
},
{
"from": "$.calendars.calendars.julian",
"to": "_julian",
"setup": [
"var main = require('../main');",
"var _julian = main.calendars.julian;"
],
"exclude": "^(main|plus|calendars\\/julian)$"
},
{
"from": "$.calendars",
"to": "main",
"setup": ["var main = require('../main');"],
"exclude": "^(main|plus)$"
},
{
"from": "$.calendars",
"to": "_exports",
"setup": [],
"include": "^main$"
},
{
"from": "_exports = ",
"to": "var _exports = module.exports = ",
"setup": [],
"include": "^main$"
},
{
"from": "$.extend",
"to": "assign",
"setup": ["var assign = require('object-assign');"]
},
{
"from": "$.calendars",
"to": "main",
"setup": ["var main = require('./main');"],
"include": "^plus$"
}
]
}
10 changes: 10 additions & 0 deletions bin/head-comment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* World Calendars
* https://github.com/alexcjohnson/world-calendars
*
* Batch-converted from kbwood/calendars
* Many thanks to Keith Wood and all of the contributors to the original project!
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
Loading

0 comments on commit 578aa4e

Please sign in to comment.