UNPKG

cxltx

Version:

CXLTX (CoffeeXeLaTeX) brings CoffeeScript to (Xe)(La)TeX documents. Fully general approach; use your own favorite language.

273 lines (236 loc) 12.5 kB
############################################################################################################ # njs_util = require 'util' njs_fs = require 'fs' njs_path = require 'path' #........................................................................................................... # BAP = require 'coffeenode-bitsnpieces' TYPES = require 'coffeenode-types' TRM = require 'coffeenode-trm' rpr = TRM.rpr.bind TRM badge = 'CXLTX/main' log = TRM.get_logger 'plain', badge info = TRM.get_logger 'info', badge whisper = TRM.get_logger 'whisper', badge alert = TRM.get_logger 'alert', badge warn = TRM.get_logger 'warn', badge help = TRM.get_logger 'help', badge _echo = TRM.echo.bind TRM #........................................................................................................... eventually = process.nextTick Line_by_line = require 'line-by-line' #........................................................................................................... #----------------------------------------------------------------------------------------------------------- @dispatch = ( provider, texroute, splitter, command, parameter, handler ) -> jobname = njs_path.basename texroute texroute = njs_path.dirname texroute # warn '@32x', texroute, jobname, splitter, command, parameter method_name = command.replace /-/g, '_' method = provider[ method_name ] arity = method.length #......................................................................................................... return handler "unknown method #{rpr method_name}" unless method? #......................................................................................................... if parameter? parameter = @decode_literal_utf8 parameter parameters = if parameter.length is 0 then [] else parameter.split splitter #......................................................................................................... else parameter = '' parameters = [] #......................................................................................................... parameters.push undefined for idx in [ 0 ... arity - 1 - parameters.length ] by 1 #......................................................................................................... @aux[ 'is-complete' ] = no @aux[ 'texroute' ] = texroute @aux[ 'auxroute' ] = ( njs_path.join texroute, jobname ).concat '.auxcopy' @aux[ 'jobname' ] = jobname @aux[ 'splitter' ] = splitter @aux[ 'method-name' ] = method_name @aux[ 'parameters' ] = parameters # info '©56t', @aux method.call provider, parameters..., handler #......................................................................................................... return null #=========================================================================================================== # AUX OBJECT #----------------------------------------------------------------------------------------------------------- # Object to represent entries in (a copy of) the `*.aux` file: @aux = {} #----------------------------------------------------------------------------------------------------------- @read_aux = ( handler ) -> return handler null, @aux if @aux[ 'is-complete' ] #......................................................................................................... texroute = @aux[ 'texroute' ] jobname = @aux[ 'jobname' ] auxroute = @aux[ 'auxroute' ] debug '©fdZOh', @aux #......................................................................................................... unless njs_fs.existsSync auxroute warn "unable to locate #{auxroute}; ignoring" eventually => handler null return null #......................................................................................................... @aux[ 'auxroute' ] = auxroute @aux[ 'labels' ] = labels = {} @aux[ 'duplicate-labels' ] = duplicate_labels = {} #......................................................................................................... @_lines_of auxroute, ( error, line, line_nr ) => return handler error if error? if line is null postprocess() @aux[ 'is-complete' ] = yes return handler null, @aux #....................................................................................................... ### De-escaping characters: ### line = line.replace @read_aux.protectchar_matcher, ( $0, $1 ) => return String.fromCharCode parseInt $1, 16 #....................................................................................................... ### Compiling and evaluating CoffeeScript: ### coffee = null if ( match = line.match @read_aux.coffeescript_matcher )? coffee ?= require 'coffee-script' try source = coffee.compile match[ 1 ], 'bare': yes, 'filename': auxroute x = eval source catch error warn "unable to parse line #{line_nr} of #{auxroute}:" warn line warn rpr error return null switch type = TYPES.type_of x when 'pod' @aux[ name ] = value for name, value of x else warn "ignoring value of type #{type} on line #{line_nr} of #{auxroute}:\n#{rpr line}" return null #....................................................................................................... ### Parsing labels and references: ### if ( match = line.match @read_aux.newlabel_matcher )? [ ignore, name, ref, pageref, title, unknown, unknown, ] = match is_duplicate = no #..................................................................................................... if ( duplicate = labels[ name ] )? is_duplicate = yes duplicate[ 'is-duplicate' ] = yes unless duplicate_labels[ name ]? duplicate_labels[ name ] = [ duplicate, ] #..................................................................................................... label = 'name': name 'ref': ref 'pageref': pageref 'title': title 'is-duplicate': is_duplicate #..................................................................................................... labels[ name ] = label duplicate_labels[ name ].push label if is_duplicate #..................................................................................................... return null #--------------------------------------------------------------------------------------------------------- postprocess = => ### Postprocessing of the data delivered by the `\auxgeo` command. All resulting lemgths are in millimeters. `firstlinev` is the distance between the top of the paper and the top of the first line of text. Similarly, the implicit 1 inch distance in `\voffset` and `\hoffset` is being made explicit so that the reference point is shifted to the paper's top left corner. See http://www.ctex.org/documents/packages/layout/layman.pdf p9 and http://en.wikibooks.org/wiki/LaTeX/Page_Layout ### one_inch = 4736286 if ( g = @aux[ 'geometry' ] )? for name, value of g value += one_inch if name is 'voffset' value += one_inch if name is 'hoffset' # g[ name ] = value / 27597261 * 148.5 g[ name ] = ( Math.round value / 39158276 * 210 * 10000 + 0.5 ) / 10000 g[ 'firstlinev' ] = g[ 'voffset' ] + g[ 'topmargin' ] + g[ 'headsep' ] + g[ 'headheight' ] #......................................................................................................... return null #........................................................................................................... ### matcher for those uber-verbosely: `\protect \char "007B\relax` escaped characters: ### @read_aux.protectchar_matcher = /// \\protect \s+ \\char \s+ "( [ 0-9 A-F ]+ )\\relax \s? ///g #........................................................................................................... ### matcher for CoffeeScript: ### @read_aux.coffeescript_matcher = /// ^ % \s+ coffee\s+ ( .+ ) $ /// ### \newlabel{otherlabel}{{2}{3}} ### ### \newlabel{otherlabel}{{2}{3}{References}{section.2}{}} ### ### TAINT not sure whether this RegEx is backtracking-safe as per http://www.regular-expressions.info/catastrophic.html ### @read_aux.newlabel_matcher = /// ^ \\newlabel \{ ( [^{}]+ ) \} \{ \{ ( [0-9.]* ) \} \{ ( [0-9]* ) \} (?: \{ ( [^{}]* ) \} \{ ( [^{}]* ) \} \{ ( [^{}]* ) \} )? \} $ /// #----------------------------------------------------------------------------------------------------------- @_lines_of = ( route, handler ) -> line_nr = 0 #......................................................................................................... line_reader = new Line_by_line route line_reader.on 'error', ( error ) => handler error line_reader.on 'end', => handler null, null #......................................................................................................... line_reader.on 'line', ( line ) => line_nr += 1 handler null, line, line_nr #......................................................................................................... return null #=========================================================================================================== # #----------------------------------------------------------------------------------------------------------- @debug = ( message ) -> return echo() unless message? warn message return @_pen_debug message #----------------------------------------------------------------------------------------------------------- @_pen_debug = ( message ) -> message = @escape_error message return "\\textbf{\\textcolor{red}{#{message}}}" #----------------------------------------------------------------------------------------------------------- @echo = ( P... ) -> # whisper P... return _echo P... #----------------------------------------------------------------------------------------------------------- debug = @debug.bind @ echo = @echo.bind @ #=========================================================================================================== # SERIALIZATION #----------------------------------------------------------------------------------------------------------- @_escape_replacements = [ [ /// \\ ///g, '\\textbackslash{}', ] [ /// \{ ///g, '\\{', ] [ /// \} ///g, '\\}', ] [ /// & ///g, '\\&', ] [ /// \$ ///g, '\\$', ] [ /// \# ///g, '\\#', ] [ /// % ///g, '\\%', ] [ /// _ ///g, '\\_', ] [ /// \^ ///g, '\\textasciicircum{}', ] [ /// ~ ///g, '\\textasciitilde{}', ] # '`' # these two are very hard to catch when TeX's character handling is switched on # "'" # ] #----------------------------------------------------------------------------------------------------------- @escape = ( text ) -> R = text for [ matcher, replacement, ] in @_escape_replacements R = R.replace matcher, replacement return R #----------------------------------------------------------------------------------------------------------- @escape_error = ( text ) -> ### Escape all characters that would cause difficulties with TeX's `\verbatiminput`. ### return ( @escape text ).replace /\n+/g, '\\par\n\n' #----------------------------------------------------------------------------------------------------------- @decode_literal_utf8 = ( text ) -> ### The `Buffer ... toString` steps decode literal UTF-8 in the request ### ### TAINT the NodeJS docs say: [the 'binary'] encoding method is deprecated and should be avoided [...] [it] will be removed in future versions of Node ### return ( new Buffer text, 'binary' ).toString 'utf-8'