Skip to content
This repository was archived by the owner on Jan 23, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions actions/sourceCodeImage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright (C) 2014 TopCoder Inc., All Rights Reserved.
*
* The api to convert the source code to image.
*
* @version 1.0
* @author TCASSEMBLER
*/
/*jslint node: true, nomen: true, plusplus: true, unparam: true */
"use strict";
var async = require('async');
var _ = require('underscore');
var IllegalArgumentError = require('../errors/IllegalArgumentError');
var highlight = require('highlight.js');
var wkhtmltoimage = require('wkhtmltoimage');
var BadRequestError = require('../errors/BadRequestError');
var exec = require('child_process').exec;

// The style name array.
var STYLE_NAMES = ['arta', 'ascetic', 'atelier-dune.dark', 'atelier-dune.light', 'atelier-forest.dark', 'atelier-forest.light',
'atelier-heath.dark', 'atelier-heath.light', 'atelier-lakeside.dark', 'atelier-lakeside.light', 'atelier-seaside.dark',
'atelier-seaside.light', 'brown_paper', 'codepen-embed', 'color-brewer', 'dark', 'default', 'docco',
'far', 'foundation', 'github', 'googlecode', 'hybrid', 'idea', 'ir_black', 'kimbie.dark', 'kimbie.light', 'magula',
'mono-blue', 'monokai', 'monokai_sublime', 'obsidian', 'paraiso.dark', 'paraiso.light', 'pojoaque', 'railscasts',
'rainbow', 'school_book', 'solarized_dark', 'solarized_light', 'sunburst', 'tomorrow-night-blue',
'tomorrow-night-bright', 'tomorrow-night-eighties', 'tomorrow-night', 'tomorrow', 'vs', 'xcode', 'zenburn'];

/**
* Convert the source code to image.
*
* @param api - the api instance
* @param connection - the request connection instance
* @param next - the callback method.
*/
var convertSourceCodeToImage = function (api, connection, next) {
var helper = api.helper,
highlightResult = '',
emptyStr = '',
code = connection.params.code + emptyStr,
style = connection.params.style,
language = connection.params.lang;

async.waterfall([
function (cb) {
if (_.isNull(language) || _.isEmpty(language) || !highlight.getLanguage(language + emptyStr)) {
cb(new IllegalArgumentError("The language name is invalid."));
return;
}

if (!_.isUndefined(style) && !_.isNull(style) && !_.isEmpty(style) && !_.contains(STYLE_NAMES, style + emptyStr)) {
cb(new IllegalArgumentError("The style name is invalid."));
return;
}

exec(api.config.tcConfig.generateSourceCodeImage.wkhtmltoimageCommandPath + ' -H', function(error, stdout, stderr) {
if (stderr !== null && stderr !== '') {
cb(new IllegalArgumentError('The wkhtmltoimageCommandPath in configuration is invalid. The return error is ' + stderr));
return;
}
cb();
});
}, function (cb) {
var styleLink = api.config.tcConfig.generateSourceCodeImage.styleLink;
if (!_.isUndefined(style) && !_.isNull(language) && !_.isEmpty(language)) {
styleLink = styleLink.replace('%OVERRIDE_STYLE_NAME%', style);
} else {
styleLink = styleLink.replace('%OVERRIDE_STYLE_NAME%', 'default');
}
highlight.configure({ 'useBR': true });
highlightResult = highlight.highlight(language, code, true).value;
highlightResult = '<html><head><link rel="stylesheet" href="' + styleLink + '"></head><body><pre>' + highlightResult;
highlightResult = highlightResult + '</pre></body></html>';
cb();
}, function (cb) {
var response = connection.rawConnection.res,
tempFileName = new Date().getTime() + (Math.floor(Math.random() * 1000) + '.jpg');

response.writeHead(200, {
'Content-Type': 'image/jpeg',
'Content-Disposition': 'inline; filename=' + tempFileName
});

wkhtmltoimage.setCommand(api.config.tcConfig.generateSourceCodeImage.wkhtmltoimageCommandPath);
wkhtmltoimage.generate(highlightResult, api.config.tcConfig.generateSourceCodeImage.wkhtmlToImageOptions, function (code, signal) {
if (code !== null && code === 0) {
// all success
cb();
} else {
cb(new BadRequestError("Failed to generate the image, the return code is " + code));
}

}).pipe(response);
}
], function (err) {
if (err) {
helper.handleError(api, connection, err);
next(connection, true);
} else {
next(connection, false); //false = response has been set
}
});
};

