diff --git a/bin/jcf b/bin/jcf new file mode 100755 index 0000000..f92094b Binary files /dev/null and b/bin/jcf differ diff --git a/bin/jcf.xml b/bin/jcf.xml new file mode 100644 index 0000000..f942140 --- /dev/null +++ b/bin/jcf.xml @@ -0,0 +1,180 @@ + + + 2.44 + 39387.7118126273 + JEDI Code Format Settings + + False + 1 + True + True + True + True + + + 0 + True + True + Sender + dpr, pas, pp + + + 2 + 2 + False + True + True + False + True + True + True + True + False + 2 + False + True + + + True + False + 2 + 2 + True + False + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 2 + True + 0 + False + False + False + False + False + + + 2 + 90 + 1 + True + True + False + True + True + True + True + True + True + True + 1 + 1 + + 1 + 1 + 1 + 0 + 0 + 1 + 1 + 0 + 1 + 0 + 1 + 1 + 0 + 1 + 0 + True + 4 + 1 + 1 + + + True + True + + + True + 1 + 1 + 1 + 1 + 1 + + + True + + + + True + ActivePage, AnsiCompareStr, AnsiCompareText, AnsiUpperCase, AsBoolean, AsDateTime, AsFloat, AsInteger, Assign, AsString, AsVariant, BeginDrag, Buttons, Caption, Checked, Classes, ClassName, Clear, Close, Components, Controls, Count, Create, Data, Dec, Delete, Destroy, Dialogs, Enabled, EndDrag, EOF, Exception, Execute, False, FieldByName, First, Forms, Free, FreeAndNil, GetFirstChild, Graphics, Height, idAbort, idCancel, idIgnore, IDispatch, idNo, idOk, idRetry, idYes, Inc, Initialize, IntToStr, ItemIndex, IUnknown, Lines, Math, MaxValue, mbAbort, mbAll, mbCancel, mbHelp, mbIgnore, mbNo, mbOK, mbRetry, mbYes, mbYesToAll, Messages, MinValue, mnNoToAll, mrAbort, mrAll, mrCancel, mrIgnore, mrNo, mrNone, mrNoToAll, mrOk, mrRetry, mrYes, mrYesToAll, mtConfirmation, mtCustom, mtError, mtInformation, mtWarning, Name, Next, Open, Ord, ParamStr, PChar, Perform, ProcessMessages, Read, ReadOnly, RecordCount, Register, Release, Result, Sender, SetFocus, Show, ShowMessage, Source, StdCtrls, StrToInt, SysUtils, TAutoObject, TButton, TComponent, TDataModule, Text, TForm, TFrame, TList, TNotifyEvent, TObject, TObjectList, TPageControl, TPersistent, True, TStringList, TStrings, TTabSheet, Unassigned, Value, Visible, WideString, Width, Windows, Write + + + True + False, Name, nil, PChar, Read, ReadOnly, True, WideString, Write + + + True + ActnColorMaps, ActnCtrls, ActnList, ActnMan, ActnMenus, ActnPopup, ActnRes, ADOConst, ADODB, ADOInt, AppEvnts, AxCtrls, BandActn, bdeconst, bdemts, Buttons, CheckLst, Classes, Clipbrd.pas, CmAdmCtl, ComCtrls, ComStrs, Consts, Controls, CtlConsts, CtlPanel, CustomizeDlg, DataBkr, DB, DBActns, dbcgrids, DBClient, DBClientActnRes, DBClientActns, DBCommon, DBConnAdmin, DBConsts, DBCtrls, DbExcept, DBGrids, DBLocal, DBLocalI, DBLogDlg, dblookup, DBOleCtl, DBPWDlg, DBTables, DBXpress, DdeMan, Dialogs, DrTable, DSIntf, ExtActns, ExtCtrls, ExtDlgs, FileCtrl, FMTBcd, Forms, Graphics, GraphUtil, Grids, HTTPIntr, IB, IBBlob, IBCustomDataSet, IBDatabase, IBDatabaseInfo, IBDCLConst, IBErrorCodes, IBEvents, IBExternals, IBExtract, IBGeneratorEditor, IBHeader, IBIntf, IBQuery, IBRestoreEditor, IBSecurityEditor, IBServiceEditor, IBSQL, IBSQLMonitor, IBStoredProc, IBTable, IBUpdateSQL, IBUtils, IBXConst, ImgList, Jcl8087, JclAbstractContainers, JclAlgorithms, JclAnsiStrings, JclAppInst, JclArrayLists, JclArraySets, JclBase, JclBinaryTrees, JclBorlandTools, JclCIL, JclCLR, JclCOM, JclComplex, JclCompression, JclConsole, JclContainerIntf, JclCounter, JclDateTime, JclDebug, JclDotNet, JclEDI, JclEDI_ANSIX12, JclEDI_ANSIX12_Ext, JclEDI_UNEDIFACT, JclEDI_UNEDIFACT_Ext, JclEDISEF, JclEDITranslators, JclEDIXML, JclExprEval, JclFileUtils, JclFont, JclGraphics, JclGraphUtils, JclHashMaps, JclHashSets, JclHookExcept, JclIniFiles, JclLANMan, JclLinkedLists, JclLocales, JclLogic, JclMapi, JclMath, JclMetadata, JclMIDI, JclMime, JclMiscel, JclMsdosSys, JclMultimedia, JclNTFS, JclPCRE, JclPeImage, JclPrint, JclQGraphics, JclQGraphUtils, JclQueues, JclRegistry, JclResources, JclRTTI, JclSchedule, JclSecurity, JclShell, JclSimpleXml, JclStacks, JclStatistics, JclStreams, JclStrHashMap, JclStringLists, JclStrings, JclStructStorage, JclSvcCtrl, JclSynch, JclSysInfo, JclSysUtils, JclTask, JclTD32, JclUnicode, JclUnitConv, JclUnitVersioning, JclUnitVersioningProviders, JclValidation, JclVectors, JclWideFormat, JclWideStrings, JclWin32, JclWin32Ex, JclWinMIDI, ListActns, Mask, MConnect, Menus, Midas, MidasCon, MidConst, MPlayer, MtsRdm, Mxconsts, ObjBrkr, OleAuto, OleConst, OleCtnrs, OleCtrls, OleDB, OleServer, Outline, Printers, Provider, recerror, ScktCnst, ScktComp, ScktMain, SConnect, ShadowWnd, SimpleDS, SMINTF, SqlConst, SqlExpr, SqlTimSt, StdActnMenus, StdActns, StdCtrls, StdStyleActnCtrls, SvcMgr, SysUtils, TabNotBk, Tabs, TConnect, Themes, ToolWin, ValEdit, VDBConsts, WinHelpViewer, XPActnCtrls, XPMan, XPStyleActnCtrls + + + 0 + 0 + False + False + 0 + False + 0 + + + True + DELPHI5_UP, MSWINDOWS + + + + True + True + True + True + True + True + False + 2 + 60 + 1 + 10 + 0 + + + False + + + + False + False + False + False + + + + + + + + 1 + True + False + False + False + False + True + 0 + False + + diff --git a/language-config.json b/language-config.json index ae84b79..1ab1396 100644 --- a/language-config.json +++ b/language-config.json @@ -3,55 +3,64 @@ "show": "C++", "highlight": "cpp", "version": "GCC 8.2.0", - "editor": "c_cpp" + "editor": "c_cpp", + "format": "cpp" }, "cpp11": { "show": "C++ 11", "highlight": "cpp", "version": "GCC 8.2.0", - "editor": "c_cpp" + "editor": "c_cpp", + "format": "cpp" }, "cpp17": { "show": "C++ 17", "highlight": "cpp", "version": "GCC 8.2.0", - "editor": "c_cpp" + "editor": "c_cpp", + "format": "cpp" }, "cpp-noilinux": { "show": "C++ (NOI)", "highlight": "cpp", "version": "GCC 4.8.4 (NOILinux 1.4.1)", - "editor": "c_cpp" + "editor": "c_cpp", + "format": "cpp" }, "cpp11-noilinux": { "show": "C++ 11 (NOI)", "highlight": "cpp", "version": "GCC 4.8.4 (NOILinux 1.4.1)", - "editor": "c_cpp" + "editor": "c_cpp", + "format": "cpp" }, "cpp11-clang": { "show": "C++ 11 (Clang)", "highlight": "cpp", "version": "Clang 7.0.1", - "editor": "c_cpp" + "editor": "c_cpp", + "format": "cpp" }, "cpp17-clang": { "show": "C++ 17 (Clang)", "highlight": "cpp", "version": "Clang 7.0.1", - "editor": "c_cpp" + "editor": "c_cpp", + "format": "cpp" }, "c": { "show": "C", "highlight": "c", "version": "Clang 7.0.1", - "editor": "c_cpp" + "editor": "c_cpp", + "format": "c" }, "c-noilinux": { "show": "C (NOI)", "highlight": "c", "version": "GCC 4.8.4 (NOILinux 1.4.1)", - "editor": "c_cpp" + "editor": "c_cpp", + "format": "c" }, "csharp": { "show": "C#", @@ -69,13 +78,15 @@ "show": "Java", "highlight": "java", "version": "OpenJDK 10.0.2", - "editor": "java" + "editor": "java", + "format": "java" }, "pascal": { "show": "Pascal", "highlight": "pascal", "version": "Free Pascal 3.0.4", - "editor": "pascal" + "editor": "pascal", + "format": "pas" }, "lua": { "show": "Lua", @@ -105,7 +116,8 @@ "show": "Node.js", "highlight": "js", "version": "10.14.0", - "editor": "javascript" + "editor": "javascript", + "format": "js" }, "ruby": { "show": "Ruby", diff --git a/libs/code_formatter.js b/libs/code_formatter.js new file mode 100644 index 0000000..3cea19b --- /dev/null +++ b/libs/code_formatter.js @@ -0,0 +1,54 @@ +let child_process = require('child_process'); +let streamToString = require('stream-to-string'); +let tempfile = require('tempfile'); +let fs = require('fs-extra'); + +module.exports = async (code, lang) => { + let timer, result, tempFile; + try { + let process, pascalAddProgram; + if (lang === 'pas') { + tempFile = tempfile('.pas'); + + if (code.indexOf('program') === -1) { + code = 'program format\n' + code; + pascalAddProgram = true; + } + + await fs.writeFile(tempFile, code); + process = child_process.spawn(`${__dirname}/../bin/jcf`, [tempFile, '-inplace']); + } else { + process = child_process.spawn(`clang-format -style="{BasedOnStyle: Google, IndentWidth: 4, AccessModifierOffset: -4, SortIncludes: false, AllowShortIfStatementsOnASingleLine: true, ColumnLimit: 110, Cpp11BracedListStyle: false }" -assume-filename="a.${lang}"`, { shell: true }); + process.stdin.setEncoding('utf-8'); + process.stdin.write(code); + process.stdin.end(); + } + + timer = setTimeout(() => process.kill(), 5000); + + if (lang === 'pas') { + await streamToString(process.stdout); + result = await fs.readFile(tempFile, 'utf-8'); + if (pascalAddProgram) result = result.replace('program format\n', ''); + } else { + result = await streamToString(process.stdout); + } + + await new Promise((resolve, reject) => { + let exit = code => { + if (code === 0) resolve(); + else reject(code); + } + + if (process.exitCode !== null) exit(process.exitCode); + else process.on('close', exit); + }); + } catch (e) { + console.log(e.stack); + result = null; + } + + clearTimeout(timer); + try { if (tempFile) await fs.delete(tempFile); } catch (e) {} + return result; +} diff --git a/models/formatted_code.js b/models/formatted_code.js new file mode 100644 index 0000000..4ff3239 --- /dev/null +++ b/models/formatted_code.js @@ -0,0 +1,31 @@ +let Sequelize = require('sequelize'); +let db = syzoj.db; + +let model = db.define('formatted_code', { + key: { type: Sequelize.STRING(50), primaryKey: true }, + code: { type: Sequelize.TEXT('medium') } +}, { + timestamps: false, + tableName: 'formatted_code', + indexes: [ + { + fields: ['key'] + } + ] +}); + +let Model = require('./common'); +class FormattedCode extends Model { + static async create(val) { + return FormattedCode.fromRecord(FormattedCode.model.build(Object.assign({ + key: "", + code: "" + }, val))); + } + + getModel() { return model; } +} + +FormattedCode.model = model; + +module.exports = FormattedCode; diff --git a/models/user.js b/models/user.js index 0fd9b21..7782154 100644 --- a/models/user.js +++ b/models/user.js @@ -17,6 +17,7 @@ let model = db.define('user', { is_admin: { type: Sequelize.BOOLEAN }, is_show: { type: Sequelize.BOOLEAN }, public_email: { type: Sequelize.BOOLEAN }, + prefer_formatted_code: { type: Sequelize.BOOLEAN }, sex: { type: Sequelize.INTEGER }, rating: { type: Sequelize.INTEGER }, @@ -65,7 +66,7 @@ class User extends Model { } })); } - + static async fromName(name) { return User.fromRecord(User.model.findOne({ where: { diff --git a/modules/problem.js b/modules/problem.js index 8b46c72..afa41dc 100644 --- a/modules/problem.js +++ b/modules/problem.js @@ -1,5 +1,6 @@ let Problem = syzoj.model('problem'); let JudgeState = syzoj.model('judge_state'); +let FormattedCode = syzoj.model('formatted_code'); let CustomTest = syzoj.model('custom_test'); let WaitingJudge = syzoj.model('waiting_judge'); let Contest = syzoj.model('contest'); @@ -9,6 +10,7 @@ let Article = syzoj.model('article'); const Sequelize = require('sequelize'); let Judger = syzoj.lib('judger'); +let CodeFormatter = syzoj.lib('code_formatter'); app.get('/problems', async (req, res) => { try { @@ -665,6 +667,27 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1 } await judge_state.updateRelatedInfo(true); + if (problem.type !== 'submit-answer' && syzoj.languages[req.body.language].format) { + let key = syzoj.utils.getFormattedCodeKey(judge_state.code, req.body.language); + let formattedCode = await FormattedCode.findOne({ + where: { + key: key + } + }); + + if (!formattedCode) { + let formatted = await CodeFormatter(judge_state.code, syzoj.languages[req.body.language].format); + if (formatted) { + formattedCode = await FormattedCode.create({ + key: key, + code: formatted + }); + + await formattedCode.save(); + } + } + } + try { await Judger.judge(judge_state, problem, contest_id ? 3 : 2); judge_state.pending = true; diff --git a/modules/submission.js b/modules/submission.js index c602d42..499e085 100644 --- a/modules/submission.js +++ b/modules/submission.js @@ -1,4 +1,5 @@ let JudgeState = syzoj.model('judge_state'); +let FormattedCode = syzoj.model('formatted_code'); let User = syzoj.model('user'); let Contest = syzoj.model('contest'); let Problem = syzoj.model('problem'); @@ -147,6 +148,19 @@ app.get('/submission/:id', async (req, res) => { if (judge.problem.type !== 'submit-answer') { judge.codeLength = judge.code.length; + + let key = syzoj.utils.getFormattedCodeKey(judge.code, judge.language); + if (key) { + let formattedCode = await FormattedCode.findOne({ + where: { + key: key + } + }); + + if (formattedCode) { + judge.formattedCode = await syzoj.utils.highlight(formattedCode.code, syzoj.languages[judge.language].highlight); + } + } judge.code = await syzoj.utils.highlight(judge.code, syzoj.languages[judge.language].highlight); } @@ -155,6 +169,8 @@ app.get('/submission/:id', async (req, res) => { info: getSubmissionInfo(judge, displayConfig), roughResult: getRoughResult(judge, displayConfig), code: (judge.problem.type !== 'submit-answer') ? judge.code.toString("utf8") : '', + formattedCode: judge.formattedCode ? judge.formattedCode.toString("utf8") : null, + preferFormattedCode: res.locals.user ? res.locals.user.prefer_formatted_code : false, detailResult: processOverallResult(judge.result, displayConfig), socketToken: (judge.pending && judge.task_id != null) ? jwt.sign({ taskId: judge.task_id, diff --git a/modules/user.js b/modules/user.js index 65985c9..39731cb 100644 --- a/modules/user.js +++ b/modules/user.js @@ -187,6 +187,8 @@ app.post('/user/:id/edit', async (req, res) => { user.information = req.body.information; user.sex = req.body.sex; user.public_email = (req.body.public_email === 'on'); + console.log(req.body); + user.prefer_formatted_code = (req.body.prefer_formatted_code === 'on'); await user.save(); diff --git a/package.json b/package.json index 91484c4..afd270b 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,9 @@ "sequelize": "^4.41.2", "session-file-store": "^1.0.0", "socket.io": "^2.2.0", + "stream-to-string": "^1.1.0", "syzoj-divine": "^1.0.2", + "tempfile": "^2.0.0", "tmp-promise": "^1.0.3", "waliyun": "^3.1.1", "winston": "^3.1.0", diff --git a/utility.js b/utility.js index 36c3b69..a5c7b1d 100644 --- a/utility.js +++ b/utility.js @@ -98,6 +98,12 @@ module.exports = { else res.suffix = res.suffix.replace('iB', ''); return res.fixed + ' ' + res.suffix; }, + getFormattedCodeKey(code, lang) { + if (syzoj.languages[lang].format) { + return syzoj.languages[lang].format + '\n' + syzoj.utils.md5(code); + } + return null; + }, judgeServer(suffix) { return JSON.stringify(url.resolve(syzoj.config.judge_server_addr, suffix)); }, diff --git a/views/submission.ejs b/views/submission.ejs index 6c98188..04d472e 100644 --- a/views/submission.ejs +++ b/views/submission.ejs @@ -51,7 +51,18 @@ - + + <% if (formattedCode !== null) { %> + + + + + <% } %> + @@ -125,6 +136,7 @@

+
{{ content }}
@@ -134,7 +146,7 @@