#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Convert some Markdown/StrapDown.js files to a single, simple HTML (.html) file, which looks as a StrapDown.js powered page, but is autonomous and *does not* require JavaScript at all. I tried to do it as (dirty) well as possible (and I included a couple of nice features). Features: - include a link to SquirtFr (https://lbesson.bitbucket.org/squirt/), - include the bootstrap theme, cf. https://bootswatch.com/united for all the possibilities, - support UTF-8 (TODO try with another encoding?), - quick and pretty, even if the script is really DIRTY... Links: - more information on StrapDown.js can be found here https://lbesson.bitbucket.org/md/, - another script I wrote for StrapDown.js is strapdown2pdf, here https://lbesson.bitbucket.org/md/strapdown2pdf.html - (TODO) Similarly, this page https://lbesson.bitbucket.org/md/strapdown2html.html will give info about that program strapdown2html Copyright: 2015-2021, Lilian Besson. License: MIT. """ from __future__ import print_function # Python 2/3 compatibility ! import sys # import codecs # Use time get string for today current hour import time import markdown import re import os.path from bs4 import BeautifulSoup, SoupStrainer __author__ = "Lilian Besson" __version__ = "0.4" # TODO: improve conformity with StrapDown.js Markdown parser: # nested list for instance, generic source code printer etc. try: try: # Load ansicolortags (Cf. http://ansicolortags.readthedocs.io/) from ansicolortags import printc except ImportError as e: print("Optional dependancy (ansicolortags) is not available, using regular print function.") print(" You can install it with : 'pip install ansicolortags' (or sudo pip)...") # Load ANSIColors (Cf. http://pythonhosted.org/ANSIColors-balises/) from ANSIColors import printc except ImportError: print("Optional dependancy (ANSIColors) is not available, using regular print function.") print(" You can install it with : 'pip install ANSIColors-balises' (or sudo pip)...") printc = print # Load some Markdown extensions (Cf. https://python-markdown.github.io/extensions/#officially-supported-extensions) try: import markdown.extensions list_extensions = [ 'markdown.extensions.extra', # https://pythonhosted.org/Markdown/extensions/extra.html 'markdown.extensions.smarty', # https://pythonhosted.org/Markdown/extensions/smarty.html # 'markdown.extensions.headerid', # https://pythonhosted.org/Markdown/extensions/header_id.html 'markdown.extensions.tables', # https://pythonhosted.org/Markdown/extensions/tables.html # 'markdown.extensions.smart_strong', # https://pythonhosted.org/Markdown/extensions/smart_strong.html # 'urlize' # https://github.com/r0wb0t/markdown-urlize ] try: # From https://github.com/selcuk/markdown-urlify from markdown.preprocessors import Preprocessor from markdown.extensions import Extension urlfinder = re.compile(r'((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+(:[0-9]+)?|' r'(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:/[\+~%/\.\w\-_]*)?\??' r'(?:[\-\+=&;%@\.\w_]*)#?(?:[\.!/\\\w]*))?)') class URLify(Preprocessor): def run(self, lines): return [urlfinder.sub(r'<\1>', line) for line in lines] class URLifyExtension(Extension): def extendMarkdown(self, md, md_globals): md.preprocessors.add('urlify', URLify(md), '_end') urlify_ext = URLifyExtension() # list_extensions.append(urlify_ext) # FIXME improve support for that extension except: printc(" Failed to define the 'urlify' extension.") except: list_extensions = [] # No extension # Fix UTF-8 output # FIXED: this codecs magic was for Python 2 # sys.stdout = codecs.getwriter('utf-8')(sys.stdout) beta = False eraseFileAlreadyThere = False def main(argv=[], path='/tmp', outfile='test.html', title='Test', use_jquery=False): """ Convert every input file from Markdown to HTML, and concatenate all them to an output.""" printc("Starting main, with:") # FIXME printc does not handle UTF-8 correctly ! AAAH! print("path='{path}', outfile='{outfile}'.".format(path=path, outfile=outfile)) print("And the title is:", title) fullpath = os.path.join(path, outfile) printc("The output file will be '{fullpath}'.".format(fullpath=fullpath)) with open(fullpath, "w") as html_file: # FIXED: this codecs magic was for Python 2 # html_file = codecs.getwriter('utf-8')(html_file) # Writing the head of the HTML file html_file.write(""" {title} """.format(title=title)) # Include jquery, and some plugins. Useless except if there is a table in the input document # FIXME improve detection if use_jquery: html_file.write(""" \n""") # Beginning of the header html_file.write("""
""") # Include the jQuery.QuickSearch plugin (no by default). if use_jquery: html_file.write("""

