Merge pull request #52 from sharelatex/bg-tikz-externalize
support for tikz externalize
This commit is contained in:
@@ -8,6 +8,7 @@ logger = require "logger-sharelatex"
|
|||||||
Metrics = require "./Metrics"
|
Metrics = require "./Metrics"
|
||||||
child_process = require "child_process"
|
child_process = require "child_process"
|
||||||
DraftModeManager = require "./DraftModeManager"
|
DraftModeManager = require "./DraftModeManager"
|
||||||
|
TikzManager = require "./TikzManager"
|
||||||
fs = require("fs")
|
fs = require("fs")
|
||||||
fse = require "fs-extra"
|
fse = require "fs-extra"
|
||||||
os = require("os")
|
os = require("os")
|
||||||
@@ -42,6 +43,12 @@ module.exports = CompileManager =
|
|||||||
else
|
else
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
|
createTikzFileIfRequired = (callback) ->
|
||||||
|
if TikzManager.needsOutputFile(request.rootResourcePath, request.resources)
|
||||||
|
TikzManager.injectOutputFile compileDir, request.rootResourcePath, callback
|
||||||
|
else
|
||||||
|
callback()
|
||||||
|
|
||||||
# set up environment variables for chktex
|
# set up environment variables for chktex
|
||||||
env = {}
|
env = {}
|
||||||
# only run chktex on LaTeX files (not knitr .Rtex files or any others)
|
# only run chktex on LaTeX files (not knitr .Rtex files or any others)
|
||||||
@@ -54,7 +61,8 @@ module.exports = CompileManager =
|
|||||||
if request.check is 'validate'
|
if request.check is 'validate'
|
||||||
env['CHKTEX_VALIDATE'] = 1
|
env['CHKTEX_VALIDATE'] = 1
|
||||||
|
|
||||||
injectDraftModeIfRequired (error) ->
|
# apply a series of file modifications/creations for draft mode and tikz
|
||||||
|
async.series [injectDraftModeIfRequired, createTikzFileIfRequired], (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
timer = new Metrics.Timer("run-compile")
|
timer = new Metrics.Timer("run-compile")
|
||||||
# find the image tag to log it as a metric, e.g. 2015.1 (convert . to - for graphite)
|
# find the image tag to log it as a metric, e.g. 2015.1 (convert . to - for graphite)
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ module.exports = ResourceWriter =
|
|||||||
should_delete = false
|
should_delete = false
|
||||||
if path == "output.pdf" or path == "output.dvi" or path == "output.log"
|
if path == "output.pdf" or path == "output.dvi" or path == "output.log"
|
||||||
should_delete = true
|
should_delete = true
|
||||||
|
if path == "output.tex" # created by TikzManager if present in output files
|
||||||
|
should_delete = true
|
||||||
if should_delete
|
if should_delete
|
||||||
jobs.push (callback) -> ResourceWriter._deleteFileIfNotDirectory Path.join(basePath, path), callback
|
jobs.push (callback) -> ResourceWriter._deleteFileIfNotDirectory Path.join(basePath, path), callback
|
||||||
|
|
||||||
@@ -73,19 +75,22 @@ module.exports = ResourceWriter =
|
|||||||
callback()
|
callback()
|
||||||
|
|
||||||
_writeResourceToDisk: (project_id, resource, basePath, callback = (error) ->) ->
|
_writeResourceToDisk: (project_id, resource, basePath, callback = (error) ->) ->
|
||||||
path = Path.normalize(Path.join(basePath, resource.path))
|
ResourceWriter.checkPath basePath, resource.path, (error, path) ->
|
||||||
if (path.slice(0, basePath.length) != basePath)
|
|
||||||
return callback new Error("resource path is outside root directory")
|
|
||||||
|
|
||||||
mkdirp Path.dirname(path), (error) ->
|
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
# TODO: Don't overwrite file if it hasn't been modified
|
mkdirp Path.dirname(path), (error) ->
|
||||||
if resource.url?
|
return callback(error) if error?
|
||||||
UrlCache.downloadUrlToFile project_id, resource.url, path, resource.modified, (err)->
|
# TODO: Don't overwrite file if it hasn't been modified
|
||||||
if err?
|
if resource.url?
|
||||||
logger.err err:err, project_id:project_id, path:path, resource_url:resource.url, modified:resource.modified, "error downloading file for resources"
|
UrlCache.downloadUrlToFile project_id, resource.url, path, resource.modified, (err)->
|
||||||
callback() #try and continue compiling even if http resource can not be downloaded at this time
|
if err?
|
||||||
else
|
logger.err err:err, project_id:project_id, path:path, resource_url:resource.url, modified:resource.modified, "error downloading file for resources"
|
||||||
fs.writeFile path, resource.content, callback
|
callback() #try and continue compiling even if http resource can not be downloaded at this time
|
||||||
|
else
|
||||||
|
fs.writeFile path, resource.content, callback
|
||||||
|
|
||||||
|
checkPath: (basePath, resourcePath, callback) ->
|
||||||
|
path = Path.normalize(Path.join(basePath, resourcePath))
|
||||||
|
if (path.slice(0, basePath.length + 1) != basePath + "/")
|
||||||
|
return callback new Error("resource path is outside root directory")
|
||||||
|
else
|
||||||
|
return callback(null, path)
|
||||||
|
|||||||
38
app/coffee/TikzManager.coffee
Normal file
38
app/coffee/TikzManager.coffee
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
fs = require "fs"
|
||||||
|
Path = require "path"
|
||||||
|
ResourceWriter = require "./ResourceWriter"
|
||||||
|
logger = require "logger-sharelatex"
|
||||||
|
|
||||||
|
# for \tikzexternalize to work the main file needs to match the
|
||||||
|
# jobname. Since we set the -jobname to output, we have to create a
|
||||||
|
# copy of the main file as 'output.tex'.
|
||||||
|
|
||||||
|
module.exports = TikzManager =
|
||||||
|
needsOutputFile: (rootResourcePath, resources) ->
|
||||||
|
# if there's already an output.tex file, we don't want to touch it
|
||||||
|
for resource in resources
|
||||||
|
if resource.path is "output.tex"
|
||||||
|
return false
|
||||||
|
# if there's no output.tex, see if we are using tikz/pgf in the main file
|
||||||
|
for resource in resources
|
||||||
|
if resource.path is rootResourcePath
|
||||||
|
return TikzManager._includesTikz (resource)
|
||||||
|
# otherwise false
|
||||||
|
return false
|
||||||
|
|
||||||
|
_includesTikz: (resource) ->
|
||||||
|
# check if we are using tikz externalize
|
||||||
|
content = resource.content.slice(0,65536)
|
||||||
|
if content.indexOf("\\tikzexternalize") >= 0
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
|
||||||
|
injectOutputFile: (compileDir, mainFile, callback = (error) ->) ->
|
||||||
|
ResourceWriter.checkPath compileDir, mainFile, (error, path) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
fs.readFile path, "utf8", (error, content) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
logger.log compileDir: compileDir, mainFile: mainFile, "copied file to ouput.tex for tikz"
|
||||||
|
# use wx flag to ensure that output file does not already exist
|
||||||
|
fs.writeFile Path.join(compileDir, "output.tex"), content, {flag:'wx'}, callback
|
||||||
@@ -157,6 +157,27 @@ describe "ResourceWriter", ->
|
|||||||
.calledWith(new Error("resource path is outside root directory"))
|
.calledWith(new Error("resource path is outside root directory"))
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
|
describe "checkPath", ->
|
||||||
|
describe "with a valid path", ->
|
||||||
|
beforeEach ->
|
||||||
|
@ResourceWriter.checkPath("foo", "bar", @callback)
|
||||||
|
|
||||||
|
it "should return the joined path", ->
|
||||||
|
@callback.calledWith(null, "foo/bar")
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
describe "with an invalid path", ->
|
||||||
|
beforeEach ->
|
||||||
|
@ResourceWriter.checkPath("foo", "baz/../../bar", @callback)
|
||||||
|
|
||||||
|
it "should return an error", ->
|
||||||
|
@callback.calledWith(new Error("resource path is outside root directory"))
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
describe "with another invalid path matching on a prefix", ->
|
||||||
|
beforeEach ->
|
||||||
|
@ResourceWriter.checkPath("foo", "../foobar/baz", @callback)
|
||||||
|
|
||||||
|
it "should return an error", ->
|
||||||
|
@callback.calledWith(new Error("resource path is outside root directory"))
|
||||||
|
.should.equal true
|
||||||
|
|||||||
66
test/unit/coffee/TikzManager.coffee
Normal file
66
test/unit/coffee/TikzManager.coffee
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
SandboxedModule = require('sandboxed-module')
|
||||||
|
sinon = require('sinon')
|
||||||
|
require('chai').should()
|
||||||
|
modulePath = require('path').join __dirname, '../../../app/js/TikzManager'
|
||||||
|
|
||||||
|
describe 'TikzManager', ->
|
||||||
|
beforeEach ->
|
||||||
|
@TikzManager = SandboxedModule.require modulePath, requires:
|
||||||
|
"./ResourceWriter": @ResourceWriter = {}
|
||||||
|
"fs": @fs = {}
|
||||||
|
"logger-sharelatex": @logger = {log: () ->}
|
||||||
|
|
||||||
|
describe "needsOutputFile", ->
|
||||||
|
it "should return true if there is a \\tikzexternalize", ->
|
||||||
|
@TikzManager.needsOutputFile("main.tex", [
|
||||||
|
{ path: 'foo.tex' },
|
||||||
|
{ path: 'main.tex', content:'foo \\usepackage{tikz} \\tikzexternalize' }
|
||||||
|
]).should.equal true
|
||||||
|
|
||||||
|
it "should return false if there is no \\tikzexternalize", ->
|
||||||
|
@TikzManager.needsOutputFile("main.tex", [
|
||||||
|
{ path: 'foo.tex' },
|
||||||
|
{ path: 'main.tex', content:'foo \\usepackage{tikz}' }
|
||||||
|
]).should.equal false
|
||||||
|
|
||||||
|
it "should return false if there is already an output.tex file", ->
|
||||||
|
@TikzManager.needsOutputFile("main.tex", [
|
||||||
|
{ path: 'foo.tex' },
|
||||||
|
{ path: 'main.tex', content:'foo \\usepackage{tikz} \\tikzexternalize' },
|
||||||
|
{ path: 'output.tex' }
|
||||||
|
]).should.equal false
|
||||||
|
|
||||||
|
describe "injectOutputFile", ->
|
||||||
|
beforeEach ->
|
||||||
|
@rootDir = "/mock"
|
||||||
|
@filename = "filename.tex"
|
||||||
|
@callback = sinon.stub()
|
||||||
|
@content = '''
|
||||||
|
\\documentclass{article}
|
||||||
|
\\usepackage{tikz}
|
||||||
|
\\tikzexternalize
|
||||||
|
\\begin{document}
|
||||||
|
Hello world
|
||||||
|
\\end{document}
|
||||||
|
'''
|
||||||
|
@fs.readFile = sinon.stub().callsArgWith(2, null, @content)
|
||||||
|
@fs.writeFile = sinon.stub().callsArg(3)
|
||||||
|
@ResourceWriter.checkPath = sinon.stub().callsArgWith(2, null, "#{@rootDir}/#{@filename}")
|
||||||
|
@TikzManager.injectOutputFile @rootDir, @filename, @callback
|
||||||
|
|
||||||
|
it "sould check the path", ->
|
||||||
|
@ResourceWriter.checkPath.calledWith(@rootDir, @filename)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should read the file", ->
|
||||||
|
@fs.readFile
|
||||||
|
.calledWith("#{@rootDir}/#{@filename}", "utf8")
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should write out the same file as output.tex", ->
|
||||||
|
@fs.writeFile
|
||||||
|
.calledWith("#{@rootDir}/output.tex", @content, {flag: 'wx'})
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should call the callback", ->
|
||||||
|
@callback.called.should.equal true
|
||||||
Reference in New Issue
Block a user