merger master

pull/22/head
lexx 2013-11-01 23:24:25 +02:00
commit 2ae3eb9b6c
14 changed files with 326 additions and 46 deletions

View File

@ -2,6 +2,6 @@ lint:
@./node_modules/.bin/jshint .
test:
@./node_modules/.bin/mocha --reporter spec
@./node_modules/.bin/mocha test/*.js
.PHONY: lint test

View File

@ -28,6 +28,7 @@
-p, --port <port> listening port, default 9001
-n, --number <number> starting lines number, default 10
-l, --lines <lines> number on lines stored in browser, default 2000
-t, --theme <theme> name of the theme (default, dark), default "default"
-d, --daemonize run as daemon
-U, --user <username> Basic Authentication username, this option works only along with -P option
-P, --password <password> Basic Authentication password, this option works only along with -U option

View File

@ -1,13 +1,14 @@
var connect = require('connect');
var cookieParser = require('cookie');
var daemon = require('daemon');
var fs = require('fs');
var http = require('http');
var https = require('https');
var program = require('commander');
var sanitizer = require('validator').sanitize;
var socketio = require('socket.io');
var tail = require('./lib/tail');
var connect = require('connect');
var cookieParser = require('cookie');
var daemon = require('daemon');
var fs = require('fs');
var http = require('http');
var https = require('https');
var program = require('commander');
var sanitizer = require('validator').sanitize;
var socketio = require('socket.io');
var tail = require('./lib/tail');
var connectBuilder = require('./lib/connect_builder');
(function () {
'use strict';
@ -56,7 +57,7 @@ var tail = require('./lib/tail');
* Daemonize process
*/
var logFile = fs.openSync(program.logPath, 'a');
var args = ['-p', program.port, '-n', program.number, '-l', program.lines];
var args = ['-p', program.port, '-n', program.number, '-l', program.lines, '-t', program.theme];
if (doAuthorization) {
args = args.concat(['-U', program.user, '-P', program.password]);
@ -77,35 +78,17 @@ var tail = require('./lib/tail');
/**
* HTTP server setup
*/
var app = connect();
var builder = connectBuilder();
if (doAuthorization) {
app
.use(connect.cookieParser())
.use(connect.session({secret: sessionSecret, key: sessionKey}))
.use(connect.basicAuth(function (user, password) {
return program.user === user && program.password === password;
}));
builder.session(sessionSecret, sessionKey);
builder.authorize(program.user, program.password);
}
app
.use(connect.static(__dirname + '/lib/web/assets'))
.use(function (req, res) {
fs.readFile(__dirname + '/lib/web/index.html', function (err, data) {
if (err) {
res.writeHead(500, {'Content-Type': 'text/plain'});
res.end('Internal error');
} else {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(data.toString('utf-8').replace(
/__TITLE__/g, 'tail -F ' + program.args.join(' ')).replace(
/__STYLE__/g, program.theme), 'utf-8'
);
}
});
});
builder.static(__dirname + '/lib/web/assets');
builder.index(__dirname + '/lib/web/index.html', program.args.join(' '), program.theme);
var app = builder.build();
var server;
if (program.key && program.certificate) {

54
lib/connect_builder.js Normal file
View File

@ -0,0 +1,54 @@
var connect = require('connect');
var fs = require('fs');
(function () {
'use strict';
var ConnectBuilder = function () {
this.app = connect();
};
ConnectBuilder.prototype.authorize = function (user, pass) {
this.app.use(connect.basicAuth(function (incomingUser, incomingPass) {
return user === incomingUser && pass === incomingPass;
}));
return this;
};
ConnectBuilder.prototype.build = function () {
return this.app;
};
ConnectBuilder.prototype.index = function (path, title, theme) {
theme = theme || 'default';
this.app.use(function (req, res) {
fs.readFile(path, function (err, data) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(data.toString('utf-8')
.replace(/__TITLE__/g, title)
.replace(/__THEME__/g, theme),
'utf-8'
);
});
});
return this;
};
ConnectBuilder.prototype.session = function (secret, key) {
this.app.use(connect.cookieParser());
this.app.use(connect.session({secret: secret, key: key}));
return this;
};
ConnectBuilder.prototype.static = function (path) {
this.app.use(connect.static(path));
return this;
};
module.exports = function () {
return new ConnectBuilder();
};
})();

View File

@ -1,14 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>__TITLE__</title>
<title>tail -F __TITLE__</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="/styles/__STYLE__.css">
<link rel="stylesheet" type="text/css" href="/styles/__THEME__.css">
<link rel="icon" href="/favicon.png">
</head>
<body>
<div class="navbar">
<span class="brand">__TITLE__</span>
<span class="brand">tail -F __TITLE__</span>
<input type="text" class="query" placeholder="Filter" tabindex="1">
</div>
<pre class="log"></pre>

View File

@ -9,7 +9,7 @@
"dependencies": {
"commander": "1.3.2",
"socket.io": "0.9.16",
"connect": "2.8.5",
"connect": "2.11.0",
"validator": "1.5.0",
"daemon": "1.1.0",
"cookie": "0.1.0"

118
test/connect_builder.js Normal file
View File

@ -0,0 +1,118 @@
var connectBuilder = require('../lib/connect_builder');
(function () {
'use strict';
describe('connectBuilder', function () {
it('build connect app', function () {
connectBuilder().build().should.have.property('use');
connectBuilder().build().should.have.property('listen');
});
it('returns app requiring authorized user', function (done) {
var app = connectBuilder().authorize('user', 'pass').build();
app
.request()
.get('/')
.end(function (res) {
res.statusCode.should.equal(401);
res.headers['www-authenticate'].should.equal('Basic realm="Authorization Required"');
done();
});
});
it('returns app allowing user to login', function (done) {
var app = connectBuilder().authorize('user', 'pass').build();
app.use(function (req, res) {
res.end('secret!');
});
app
.request()
.get('/')
.set('Authorization', 'Basic dXNlcjpwYXNz')
.end(function (res) {
res.statusCode.should.equal(200);
res.body.should.equal('secret!');
done();
});
});
it('returns app that setup session', function (done) {
var app = connectBuilder().session('secret', 'sessionkey').build();
app.use(function (req, res) {
res.end();
});
app
.request()
.get('/')
.end(function (res) {
res.headers['set-cookie'][0].should.startWith('sessionkey');
done();
});
});
it('returns app that serve static files', function (done) {
var app = connectBuilder().static(__dirname + '/fixtures').build();
app
.request()
.get('/foo')
.expect('bar', done);
});
it('returns app that serve index file', function (done) {
var app = connectBuilder().index(__dirname + '/fixtures/index').build();
app
.request()
.get('/')
.end(function (res) {
res.headers['content-type'].should.equal('text/html');
res.statusCode.should.equal(200);
done();
});
});
it('returns app that replace index title', function (done) {
var app = connectBuilder()
.index(__dirname + '/fixtures/index_with_title', 'Test')
.build();
app
.request()
.get('/')
.expect('<head><title>Test</title></head>', done);
});
it('returns app that sets theme', function (done) {
var app = connectBuilder()
.index(__dirname + '/fixtures/index_with_theme', 'Test', 'dark')
.build();
app
.request()
.get('/')
.expect(
'<head><title>Test</title><link href="dark.css" rel="stylesheet" type="text/css"/></head>',
done
);
});
it('returns app that sets default theme', function (done) {
var app = connectBuilder()
.index(__dirname + '/fixtures/index_with_theme', 'Test')
.build();
app
.request()
.get('/')
.expect(
'<head><title>Test</title><link href="default.css" rel="stylesheet" type="text/css"/></head>',
done
);
});
});
})();

1
test/fixtures/foo vendored Normal file
View File

@ -0,0 +1 @@
bar

1
test/fixtures/index vendored Normal file
View File

@ -0,0 +1 @@
index

1
test/fixtures/index_with_theme vendored Normal file
View File

@ -0,0 +1 @@
<head><title>__TITLE__</title><link href="__THEME__.css" rel="stylesheet" type="text/css"/></head>

1
test/fixtures/index_with_title vendored Normal file
View File

@ -0,0 +1 @@
<head><title>__TITLE__</title></head>

3
test/mocha.opts Normal file
View File

@ -0,0 +1,3 @@
--reporter spec
--require test/support/http
--require should

119
test/support/http.js Normal file
View File

@ -0,0 +1,119 @@
var EventEmitter = require('events').EventEmitter;
var methods = ['get', 'post', 'put', 'delete', 'head'];
var connect = require('connect');
var http = require('http');
var util = require('util');
(function () {
'use strict';
function request(app) {
return new Request(app);
}
module.exports = request;
connect.proto.request = function () {
return request(this);
};
function Request(app) {
EventEmitter.call(this);
var self = this;
this.data = [];
this.header = {};
this.app = app;
if (!this.server) {
this.server = http.Server(app);
this.server.listen(0, '127.0.0.1', function () {
self.addr = self.server.address();
self.listening = true;
});
}
}
/**
* Inherit from `EventEmitter.prototype`.
*/
util.inherits(Request, EventEmitter);
methods.forEach(function (method) {
Request.prototype[method] = function (path) {
return this.request(method, path);
};
});
Request.prototype.set = function (field, val) {
this.header[field] = val;
return this;
};
Request.prototype.write = function (data) {
this.data.push(data);
return this;
};
Request.prototype.request = function (method, path) {
this.method = method;
this.path = path;
return this;
};
Request.prototype.expect = function (body, fn) {
var args = arguments;
this.end(function (res) {
switch (args.length) {
case 3:
res.headers.should.have.property(body.toLowerCase(), args[1]);
args[2]();
break;
default:
if ('number' === typeof body) {
res.statusCode.should.equal(body);
} else {
res.body.should.equal(body);
}
fn();
}
});
};
Request.prototype.end = function (fn) {
var self = this;
if (this.listening) {
var req = http.request({
method: this.method,
port: this.addr.port,
host: this.addr.address,
path: this.path,
headers: this.header
});
this.data.forEach(function (chunk) {
req.write(chunk);
});
req.on('response', function (res) {
var buf = '';
res.setEncoding('utf8');
res.on('data', function (chunk) { buf += chunk; });
res.on('end', function () {
res.body = buf;
fn(res);
});
});
req.end();
} else {
this.server.on('listening', function () {
self.end(fn);
});
}
return this;
};
})();

View File

@ -1,12 +1,10 @@
var fs = require('fs');
var tail = require('../lib/tail');
var temp = require('temp');
(function () {
'use strict';
require('should');
var fs = require('fs');
var tail = require('../lib/tail');
var temp = require('temp');
describe('tail', function () {
temp.track();