Merge pull request #347 from MinchinWeb/2.0-rc1-markdown-export-fix

2.0 rc1 markdown export fix
This commit is contained in:
Manuel Ebert 2015-04-20 14:29:17 +02:00
commit 0d1b381bd1
12 changed files with 225 additions and 16 deletions

3
.gitignore vendored
View file

@ -50,3 +50,6 @@ docs/_themes/jrnl/static/less/3L.less
# export testing director # export testing director
exp/ exp/
_extras/
*.sublime-*

View file

@ -0,0 +1,11 @@
default_hour: 9
default_minute: 0
editor: ''
encrypt: false
highlight: true
journals:
default: features/journals/markdown-headings-335.journal
linewrap: 80
password: ''
tagsymbols: '@'
timeformat: '%Y-%m-%d %H:%M'

View file

@ -0,0 +1,42 @@
[2015-04-14 13:23] Heading Test
H1-1
=
H1-2
===
H1-3
============================
H2-1
-
H2-2
---
H2-3
----------------------------------
Horizontal Rules (ignore)
---
===
# ATX H1
## ATX H2
### ATX H3
#### ATX H4
##### ATX H5
###### ATX H6
Stuff
More stuff
more stuff again

View file

@ -26,3 +26,52 @@ Feature: Exporting a Journal
Then we should get no error Then we should get no error
and the output should be parsable as json and the output should be parsable as json
and the json output should contain entries.0.uuid = "4BB1F46946AD439996C9B59DE7C4DDC1" and the json output should contain entries.0.uuid = "4BB1F46946AD439996C9B59DE7C4DDC1"
Scenario: Increasing Headings on Markdown export
Given we use the config "markdown-headings-335.yaml"
When we run "jrnl --export markdown"
Then the output should be
"""
2015
====
April
-----
### 2015-04-14 13:23 Heading Test
#### H1-1
#### H1-2
#### H1-3
##### H2-1
##### H2-2
##### H2-3
Horizontal Rules (ignore)
---
===
#### ATX H1
##### ATX H2
###### ATX H3
####### ATX H4
######## ATX H5
######### ATX H6
Stuff
More stuff
more stuff again
"""

View file

@ -271,6 +271,3 @@ def run(manual_args=None):
journal.entries += other_entries journal.entries += other_entries
journal.sort() journal.sort()
journal.write() journal.write()
if __name__ == "__main__":
run()

View file

@ -8,6 +8,7 @@ import importlib
class PluginMeta(type): class PluginMeta(type):
def __init__(cls, name, bases, attrs): def __init__(cls, name, bases, attrs):
"""Called when a Plugin derived class is imported""" """Called when a Plugin derived class is imported"""
if not hasattr(cls, 'PLUGINS'): if not hasattr(cls, 'PLUGINS'):

View file

@ -1,33 +1,67 @@
#!/usr/bin/env python #!/usr/bin/env python
# encoding: utf-8 # encoding: utf-8
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals, print_function
from .text_exporter import TextExporter from .text_exporter import TextExporter
import re
import sys
class MarkdownExporter(TextExporter): class MarkdownExporter(TextExporter):
"""This Exporter can convert entries and journals into json.""" """This Exporter can convert entries and journals into Markdown."""
names = ["md", "markdown"] names = ["md", "markdown"]
extension = "md" extension = "md"
@classmethod @classmethod
def export_entry(cls, entry): def export_entry(cls, entry, to_multifile=True):
"""Returns a markdown representation of a single entry.""" """Returns a markdown representation of a single entry."""
date_str = entry.date.strftime(entry.journal.config['timeformat']) date_str = entry.date.strftime(entry.journal.config['timeformat'])
body_wrapper = "\n" if entry.body else "" body_wrapper = "\n" if entry.body else ""
body = body_wrapper + entry.body body = body_wrapper + entry.body
if to_multifile is True:
heading = '#'
else:
heading = '###'
'''Increase heading levels in body text'''
newbody = ''
previous_line = ''
warn_on_heading_level = False
for line in body.splitlines(True):
if re.match(r"#+ ", line):
"""ATX style headings"""
newbody = newbody + previous_line + heading + line
if re.match(r"#######+ ", heading + line):
warn_on_heading_level = True
line = ''
elif re.match(r"=+$", line) and not re.match(r"^$", previous_line):
"""Setext style H1"""
newbody = newbody + heading + "# " + previous_line
line = ''
elif re.match(r"-+$", line) and not re.match(r"^$", previous_line):
"""Setext style H2"""
newbody = newbody + heading + "## " + previous_line
line = ''
else:
newbody = newbody + previous_line
previous_line = line
newbody = newbody + previous_line # add very last line
if warn_on_heading_level is True:
print("{}WARNING{}: Headings increased past H6 on export - {} {}".format("\033[33m", "\033[0m", date_str, entry.title), file=sys.stderr)
return "{md} {date} {title} {body} {space}".format( return "{md} {date} {title} {body} {space}".format(
md="###", md=heading,
date=date_str, date=date_str,
title=entry.title, title=entry.title,
body=body, body=newbody,
space="" space=""
) )
@classmethod @classmethod
def export_journal(cls, journal): def export_journal(cls, journal):
"""Returns a json representation of an entire journal.""" """Returns a Markdown representation of an entire journal."""
out = [] out = []
year, month = -1, -1 year, month = -1, -1
for e in journal.entries: for e in journal.entries:
@ -39,6 +73,6 @@ class MarkdownExporter(TextExporter):
month = e.date.month month = e.date.month
out.append(e.date.strftime("%B")) out.append(e.date.strftime("%B"))
out.append('-' * len(e.date.strftime("%B")) + "\n") out.append('-' * len(e.date.strftime("%B")) + "\n")
out.append(cls.export_entry(e)) out.append(cls.export_entry(e, False))
result = "\n".join(out) result = "\n".join(out)
return result return result

