codenautas/fast-devel-server

View on GitHub
server/server.js

Summary

Maintainability
F
3 days
Test Coverage
"use strict";

// APP

var _ = require('lodash');
var express = require('express');
var app = express();
var fs = require('fs-promise');
var pug = require('pug');
var stylus = require('stylus');
var moment = require('moment');
var multilang = require('multilang');
var numeral = require('numeral');
var Path = require('path');

numeral.register('locale', 'ar', {
    delimiters: {
        thousands: '.',
        decimal: ','
    },
    abbreviations: {
        thousand: 'Kb',
        million: 'Mb',
        billion: 'Gb',
        trillion: 'Tb'
    },
    ordinal : function (number) {
        return number === 1 ? 'er' : 
               number === 2 ? 'do' :
               number === 3 ? 'ro' :
               number === 4 ? 'to' :
               number === 5 ? 'to' :
               number === 6 ? 'to' :
               number === 7 ? 'mo' :
               number === 8 ? 'vo' :
               number === 9 ? 'no' :
               number === 10 ? 'mo' : 'mo' ;
    },
    currency: {
        symbol: '$'
    }
});
numeral.locale('ar');

var toBinary = require('to-binary');

var html = require('js-to-html').html;
// var autoDeploy = require('auto-deploy');
var dirInfo = require('dir-info');
// var qaControl = require('qa-control');
var kill9 = require('kill-9');
var execToHmtl = require('exec-to-html');
var MiniTools = require('mini-tools');

app.use('/ajax-best-promise.js',function(req,res){
    res.sendFile(process.cwd()+'/node_modules/ajax-best-promise/bin/ajax-best-promise.js');
});


app.use(function(req,res,next){
    next();
    return ;
    console.log('path', req.path);
    console.log('domain', req.domain);
    console.log('params', req.params);
    console.log('query', req.query);
    console.log('req.headers');
    console.dir(req.headers, {depth:1});
    next();
});

