Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Synthesize Github WikiLinks #175

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ along with the following contributors:
- Tom Dunlap ([@motevets](https://github.com/motevets))
- Konstantin Baierer ([@kba](https://github.com/kba))
- Jakub Wilk ([@jwilk](https://github.com/jwilk))
- Tommy Allen ([@tweekmonster](https://github.com/tweekmonster))


[home]: README.md
23 changes: 14 additions & 9 deletions grip/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@

from .app import Grip
from .readers import DirectoryReader, StdinReader, TextReader
from .renderers import GitHubRenderer, OfflineRenderer
from .renderers import GitHubRenderer, GitHubWikiRenderer, OfflineRenderer


def create_app(path=None, user_content=False, context=None, username=None,
password=None, render_offline=False, render_wide=False,
render_inline=False, api_url=None, title=None, text=None,
autorefresh=None, quiet=None, grip_class=None):
autorefresh=None, quiet=None, grip_class=None, wiki=False):
"""
Creates an Grip application with the specified overrides.
"""
Expand All @@ -23,18 +23,21 @@ def create_app(path=None, user_content=False, context=None, username=None,

# Customize the reader
if text is not None:
display_filename = DirectoryReader(path, True).filename_for(None)
display_filename = DirectoryReader(path, True, infer_extensions=wiki) \
.filename_for(None)
source = TextReader(text, display_filename)
elif path == '-':
source = StdinReader()
else:
source = DirectoryReader(path)
source = DirectoryReader(path, infer_extensions=wiki)

# Customize the renderer
if render_offline:
renderer = OfflineRenderer(user_content, context)
elif user_content or context or api_url:
renderer = GitHubRenderer(user_content, context, api_url)
elif wiki:
renderer = GitHubWikiRenderer(user_content, context, api_url)
else:
renderer = None

Expand All @@ -43,20 +46,21 @@ def create_app(path=None, user_content=False, context=None, username=None,

# Create the customized app with default asset manager
return grip_class(source, auth, renderer, None, render_wide,
render_inline, title, autorefresh, quiet)
render_inline, title, autorefresh, quiet, wiki=wiki)


def serve(path=None, host=None, port=None, user_content=False, context=None,
username=None, password=None, render_offline=False,
render_wide=False, render_inline=False, api_url=None, title=None,
autorefresh=True, browser=False, quiet=None, grip_class=None):
autorefresh=True, browser=False, quiet=None, grip_class=None,
wiki=False):
"""
Starts a server to render the specified file or directory containing
a README.
"""
app = create_app(path, user_content, context, username, password,
render_offline, render_wide, render_inline, api_url,
title, None, autorefresh, quiet, grip_class)
title, None, autorefresh, quiet, grip_class, wiki)
app.run(host, port, open_browser=browser)


Expand All @@ -72,13 +76,14 @@ def clear_cache(grip_class=None):
def render_page(path=None, user_content=False, context=None,
username=None, password=None,
render_offline=False, render_wide=False, render_inline=False,
api_url=None, title=None, text=None, grip_class=None):
api_url=None, title=None, text=None, grip_class=None,
wiki=None):
"""
Renders the specified markup text to an HTML page and returns it.
"""
return create_app(path, user_content, context, username, password,
render_offline, render_wide, render_inline, api_url,
title, text, False, None, grip_class).render()
title, text, False, None, grip_class, wiki).render()


def render_content(text, user_content=False, context=None, username=None,
Expand Down
6 changes: 4 additions & 2 deletions grip/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ class Grip(Flask):
def __init__(self, source=None, auth=None, renderer=None,
assets=None, render_wide=None, render_inline=None, title=None,
autorefresh=None, quiet=None, grip_url=None,
static_url_path=None, instance_path=None, **kwargs):
static_url_path=None, instance_path=None, wiki=False,
**kwargs):
# Defaults
if source is None or isinstance(source, str_type):
source = DirectoryReader(source)
source = DirectoryReader(source,
infer_extensions=kwargs.pop('wiki', False))
if render_wide is None:
render_wide = False
if render_inline is None:
Expand Down
5 changes: 3 additions & 2 deletions grip/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
The default is the filename.
--norefresh Do not automatically refresh the Readme content when
the file changes.
--wiki Render as GitHub wiki page.
--quiet Do not print to the terminal.
"""

Expand Down Expand Up @@ -105,7 +106,7 @@ def main(argv=None, force_utf8=True, patch_svg=True):
export(args['<path>'], args['--user-content'], args['--context'],
args['--user'], password, False, args['--wide'],
not args['--no-inline'], args['<address>'],
args['--api-url'], args['--title'])
args['--api-url'], args['--title'], args['--wiki'])
return 0
except ReadmeNotFoundError as ex:
print('Error:', ex)
Expand All @@ -124,7 +125,7 @@ def main(argv=None, force_utf8=True, patch_svg=True):
serve(path, host, port, args['--user-content'], args['--context'],
args['--user'], password, False, args['--wide'], False,
args['--api-url'], args['--title'], not args['--norefresh'],
args['--browser'], args['--quiet'], None)
args['--browser'], args['--quiet'], None, args['--wiki'])
return 0
except ReadmeNotFoundError as ex:
print('Error:', ex)
Expand Down
2 changes: 1 addition & 1 deletion grip/constants.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# The common titles and supported extensions,
# as defined by https://github.com/github/markup
SUPPORTED_TITLES = ['README', 'Readme', 'readme', 'Home']
SUPPORTED_EXTENSIONS = ['.md', '.markdown']
SUPPORTED_EXTENSIONS = ('.md', '.markdown')


# The default filenames when no file is provided
Expand Down
13 changes: 11 additions & 2 deletions grip/readers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from flask import safe_join

from .constants import DEFAULT_FILENAMES, DEFAULT_FILENAME
from .constants import DEFAULT_FILENAMES, DEFAULT_FILENAME, SUPPORTED_EXTENSIONS
from .exceptions import ReadmeNotFoundError
from .vendor.six import add_metaclass

Expand Down Expand Up @@ -85,11 +85,12 @@ class DirectoryReader(ReadmeReader):
"""
Reads Readme files from URL subpaths.
"""
def __init__(self, path=None, silent=False):
def __init__(self, path=None, silent=False, infer_extensions=False):
super(DirectoryReader, self).__init__()
root_filename = os.path.abspath(self._resolve_readme(path, silent))
self.root_filename = root_filename
self.root_directory = os.path.dirname(root_filename)
self.infer_extensions = infer_extensions

def _find_file(self, path, silent=False):
"""
Expand Down Expand Up @@ -193,6 +194,14 @@ def readme_for(self, subpath):
# Join for safety and to convert subpath to normalized OS-specific path
filename = os.path.normpath(safe_join(self.root_directory, subpath))

if self.infer_extensions:
# Append extension if absent in the filename
if not filename.endswith(SUPPORTED_EXTENSIONS):
for ext in SUPPORTED_EXTENSIONS:
if os.path.isfile(filename + ext):
filename += ext
break

# Check for existence
if not os.path.exists(filename):
raise ReadmeNotFoundError(filename)
Expand Down
30 changes: 29 additions & 1 deletion grip/renderers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import print_function, unicode_literals

import os
import re
import json
import sys
Expand Down Expand Up @@ -94,7 +95,7 @@ def render(self, text, auth=None):
headers = {'content-type': 'application/json; charset=UTF-8'}
else:
url = '{0}/markdown/raw'.format(self.api_url)
data = text.encode('utf-8')
data = text.encode('utf8')
headers = {'content-type': 'text/x-markdown; charset=UTF-8'}

r = requests.post(url, headers=headers, data=data, auth=auth)
Expand All @@ -106,6 +107,33 @@ def render(self, text, auth=None):
return r.text if self.raw else self.patch(r.text)


class GitHubWikiRenderer(GitHubRenderer):
def render(self, text, auth=None):
if not self.user_content:
text = self.wiki_patch(text)
return super(GitHubWikiRenderer, self).render(text, auth=auth)

def wiki_patch(self, text):
"""
Pre-process Markdown text to synthesize Github WikiLinks
"""
def urlencode(match):
c = match.group(0)
return ''.join('%{:02X}'.format(ord(b)) for b in c)

def wiki_url(match):
url = match.group(2) or match.group(1)
url = re.sub(r'[\s\+]', '-', url)
url = re.sub(r'[^a-z0-9\-_]', urlencode, url)
md = '[{0}]({1})'.format(match.group(1), url)
ext = os.path.splitext(url)[1]
if ext and ext in ('.jpg', '.jpeg', '.png', '.gif'):
return '!' + md
return md

return re.sub(r'\[\[([^\]\|]+)(?:\|([^\]]+))?\]\]', wiki_url, text)


class OfflineRenderer(ReadmeRenderer):
"""
Renders the specified Readme locally using pure Python.
Expand Down
9 changes: 9 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def test_readme_reader():
def test_directory_reader():
input_path = 'input'
markdown_path = posixpath.join(input_path, 'gfm-test.md')
markdown_no_ext_path = os.path.splitext(markdown_path)[0]
default_path = posixpath.join(input_path, 'default')
input_img_path = posixpath.join(input_path, 'img.png')

Expand Down Expand Up @@ -103,6 +104,14 @@ def test_directory_reader():
assert reader.readme_for(markdown_path) == os.path.abspath(markdown_file)
assert reader.readme_for(default_path) == os.path.abspath(default_file)

with pytest.raises(ReadmeNotFoundError):
reader.readme_for(markdown_no_ext_path)

inferred_reader = DirectoryReader(DIRNAME, silent=True,
infer_extensions=True)
assert inferred_reader.readme_for(markdown_no_ext_path) == \
os.path.abspath(markdown_file)

# TODO: 'README.md' vs 'readme.md'

assert reader.filename_for(None) == DEFAULT_FILENAME
Expand Down