View file

@ -7,18 +7,18 @@ from .util import get_tags_count
class TagExporter(TextExporter): class TagExporter(TextExporter):
"""This Exporter can convert entries and journals into json.""" """This Exporter can lists the tags for entries and journals, exported as a plain text file."""
names = ["tags"] names = ["tags"]
extension = "tags" extension = "tags"
@classmethod @classmethod
def export_entry(cls, entry): def export_entry(cls, entry):
"""Returns a markdown representation of a single entry.""" """Returns a list of tags for a single entry."""
return ", ".join(entry.tags) return ", ".join(entry.tags)
@classmethod @classmethod
def export_journal(cls, journal): def export_journal(cls, journal):
"""Returns a json representation of an entire journal.""" """Returns a list of tags and their frequency for an entire journal."""
tag_counts = get_tags_count(journal) tag_counts = get_tags_count(journal)
result = "" result = ""
if not tag_counts: if not tag_counts:

View file

@ -56,7 +56,7 @@ class TextExporter(BaseExporter):
representation as unicode if output is None.""" representation as unicode if output is None."""
if output and os.path.isdir(output): # multiple files if output and os.path.isdir(output): # multiple files
return cls.write_files(journal, output) return cls.write_files(journal, output)
elif output: elif output: # single file
return cls.write_file(journal, output) return cls.write_file(journal, output)
else: else:
return cls.export_journal(journal) return cls.export_journal(journal)

View file

@ -0,0 +1,70 @@
#!/usr/bin/env python
# encoding: utf-8
from __future__ import absolute_import, unicode_literals, print_function
from .text_exporter import TextExporter
import re
import sys
import yaml
class MarkdownExporter(TextExporter):
"""This Exporter can convert entries and journals into Markdown with YAML front matter."""
names = ["yaml"]
extension = "md"
@classmethod
def export_entry(cls, entry, to_multifile=True):
"""Returns a markdown representation of a single entry, with YAML front matter."""
if to_multifile is False:
print("{}ERROR{}: YAML export must be to individual files. Please specify a directory to export to.".format("\033[31m", "\033[0m", file=sys.stderr))
return
date_str = entry.date.strftime(entry.journal.config['timeformat'])
body_wrapper = "\n" if entry.body else ""
body = body_wrapper + entry.body
'''Increase heading levels in body text'''
newbody = ''
heading = '###'
previous_line = ''
warn_on_heading_level = False
for line in entry.body.splitlines(True):
if re.match(r"#+ ", line):
"""ATX style headings"""
newbody = newbody + previous_line + heading + line
if re.match(r"#######+ ", heading + line):
warn_on_heading_level = True
line = ''
elif re.match(r"=+$", line) and not re.match(r"^$", previous_line):
"""Setext style H1"""
newbody = newbody + heading + "# " + previous_line
line = ''
elif re.match(r"-+$", line) and not re.match(r"^$", previous_line):
"""Setext style H2"""
newbody = newbody + heading + "## " + previous_line
line = ''
else:
newbody = newbody + previous_line
previous_line = line
newbody = newbody + previous_line # add very last line
if warn_on_heading_level is True:
print("{}WARNING{}: Headings increased past H6 on export - {} {}".format("\033[33m", "\033[0m", date_str, entry.title), file=sys.stderr)
# top = yaml.dump(entry)
return "title: {title}\ndate: {date}\nstared: {stared}\ntags: {tags}\n{body} {space}".format(
date=date_str,
title=entry.title,
stared=entry.starred,
tags=', '.join([tag[1:] for tag in entry.tags]),
body=newbody,
space=""
)
@classmethod
def export_journal(cls, journal):
"""Returns an error, as YAML export requires a directory as a target."""
print("{}ERROR{}: YAML export must be to individual files. Please specify a directory to export to.".format("\033[31m", "\033[0m", file=sys.stderr))
return

View file

@ -146,6 +146,8 @@ def slugify(string):
""" """
string = u(string) string = u(string)
ascii_string = str(unicodedata.normalize('NFKD', string).encode('ascii', 'ignore')) ascii_string = str(unicodedata.normalize('NFKD', string).encode('ascii', 'ignore'))
if PY3:
ascii_string = ascii_string[1:] # removed the leading 'b'
no_punctuation = re.sub(r'[^\w\s-]', '', ascii_string).strip().lower() no_punctuation = re.sub(r'[^\w\s-]', '', ascii_string).strip().lower()
slug = re.sub(r'[-\s]+', '-', no_punctuation) slug = re.sub(r'[-\s]+', '-', no_punctuation)
return u(slug) return u(slug)

View file

@ -78,12 +78,12 @@ setup(
name="jrnl", name="jrnl",
version=get_version(), version=get_version(),
description="A command line journal application that stores your journal in a plain text file", description="A command line journal application that stores your journal in a plain text file",
packages=['jrnl'], packages=['jrnl', 'jrnl.plugins'],
install_requires=[ install_requires=[
"parsedatetime>=1.2", "parsedatetime>=1.2",
"pytz>=2013b", "pytz>=2013b",
"six>=1.7.4", "six>=1.7.4",
"cryptography==0.8.1", "cryptography>=0.8.1",
"tzlocal>=1.1", "tzlocal>=1.1",
"pyyaml>=3.11", "pyyaml>=3.11",
"keyring>=3.3", "keyring>=3.3",