/**
* The API for converted source code to image.
*/
exports.convertSourceCodeToImage = {
name: "convertSourceCodeToImage",
description: "Convert source code to image",
inputs: {
required: ['code', 'lang'],
optional: ['style']
},
blockedConnectionTypes: [],
cacheEnabled: false,
outputExample: {},
version: 'v2',
run: function (api, connection, next) {
api.log("Execute convertSourceCodeToImage#run", 'debug');
convertSourceCodeToImage(api, connection, next);
}
};
62 changes: 62 additions & 0 deletions apiary.apib
Original file line number Diff line number Diff line change
Expand Up @@ -14635,3 +14635,65 @@ Managing Round Question Answers APIs
"description":"Servers are up but overloaded. Try again later."
}


# Group Source Code Image Generation APIs
Source Code Image Generation APIs

## Source Code Image Generation API [/src2image]

### Source Code Image Generation API [POST]

+ Parameters
+ code (required, string) ... the code value to convert
+ lang (required, string) ... the code's language name, it should be one of ["1c","actionscript","apache","applescript","xml","asciidoc","autohotkey","avrasm","axapta","bash","brainfuck","capnproto","clojure","cmake","coffeescript","cpp","cs","css","d","markdown","dart","delphi","diff","django","dos","dust","elixir","ruby","erb","erlang-repl","erlang","fix","fsharp","gcode","gherkin","glsl","go","gradle","groovy","haml","handlebars","haskell","haxe","http","ini","java","javascript","json","lasso","less","lisp","livecodeserver","livescript","lua","makefile","mathematica","matlab","mel","mizar","monkey","nginx","nimrod","nix","nsis","objectivec","ocaml","oxygene","parser3","perl","php","powershell","processing","profile","protobuf","puppet","python","q","r","rib","rsl","ruleslanguage","rust","scala","scheme","scilab","scss","smalltalk","sql","stylus","swift","tcl","tex","thrift","twig","typescript","vala","vbnet","vbscript","vbscript-html","vhdl","vim","x86asm","xl"]
+ style (optional, string) ... the code's format style, it should be one of ['arta', 'ascetic', 'atelier-dune.dark', 'atelier-dune.light', 'atelier-forest.dark', 'atelier-forest.light', 'atelier-heath.dark', 'atelier-heath.light', 'atelier-lakeside.dark', 'atelier-lakeside.light', 'atelier-seaside.dark', 'atelier-seaside.light', 'brown_paper', 'codepen-embed', 'color-brewer', 'dark', 'default', 'docco', 'far', 'foundation', 'github', 'googlecode', 'hybrid', 'idea', 'ir_black', 'kimbie.dark', 'kimbie.light', 'magula', 'mono-blue', 'monokai', 'monokai_sublime', 'obsidian', 'paraiso.dark', 'paraiso.light', 'pojoaque', 'railscasts', 'rainbow', 'school_book', 'solarized_dark', 'solarized_light', 'sunburst', 'tomorrow-night-blue', 'tomorrow-night-bright', 'tomorrow-night-eighties', 'tomorrow-night', 'tomorrow', 'vs', 'xcode', 'zenburn']

+ Response 200 (image/jpeg)

{
// it contains the generation jpeg in response.
}

+ Response 200 (application/json)

{
"error": "Error: code is a required parameter for this action"
}

+ Response 200 (application/json)

{
"error": "Error: lang is a required parameter for this action"
}

+ Response 400 (application/json)

{
"name":"Bad Request",
"value":"400",
"description":"The language name is invalid."
}

+ Response 400 (application/json)

{
"name":"Bad Request",
"value":"400",
"description":"The style name is invalid."
}

+ Response 500 (application/json)

{
"name":"Internal Server Error",
"value":"500",
"description":"Unknown server error. Please contact support."
}

+ Response 503 (application/json)

