markdown.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. #####################################################################
  2. # #
  3. # THIS IS A SOURCE CODE FILE FROM A PROGRAM TO INTERACT WITH THE #
  4. # LBRY PROTOCOL ( lbry.com ). IT WILL USE THE LBRY SDK ( lbrynet ) #
  5. # FROM THEIR REPOSITORY ( https://github.com/lbryio/lbry-sdk ) #
  6. # WHICH I GONNA PRESENT TO YOU AS A BINARY. SINCE I DID NOT DEVELOP #
  7. # IT AND I'M LAZY TO INTEGRATE IN A MORE SMART WAY. THE SOURCE CODE #
  8. # OF THE SDK IS AVAILABLE IN THE REPOSITORY MENTIONED ABOVE. #
  9. # #
  10. # ALL THE CODE IN THIS REPOSITORY INCLUDING THIS FILE IS #
  11. # (C) J.Y.Amihud and Other Contributors 2021. EXCEPT THE LBRY SDK. #
  12. # YOU CAN USE THIS FILE AND ANY OTHER FILE IN THIS REPOSITORY UNDER #
  13. # THE TERMS OF GNU GENERAL PUBLIC LICENSE VERSION 3 OR ANY LATER #
  14. # VERSION. TO FIND THE FULL TEXT OF THE LICENSE GO TO THE GNU.ORG #
  15. # WEBSITE AT ( https://www.gnu.org/licenses/gpl-3.0.html ). #
  16. # #
  17. # THE LBRY SDK IS UNFORTUNATELY UNDER THE MIT LICENSE. IF YOU ARE #
  18. # NOT INTENDING TO USE MY CODE AND JUST THE SDK. YOU CAN FIND IT ON #
  19. # THEIR OFFICIAL REPOSITORY ABOVE. THEIR LICENSE CHOICE DOES NOT #
  20. # SPREAD ONTO THIS PROJECT. DON'T GET A FALSE ASSUMPTION THAT SINCE #
  21. # THEY USE A PUSH-OVER LICENSE, I GONNA DO THE SAME. I'M NOT. #
  22. # #
  23. # THE LICENSE CHOSEN FOR THIS PROJECT WILL PROTECT THE 4 ESSENTIAL #
  24. # FREEDOMS OF THE USER FURTHER, BY NOT ALLOWING ANY WHO TO CHANGE #
  25. # THE LICENSE AT WILL. SO NO PROPRIETARY SOFTWARE DEVELOPER COULD #
  26. # TAKE THIS CODE AND MAKE THEIR USER-SUBJUGATING SOFTWARE FROM IT. #
  27. # #
  28. #####################################################################
  29. import os
  30. from flbry.variables import *
  31. from subprocess import *
  32. from flbry import settings
  33. from flbry import plugin
  34. from flbry import url
  35. ################################################################################
  36. # Markdown. Or .md file format is an easy way to give your simple text documents
  37. # a bit of flare. Stuff like links, images and quotes are supported. Also bold
  38. # an italic characters.
  39. def Open(filename):
  40. # This function will parse a Markdown (.md) file into a readable python
  41. # dictionary object. That you can use for various things.
  42. try:
  43. md = open(filename)
  44. md = md.read()
  45. except:
  46. center("Failed to load the article!!!", "bdrd")
  47. return
  48. # After we read the file, we gonna call for all plugins
  49. # that will do something with the file data.
  50. filename, md = plugin.run([filename, md])
  51. # Spliting it for the read.
  52. md = md.split("\n")
  53. # First thing is I was to read the headings and convert it into a tree.
  54. tree = []
  55. indent = 1
  56. c = []
  57. skip = 0
  58. for n,line in enumerate(md):
  59. if skip > n:
  60. continue
  61. ty = "text"
  62. te = line
  63. # Here I want to simply get a type of each line. Later we going to parse
  64. # the links and other things. But first. Let's parse stuff based on
  65. # lines.
  66. if line.startswith("```"):
  67. # THREE ``` aka code block
  68. # This tag will block any other tags
  69. # untill it's untagged
  70. code = ""
  71. for l in md[n+1:]:
  72. if not l.startswith("```"):
  73. code = code + l + "\n"
  74. else:
  75. skip = n + code.count("\n") + 2
  76. break
  77. tree.append(["text_c", code+"\n"])
  78. te = ""
  79. elif line.startswith("#"):
  80. # The titles of the chapter. The Headers are usually written similar
  81. # to how here in python you write comments. It's a # , space, and the
  82. # text.
  83. # The amount of hashes. ## or ### gives different sized text. Officialy
  84. # it should support up to 6 hashes. ######. But why not make it more
  85. # just in case.
  86. ty = line.count("#") # This might give bugs
  87. elif line.startswith(">"):
  88. # The > sign in the Markdown language is used for quatations.
  89. ty = "text_c"
  90. tree.append([ty, te+"\n"])
  91. # Now the stage 0 is over and we parsed the basic things. Now is the hard
  92. # part to parse out all the images and stuff inside them. It's going to be
  93. # done per part. And we are going to use the same technique I used for the
  94. # conversion of the legacy projects. See : studio/story.py ( in VCStudio )
  95. # We are going to itterate over each letter. And decide what to do by that
  96. newtree = []
  97. for block in tree:
  98. if block[0] == "text_c":
  99. newtree.append(block)
  100. continue
  101. part = ""
  102. skip = 0
  103. for n, l in enumerate(block[-1]):
  104. if skip > n:
  105. continue
  106. part = part + l
  107. # Here we are going to do something if a give condition is met.
  108. # Usually I gonna do something if [part] ends with a given markdown
  109. # thing. I don't have a manual of markdown on me. So please make it
  110. # more supported. I guess. I might forget things I rarely use.
  111. # Links are made with [stuff you click on](https://example.com)
  112. # but similar to it. Images are done ![Tooltip](Image.png)
  113. # and even weirder you can put one into the other. Like
  114. # [![Tooltip](Image.png)](https://example.com)
  115. # Which going to give you a clickable image.
  116. # For this version what we are going to do is next.
  117. # If we got [![ then it's a clickable image
  118. # If we got ![ then it's just image
  119. # and if we got [ then it's a link.
  120. if part.endswith("[!["):
  121. # IMAGE LINK
  122. newtree.append([block[0], part[:-3]])
  123. tooltip = ""
  124. imageurl = ""
  125. url = ""
  126. t = False
  127. iu = False
  128. skip = n
  129. for le in block[-1][n:]: # For letters in the rest of text
  130. skip = skip + 1
  131. if le == "]":
  132. t = True
  133. elif le == ")" and t and not iu:
  134. iu = True
  135. elif le == ")" and t and iu:
  136. break
  137. elif not t:
  138. tooltip = tooltip +le
  139. elif t and not iu:
  140. imageurl = imageurl + le
  141. else:
  142. url = url+le
  143. tooltip = tooltip[tooltip.find("[")+1:]
  144. imageurl = imageurl[imageurl.find("(")+1:]
  145. url = url[url.find("(")+1:]
  146. apnd = ["image", "[IMAGE_", imageurl]
  147. newtree.append(apnd)
  148. apnd = ["link", "_LINK]", url]
  149. newtree.append(apnd)
  150. part = ""
  151. elif part.endswith("!["):
  152. # IMAGE
  153. newtree.append([block[0], part[:-2]])
  154. tooltip = ""
  155. url = ""
  156. t = False
  157. skip = n
  158. for le in block[-1][n:]: # For letters in the rest of text
  159. skip = skip + 1
  160. if le == "]":
  161. t = True
  162. elif le == ")" and t:
  163. break
  164. elif not t:
  165. tooltip = tooltip +le
  166. else:
  167. url = url+le
  168. tooltip = tooltip[tooltip.find("[")+1:]
  169. url = url[url.find("(")+1:]
  170. apnd = ["image", "[IMAGE]", url]
  171. newtree.append(apnd)
  172. part = ""
  173. elif part.endswith("[") and not block[-1][n:].startswith('[!['):
  174. # LINK
  175. newtree.append([block[0], part[:-1]])
  176. tooltip = ""
  177. url = ""
  178. t = False
  179. skip = n
  180. for le in block[-1][n:]: # For letters in the rest of text
  181. skip = skip + 1
  182. if le == "]":
  183. t = True
  184. elif le == ")" and t:
  185. break
  186. elif not t:
  187. tooltip = tooltip +le
  188. else:
  189. url = url+le
  190. tooltip = tooltip[tooltip.find("[")+1:]
  191. url = url[url.find("(")+1:]
  192. apnd = ["link", tooltip, url]
  193. newtree.append(apnd)
  194. part = ""
  195. # Now I want to deal with `, *, ** and ***. If you want to help me you
  196. # can implement other types. Such as _, __, ___ and so on. Markdown is
  197. # a very rich language. I'm going to use the cut down version I see other
  198. # people use.
  199. # BTW this is the time. Feb 28. When I switched from Gedit to GNU Emacs.
  200. # Interesting feeling using this programm. I kind a love it even tho
  201. # so many stuff in not intuitive. Like saving is not Ctrl - S but
  202. # Ctrl - X -> Ctrl - S.
  203. # Things like Alt-; to comment multiple lines at ones is HUGE. Also it
  204. # was built by programmers for programmers. So it's a very good tool.
  205. elif part.endswith("**") and not block[-1][n+2:].startswith('*'):
  206. # DOUBLE **
  207. newtree.append([block[0], part[:-2]])
  208. if block[0] == "text":
  209. block[0] = "text_b"
  210. else:
  211. block[0] = "text"
  212. part = ""
  213. elif part.endswith("*") and not block[-1][n+1:].startswith('*'):
  214. # SINGLE *
  215. newtree.append([block[0], part[:-1]])
  216. if block[0] == "text":
  217. block[0] = "text_i"
  218. else:
  219. block[0] = "text"
  220. part = ""
  221. elif part.endswith("`"):
  222. # SINGLE `
  223. newtree.append([block[0], part[:-1]])
  224. tmpart = block[-1][n+1:]
  225. tmpart = tmpart[:tmpart.find("`")]
  226. newtree.append(["text_c", tmpart])
  227. skip = n+len(tmpart)+2
  228. block[0] = "text"
  229. part = ""
  230. newtree.append([block[0], part])
  231. w,h = tsize()
  232. newtree.append(["text", "\n"*h])
  233. tree = newtree
  234. return(tree)
  235. def search_convert(s):
  236. # This function convers a chapter name into a link
  237. # such links are use in notabug.org to link to chapters
  238. # for example example.com/file.md#chapter-name
  239. # With this url it will load the example.com/file.md and
  240. # then skip to the "Chapter Name" chapter.
  241. # This function transforms "Chapter Name" into "chapter-name"
  242. l = " ./\|[]{}()?!@#$%^&*`~:;'\"=,<>"
  243. s = s.lower().replace(" ","-")
  244. r = ""
  245. for i in s:
  246. if i not in l:
  247. r = r + i
  248. return r
  249. def draw(filename, title, convert=True):
  250. # Write the file to a temporary file so plugins can modify it
  251. with open(filename) as f:
  252. md = f.read()
  253. filename, md = plugin.run([filename, md])
  254. filename = "/tmp/fastlbrymarkdownreader.md"
  255. with open(filename, "w") as f:
  256. f.write(md)
  257. if settings.get("markdown_reader"):
  258. os.system(settings.get("markdown_reader") + " " + filename)
  259. else:
  260. draw_default(filename, title, convert)
  261. def draw_default(filename, title, convert=True):
  262. ###########
  263. # THE FOLLOWING CODE IS VERY DIRTY. I WAS RUNNING OUT OF TIME WHILE IMPLEMENTING
  264. # IT. PLEASE SEE WHAT CAN BE DONE ABOUT IT. I THINK SOMEBODY NEEDS TO HACK
  265. # COMMENTS TO IT. SINCE IT COULD BE CONFUSING AS HELL...
  266. ##########
  267. # Getting size of the terminal
  268. try:
  269. import os
  270. w, l = os.get_terminal_size()
  271. if not w % 2: # to solve the tearing when it's a weird amount
  272. w = w - 1
  273. l = l - 5
  274. w = w - 8
  275. except:
  276. w = 89 # The width of the frame
  277. l = 20 # Total lines amount possible.
  278. # First we want to parse the article
  279. if convert:
  280. md = Open(filename)
  281. else:
  282. mdf = open(filename)
  283. mdf = mdf.read()
  284. mdf = mdf.split("\n")
  285. md = []
  286. for i in mdf:
  287. md.append(["text", i+"\n"])
  288. md.append(["text", "\n"*l])
  289. # Now we want to print what we have
  290. # Top banner thingy. Purple with the name of the article.
  291. # Title line
  292. center(title)
  293. pline = ""
  294. lenis = 0
  295. linen = 0
  296. colors = {
  297. "text":clr["norm"]+clr["tbwh"]+clr["bdbu"],
  298. "text_b":clr["norm"]+clr["bold"]+clr["tbwh"]+clr["bdbu"],
  299. "text_i":clr["norm"]+clr["ital"]+clr["tbwh"]+clr["bdbu"],
  300. "text_c":clr["norm"]+clr["tbwh"]+clr["bdgr"],
  301. "link":clr["norm"]+clr["tbwh"]+clr["bdma"],
  302. "image":clr["norm"]+clr["tbwh"]+clr["bdcy"]
  303. }
  304. # Let's store all the links that the user might want to use
  305. links = []
  306. linkn = 0
  307. linkw = False
  308. for part in md:
  309. if part[0] in [1,2,3,4,5,6,7]: # Header, similar to <h1> or <h2> in html
  310. center(part[1].replace("\n", "").replace("#", ""), "bdcy")
  311. linen = linen + 1
  312. elif part[1].startswith("---") or part[1].startswith("___"): # The <hr> horrizontal line
  313. center("═"*(w-12), "bdbu")
  314. linen = linen + 1
  315. elif part[0] in ["text", "text_b", "text_c", "text_i", "link", "image"]: # Text that must be wrapped
  316. if linkw:
  317. # The number of the link
  318. pline = pline + clr["bbma"] + wdth(linkn, 4) + " "
  319. linkn = linkn + 1
  320. lenis = lenis + 5
  321. linkw = False
  322. pline = pline + colors[part[0]]
  323. for num, letter in enumerate(part[1]):
  324. br = False
  325. rest = part[1][num:] # all the rest of the part
  326. if letter not in [" ", "\n"]:
  327. pline = pline + letter
  328. lenis = lenis + 1
  329. if part[0] in ["link", "image"] and part[2] not in links:
  330. links.append(part[2])
  331. linkw = True
  332. # TODO: There has to be a break when there is a too-large string
  333. # With freaquant spaces it looks right. But it doesn't break for
  334. # words that are larger then the frame.
  335. elif letter == " ": # At a brake
  336. # This code seems reasonable for text with small enough words
  337. if not lenis > w - 20 - len(rest[:rest.replace(" ","_",1).find(" ")]):
  338. pline = pline + " "
  339. lenis = lenis + 1
  340. else:
  341. br = True
  342. elif letter == "\n":
  343. br = True
  344. if br: # At line-brake
  345. print(" "+clr["bdbu"]+" "+pline+clr["bdbu"]+wdth("", w-lenis-6)+clr["norm"])
  346. pline = colors[part[0]]
  347. lenis = 0
  348. linen = linen + 1
  349. # If reached the line number
  350. # TODO: To remove the dependancy on 20+ new lines the following code should be done
  351. # as a function. And run in two places. Here and after the end of the loop.
  352. if linen >= l:
  353. center("---type 'more' to continue reading it--- ")
  354. complete(["more"])
  355. while True:
  356. plugin.run(execute=False)
  357. c = input(typing_dots())
  358. if not c:
  359. return
  360. if c != "more":
  361. try:
  362. c = int(c)
  363. except:
  364. pass
  365. if type(c) == type(10):
  366. # Pass the link to the link parser
  367. link_open(links[c], filename)
  368. else:
  369. md, links, filename, title = plugin.run([md, links, filename, title], command=c)
  370. elif c == "more":
  371. links = []
  372. linkn = 0
  373. linkw = False
  374. linen = 0
  375. center(title)
  376. break
  377. def link_open(link, start_from=""):
  378. # This function will decide what to do with links.
  379. ####### TRY TO LOAD A LOCAL FILE FIRST #######
  380. local_link = link
  381. if not link.startswith("/"):
  382. start_from = start_from.replace(os.getcwd(), "")
  383. if not link.startswith("../"):
  384. local_link = os.getcwd()+"/"+start_from[:start_from.rfind("/")+1]+link
  385. else:
  386. local_link = os.getcwd()+"/"+start_from
  387. for i in range(link.count("../")+1):
  388. local_link = local_link[:local_link.rfind("/")-1]
  389. local_link = local_link + '/' + link.replace("../", "")
  390. if os.path.exists(local_link):
  391. try:
  392. # Testing if it's a text
  393. open(local_link)
  394. if local_link.endswith(".md"):
  395. draw(local_link, local_link)
  396. else:
  397. draw(local_link, local_link , False)
  398. except Exception as e:
  399. #print(e)
  400. Popen(['xdg-open',
  401. local_link],
  402. stdout=DEVNULL,
  403. stderr=STDOUT)
  404. return
  405. ########## TRY TO LOAD AN LBRY LINK ##############
  406. # TODO : This part confuses me. Something is wrong. Some good
  407. # links just break the url.get() function and I don't
  408. # understand what the hell is going on.
  409. try:
  410. if url.get(link, False): # If it fails, it will return the error message
  411. print(" "+link)
  412. except:
  413. print(" "+link)