Search on that table?

(Thanks to the QuickSearch jQuery plugin.)



""") # Not useful anymore, my script works fine. if beta: html_file.write("""

Warning!

This page has been converted from Markdown documents, with a Python script.
This script is still experimental! If needed, please fill a bug report?



""") html_file.write("""\n\n""") # Now, work with each file. for inputfile in argv: try: printc("# Trying to read from the file '{inputfile}'.".format(inputfile=inputfile)) with open(inputfile, 'r') as openinputfile: printc(" I opened it, to '{openinputfile}'.".format(openinputfile=openinputfile)) # FIXME detect encoding better? # FIXED: this codecs magic was for Python 2 # openinputfile = codecs.getreader('utf-8')(openinputfile) printc(" Codec manually changed to utf8.") html_text = "\t".format(inputfile=inputfile) # Read that file ! markdown_text = openinputfile.read() printc(" I just read from that file.") # if beta: # print(markdown_text) # yes this works, useless now # Let try to convert this text to HTML from Markdown try: # First, let try to see if the input file was not a StrapDown.js file. try: only_xmp_tag = SoupStrainer("xmp") html = BeautifulSoup(markdown_text, "html.parser", parse_only=only_xmp_tag) # FIXED: this codecs magic was for Python 2 # , from_encoding="utf-8" if beta: print(" BTW, this html read with Beautiful soup has the encoding,", html.original_encoding) x = html.xmp printc(" BeautifulSoup was used to read the input file as an HTML file, and reading its first xmp tag.") # new_markdown_text = unicode(x.prettify("utf-8"), encoding="utf-8") # new_markdown_text = unicode(x.encode("utf-8"), encoding="utf-8") new_markdown_text = x.text printc(" I found the xmp tag and its content. Printing it:") # OMG this is so dirty ! FIXME do better? if beta: print(type(new_markdown_text)) print(new_markdown_text) printc(" Now lets replaced '' --> '' and '' --> ''. Lets go!") markdown_text = new_markdown_text.replace('', '').replace('', '') printc(" Yeah, I replaced '' --> '' and '' --> ''. I did it!") # Add code to add the good Prism.js class to and
, to color the code accordingly.
                            markdown_text = markdown_text.replace('```ocaml', '
')
                            markdown_text = markdown_text.replace('```javascript', '
')
                            markdown_text = markdown_text.replace('```julia', '
')
                            markdown_text = markdown_text.replace('```sql', '
')
                            markdown_text = markdown_text.replace('```c', '
')
                            markdown_text = markdown_text.replace('```latex', '
')
                            markdown_text = markdown_text.replace('```html', '
')
                            markdown_text = markdown_text.replace('```python', '
')
                            markdown_text = markdown_text.replace('```bash', '
')
                            # TODO: this is WRONG! if the ``` block``` has no language,
                            #  both start and end markups get converted to closing blocks!
                            markdown_text = markdown_text.replace('```', '
') # This should be good. except Exception as e: printc(" Exception found: {e}.".format(e=e)) printc(" ===> I tried to read the file as a StrapDown.js powered file, but failed.\n I will now read it as a simple Markdown file.") raise e # This is so dirty... FIXME do better? try: markdown_text = markdown_text.replace('', '<h1>') markdown_text = markdown_text.replace('<!DOCTYPE html><html><head><meta charset="UTF-8"/><title>', '<h1>') markdown_text = markdown_text.replace('', '</h1>') markdown_text = markdown_text.replace('</title></head><body><xmp theme="united">', '</h1>') markdown_text = markdown_text.replace('<xmp theme="united">', '') markdown_text = markdown_text.replace('</title></head><body><xmp theme="cyborg">', '</h1>') markdown_text = markdown_text.replace('<xmp theme="cyborg">', '') markdown_text = markdown_text.replace('<p>

', '') printc(" Now I replace '' --> '' and '' --> ''. Lets go!") except: printc(" I tried (again) to replace '' --> '' and '' --> '' byt failed") # Alright, let us convert this MD text to HTML printc(" Let convert the content I read to HTML with markdown.markdown.") if beta: print(markdown_text) # FIXME: use markdown.markdownFromFile instead (simpler ?) markdown_text = markdown_text.replace('> ', '> ') # Cf. https://pythonhosted.org/Markdown/reference.html#markdownFromFile html_text = markdown.markdown(markdown_text, extensions=list_extensions) # BETA: improve how tables look like html_text = html_text.replace('', '
') printc(" I converted from Markdown to HTML: yeah!!") # Oups ! Bug ! except Exception as e: printc(" Exception found: {e}.".format(e=e)) printc(" ===> I failed to markdownise these lines. Next!") # Now we have that html_text, lets write to the output file (append mode). html_file.write(html_text) printc(" I wrote this to the output file '{html_file}'.".format(html_file=html_file)) # Done for that reading from that file html_file.write("\n\n


\n\n".format(inputfile=inputfile)) # Opening the input file failed ! except Exception as e: printc(" Exception found: {e}.".format(e=e)) printc(" ==> : Failed to read from the file {inputfile}. Going to the next one.\n".format(inputfile=inputfile)) if use_jquery: html_file.write(""" """) # Print the © message today = time.strftime("%H:%M, %d-%m-%Y") html_file.write(f"""

