gemline.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. --[[
  2. Copyright (c) 2021 <roberto.vpt@protonmail.com> . All rights reserved.
  3. Redistribution and use in source and binary forms, with or without modification,
  4. are permitted provided that the following conditions are met:
  5. 1. Redistributions of source code must retain the above copyright notice,
  6. this list of conditions and the following disclaimer.
  7. 2. Redistributions in binary form must reproduce the above copyright notice,
  8. this list of conditions and the following disclaimer in the documentation
  9. and/or other materials provided with the distribution.
  10. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  11. AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  12. IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  13. ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
  14. LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  15. DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  16. SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  17. CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  18. OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
  19. USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  20. ]]--
  21. --[[
  22. **2021-11-26 Roberto Soccoli
  23. # RENDER GEMLINE ver 1
  24. Dopo una mia proposta iniziale, che non era allineata con gli usi della
  25. comunità Gemini che principalmento usano interfacce testuali, ho cercato di
  26. implementare le mie idee assecondando l'uso comune.
  27. Il corretto uso dei tag è devoluto allo scrivente.
  28. > Questa versione è predisposta per essere usata nel client DEMO di
  29. > solderpunk senza sostanziali modifiche.
  30. ## I TAG DI EVIDENZIAZIONE BOLD E ITALIC
  31. * I tag sono * per *BOLD* e " per "ITALIC".
  32. * I tag sono riconosciuti solo a inizio o fine parola, ** altrimenti sono testo.
  33. * I tag isolati *sono mostrati*.
  34. * Se ci sono entrambi "*bold non viene mostrato*".
  35. * I tag raddoppiati, ""**non sono mostrati**"".
  36. ## VALORI TAG
  37. 0 SPAZIO
  38. 1 BOLD mostrato *
  39. 2 ITALIC mostrato "
  40. 3 entrambi, mostrato solo "
  41. 4 NULL
  42. 5 bold nascosto
  43. 6 italic nascosto
  44. 7 entrambi nascosti
  45. 2021-11-29
  46. Il formattare in un testo da scrivere direttamente su terminale non permette una buona paginazione.
  47. Gemline è meglio che torni la tabella rows ed il livello superiore farà quanto deve.
  48. Peraltro la paginazione deve essere semplice, con testo abbastanza corto e le linee complete.
  49. Le pagine vanno calcolate sul numero di righe sommate per linea.
  50. ]]--
  51. -- danno fastidio se li metto anche qui??
  52. preformatted = false
  53. links = {}
  54. socket = require("socket")
  55. socket.url = require("socket.url")
  56. function render ( line, margin )
  57. -- A CAPO GIUSTIFICATO, BOLD E ITALIC
  58. margin = margin or 74 -- 2/3=51, 1/2=38 e 1/3=27
  59. -- indentazione a 4 caratteri per supportare più di 9 link
  60. -- per =>_ che diventa _1>_ o 10>_
  61. local isIndent = false
  62. local indent = " "
  63. local start = 1
  64. local len = 0
  65. local tags = true -- not for #, ## and ###
  66. local title = false -- onli #
  67. local words = {} -- first step
  68. local rows = {} -- line rows
  69. local row = {}
  70. local url
  71. function skip()
  72. if line and start < #line then
  73. while string.byte( line, start ) < 33 do
  74. if start == #line then break end
  75. start = start + 1
  76. end
  77. end
  78. end
  79. function toWords ( text )
  80. text = text or ""
  81. local p = 1
  82. while p <= #text do
  83. local i, f = string.find( text, "%s+", p )
  84. if i then
  85. if i > p then
  86. local temp = string.sub( text, p, i - 1 )
  87. if #temp == 1 and string.find( temp, "^%p+" ) then
  88. words[#words+1] = 4
  89. end
  90. words[#words+1] = temp
  91. end
  92. words[#words+1] = 0
  93. p = f + 1
  94. else
  95. local temp = string.sub( text, p )
  96. if #temp == 1 and string.find( temp, "^%p+" ) then
  97. words[#words+1] = 4
  98. end
  99. words[#words+1] = temp
  100. break
  101. end
  102. end
  103. end
  104. function switch ( pos )
  105. if pos == 1 or words[pos-1] == 0 then -- apertura
  106. if (words[pos] & 2) == 2 then
  107. if words[pos] < 4 then row[#row+1] = '“' end
  108. row[#row+1] = "\27[3m"
  109. end
  110. if (words[pos] & 1) == 1 then
  111. row[#row+1] = "\27[1m"
  112. if words[pos] == 1 then row[#row+1] = '*' end
  113. end
  114. else
  115. if (words[pos] & 1) == 1 then
  116. if words[pos] == 1 then row[#row+1] = '*' end
  117. row[#row+1] = "\27[22m"
  118. end
  119. if (words[pos] & 2) == 2 then
  120. row[#row+1] = "\27[23m"
  121. if words[pos] < 4 then row[#row+1] = '”' end
  122. end
  123. end
  124. end
  125. -- MAIN
  126. if line and #line > 0 then
  127. if string.sub(line,1,3) == "```" then
  128. preformatted = not preformatted
  129. elseif preformatted then
  130. return line .. "\n"
  131. elseif string.sub( line, 1, 1 ) == ">" then
  132. indent = " ┃ "
  133. row[#row+1] = indent
  134. start = 2
  135. elseif string.sub( line, 1, 2 ) == "=>" then
  136. start = 3
  137. while string.byte( line, start ) < 33 do start = start + 1 end
  138. local i, f = string.find( line, "%s+", start )
  139. local link
  140. if i then
  141. link = string.sub( line, start, i - 1 )
  142. start = f + 1
  143. else
  144. link = string.sub( line, start )
  145. end
  146. table.insert(links, socket.url.absolute(url, link))
  147. local temp = string.sub( " "..#links, -2 )
  148. row[#row+1] = "\27[34m"..temp..">\27[39m "
  149. elseif string.sub( line, 1, 2 ) == "* " then
  150. row[#row+1] = " \27[1m•\27[m "
  151. start = 3
  152. elseif string.sub( line, 1, 1 ) == "#" then
  153. indent = ""
  154. tags = false
  155. if string.sub( line, 1, 3 ) == "###" then
  156. row[#row+1] = "\27[1m\27[4m"
  157. start = 4
  158. elseif string.sub( line, 1, 2 ) == "##" then
  159. row[#row+1] ="\27[1m\27[21m"
  160. start = 3
  161. elseif string.sub( line, 1, 1 ) == "#" then
  162. indent = " "
  163. row[#row+1] = indent.."\27[1m"
  164. title = true
  165. start = 2
  166. end
  167. else
  168. indent = ""
  169. end
  170. -- LINE INIT
  171. if start > 1 then
  172. skip()
  173. else
  174. skip()
  175. if start > 1 then
  176. indent = " "
  177. row[#row+1] = indent
  178. end
  179. end
  180. if #indent > 0 then
  181. isIndent = true
  182. len = 4
  183. end
  184. -- TEXT LOAD
  185. -- first round: line to words, spaces and text tags
  186. while start <= #line do
  187. local pos
  188. if tags then
  189. pos, f = string.find( line, '["%*]+', start )
  190. end
  191. if pos then
  192. if pos > start then
  193. toWords( string.sub( line, start, pos - 1 ) )
  194. end
  195. words[#words+1] = string.sub( line, pos, f )
  196. start = f + 1
  197. else
  198. toWords( string.sub( line, start ) )
  199. break
  200. end
  201. end
  202. -- second round: find and compile text tags
  203. for n = 1, #words do
  204. if type( words[n] ) == "string" then
  205. local tag = string.sub( words[n], 1, 1 )
  206. if ( tag == '*' or tag == '"' ) and
  207. -- una tag è circondato almeno da uno spazio
  208. (
  209. n == 1 or n == #words or
  210. type( words[n-1] ) ~= type( words[n+1] )
  211. )
  212. then
  213. local temp = words[n]
  214. --print( temp )
  215. local code = 0
  216. for t = 1, #temp do
  217. tag = string.sub( temp, t, t )
  218. if tag == '*' then
  219. if ( code & 1 ) == 1 then -- bold
  220. code = ( code & 3 ) + 4 -- nascosto
  221. else
  222. code = ( code & 6 ) + 1 -- set bold
  223. end
  224. elseif tag == '"' then
  225. if ( code & 2 ) == 2 then -- italic
  226. code = ( code & 3 ) + 4 -- nascosto
  227. else
  228. code = ( code & 5 ) + 2 -- set italic
  229. end
  230. end
  231. end -- for
  232. words[n] = code
  233. end
  234. end -- solo stringhe, salta gli spazi
  235. end -- end for
  236. -- ROW FORMAT
  237. local head = 1
  238. local tail = 1
  239. local spaces = 0
  240. for n = 1, #words do
  241. if type( words[ n ] ) == "string" then
  242. if len + #words[n] > margin then
  243. local riporto = words[n]
  244. local rip = #riporto
  245. pos = n+1 -- salva posizione successiva
  246. local trat = string.find( riporto, '-', 1, true )
  247. if trat and trat <= margin - len then
  248. -- divide la parola al trattino
  249. riporto = string.sub( words[n], trat + 1 )
  250. rip = #riporto
  251. words[n] = string.sub( words[n], 1, trat )
  252. len = len + trat
  253. tail = n
  254. else
  255. -- riporto a capo
  256. tail = n - 1
  257. -- elimina fino a spazio finale
  258. if words[tail] ~= 0 then -- sono tags
  259. if words[tail] & 1 == 1 then
  260. riporto = "\27[1m"..riporto
  261. end
  262. if words[tail] < 4 then
  263. len = len - 1
  264. rip = rip + 1
  265. if words[tail] == 1 then
  266. riporto = "*"..riporto
  267. end
  268. end
  269. if words[tail] & 2 == 2 then
  270. riporto = "\27[3m"..riporto
  271. if words[tail] < 4 then
  272. riporto = "“"..riporto
  273. end
  274. end
  275. tail = tail - 1
  276. end
  277. -- lo spazio viene saltato
  278. spaces = spaces - 1
  279. len = len - 1
  280. tail = tail - 1
  281. end
  282. -- giustifica la riga
  283. local a = margin - len
  284. local t = a + spaces
  285. local x = t / spaces
  286. local s = 0
  287. for m = head, tail do
  288. if type( words[m] ) == "string" then
  289. row[#row+1] = words[m]
  290. else
  291. if words[m] == 0 then
  292. spaces = spaces - 1
  293. s = s + x
  294. local temp = math.floor( s + 0.5 )
  295. t=t-temp
  296. if spaces == 1 then temp = t end
  297. row[#row+1] = string.rep( " ", temp )
  298. s = s - temp
  299. else
  300. switch ( m )
  301. end
  302. end
  303. end
  304. -- si va a capo
  305. row[#row+1] = "\n" -- CHIUSURA
  306. rows[#rows+1] = table.concat( row )
  307. -- nuova row
  308. row = {}
  309. row[1] = indent..riporto
  310. riporto = ""
  311. len = rip
  312. if isIndent then len = len + 4 end
  313. spaces = 0
  314. -- PRONTI AD AVANZARE
  315. head = pos -- OK
  316. else
  317. len = len + #words[n]
  318. end
  319. else -- SPACE AND TAGS
  320. if words[n] < 4 then len = len + 1 end
  321. if words[n] == 0 then spaces = spaces + 1 end
  322. end
  323. end -- fine scansione
  324. -- parte non giustificata
  325. for m = head, #words do
  326. if type( words[m] ) == "string" then
  327. row[#row+1] = words[m]
  328. else
  329. if words[m] == 0 then
  330. row[#row+1] = " "
  331. else
  332. switch( m )
  333. end
  334. end
  335. end
  336. row = table.concat( row ) -- compreso margine
  337. if title then -- centrato
  338. local s = math.tointeger( math.floor(( margin - #row + 0.5 ) / 2 ) )
  339. row = string.rep( " ", s )..row
  340. end
  341. rows[#rows+1] = row
  342. -- CHIUSURA ULTIMA RIGA
  343. rows[#rows+1] = "\27[m\n"
  344. else
  345. rows[#rows+1] = "\n"
  346. end
  347. return rows
  348. end
  349. return render