def __init__(self, settings=None): "Just a thin wrapper for Placeholders; parse options in replace()." self.placeholders = Placeholders(self.regex, 'demo') if settings: self.settings = settings.copy() else: self.settings = Settings() self.settings.set('config:user', '_') self.settings.set('config:document', random_slug('demo'))
def test_replace(): defaults = {'default_variable': 'default setting'} settings = Settings(defaults) settings.read_settings_block( trim(""" $ variable = setting """)) out = settings.replace( trim(""" This is a $[variable], and a $[default_variable]! """)) assert out == "This is a setting, and a default setting!"
def test_global_settings(): parts = { 'index': trim(""" My Document $ AUTHOR = Arthur C. Clarke ` Part One """), } settings = Settings() settings.extract(parts) assert settings._ == {'AUTHOR': 'Arthur C. Clarke'}
def test_multiline_authors(): parts = { 'index': trim(""" My Document $ AUTHOR = Arthur C. Clarke + Isaac Asimov ` Part One """), } settings = Settings() settings.extract(parts) assert settings._ == {'AUTHOR': 'Arthur C. Clarke + Isaac Asimov'}
def test_character_block(): block = CharacterBlock(trim(""" @ X """)) assert block.text() == '\n@ X' # <-- with leading space for a heading expected = '<p class="subhead">X</p>' assert block.html_only(Html(), Settings()) == expected
def test_process_simple(): wiki = Wiki(Settings()) document = { 'sample': trim(""" Title > Quote = Calvin, /Institutes/ @ Headline Paragraph * Bullets * Bullets """) } _ = wiki.process('user-slug', 'doc-slug', document) __ = html.fromstring(str(_)) assert __.xpath("//article/section") assert __.xpath("//h1/a[contains(., 'Title')]") assert __.xpath("//blockquote[contains(., 'Quote')]") assert __.xpath("//p[@class='caption'] and contains(., 'Calvin')") assert __.xpath("//p[@class='caption']/em[contains(., 'Institutes')]") assert __.xpath("//p[@class='subhead'][contains(., 'Headline')]") assert __.xpath("//p[contains(., 'Paragraph')]") assert __.xpath("count(//ul/li[contains(., 'Bullets')])") == 2
def test_basic_replacements(): """ @todo: There is an occassional thus-far-non-reproducable issue with DEMO blocks in the editor; they will sometimes wrap into e.g. ... DEMO === + Headline ... in cases like the one below. It's caused by the DEMO placeholders not matching their blocks, and the DEMO line being taken as the start of a paragraph and rewrapped accordingly. """ settings = Settings() demo = Demo(settings) parts = { 'test': trim(""" Test DEMO === TEST === Test ~ DEMO === + Headline > Blockquote === Test """) } tokenized = demo.insert(parts) expected = { 'test': trim(""" Test %sdemo:1%s Test ~ %sdemo:2%s Test """) % (DELIMITER, DELIMITER, DELIMITER, DELIMITER) } assert expected == tokenized decorated = demo.replace(tokenized) __ = html.fromstring(decorated['test']) assert __.xpath("count(//div[@class='wiki-demo space'])") == 2 assert __.xpath("//pre[contains(., Test)]") assert __.xpath("//article/section")
def test_paragraph(): block = Paragraph(trim(""" x y x """)) assert block.text() == 'x y x' assert block.html_only(Html(), Settings()) == '<p>x y x</p>'
def save(self, pregenerate=True, update_doc_slug=None): """ Stores self.parts; compare with self.old to know how to update the metadata and cache. """ self.require_slugs() self.require_parts() # Old and new doc slugs may differ old_doc_slug = self.doc_slug new_doc_slug = self.doc_slug if update_doc_slug is None: update_doc_slug = all([ # fixtures, templates old_doc_slug not in self.protected_doc_slugs(), ]) if update_doc_slug: if 'index' in self.parts: new_text = self.parts['index'] _, _, title_slug, _ = get_title_data(new_text, 'index') if self.doc_slug != title_slug: new_doc_slug = title_slug with self.data as _: _.userDocument_set(self.user_slug, new_doc_slug, self.parts) if old_doc_slug not in self.protected_doc_slugs(): _.userDocumentLastChanged_set(self.user_slug, old_doc_slug, new_doc_slug) _.userDocumentCache_delete(self.user_slug, old_doc_slug) _.userDocumentMetadata_delete(self.user_slug, old_doc_slug) self.doc_slug = new_doc_slug if pregenerate: wiki = Wiki( Settings({ 'config:host': self.host, # <-- ebooks req. FQDN 'config:user': self.user_slug, 'config:document': self.doc_slug })) html = wiki.process(self.user_slug, self.doc_slug, self.parts) self.data.userDocumentCache_set(self.user_slug, self.doc_slug, html) metadata = wiki.compile_metadata(self.data.time_zone, self.user_slug, self.doc_slug) self.data.userDocumentMetadata_set(self.user_slug, self.doc_slug, metadata) return self.doc_slug
def test_list_block(): "Test function block with zero lines" text = trim(""" # Test 1 # Test 2 """) expect = trim(""" <ol> <li> Test 1</li><li>Test 2 </li> </ol> """) actual = list_block(text, Settings()) assert expect == actual
def reformat_part(slug, part): """ Normalise the layout of user-entered text. Remove bibliography and Demo blocks, process as a Blocklist, then put them back. """ if slug == 'biblio': return part else: content, bibliography = split_bibliography(clean_text(part)) demo_placeholders = Demo(Settings()) parts_sans_demo_blocks = demo_placeholders.insert({slug: content}) blocks = BlockList(parts_sans_demo_blocks[slug]) out_sans_demo_blocks = blocks.text() out_parts = demo_placeholders.replace({slug: out_sans_demo_blocks}, lambda text, slug: text) out = out_parts[slug] if bibliography: out += "\n\n\n_____\n\n" + bibliography return out
def show_editor(source: str, user_slug: str = '', doc_slug: str = '', part_slug: str = '', is_preview: bool = False, can_be_saved: bool = False): """ Common renderer for /playground and /edit/user_slug/doc_slug/part_slug. """ settings = Settings({ 'config:host': domain_name(bottle.request), 'config:user': user_slug, 'config:document': doc_slug, }) wiki = Wiki(settings) part_slug, title, title_slug, summary = get_title_data(source, part_slug) text = reformat_part(part_slug, source) if part_slug == '': slug = slugify(title) elif part_slug != 'index' and is_index_part(text): slug = 'index' elif part_slug == 'biblio': slug = 'biblio' else: slug = part_slug html = wiki.process(user_slug, doc_slug, { slug: copy(text), }, fragment=False, preview=True) template = views.get_template('editor.html') return template.render(page_title="Editing: {:s}".format(title), config=config, user_slug=user_slug, doc_slug=doc_slug, part_slug=part_slug, preview=html, source=escape(text), is_preview=is_preview, can_be_saved=can_be_saved)
def test_split_author(): wiki = Wiki(Settings()) expect = [['Name']] actual = wiki.split_author(trim(""" Name """)) assert expect == actual expect = [['Name', 'Email', 'Affiliation']] actual = wiki.split_author( trim(""" Name / Email / Affiliation """)) assert expect == actual expect = [['Name', 'Email', 'Affiliation'], ['Name2', 'Email2', 'Affiliation2']] actual = wiki.split_author( trim(""" Name / Email / Affiliation + Name2 / Email2 / Affiliation2 """)) assert expect == actual
def test_table_block(): "Simple table, with headers" text = trim(""" | Test 1 | Test 2 | Test 3 | Test 4 """) expect = trim(""" <table class="table table-condensed"> <tbody> <tr> <td class="text-left">Test 1</td> <td class="text-left">Test 2</td> </tr> <tr> <td class="text-left">Test 3</td> <td class="text-left">Test 4</td> </tr> </tbody> </table> """) actual = table_block(text, Settings()) assert expect == actual
def read_document(user_slug, doc_slug): """ Compile the complete html document. """ header_buttons = [ home_button(), edit_button(user_slug, doc_slug, 'index'), ] login = get_login() if not login: header_buttons += [subscribe_button(), rss_button(user_slug)] footer_buttons = [biblio_button(user_slug, doc_slug)] if has_authority_for_user(user_slug): footer_buttons += [upload_button(user_slug, doc_slug)] footer_buttons += [download_button(user_slug, doc_slug)] settings = Settings({ 'config:host': domain_name(bottle.request), 'config:user': user_slug, 'config:document': doc_slug, }) metadata = data.userDocumentMetadata_get(user_slug, doc_slug) html = data.userDocumentCache_get(user_slug, doc_slug) if not html or not metadata: wiki = Wiki(settings) doc_parts = require_document(user_slug, doc_slug) html = wiki.process(user_slug, doc_slug, doc_parts) data.userDocumentCache_set(user_slug, doc_slug, html) metadata = wiki.compile_metadata(config['TIME_ZONE'], user_slug, doc_slug) metadata['url'] = '/read/{:s}/{:s}'.format(user_slug, doc_slug) data.userDocumentMetadata_set(user_slug, doc_slug, metadata) uri = '/read/{:s}/{:s}'.format(user_slug, doc_slug) metadata['url'] = abs_url(bottle.request, uri) author_uri = '/user/{:s}'.format(user_slug) metadata['author_url'] = abs_url(bottle.request, author_uri) metadata['home_url'] = abs_url(bottle.request, '/') image_uri = '/image/card/{:s}/{:s}.jpg'.format(user_slug, doc_slug) metadata['image_url'] = abs_url(bottle.request, image_uri) # @todo: function to split on multi authors as well as emails. title = metadata.get('title', 'Untitled') author = metadata.get('author', 'Anonymous') page_title = "{:s} - {:s}".format(title, author) template = views.get_template('read.html') template.trim_blocks = True template.lstrip_blocks = True page_html = template.render(config=config, page_title=page_title, metadata=metadata, user_slug=user_slug, doc_slug=doc_slug, web_buttons=web_buttons(user_slug, doc_slug), header_buttons=header_buttons, footer_buttons=footer_buttons, content_html=html) return page_html
def user_page(user_slug): """ Show <user_slug>/fixtures/author + user documents. """ header_buttons = [login_or_logout_button()] login = get_login() if login and login['username'] == user_slug: header_buttons += [ new_article_button(user_slug), ] header_buttons += [ edit_button(user_slug, 'fixtures', 'author'), ] if not login: header_buttons += [subscribe_button(), rss_button(user_slug)] footer_buttons = [] if config['ARTICLE_WIKI_CREDIT'] == 'YES': footer_buttons += [source_button()] footer_buttons += [help_button()] if has_authority_for_user(user_slug): footer_buttons += [export_archive_button(user_slug)] slugs = data.userDocumentSet_list(user_slug) changes_list = data.userDocumentLastChanged_list(user_slug) if not has_authority_for_user(user_slug): # Show only those that have been published changes_list, __ = split_published(changes_list) article_slugs = [_ for _ in slugs if _ not in ['fixtures', 'templates']] article_keys = [ data.userDocumentMetadata_key(user_slug, _) for _ in article_slugs ] article_list = sorted(data.get_hashes(article_keys), key=lambda _: _.get('title', '')) published_articles, unpublished_articles = split_published(article_list) if not has_authority_for_user(user_slug): unpublished_articles = [] settings = Settings({ 'config:host': domain_name(bottle.request), 'config:user': user_slug, 'config:document': 'fixtures', }) wiki = Wiki(settings) document = data.userDocument_get(user_slug, 'fixtures') if not document: msg = "User '{:s}' not found." bottle.abort(HTTP_NOT_FOUND, msg.format(user_slug)) if 'author' in document: text = document['author'] else: text = trim(""" Author Page (Author information to be added here...) """) blocks = BlockList(clean_text(text)) page_title, page_summary = blocks.pop_titles() content_html = wiki.process(None, None, {'index': blocks.text()}, fragment=True, preview=True) inline = Inline() return views.get_template('user.html').render( config=config, user=user_slug, page_title="{:s} - {:s}".format(page_title, page_summary), title_html=inline.process(page_title), summary_html=inline.process(page_summary), header_buttons=header_buttons, footer_buttons=footer_buttons, changes_list=changes_list, published_articles=published_articles, unpublished_articles=unpublished_articles, content_html=content_html, pluralize=pluralize # <-- hack function injection )
def test_counters(): settings = Settings() out = settings.replace(trim(""" $[a++], $[a++], $[a++] """)) assert out == "1, 2, 3"
def test_function_block(): block = FunctionBlock(Text, [], '-', 'x') assert block.text() == 'TEXT ---\nx\n---' assert block.html_only(Html(), Settings()) == '<pre>x</pre>'
def test_divider(): block = Divider(trim(""" - - - """)) assert block.text() == '- - -' assert block.html_only(Html(), Settings()) == '<hr class="div-solid" />'
def write_epub(user_slug, doc_slug, file_path): # Get all the data config = load_env_config() data = Data(config) user = data.user_get(user_slug) # or None if not user: raise RuntimeError("User not found: %s", user_slug) document = data.userDocument_get(user_slug, doc_slug) # or Noen if not document: raise RuntimeError("Document not found: %s" % doc_slug) # ------------------------- # 0. Create book # 1. Create cover # 2. Create title page # 3. Create chapter (which basically is the book) # ... This upgrades to multiple chapters when compiling books. # Pre-processing... settings = Settings({ 'config:user': user_slug, 'config:document': doc_slug, }) wiki = Wiki(settings) xhtml = wiki.process(user_slug, doc_slug, document) metadata = wiki.compile_metadata(config['TIME_ZONE'], user_slug, doc_slug) metadata['url'] = '/read/{:s}/{:s}'.format(user_slug, doc_slug), title = metadata.get('title', 'Untitled') summary = metadata.get('summary', '') author = metadata.get('author', 'Anonymous') date = metadata.get('date', '') # ------------------------- # 0. CREATE BOOK book = epub.EpubBook() # set metadata book.set_identifier(user_slug + '+' + doc_slug) book.set_title(title) book.set_language('en') book.add_author(author) # define CSS style with open('static/epub.css') as f: style = f.read() global_css = epub.EpubItem(uid="style_nav", file_name="style/nav.css", media_type="text/css", content=style) book.add_item(global_css) # ------------------------- # 1. Create Cover tmp_cover_file = "/tmp/%s-%s-cover.png" % (user_slug, doc_slug) image = make_background((1600, 2200), (160, 184, 160)) cover = make_cover(image, [title, summary, author, date], [COLOR_TEXT, COLOR_SHADOW]) cover.save(tmp_cover_file, "JPEG") chapter_file_name = doc_slug + '.xhtml' assert os.path.exists(tmp_cover_file) cover_image = open(tmp_cover_file, 'rb').read() book.set_cover("image.jpg", cover_image) # ------------------------- # 2. Create Title Page date_string = datetime.now().strftime("%Y-%m-%d %H:%M:%S") title_xhtml = """ <html> <body> <div>Generated by <i>Article Wiki</i>:</div> <div>%s</div> <div> </div> <div>Permanent URL:</div> <div>http://chapman.wiki/read/%s/%s</div> </body> </html> """ % (date_string, user_slug, doc_slug) c1 = epub.EpubHtml(title="About this book", file_name="title.xhtml", lang='en') c1.content = title_xhtml c1.add_item(global_css) book.add_item(c1) # ------------------------- # 3. Create Chapter c2 = epub.EpubHtml(title=title, file_name=chapter_file_name, lang='en') c2.content = xhtml c2.add_item(global_css) book.add_item(c2) # Define Table Of Contents book.toc = ( epub.Link(chapter_file_name, title, doc_slug), # (epub.Section(user_slug), (c2)) ) # add default NCX and Nav file book.add_item(epub.EpubNcx()) book.add_item(epub.EpubNav()) # basic spine book.spine = ['nav', c1, c2] # write to the file epub.write_epub(file_path, book, {})
class Demo(object): """ Process DEMO blocks and their arguments. Located here because of interdependency with Wiki(). """ regex = r"DEMO\s+(\([^)]+\)\s)?\s*([%s])\2\2\s*\n.+?\n\2\2\2" % \ re.escape(Config.delimiters) def __init__(self, settings=None): "Just a thin wrapper for Placeholders; parse options in replace()." self.placeholders = Placeholders(self.regex, 'demo') if settings: self.settings = settings.copy() else: self.settings = Settings() self.settings.set('config:user', '_') self.settings.set('config:document', random_slug('demo')) def insert(self, parts): "Add placeholders." return self.placeholders.insert(parts) def decorate(self, pattern, part_slug): """ When we process a new demo block it needs to be assigned a unique id_prefix as its config:document name. Micro chance of a collision; maybe replace with a singleton to track random IDs in use. """ self.settings.set('config:document', random_slug('demo')) wiki = Wiki(self.settings) options = match_demo_options(pattern) fragment = ('index' not in options) part_slug = 'index' if 'index' in options else random_slug('demo-') lines = pattern.splitlines() source = "\n".join(lines[1:-1]) output = wiki.process(None, None, {part_slug: source}, fragment) env = Environment(autoescape=True) if 'wide' in options: tpl = env.from_string( trim(""" <div class="wiki-demo-wide space"> <table> <tbody> <tr> <td> <pre>{{ source|safe }}</pre> </td> </tr> <tr> <td class="wiki-demo-output"> {{ output|safe }} </td> </tr> </tbody> </table> </div> """)) else: tpl = env.from_string( trim(""" <div class="wiki-demo space"> <table> <tbody> <tr> <td width="50%"> <pre>{{ source|safe }}</pre> </td> <td> </td> <td width="48%" class="wiki-demo-output"> {{ output|safe }} </td> </tr> </tbody> </table> </div> """)) return tpl.render(source=escape(source), output=output) def replace(self, html_parts, decorator=None): "Generate demo blocks." if decorator: return self.placeholders.replace(decorator, html_parts) else: return self.placeholders.replace(self.decorate, html_parts)