© 2015-2021 Lilian Besson, generated on {today} by an open-source (MIT) Python script.

""") # I removed the spying tools # # return True # Now let us use that thing. if __name__ == '__main__': args = sys.argv # J'ai la flemme, je fais la gestion des options à la main. Et j'écris ce commentaire en français, OHOO! if '-?' in args or '-h' in args or '--help' in args: printc("""strapdown2html: -h | [options] file1 [file2 [file3 ..]] Convert the input files (Markdown (.md) or HTML (.html) StrapDown.js-powered) to one autonomous HTML file. Options: -?|-h|--help:\n\t\tdisplay this help, -o|--out:\n\t\tspecify the output file. Default is based on the first file name. -t|--title:\n\t\tspecify the title of the output. Default is based on the first file name (autodetection is not perfect). -v|--view:\n\t\topen the output file when done (default is not to do it). --use-jquery:\n\t\tforce to include jQuery and the jQuery.QuickSearch plugin (in case of a table for example). -f|--force:\n\t\teven if the output file is present, erase it (default is to find a new name). Warning: Experimental! Almost done? Copyright: 2015-2021, Lilian Besson. License: MIT (https://lbesson.mit-license.org/).""") exit(1) # OK get it from the user out = "/tmp/test.html" if '-o' in args: out = str(args.pop(1 + args.index('-o'))) args.remove('-o') if '--out' in args: out = str(args.pop(1 + args.index('--out'))) args.remove('--out') # OK get from the user or from the file title = '' if '-t' in args: title = args.pop(1 + args.index('-t')) args.remove('-t') if '--title' in args: title = args.pop(1 + args.index('--title')) args.remove('--title') if '--force' in args: eraseFileAlreadyThere = True args.pop(args.index('--force')) if '-f' in args: eraseFileAlreadyThere = True args.pop(args.index('-f')) use_jquery = False if '--use-jquery' in args: use_jquery = True args.pop(args.index('--use-jquery')) # Cleverly detect the title. So dirty, again! i = 0 while title == '': i += 1 try: # FIXED: this codecs magic was for Python 2 # with codecs.open(args[i], 'r', encoding='utf-8') as file1: with open(args[i], 'r') as file1: try: contentfile1 = file1.read() # FIXME experimental detection of the need for QuickSearch # use_jquery = use_jquery or ((contentfile1.find('
') >= 0) or (contentfile1.find('') >= 0)) title = re.search("[^<]+", contentfile1).group() title = title.replace('', '').replace('', '') except Exception as e: # printc(" Exception found: {e}.".format(e=e)) printc(" Failed to read title from the file '{file1}'.".format(file1=file1)) except: break if title == '': printc(" I tried to read the title in one of the input file, but failed.\n") title = '(No title for that StrapDown document)' # Try to guess path+outfile from the first inputfile. if out == "/tmp/test.html": try: out = args[1].replace('.md', '.html').replace('.markdown', '.html') while os.path.exists(out): if eraseFileAlreadyThere: # OMG, so bad! Only for Linux! FIXME import distutils.file_util # Mais quel débile ! # distutils.file_util.copy_file(out, '/tmp/') distutils.file_util.copy_file(out + '~', '/tmp/') distutils.file_util.move_file(out, out + '~') else: out = out.replace('.html', '__new.html') if len(out) > 100: break except: printc(" I tried to guess the output file myself, but failed. Let use '/tmp/test.html'...") path = os.path.dirname(out) if out else '/tmp/' outfile = os.path.basename(out) if out else 'test.html' # Calling main main(args[1:], path=path, outfile=outfile, title=title, use_jquery=use_jquery) printc("\nDone, I wrote to the file '{outfile}' in the dir '{path}'.".format(path=path, outfile=outfile)) if '-v' in args or '--view' in args: try: printc("Opening that document in your favorite browser...") import webbrowser # Thanks to antigravity.py! webbrowser.open(os.path.join(path, outfile)) except Exception as e: printc("But I failed in opening that page to show you the content")