{
"name":"Service Unavailable",
"value":"503",
"description":"Servers are up but overloaded. Try again later."
}
14 changes: 13 additions & 1 deletion config/tc-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved.
*
* @author vangavroche, Ghost_141, kurtrips, Sky_, isv, bugbuka, flytoj2ee, TCSASSEMBLER
* @version 1.27
* @version 1.28
* changes in 1.1:
* - add defaultCacheLifetime parameter
* changes in 1.2:
Expand Down Expand Up @@ -64,6 +64,8 @@
* - Add studioReview object for get studio review opportunities api.
* Changes in 1.27:
* Add userActivationResendLimit and userActivationCacheLifeTime for user activation email api.
* Changes in 1.28:
* Add source code image generation configuration.
*/

"use strict";
Expand Down Expand Up @@ -224,6 +226,16 @@ var config = {
studioReview: {
specTerms: 'http://studio.topcoder.com/?module=SpecViewReviewTerms&ct=',
reviewTerms: 'http://studio.topcoder.com/?module=ViewReviewTerms&ct='
},

generateSourceCodeImage: {
wkhtmltoimageCommandPath: process.env.WKHTMLTOIMAGE_COMMAND_PATH || 'wkhtmltoimage',
styleLink: process.env.HIGHLIGHT_STYLE_LINK || 'http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.3/styles/%OVERRIDE_STYLE_NAME%.min.css',
wkhtmlToImageOptions: {
Format: 'jpg',
Quality: 94,
width: process.env.WKHTMLTOIMAGE_IMAGE_WIDTH || 1024
}
}
};
module.exports.tcConfig = config;
Expand Down
12 changes: 10 additions & 2 deletions deploy/ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
#
# Copyright (C) 2013-2014 TopCoder Inc., All Rights Reserved.
#
# Version: 1.1
# Author: vangavroche, delemach, isv
# Version: 1.2
# Author: vangavroche, delemach, isv, TCASSEMBLER
#
# changes in 1.1:
# - added RESET_PASSWORD_TOKEN_CACHE_EXPIRY environment variable
# - added RESET_PASSWORD_TOKEN_EMAIL_SUBJECT environment variable
# - added REDIS_HOST environment variable
# - added REDIS_PORT environment variable
# changes in 1.2
# - added WKHTMLTOIMAGE_COMMAND_PATH environment variable
# - added WKHTMLTOIMAGE_IMAGE_WIDTH environment variable
# - added HIGHLIGHT_STYLE_LINK environment variable
#
export CACHE_EXPIRY=-1

Expand Down Expand Up @@ -86,3 +90,7 @@ export REDIS_PORT=6379

export DEVELOP_SUBMISSION_MAX_SIZE=6144
export WATERMARK_FILE_PATH=test/test_files/design_image_file_generator/studio_logo_watermark.png

export WKHTMLTOIMAGE_COMMAND_PATH=/home/ubuntu/tmp/wkhtmltox-0.12.1/static-build/posix-local/wkhtmltox-0.12.1/bin/wkhtmltoimage
export WKHTMLTOIMAGE_IMAGE_WIDTH=1024
export HIGHLIGHT_STYLE_LINK=http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.3/styles/%OVERRIDE_STYLE_NAME%.min.css
12 changes: 10 additions & 2 deletions deploy/development.bat
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
REM
REM Copyright (C) 2014 TopCoder Inc., All Rights Reserved.
REM
REM Version: 1.1
REM Author: TrePe, isv
REM Version: 1.2
REM Author: TrePe, isv, TCASSEMBLER
REM
REM Changes in 1.1
REM - added RESET_PASSWORD_TOKEN_CACHE_EXPIRY environment variable
REM - added RESET_PASSWORD_TOKEN_EMAIL_SUBJECT environment variable
REM - added REDIS_HOST environment variable
REM - added REDIS_PORT environment variable
REM Changes in 1.2
REM - added WKHTMLTOIMAGE_COMMAND_PATH environment variable
REM - added WKHTMLTOIMAGE_IMAGE_WIDTH environment variable
REM - added HIGHLIGHT_STYLE_LINK environment variable

REM tests rely on caching being off. But set this to a real value (or remove) while coding.

Expand Down Expand Up @@ -83,3 +87,7 @@ rem set REDIS_HOST=localhost
rem set REDIS_PORT=6379

set WATERMARK_FILE_PATH=test/test_files/design_image_file_generator/studio_logo_watermark.png

