Skip to content

Commit

Permalink
Added code quality checks and cleaned up code.
Browse files Browse the repository at this point in the history
flake8, isort
  • Loading branch information
spookylukey committed Sep 27, 2017
1 parent 753cfcf commit d3f0733
Show file tree
Hide file tree
Showing 11 changed files with 125 additions and 37 deletions.
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# See http://editorconfig.org/
root = true

[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space

[*.py]
indent_size = 4
9 changes: 9 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
include *.md
include LICENSE
include tox.ini
include .editorconfig
recursive-include docs *.bat
recursive-include docs *.py
recursive-include docs *.rst
recursive-include docs Makefile
recursive-include examples *.py
5 changes: 5 additions & 0 deletions docs/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Please contribute by making a fork, and submitting a PR on `GitHub
<https://github.com/python-parsy/parsy>`_.

All contributions will need to be fully covered by unit tests and documentation.
Code should be formatted according to pep8, and the formatting defined by
the ``../.editorconfig`` file.

To run the test suite::

Expand All @@ -22,3 +24,6 @@ To build the docs, do::
pip install sphinx
cd docs
make html

We also require that flake8, isort and checkmanifest report zero errors (these
are run by tox).
27 changes: 16 additions & 11 deletions examples/json.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from parsy import string, regex, generate
import re
from sys import stdin

from parsy import generate, regex, string

whitespace = regex(r'\s*', re.MULTILINE)

lexeme = lambda p: p << whitespace
Expand All @@ -17,21 +18,22 @@
null = lexeme(string('null')).result(None)

number = lexeme(
regex(r'-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?')
regex(r'-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?')
).map(float)

string_part = regex(r'[^"\\]+')
string_esc = string('\\') >> (
string('\\')
| string('/')
| string('b').result('\b')
| string('f').result('\f')
| string('n').result('\n')
| string('r').result('\r')
| string('t').result('\t')
| regex(r'u[0-9a-fA-F]{4}').map(lambda s: chr(int(s[1:], 16)))
string('\\')
| string('/')
| string('b').result('\b')
| string('f').result('\f')
| string('n').result('\n')
| string('r').result('\r')
| string('t').result('\t')
| regex(r'u[0-9a-fA-F]{4}').map(lambda s: chr(int(s[1:], 16)))
)


@lexeme
@generate
def quoted():
Expand All @@ -40,6 +42,7 @@ def quoted():
yield string('"')
return ''.join(body)


@generate
def array():
yield lbrack
Expand All @@ -49,13 +52,15 @@ def array():
rest.insert(0, first)
return rest


@generate
def object_pair():
key = yield quoted
yield colon
val = yield value
return (key, val)


@generate
def json_object():
yield lbrace
Expand All @@ -65,8 +70,8 @@ def json_object():
rest.insert(0, first)
return dict(rest)

value = quoted | number | json_object | array | true | false | null

value = quoted | number | json_object | array | true | false | null
json = whitespace >> value

if __name__ == '__main__':
Expand Down
1 change: 0 additions & 1 deletion requirements.txt

This file was deleted.

13 changes: 13 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[wheel]

[isort]
multi_line_output = 5
line_length = 119
default_section = THIRDPARTY
skip = .tox,.git,docs,dist,build
known_first_party = parsy

[flake8]
exclude = .tox,.git,docs,dist,build
ignore = E731,E221,W503
max-line-length = 119
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env python

from setuptools import setup, find_packages

import os.path

from setuptools import find_packages, setup

# Evaluate version module without importing parsy, which could have undesirable
# effects.
version_file = os.path.join(os.path.dirname(__file__),
Expand Down
41 changes: 31 additions & 10 deletions src/parsy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- #

import re
from .version import __version__
from .version import __version__ # noqa: F401
from functools import wraps
from collections import namedtuple


def line_info_at(stream, index):
if index > len(stream):
raise ValueError("invalid index")
Expand All @@ -13,6 +14,7 @@ def line_info_at(stream, index):
col = index - (last_nl + 1)
return (line, col)


class ParseError(RuntimeError):
def __init__(self, expected, stream, index):
self.expected = expected
Expand All @@ -28,20 +30,26 @@ def line_info(self):
def __str__(self):
return 'expected {} at {}'.format(self.expected, self.line_info())


class Result(namedtuple('Result', 'status index value furthest expected')):
@staticmethod
def success(index, value): return Result(True, index, value, -1, None)
def success(index, value):
return Result(True, index, value, -1, None)

@staticmethod
def failure(index, expected): return Result(False, -1, None, index, expected)
def failure(index, expected):
return Result(False, -1, None, index, expected)

# collect the furthest failure from self and other
def aggregate(self, other):
if not other: return self
if self.furthest >= other.furthest: return self
if not other:
return self
if self.furthest >= other.furthest:
return self

return Result(self.status, self.index, self.value, other.furthest, other.expected)


class Parser(object):
"""
A Parser is an object that wraps a function whose arguments are
Expand Down Expand Up @@ -159,7 +167,7 @@ def __add__(self, other):

def __mul__(self, other):
if isinstance(other, range):
return self.times(other.start, other.stop-1)
return self.times(other.start, other.stop - 1)
return self.times(other)

def __or__(self, other):
Expand All @@ -178,6 +186,7 @@ def __rshift__(self, other):
def __lshift__(self, other):
return self.skip(other)


def alt(*parsers):
if not parsers:
return fail('<empty alt>')
Expand All @@ -194,6 +203,7 @@ def alt_parser(stream, index):

return alt_parser


def seq(*parsers):
"""
Takes a list of list of parsers, runs them in order,
Expand All @@ -217,6 +227,7 @@ def seq_parser(stream, index):

return seq_parser


# combinator syntax
def generate(fn):
if isinstance(fn, str):
Expand All @@ -234,7 +245,8 @@ def generated(stream, index):
while True:
next_parser = iterator.send(value)
result = next_parser(stream, index).aggregate(result)
if not result.status: return result
if not result.status:
return result
value = result.value
index = result.index
except StopIteration as stop:
Expand All @@ -246,21 +258,25 @@ def generated(stream, index):

return generated.desc(fn.__name__)


index = Parser(lambda _, index: Result.success(index, index))
line_info = Parser(lambda stream, index: Result.success(index, line_info_at(stream, index)))


def success(val):
return Parser(lambda _, index: Result.success(index, val))


def fail(expected):
return Parser(lambda _, index: Result.failure(index, expected))


def string(s):
slen = len(s)

@Parser
def string_parser(stream, index):
if stream[index:index+slen] == s:
if stream[index:index + slen] == s:
return Result.success(index + slen, s)
else:
return Result.failure(index, s)
Expand All @@ -269,6 +285,7 @@ def string_parser(stream, index):

return string_parser


def regex(exp, flags=0):
if isinstance(exp, str):
exp = re.compile(exp, flags)
Expand All @@ -285,22 +302,26 @@ def regex_parser(stream, index):

return regex_parser


whitespace = regex(r'\s+')


@Parser
def letter(stream, index):
if index < len(stream) and stream[index].isalpha():
return Result.success(index+1, stream[index])
return Result.success(index + 1, stream[index])
else:
return Result.failure(index, 'a letter')


@Parser
def digit(stream, index):
if index < len(stream) and stream[index].isdigit():
return Result.success(index+1, stream[index])
return Result.success(index + 1, stream[index])
else:
return Result.failure(index, 'a digit')


@Parser
def eof(stream, index):
if index >= len(stream):
Expand Down
10 changes: 7 additions & 3 deletions test/test_parsy.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from parsy import string, regex, generate, ParseError, letter, digit
import pdb
import unittest

from parsy import ParseError, digit, generate, letter, regex, string


class TestParser(unittest.TestCase):

def test_string(self):
Expand Down Expand Up @@ -42,6 +43,7 @@ def binder(x):

def test_generate(self):
x = y = None

@generate
def xy():
nonlocal x
Expand Down Expand Up @@ -76,7 +78,8 @@ def test_generate_desc(self):
def thing():
yield string('t')

with self.assertRaises(ParseError) as err: thing.parse('x')
with self.assertRaises(ParseError) as err:
thing.parse('x')

ex = err.exception

Expand Down Expand Up @@ -165,5 +168,6 @@ def test_times_with_min_and_max_and_then(self):
self.assertRaises(ParseError, then_digit.parse, 'xyzwv1')
self.assertRaises(ParseError, then_digit.parse, 'x1')


if __name__ == '__main__':
unittest.main()
22 changes: 13 additions & 9 deletions test/test_sexpr.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from parsy import string, regex, generate
import re
import pdb
import unittest

from parsy import generate, regex, string

whitespace = regex(r'\s+', re.MULTILINE)
comment = regex(r';.*')
ignore = (whitespace | comment).many()
Expand All @@ -18,23 +18,26 @@

atom = true | false | number | symbol


@generate('a form')
def form():
yield lparen
els = yield expr.many()
yield rparen
return els


@generate
def quote():
yield string("'")
e = yield expr
return ['quote', e]

expr = form | quote | atom

expr = form | quote | atom
program = ignore >> expr.many()


class TestSexpr(unittest.TestCase):
def test_form(self):
result = program.parse('(1 2 3)')
Expand All @@ -55,15 +58,16 @@ def test_boolean(self):

def test_comments(self):
result = program.parse(
"""
; a program with a comment
( foo ; that's a foo
bar )
; some comments at the end
"""
"""
; a program with a comment
( foo ; that's a foo
bar )
; some comments at the end
"""
)

self.assertEqual(result, [['foo', 'bar']])


if __name__ == '__main__':
unittest.main()
Loading

0 comments on commit d3f0733

Please sign in to comment.