Module: Polytexnic::Utils
Instance Method Summary collapse
-
#add_font_info(string) ⇒ Object
Adds some verbatim font info (including size).
-
#apple_silicon? ⇒ Boolean
Returns true for Apple Silicon.
-
#cache_urls(doc, latex = false) ⇒ Object
Caches URLs for href and url commands.
-
#debug? ⇒ Boolean
Returns true if we are debugging, false otherwise.
-
#digest(string, options = {}) ⇒ Object
Returns a salted hash digest of the string.
-
#escape_backslashes(string) ⇒ Object
Escapes backslashes.
-
#expand_input!(text, code_function, ext = 'md') ⇒ Object
Expands ‘input’ command by processing & inserting the target source.
-
#framed(code) ⇒ Object
Puts a frame around code.
-
#highlight(key, content, language, formatter, options) ⇒ Object
Highlights a code sample.
-
#highlight_lines(output, options) ⇒ Object
Highlight lines (i.e., with a yellow background).
-
#highlight_source_code(document) ⇒ Object
Highlights source code.
-
#highlighted_lines(options) ⇒ Object
Returns an array with the highlighted lines.
-
#horrible_backslash_kludge(string) ⇒ Object
Does something horrible with backslashes.
-
#linux? ⇒ Boolean
Returns true if platform is Linux.
-
#os_x? ⇒ Boolean
Returns true if platform is OS X.
-
#os_x_newer? ⇒ Boolean
Returns true for OS X Mountain Lion (10.8) and later.
-
#os_x_older? ⇒ Boolean
Returns true for OS X Lion (10.7) and earlier.
-
#pipeline_digest(element) ⇒ Object
Returns a digest for passing things through the pipeline.
-
#profiling? ⇒ Boolean
Returns true if we are profiling the code, false otherwise.
- #set_test_mode! ⇒ Object
- #test? ⇒ Boolean
-
#tralics ⇒ Object
Returns the executable for the Tralics LaTeX-to-XML converter.
-
#tralics_commands ⇒ Object
Returns some commands for Tralics.
-
#underscore_digest ⇒ Object
Returns a digest for use in labels.
-
#xmlelement(name, skip = false) ⇒ Object
Returns a Tralics pseudo-LaTeX XML element.
Instance Method Details
#add_font_info(string) ⇒ Object
Adds some verbatim font info (including size). We prepend rather than replace the styles because the Pygments output includes a required override of the default commandchars. Since the substitution is only important in the context of a PDF book, it only gets made if there’s a style in the ‘softcover.sty’ file. We also support custom overrides in ‘custom_pdf.sty’.
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/polytexnic/utils.rb', line 242 def add_font_info(string) softcover_sty = File.join('latex_styles', 'softcover.sty') custom_pdf_sty = File.join('latex_styles', 'custom_pdf.sty') regex = '{code}{Verbatim}{(.*)}' styles = nil [softcover_sty, custom_pdf_sty].reverse.each do |filename| if File.exist?(filename) styles ||= File.read(filename).scan(/#{regex}/).flatten.first end end unless styles.nil? string.to_s.gsub!("\\begin{Verbatim}[", "\\begin{Verbatim}[#{styles},") end string end |
#apple_silicon? ⇒ Boolean
Returns true for Apple Silicon.
54 55 56 |
# File 'lib/polytexnic/utils.rb', line 54 def apple_silicon? RUBY_PLATFORM.match(/arm64/) end |
#cache_urls(doc, latex = false) ⇒ Object
Caches URLs for href and url commands.
106 107 108 109 110 111 112 113 114 115 |
# File 'lib/polytexnic/utils.rb', line 106 def cache_urls(doc, latex=false) doc.tap do |text| text.gsub!(/\\(href|url){(.*?)}/) do command, url = $1, $2 key = digest(url) literal_cache[key] = url command == 'url' ? "\\href{#{key}}{#{url}}" : "\\href{#{key}}" end end end |
#debug? ⇒ Boolean
Returns true if we are debugging, false otherwise. Manually change to ‘true` on an as-needed basis.
278 279 280 |
# File 'lib/polytexnic/utils.rb', line 278 def debug? false end |
#digest(string, options = {}) ⇒ Object
Returns a salted hash digest of the string.
79 80 81 82 |
# File 'lib/polytexnic/utils.rb', line 79 def digest(string, = {}) salt = [:salt] || SecureRandom.base64 Digest::SHA1.hexdigest("#{salt}--#{string}") end |
#escape_backslashes(string) ⇒ Object
Escapes backslashes. Interpolated backslashes need extra escaping. We only escape ‘\’ by itself, i.e., a backslash followed by spaces or the end of line.
101 102 103 |
# File 'lib/polytexnic/utils.rb', line 101 def escape_backslashes(string) string.gsub(/\\(\s+|$)/) { '\\\\' + $1.to_s } end |
#expand_input!(text, code_function, ext = 'md') ⇒ Object
Expands ‘input’ command by processing & inserting the target source. We skip tikzstyles since those don’t get inserted into the text.
39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/polytexnic/utils.rb', line 39 def (text, code_function, ext = 'md') uuid = "52576c7eb84a49b5afc0d9913891f346" text.gsub!(/\\input\{(.*\.tikzstyles)\}/) { "#{uuid}-#{$1}-#{uuid}" } text.gsub!(/^[ \t]*\\input\{(.*?)\}[ \t]*$/) do # Prepend a newline for safety. included_text = "\n" + File.read("#{$1}.#{ext}") code_function.call(included_text).tap do |clean_text| # Recursively substitute '\input' in included text. (clean_text, code_function, ext) end end text.gsub!(/#{uuid}-(.*)-#{uuid}/) { "\\input{#{$1}}" } end |
#framed(code) ⇒ Object
Puts a frame around code.
211 212 213 |
# File 'lib/polytexnic/utils.rb', line 211 def framed(code) "\\begin{framed_shaded}\n#{code}\n\\end{framed_shaded}" end |
#highlight(key, content, language, formatter, options) ⇒ Object
Highlights a code sample.
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/polytexnic/utils.rb', line 216 def highlight(key, content, language, formatter, ) require 'pygments' = JSON.parse('{' + .to_s + '}') if ['linenos'] && formatter == 'html' # Inline numbers look much better in HTML but are invalid in LaTeX. ['linenos'] = 'inline' end if (lines = ['hl_lines']) content_lines = content.split("\n") if lines.max > content_lines.length err = "\nHighlight line(s) out of range: #{lines.inspect}\n" err += content raise err end end highlight_cache[key] ||= Pygments.highlight(content, lexer: language, formatter: formatter, options: ) end |
#highlight_lines(output, options) ⇒ Object
Highlight lines (i.e., with a yellow background). This is needed due to a Pygments bug that fails to highlight lines in the LaTeX output.
193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/polytexnic/utils.rb', line 193 def highlight_lines(output, ) highlighted_lines().each do |i| if i > output.length - 1 $stderr.puts "Warning: Highlighted line #{i} out of range" unless test? $stderr.puts output.inspect unless test? else output[i] = '\setlength{\fboxsep}{0pt}\colorbox{hilightyellow}{' + output[i] + '}' end end end |
#highlight_source_code(document) ⇒ Object
Highlights source code.
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/polytexnic/utils.rb', line 165 def highlight_source_code(document) if document.is_a?(String) # LaTeX substitutions = {} document.tap do code_cache.each do |key, (content, language, in_codelisting, )| code = highlight(key, content, language, 'latex', ) output = code.split("\n") horrible_backslash_kludge(add_font_info(output.first)) highlight_lines(output, ) code = output.join("\n") substitutions[key] = in_codelisting ? code : framed(code) end document.gsub!(Regexp.union(substitutions.keys), substitutions) end else # HTML document.css('div.code').each do |code_block| key = code_block.content next unless (value = code_cache[key]) content, language, _, = value code_block.inner_html = highlight(key, content, language, 'html', ) end end end |
#highlighted_lines(options) ⇒ Object
Returns an array with the highlighted lines.
206 207 208 |
# File 'lib/polytexnic/utils.rb', line 206 def highlighted_lines() JSON.parse('{' + .to_s + '}')['hl_lines'] || [] end |
#horrible_backslash_kludge(string) ⇒ Object
Does something horrible with backslashes. OK, so the deal is that code highlighted for LaTeX contains the line beginVerbatim Oh crap, there are backslashes in there. This means we have no chance of getting things to work after interpolating, gsubbing, and so on, because in Ruby ‘\foo’ is the same as ‘\\foo’, ‘}’ is ‘}’, etc. I thought I escaped (heh) this problem with the ‘escape_backslashes` method, but here the problem is extremely specific. In particular, \{} is really \ and { and }, but Ruby doesn’t know WTF to do with it, and thinks that it’s “\{}”, which is the same as ‘{}’. The solution is to replace ‘\\’ with some number of backslashes. How many? I literally had to just keep adding backslashes until the output was correct when running ‘softcover build:pdf`.
272 273 274 |
# File 'lib/polytexnic/utils.rb', line 272 def horrible_backslash_kludge(string) string.to_s.gsub!(/commandchars=\\\\/, 'commandchars=\\\\\\\\') end |
#linux? ⇒ Boolean
Returns true if platform is Linux.
74 75 76 |
# File 'lib/polytexnic/utils.rb', line 74 def linux? RUBY_PLATFORM.match(/linux/) end |
#os_x? ⇒ Boolean
Returns true if platform is OS X.
69 70 71 |
# File 'lib/polytexnic/utils.rb', line 69 def os_x? RUBY_PLATFORM.match(/darwin/) end |
#os_x_newer? ⇒ Boolean
Returns true for OS X Mountain Lion (10.8) and later.
59 60 61 |
# File 'lib/polytexnic/utils.rb', line 59 def os_x_newer? os_x? && !os_x_older? end |
#os_x_older? ⇒ Boolean
Returns true for OS X Lion (10.7) and earlier.
64 65 66 |
# File 'lib/polytexnic/utils.rb', line 64 def os_x_older? os_x? && RUBY_PLATFORM.include?('11') end |
#pipeline_digest(element) ⇒ Object
Returns a digest for passing things through the pipeline.
85 86 87 88 |
# File 'lib/polytexnic/utils.rb', line 85 def pipeline_digest(element) value = digest("#{Time.now.to_s}::#{element}") @literal_cache[element.to_s] ||= value end |
#profiling? ⇒ Boolean
Returns true if we are profiling the code, false otherwise. Manually change to ‘true` on an as-needed basis.
284 285 286 287 |
# File 'lib/polytexnic/utils.rb', line 284 def profiling? return false if test? false end |
#set_test_mode! ⇒ Object
289 290 291 |
# File 'lib/polytexnic/utils.rb', line 289 def set_test_mode! @@test_mode = true end |
#test? ⇒ Boolean
293 294 295 |
# File 'lib/polytexnic/utils.rb', line 293 def test? defined?(@@test_mode) && @@test_mode end |
#tralics ⇒ Object
Returns the executable for the Tralics LaTeX-to-XML converter.
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/polytexnic/utils.rb', line 10 def tralics executable = `which tralics`.chomp return executable unless executable.empty? filename = if apple_silicon? 'tralics-apple-silicon' elsif os_x_newer? 'tralics-os-x-newer' elsif os_x_older? 'tralics-os-x-older' elsif linux? "tralics-#{RUBY_PLATFORM}" end project_root = File.join(File.dirname(__FILE__), '..', '..') executable = File.join(project_root, 'precompiled_binaries', filename) output = `#{executable}` unless output.include?('This is tralics') url = 'https://github.com/softcover/tralics' $stderr.puts "\nError: Document not built" $stderr.puts "No compatible Tralics LaTeX-to-XML translator found" $stderr.puts "Follow the instructions at\n #{url}\n" $stderr.puts "to compile tralics and put it on your path" exit(1) end @tralics ||= executable end |
#tralics_commands ⇒ Object
Returns some commands for Tralics. For various reasons, we don’t actually want to include these in the style file that gets passed to LaTeX. For example, the commands with ‘xmlelt’ aren’t even valid LaTeX; they’re actually pseudo-LaTeX that has special meaning to the Tralics processor.
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/polytexnic/utils.rb', line 132 def tralics_commands base_commands = <<-'EOS' % Commands specific to Tralics \def\hyperref[#1]#2{\xmlelt{a}{\XMLaddatt{target}{#1}#2}} \newcommand{\heading}[1]{\xmlelt{heading}{#1}} \newcommand{\codecaption}[1]{\xmlelt{heading}{#1}} \newcommand{\sout}[1]{\xmlelt{sout}{#1}} \newcommand{\kode}[1]{\xmlelt{kode}{#1}} \newcommand{\coloredtext}[2]{\xmlelt{coloredtext}{\AddAttToCurrent{color}{#1}#2}} \newcommand{\coloredtexthtml}[2]{\xmlelt{coloredtexthtml}{\AddAttToCurrent{color}{#1}#2}} \newcommand{\filepath}[1]{\xmlelt{filepath}{#1}} \newcommand{\image}[1]{\xmlelt{image}{#1}} \newcommand{\imagebox}[1]{\xmlelt{imagebox}{#1}} % Ignore pbox argument, just replacing with content. \newcommand{\pbox}[2]{#2} % Ignore some other commands. \newcommand{\includepdf}[1]{} \newcommand{\newunicodechar}[2]{} \newcommand{\extrafloats}[1]{} EOS custom = <<-EOS \\usepackage{amsthm} \\newtheorem{codelisting}{#{language_labels["listing"]}}[chapter] \\newtheorem{aside}{#{language_labels["aside"]}}[chapter] \\newtheorem{theorem}{#{language_labels["theorem"]}}[chapter] EOS (@supported_theorem_types - ["theorem"]).each do |lab| custom += "\\newtheorem{#{lab}}[theorem]{#{language_labels[lab]}}\n" end [base_commands, custom].join("\n") end |
#underscore_digest ⇒ Object
Returns a digest for use in labels. I like to use labels of the form cha:foo_bar, but for some reason Tralics removes the underscore in this case.
93 94 95 |
# File 'lib/polytexnic/utils.rb', line 93 def underscore_digest pipeline_digest('_') end |
#xmlelement(name, skip = false) ⇒ Object
Returns a Tralics pseudo-LaTeX XML element. The use of the ‘skip’ flag is a hack to be able to use xmlelement even when generating, e.g., LaTeX, where we simply want to yield the block.
121 122 123 124 125 |
# File 'lib/polytexnic/utils.rb', line 121 def xmlelement(name, skip = false) output = (skip ? "" : "\\begin{xmlelement}{#{name}}") output << yield if block_given? output << (skip ? "" : "\\end{xmlelement}") end |