set WKHTMLTOIMAGE_COMMAND_PATH=/home/ubuntu/tmp/wkhtmltox-0.12.1/static-build/posix-local/wkhtmltox-0.12.1/bin/wkhtmltoimage
set WKHTMLTOIMAGE_IMAGE_WIDTH=1024
set HIGHLIGHT_STYLE_LINK=http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.3/styles/%OVERRIDE_STYLE_NAME%.min.css
12 changes: 10 additions & 2 deletions deploy/development.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
#
# Copyright (C) 2013-2014 TopCoder Inc., All Rights Reserved.
#
# Version: 1.2
# Author: vangavroche, isv
# Version: 1.3
# Author: vangavroche, isv, TCASSEMBLER
# changes in 1.1:
# - add JIRA_USERNAME and JIRA_PASSWORD
# changes in 1.2:
# - added RESET_PASSWORD_TOKEN_CACHE_EXPIRY environment variable
# - added RESET_PASSWORD_TOKEN_EMAIL_SUBJECT environment variable
# - added REDIS_HOST environment variable
# - added REDIS_PORT environment variable
# changes in 1.3
# - added WKHTMLTOIMAGE_COMMAND_PATH environment variable
# - added WKHTMLTOIMAGE_IMAGE_WIDTH environment variable
# - added HIGHLIGHT_STYLE_LINK environment variable
#

# tests rely on caching being off. But set this to a real value (or remove) while coding.
Expand Down Expand Up @@ -88,3 +92,7 @@ export REDIS_PORT=6379
export DEVELOP_SUBMISSION_MAX_SIZE=6144

export WATERMARK_FILE_PATH=test/test_files/design_image_file_generator/studio_logo_watermark.png

export WKHTMLTOIMAGE_COMMAND_PATH=/home/ubuntu/tmp/wkhtmltox-0.12.1/static-build/posix-local/wkhtmltox-0.12.1/bin/wkhtmltoimage
export WKHTMLTOIMAGE_IMAGE_WIDTH=1024
export HIGHLIGHT_STYLE_LINK=http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.3/styles/%OVERRIDE_STYLE_NAME%.min.css
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@
"archiver": "~0.6.1",
"redis": "0.10.x",
"temp": "0.7.0",
"ssha": "*"
"ssha": "*",
"highlight.js": ">= 8.3.0",
"wkhtmltoimage": ">= 0.1.3"
},
"devDependencies": {
"supertest": "0.8.x",
Expand Down
10 changes: 7 additions & 3 deletions routes.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/*
* Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved.
*
* @version 1.61
* @version 1.62
* @author vangavroche, Sky_, muzehyun, kurtrips, Ghost_141, ecnu_haozi, hesibo, LazyChild, isv, flytoj2ee,
* @author panoptimum, bugbuka, Easyhard
* @author panoptimum, bugbuka, Easyhard, TCASSEMBLER
*
* Changes in 1.1:
* - add routes for search challenges
Expand Down Expand Up @@ -142,6 +142,8 @@
* - Add route for get user identity api.
* Changes in 1.61:
* - Added routes for modifying/deleting round question answers.
* Changes in 1.62:
* - Added route for src2image api.
*/
/*jslint node:true, nomen: true */
"use strict";
Expand Down Expand Up @@ -344,6 +346,7 @@ exports.routes = {
{ path: "/:apiVersion/software/reviewers/:contestType", action: "getChallengeReviewers" },
{ path: "/:apiVersion/design/statistics/tops/:challengeType", action: "getStudioTops" },
{ path: "/:apiVersion/data/challengetypes", action: "algorithmsChallengeTypes" }

].concat(testMethods.get),
post: [
{ path: "/:apiVersion/users/resetPassword/:handle", action: "resetPassword" },
Expand Down Expand Up @@ -381,7 +384,8 @@ exports.routes = {
{ path: "/:apiVersion/data/srm/rounds/:questionId/question", action: "modifyRoundQuestion"},
{ path: "/:apiVersion/data/srm/rounds/:roundId/components", action: "setRoundComponents"},
{ path: "/:apiVersion/data/srm/rounds/:roundId/terms", action: "setRoundTerms"},
{ path: "/:apiVersion/data/srm/rounds", action: "createSRMContestRound" }
{ path: "/:apiVersion/data/srm/rounds", action: "createSRMContestRound" },
{ path: "/:apiVersion/src2image", action: "convertSourceCodeToImage" }
],
put: [

Expand Down
Loading