Guest User

cctype

a guest
Nov 20th, 2016
82
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 13.79 KB | None | 0 0
  1. -- CONSTANTS --
  2. local PAGE_WIDTH = 25
  3. local PAGE_HEIGHT = 21
  4. local MIN_BLOCK_SIZE_SPLIT = 5
  5.  
  6. local PAGE_COLOR = colors.white
  7. local TEXT_COLOR = colors.black
  8. local BG_COLOR = colors.lightGray
  9. local SHADOW_COLOR = colors.gray
  10. local DISPLAY_PAGE_HEIGHT = PAGE_HEIGHT+4
  11.  
  12. -- FUNCTION DECLARATIONS --
  13. -- this shouldn't be necessary, but things weren't working otherwise
  14. local splitBlocks
  15. local classifyElements
  16. local renderElement
  17.  
  18. -- PATTERNS --
  19.  
  20. local emptyPattern = "^%s*$"
  21. local indentPattern = "^%s%s+(.+)"
  22.  
  23. local headerPattern = "^#+%s*(.-)%s*#*$"
  24. local pageBreakPattern = "^//+$"
  25.  
  26. local unorderedListPattern = "^%s*[%*%+%-]%s*(.+)"
  27. local orderedListPattern = "^[%s]*%d+%.%s*(.+)"
  28. local blockquotePattern = "^>%s*(.*)" -- there may be a line in a blockquote that is otherwise empty
  29.  
  30. -- PATTERN FUNCTIONS --
  31.  
  32. local function processBlock(block)
  33.   local text = table.concat(block, " ")
  34.   return {type = "block", content = string.gsub(text, "%s+", " ")}
  35. end
  36.  
  37. local function processHeader(block)
  38.   local _, _, content = string.find(block[1], headerPattern)
  39.   return {type = "header", content = content}
  40. end
  41.  
  42. local function processPageBreak(block)
  43.   return {type = "pagebreak"}
  44. end
  45.  
  46. local function processUnorderedList(block)
  47.   local items = {}
  48.   local current = {}
  49.   for i = 1, #block do
  50.     local _, _, text = string.find(block[i], unorderedListPattern)
  51.     if text and #current > 0 then
  52.       items[#items+1] = processBlock(current)
  53.     else
  54.       current[#current+1] = text or block[i]
  55.     end
  56.   end
  57.   if #current > 0 then
  58.     items[#items+1] = processBlock(current)
  59.   end
  60.   return {type = "unorderedlist", content = items}
  61. end
  62.  
  63. local function processOrderedList(block)
  64.   local items = {}
  65.   local current = {}
  66.   for i = 1, #block do
  67.     local _, _, text = string.find(block[i], orderedListPattern)
  68.     if text and #current > 0 then
  69.       items[#items+1] = processBlock(current)
  70.       current = {text}
  71.     else
  72.       current[#current+1] = text or block[i]
  73.     end
  74.   end
  75.   if #current > 0 then
  76.     items[#items+1] = processBlock(current)
  77.   end
  78.   return {type = "orderedlist", content = items}
  79. end
  80.  
  81. local function processBlockquote(block)
  82.   local rawContent = {}
  83.   for i = 1, #block do
  84.     local _, _, text = string.find(block[i], blockquotePattern)
  85.     rawContent[#rawContent+1] = text or block[i]
  86.   end
  87.   local childBlocks = splitBlocks(rawContent)
  88.   local childElements = classifyElements(childBlocks)
  89.   return {type = "blockquote", content = childElements}
  90. end
  91.  
  92. -- PROCESSING CODE --
  93.  
  94. function splitBlocks(lines)
  95.   local blocks = {}
  96.   local current = {}
  97.   for i = 1, #lines do
  98.     if string.find(lines[i], emptyPattern) then
  99.       if #current > 0 then
  100.         blocks[#blocks+1] = current
  101.         current = {}
  102.       end
  103.     else
  104.       current[#current+1] = lines[i]
  105.     end
  106.   end
  107.   -- catch stragglers
  108.   if #current > 0 then
  109.     blocks[#blocks+1] = current
  110.   end
  111.   return blocks
  112. end
  113.  
  114. function classifyElements(blocks)
  115.   local patternFunctions = {}
  116.   patternFunctions[headerPattern] = processHeader
  117.   patternFunctions[pageBreakPattern] = processPageBreak
  118.   patternFunctions[unorderedListPattern] = processUnorderedList
  119.   patternFunctions[orderedListPattern] = processOrderedList
  120.   patternFunctions[blockquotePattern] = processBlockquote
  121.   local elements = {}
  122.  
  123.   for i = 1, #blocks do
  124.     local current = blocks[i]
  125.     local found = false
  126.     for p, f in pairs(patternFunctions) do
  127.       if string.find(current[1], p) then
  128.         found = true
  129.         elements[#elements+1] = f(current)
  130.         break
  131.       end
  132.     end  
  133.     if not found then
  134.       elements[#elements+1] = processBlock(current)
  135.     end
  136.   end
  137.   return elements
  138. end
  139.  
  140. local function groupLists(elements)
  141.   local newElements = {}
  142.   local i = 1
  143.   while i <= #elements do
  144.     if elements[i].type == "blockquote" then
  145.       elements[i].content = groupLists(elements[i].content)
  146.     elseif elements[i].type == "orderedlist" or
  147.       elements[i].type == "unorderedlist" then
  148.       while i < #elements and elements[i+1].type == elements[i].type do
  149.         for k = 1, #elements[i+1].content do
  150.           elements[i].content[#elements[i].content+1] = elements[i+1].content[k]
  151.         end
  152.         table.remove(elements, i+1)
  153.       end
  154.     end
  155.     newElements[#newElements+1] = elements[i]
  156.     i = i+1
  157.   end
  158.   return newElements
  159. end
  160.  
  161. -- TEXT MANIPULATION --
  162.  
  163. local function loadFileLines(file)
  164.   local lines = {}
  165.   local handle = fs.open(file, "r")
  166.   while true do
  167.     local line = handle.readLine()
  168.     if line ~= nil then
  169.       lines[#lines+1] = line
  170.     else
  171.       break
  172.     end
  173.   end
  174.   handle.close()
  175.   return lines
  176. end
  177.  
  178. -- RENDERING AND PAGINATION --
  179.  
  180. local function renderBlock(block, maxWidth, justify)
  181.   justify = justify or "left"
  182.   local lines = {""}
  183.   for word, delim in string.gmatch(block.content, "([^%s-]+)([%s-]?)") do
  184.     if #lines[#lines] == 0 then
  185.       lines[#lines] = word..delim
  186.     else
  187.       if delim == "-" then
  188.         if #lines[#lines]+#word+#delim <= maxWidth then
  189.           lines[#lines] = lines[#lines]..word..delim
  190.         else
  191.           lines[#lines+1] = word..delim
  192.         end
  193.       else
  194.         if #lines[#lines]+#word <= maxWidth then
  195.           lines[#lines] = lines[#lines]..word
  196.           if #lines[#lines]+#delim then
  197.             lines[#lines] = lines[#lines]..delim
  198.           else
  199.             lines[#lines+1] = ""
  200.           end
  201.         else
  202.           lines[#lines+1] = word..delim
  203.         end
  204.       end
  205.     end
  206.   end
  207.   for i = 1, #lines do
  208.     if justify == "center" then
  209.       local l = #lines[i]
  210.       lines[i] = string.rep(" ", math.floor((maxWidth-l)/2))..lines[i]
  211.     elseif justify == "right" then
  212.       local l = #lines[i]  
  213.       lines[i] = string.rep(" ", maxWidth-l)..lines[i]
  214.     end
  215.   end
  216.   return lines
  217. end
  218.  
  219. local function renderHeader(block, width)
  220.   return renderBlock(block, width, "center")
  221. end
  222.  
  223. local function renderOrderedList(block, width)
  224.   local count = #block.content
  225.   local indent = 4+math.floor(math.log10(count))
  226.   local lines = {}
  227.   for i = 1, #block.content do
  228.     local current = block.content[i]
  229.     local prefix = i..". "
  230.     prefix = string.rep(" ", indent-#prefix)..prefix
  231.     local blockLines = renderBlock(current, width-indent)
  232.     lines[#lines+1] = prefix..blockLines[1]
  233.     for j = 2, #blockLines do
  234.       lines[#lines+1] = string.rep(" ", indent)..blockLines[j]
  235.     end
  236.     if i < #block.content then
  237.       lines[#lines+1] = ""
  238.     end
  239.   end
  240.   return lines
  241. end
  242.  
  243. local function renderUnorderedList(block, width)
  244.   local prefix = "  \132 "
  245.   local lines = {}
  246.   for i = 1, #block.content do
  247.     local current = block.content[i]
  248.     local blockLines = renderBlock(current, width-#prefix)
  249.     lines[#lines+1] = prefix..blockLines[1]
  250.     for j = 2, #blockLines do
  251.       lines[#lines+1] = string.rep(" ", #prefix)..blockLines[j]
  252.     end
  253.     if i < #block.content then
  254.       lines[#lines+1] = ""
  255.     end
  256.   end
  257.   return lines
  258. end
  259.  
  260. local function renderBlockquote(block, width)
  261.   local prefix = '\149' -- pretty-printing
  262.   local lines = {}
  263.   for i = 1, #block.content do
  264.     if block.content[i].type ~= "pagebreak" then -- it really doesn't make sense in a blockquote
  265.       local blockLines = renderElement(block.content[i], width-#prefix)
  266.       for j = 1, #blockLines do
  267.         lines[#lines+1] = prefix..blockLines[j]
  268.       end
  269.       if i < #block.content then
  270.         lines[#lines+1] = prefix
  271.       end
  272.     end
  273.   end
  274.   return lines
  275. end
  276.  
  277. function renderElement(element, width)
  278.   local renderingFunctions = {}
  279.   renderingFunctions.header = renderHeader
  280.   renderingFunctions.block = renderBlock
  281.   renderingFunctions.unorderedlist = renderUnorderedList
  282.   renderingFunctions.orderedlist = renderOrderedList
  283.   renderingFunctions.blockquote = renderBlockquote
  284.   if renderingFunctions[element.type] then
  285.     return renderingFunctions[element.type](element, width)
  286.   end
  287.   return nil
  288. end
  289.  
  290. local function paginate(elements)
  291.   local pages = {{}}
  292.   for i = 1, #elements do
  293.     if elements[i].type == "pagebreak" then
  294.       if i < #elements and #pages[#pages] > 0 then
  295.         pages[#pages+1] = {}
  296.       end    
  297.     else
  298.       local block = renderElement(elements[i], PAGE_WIDTH)
  299.       local current = pages[#pages]
  300.       if #current > 0 and current[#current] ~= "" then
  301.         if #current >= PAGE_HEIGHT then
  302.           current = {}
  303.           pages[#pages+1] = current
  304.         else
  305.           current[#current+1] = ""
  306.         end
  307.       end
  308.       if #current+#block > PAGE_HEIGHT and #block < MIN_BLOCK_SIZE_SPLIT then
  309.         current = block
  310.         pages[#pages+1] = current
  311.       else
  312.         for j = 1, #block do
  313.           if #current >= PAGE_HEIGHT then
  314.             current = {}
  315.             pages[#pages+1] = current
  316.           end
  317.           current[#current+1] = block[j]
  318.         end
  319.       end
  320.     end
  321.   end
  322.   return pages
  323. end
  324.  
  325. -- PREVIEWING --
  326.  
  327. local function drawPageLine(pages, width, height, pageLine, screenLine)
  328.   local onPageLine = (pageLine-1)%(DISPLAY_PAGE_HEIGHT)+1
  329.   term.setBackgroundColor(BG_COLOR)
  330.   term.setCursorPos(1, screenLine)
  331.   term.clearLine()
  332.   if onPageLine == DISPLAY_PAGE_HEIGHT then
  333.     local shadow = '\130'..string.rep('\131', PAGE_WIDTH+1)
  334.       ..'\129'
  335.     term.setBackgroundColor(BG_COLOR)
  336.     term.setTextColor(SHADOW_COLOR)
  337.     term.setCursorPos(math.floor((width-(#shadow-1))/2), screenLine)
  338.     term.write(shadow)
  339.   elseif onPageLine > 1 then
  340.     local text = pages[math.ceil(pageLine/DISPLAY_PAGE_HEIGHT)][onPageLine-2]
  341.     text = text or ""
  342.     text = " "..text..string.rep(" ", PAGE_WIDTH-#text+1)
  343.     term.setBackgroundColor(PAGE_COLOR)
  344.     term.setTextColor(TEXT_COLOR)
  345.     term.setCursorPos(math.floor((width-#text)/2), screenLine)
  346.     term.write(text)  
  347.     term.setTextColor(SHADOW_COLOR)
  348.     term.setBackgroundColor(BG_COLOR)
  349.     if onPageLine == 2 then
  350.       term.write('\148')
  351.     else
  352.       term.write('\149')
  353.     end
  354.   end
  355. end
  356.  
  357. local function menu(choices)
  358.   local currentItem = 1
  359.   local scrW, scrH = term.getSize()
  360.   term.setCursorPos(1, scrH)
  361.   term.setBackgroundColor(colors.black)
  362.   term.setTextColor(colors.white)
  363.   term.clearLine()
  364.   while true do
  365.     term.setCursorPos(1, scrH)
  366.     for i = 1, #choices do
  367.       if i == currentItem then
  368.         term.write("["..choices[i].."]")
  369.       else
  370.         term.write(" "..choices[i].." ")
  371.       end
  372.     end
  373.     local _, key = os.pullEvent("key")
  374.     if key == keys.left then
  375.       currentItem = math.max(1, currentItem-1)
  376.     elseif key == keys.right then
  377.       currentItem = math.min(#choices, currentItem+1)
  378.     elseif key == keys.enter then
  379.       return choices[currentItem]
  380.     elseif key == keys.leftCtrl or key == keys.rightCtrl then
  381.       return nil
  382.     end
  383.   end
  384. end
  385.  
  386. local function preview(pages)
  387.   local scroll = 0
  388.   local scrW, scrH = term.getSize()
  389.   local doPrint = false
  390.   local running = true
  391.   for i = 1, scrH do
  392.     drawPageLine(pages, scrW, scrH, i+scroll, i)
  393.   end
  394.   while running do
  395.     event, a, b, c = os.pullEvent()
  396.     if event == "term_resize" then
  397.       scrW, scrH = term.getSize()
  398.       for i = 1, scrH do
  399.         drawPageLine(pages, scrW, scrH, i+scroll, i)
  400.       end
  401.     elseif event == "key" then
  402.       local targetScroll = scroll
  403.       if a == keys.down then
  404.         targetScroll = math.min(scroll+1, #pages*DISPLAY_PAGE_HEIGHT-scrH)
  405.       elseif a == keys.up then
  406.         targetScroll = math.max(scroll-1, 0)
  407.       elseif a == keys.pageDown then
  408.         targetScroll = math.min(#pages*DISPLAY_PAGE_HEIGHT-scrH, scroll+scrH)
  409.       elseif a == keys.pageUp then
  410.         targetScroll = math.max(0, scroll-scrH)
  411.       end
  412.       local scrollDiff = targetScroll-scroll
  413.       term.scroll(scrollDiff)
  414.       scroll = targetScroll
  415.       if scrollDiff > 0 then
  416.         for i = scrH-scrollDiff+1, scrH do
  417.           drawPageLine(pages, scrW, scrH, i+scroll, i)
  418.         end
  419.       elseif scrollDiff < 0 then
  420.         for i = 1, -scrollDiff do
  421.           drawPageLine(pages, scrW, scrH, i+scroll, i)
  422.         end
  423.       end
  424.       if a == keys.leftCtrl or a == keys.rightCtrl then
  425.         local action = menu({"Print", "Exit"})
  426.         drawPageLine(pages, scrW, scrH, scrH+scroll, scrH)
  427.         if action == "Print" then
  428.           running = false
  429.           doPrint = true
  430.         end
  431.         if action == "Exit" then
  432.           running = false
  433.         end
  434.       end
  435.     end
  436.   end
  437.   term.setBackgroundColor(colors.black)
  438.   term.setTextColor(colors.white)
  439.   term.clear()
  440.   term.setCursorPos(1, 1)
  441.   return doPrint
  442. end
  443.  
  444. -- PRINTING --
  445.  
  446. local function printOut(title, pages)
  447.   local printer = peripheral.find("printer")
  448.   if not printer then
  449.     print("No printer detected.")
  450.     return
  451.   end
  452.   for i = 1, #pages do
  453.     if printer.getPaperLevel() == 0 then
  454.       print("Please insert more paper.")
  455.       while printer.getPaperLevel() == 0 do
  456.         sleep(0.5)
  457.       end
  458.     end
  459.     if printer.getInkLevel() == 0 then
  460.       print("Please insert more ink.")
  461.       while printer.getInkLevel() == 0 do
  462.         sleep(0.5)
  463.       end
  464.     end
  465.     printer.newPage()
  466.     printer.setPageTitle(title..": page "..i)
  467.     for j = 1, #pages[i] do
  468.       printer.setCursorPos(1, j)
  469.       printer.write(pages[i][j])
  470.     end
  471.     while not printer.endPage() do -- in case the tray is full
  472.       sleep(0.5)
  473.     end
  474.   end
  475. end
  476.  
  477. -- ENTRY CODE --
  478.  
  479. local args = {...}
  480. if #args == 0 then
  481.   print("Usage: "..shell.getRunningProgram().." <filename>")
  482.   return
  483. end
  484. local filename = args[1]
  485. if not fs.exists(filename) then
  486.   print(filename.." does not exist.")
  487.   return
  488. end
  489.  
  490. local lines = loadFileLines(filename)
  491. local blocks = splitBlocks(lines)
  492. local elements = classifyElements(blocks)
  493. elements = groupLists(elements)
  494. local pages = paginate(elements)
  495. local doPrint = preview(pages)
  496. if doPrint then
  497.   printOut(filename, pages)
  498. end
Add Comment
Please, Sign In to add comment