///////////////////////////////////////////
MiniTools.readConfig([{
    baseDir:'..'
}, "local-config"], {whenNotExist:'ignore'}).then(function(config){

app.use('/exec-action',execToHmtl.middleware({baseDir:config.baseDir+'/', control:true}));

{
    var {Remarkable} = require("remarkable");
    var hljs       = require('highlight.js') // https://highlightjs.org/ 
    var md = new Remarkable("full",{
        html:         true,        // Enable HTML tags in source 
        highlight: function (str, lang) {
            if (lang && hljs.getLanguage(lang)) {
                try {
                    return hljs.highlight(lang, str).value;
                } catch (err) {}
            }
            try {
                return hljs.highlightAuto(str).value;
            } catch (err) {}
            return ''; // use external default escaping 
        }
    });
    var markdownRender=function markdownRender(fdsFormat, content){
        if("resolve problem with comments in remarkable"){
            content = content.replace(/\-->/g, '-->\n');
        }
        return new Promise(function(resolve, reject){
            if(fdsFormat=='html'){
                var html='<!doctype html>\n<html><head>\n'+
                    '<link href="/markdown.css" media="all" rel="stylesheet" />\n'+
                    '<link href="/markdown2.css" media="all" rel="stylesheet" />\n'+
                    '<link href="/github.css" media="all" rel="stylesheet" />\n'+
                    '<link rel="shortcut icon" href="/favicon.png" type="image/png" />\n'+
                    '<link rel="apple-touch-icon", href="/favicon.png" />\n'+
                    '</head><body><article class="markdown-body entry-content" itemprop="mainContentOfPage">\n'+
                    md.render(content).replace(/--\&gt;/g,'-->')+
                    '\n</article></body></html>';
                resolve({content:html, type:'html'});
            }else{
                resolve({content:content, type:'text'});
            }
        });
    };
}
var serveIndex = require('serve-index');

var serveContent = require('serve-content');
var FDH = require('..');

var server = app.listen(54321, function() {
    console.log('Listening on port %d', server.address().port);
});

var mime = serveContent.mime;

app.get('/',function(req,res){
    res.end("<h1>Fast Devel Server</h1>");
});

var externalInfoTemplate=null;

function middlewareDeLogueo(donde){
    return function(req,res,next){
        next();
    };
}

function uriToPath(uri) { return Path.normalize(decodeURI(uri)); };

app.use('/file',middlewareDeLogueo('file'));
app.use('/info',middlewareDeLogueo('info'));

var validExts=_.keys(mime.types);

app.use('/info',function(req,res,next){
    //filter path
    var info={
        mtime:null, 
        originFileName:null
    };
    var fileName=config.baseDir+'/'+req.path;
    var fileNameForStat=fileName;
    if(req.query["from-original"]){
        fileNameForStat=Path.dirname(fileName)+'/'+req.query["from-original"];
    }
    fs.stat(fileNameForStat).then(function(stat){
        info.mtime=stat.mtime;
        if(Path.extname(req.path)=='.md'){
            return fs.readFile(fileName, {encoding: 'utf8'}).then(function(content){
                var matches=content.split('\n')[0].match(/^.*<!-- multilang from\s*(\S*)\s*$/);
                if(!matches){
                    return {};
                }
                return {originFileName: matches[1]};
            });
        }
        return {};
    }).then(function(moreInfo){
        res.set('Content-Type', 'application/json');
        /*jshint forin:false */
        for(var attr in moreInfo){
            info[attr]=moreInfo[attr];
        }
        /*jshint forin:true */
        res.end(JSON.stringify(info));
    }).catch(function(err){
        console.log('ERROR /info stat');
        console.log(err);
        next(err);
    });
});

serveIndex.dateTimeToString=function(mtime){
    var today=new Date();
    if(mtime.toDateString()==today.toDateString()){
        return 'today ' + mtime.toLocaleTimeString();
    }
    return mtime.toDateString() + ' ' + mtime.toLocaleTimeString();
};

var fdsServeIndex = serveIndex(config.baseDir, {
    hidden: true,
    icons: true,
    view: 'details',
    template: function(locals, done){
        if(locals.directory.match(/\/$/)){
            locals.directory=locals.directory.replace(/\/$/,'');
        }
        var pathParts=locals.directory.split('/');
        var content=html.div({'class':'main-dir'},
        [ 
            html.div({'class':'path-title'},_.map(pathParts,function(part, index, parts){
                if(index==parts.length-1){
                    return html.span(part);
                }
                return html.span([html.a({href: parts.slice(0,index-2).join('/')},part),' / ']);
            })),
            html.table({'class':'file-list'},[html.tr({'class':'title'},[
                html.th(''),
                html.th('name'),
                html.th('ext'),
                html.th('size'),
                html.th('date'),
                html.th('info'),
                html.th('actions'),
            ])].concat(_.map(locals.fileList,function(fileInfo,index){
                var href=locals.directory+'/'+fileInfo.name;
                var fileNameClass;
                var fileNameContent;
                if(fileInfo.stat.isDirectory()){
                    fileNameClass='dir-name';
                    fileNameContent=fileInfo.name;
                }else{
                    fileNameClass='name';
                    fileNameContent=[
                        html.span(Path.basename(fileInfo.name,Path.extname(fileInfo.name))),
                        html.span({'class':'ext-name'},Path.extname(fileInfo.name))
                    ];
                }
                return html.tr([
                    html.td({'class':'icon'},fileInfo.name==='..'?'\uD83D\uDCC2':(fileInfo.stat.isDirectory()?'\uD83D\uDCC1':'\u274f')),
                    //html.td({'class':'icon'},fileInfo.name==='..'?'\u2711':(fileInfo.stat.isDirectory()?'\u274d':'\u274f')),
                    // html.td({'class':'icon'},fileInfo.name==='..'?'D':(fileInfo.stat.isDirectory()?'d':'-')),
                    html.td({'class':fileNameClass},html.a({href:href},fileNameContent)),
                    (fileInfo.stat.isDirectory()?
                        html.td({'class':'ext-dir',colspan:2},html.a({href:href},'<DIR>')):
                        html.td({'class':'ext'},Path.extname(fileInfo.name))
                    ),
                    (fileInfo.stat.isDirectory()?null:
                        html.td({'class':'size'},numeral(fileInfo.stat.size).format())
                    ),
                    html.td({'class':'date'},moment(fileInfo.stat.mtime).format('DD/MM/YYYY HH:mm:ss')),
                    html.td({
                        'class':'info',
                        'data-dirinfo':'dirinfo',
                        'data-dirinfotype':fileInfo.stat.isDirectory()?(locals.fileList[0].name==='..' && index?'sub':'dir'):'file',
                        id:"dirinfo-"+fileInfo.name,
                        'data-name':fileInfo.name,
                        'data-parent':pathParts[pathParts.length-1],
                        'data-path':locals.directory.replace(/^\/(file|auto)(?=\/|$)/,'/dir-info')+(fileInfo.name=='..'?'':'/'+fileInfo.name)
                    },[
                        html.img({style:'height:18px',src:'space.png'}),
                        html.img({style:'height:18px',src:'space.png'}),
                    ]),
                    html.td({
                        'class':'actions',
                        'data-execaction':'execaction',
                        id:"execaction-"+fileInfo.name,
                        'data-path':locals.directory.replace(/^\/file(?=\/|$)/,'/exec-action')+(fileInfo.name=='..'?'':'/'+fileInfo.name)
                    })
                ]);
            })))
        ]);
        var result=html.html([
            html.head([
                html.meta({charset:'utf8'}),
                html.title(locals.directory+' - fast-devel-server'),
                html.link({rel:"stylesheet", type:"text/css", href:"/dir.css"}),
                html.link({rel:"shortcut icon", href:"/favicon.png", type:"image/png"}),
                html.link({rel:"apple-touch-icon", href:"/favicon.png"})
            ]),
            html.body([content,html.script({src:"/auto-dir-info.js"})])
        ]);
        done(null, result.toHtmlText({pretty:true}));
    }
});

app.use('/file',fdsServeIndex);
app.use('/auto',fdsServeIndex);

var serveConvert=function serveConvert(root, opts, adapter){
    if(!adapter){
        adapter=function(){ return function(x){ return x; }; };
    }
    return function(req,res,next){
        var ext=Path.extname(req.path).substring(1);
        var converter=serveConvert.fileConverters[Path.basename(req.path)]||serveConvert.converters[ext];
        if(converter && (req.query.fds || converter.auto || converter.others)){
            var fileName=root+'/'+req.path;
            var exts=[ext].concat(converter.others||[]);
            var p=Promise.resolve({pending:true});
            var fdsFormat = req.query.fds=='auto' && converter.auto || req.query.fds || converter.auto || 'auto';
            exts.forEach(function(actualExt){
                var actualName=fileName.replace(new RegExp('.'+ext+'$'),'.'+actualExt);
                var converter;
                p=p.then(function(content){
                    if(content.pending){
                        converter=serveConvert.fileConverters[Path.basename(req.path)]||serveConvert.converters[actualExt];
                        return Promise.resolve().then(function(){
                            return fs.readFile(actualName, {encoding: 'utf8'});
                        }).then(
                            converter.convert.bind(null,fdsFormat)
                        ).catch(function(err){
                            if(err.code=='ENOENT'){
                                throw err;
                            }else{
                                return {content:'<H1>ERROR</H1><PRE>'+err, type:'html'};
                            }
                        }).then(adapter(req.path, !!req.query.fds)).then(function(buf){
                            MiniTools.serveText(buf.content,buf.type)(req,res,next);
                        }).catch(function(err){
                            return {pending:true}
                        })
                    }else{
                        return content;
                    }
                });
            });
            return p.catch(MiniTools.serveErr(res,req,next));
        }else{
            next();
        }
    };
};

function sourceRenderer(type){
    return function(fdsFormat, content){
        return markdownRender('html', '```'+type+'\n'+content.replace(/```/g,'` ` `')+'\n```');
    };
}

function jadeRender(fdsFormat, jadeContent){
    if(fdsFormat=='source'){
        return sourceRenderer('jade')('source',jadeContent);
    }else{
        return Promise.resolve().then(function(){
            return pug.render(jadeContent,{});
        }).then(function(htmlContent){
            return {content:htmlContent, type:'html'};
        });
    }
}

function stylusRender(fdsFormat, stylusContent){
    if(fdsFormat=='source'){
        return sourceRenderer('stylus')('source',stylusContent);
    }else{
        return Promise.resolve().then(function(){
            return stylus.render(stylusContent,{});
        }).then(function(cssContent){
            return {content:cssContent, type:'css'};
        });
    }
}

serveConvert.converters={
    ''       :{convert:sourceRenderer('')        ,auto:'source'},
    bat      :{convert:sourceRenderer('dos')     ,auto:'source'},
    css      :{convert:sourceRenderer('css')     ,auto:'css'   ,others:['styl']},
    diff     :{convert:sourceRenderer('diff')    ,auto:'source'},
    gitignore:{convert:sourceRenderer('')        ,auto:'source'},
    ini      :{convert:sourceRenderer('ini')     ,auto:'source'},
    jade     :{convert:jadeRender                ,auto:'html'  },
    js       :{convert:sourceRenderer('js')      },
    json     :{convert:sourceRenderer('json')    },
    less     :{convert:sourceRenderer('less')    ,auto:'source'},
    makefile :{convert:sourceRenderer('makefile'),auto:'source'},
    markdown :{convert:markdownRender            ,auto:'html'  },
    md       :{convert:markdownRender            ,auto:'html'  },
    php      :{convert:sourceRenderer('php')     ,auto:'source'},
    psql     :{convert:sourceRenderer('sql')     ,auto:'source'},
    sh       :{convert:sourceRenderer('bash')    ,auto:'source'},
    styl     :{convert:stylusRender              ,auto:'source'},
    stylus   :{convert:stylusRender              ,auto:'source'},
    sql      :{convert:sourceRenderer('sql')     ,auto:'source'},
    xml      :{convert:sourceRenderer('xml')     ,auto:'source'},
    yaml     :{convert:sourceRenderer('json')    ,auto:'source'},
    yml      :{convert:sourceRenderer('json')    ,auto:'source'},
};

function autoViewer(path){
    return function(content){
        if(content.type==='html'){
            return Promise.resolve().then(function(){
                return fs.readFile('./server/auto-view-template.html', {encoding: 'utf8'});
            }).then(function(jsCode){
                jsCode = jsCode.replace(/"##PATH##"/g, JSON.stringify(path));
                return {content:content.content.replace(/<\/html>\s*$/m,'\n'+jsCode+'\n</html>'), type:'html'};
            });
        }else{
            return Promise.resolve(content);
        }
    };
}

serveConvert.fileConverters={
    '.htaccess': sourceRenderer('apache'),
    'httpd.conf': sourceRenderer('apache'),
};

app.use('/file',serveConvert(config.baseDir, {}));

app.use('/auto',serveConvert(config.baseDir, {}, autoViewer));

app.use('/file',serveContent(config.baseDir, {
    index: ['index.html'], 
    extensions:[''], 
    allowedExts:validExts
}));

app.use('/auto',serveContent(config.baseDir, {
    index: ['index.html'], 
    extensions:[''], 
    allowedExts:validExts
}));

app.use(serveContent('./server', {
    index: ['index.html'], 
    extensions:[''], 
    allowedExts:['js','css','html','png']
}));

app.use('/dir-info',function(req,res){
    Promise.resolve().then(function(){
        return dirInfo.getInfo(config.baseDir+uriToPath(req.path), {net:true, cmd:true});
    }).then(function(info){
        res.end(JSON.stringify(info));
    }).catch(MiniTools.serveErr(req,res));
});


// app.use('/qa-control',function(req,res){
//     Promise.resolve().then(function(){
//         // return fs.stat()
//         return qaControl.controlProject(config.baseDir+'/'+uriToPath(req.path));
//     }).then(function(warnings){
//         res.end(JSON.stringify(warnings));
//     }).catch(MiniTools.serveErr(req,res));
// });

///////////////////////////////////////////
});