Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
waynemoore committed Dec 18, 2012
0 parents commit a90cc44
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.DS_Store

*.pyc
*.sublime-project
*.sublime-workspace
2 changes: 2 additions & 0 deletions Default (Linux).sublime-keymap
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[
]
6 changes: 6 additions & 0 deletions Default (OSX).sublime-keymap
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
{
"keys": ["ctrl+alt+g"], "command": "format_gherkin"
}

]
2 changes: 2 additions & 0 deletions Default (Windows).sublime-keymap
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[
]
92 changes: 92 additions & 0 deletions gherkin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import re

from StringIO import StringIO


class GherkinParser(object):
TEXT = 1
GROUP = 2

def __init__(self, gherkin_text):
self._gherkin_text = StringIO(gherkin_text)
self._tokens = []
self._group = []

def parse(self):
example_re = re.compile(r'\s*\|[^|]+\|\s*')

for line in self._gherkin_text.readlines():
line = line.strip()

if example_re.match(line):
self._collect_group_item(line)
else:
self._store_and_reset_group()
self._add_text_token(line)

self._finish()

return self._tokens

def _collect_group_item(self, line):
elements = [item.strip() for item in line.split('|')[1:-1]]
self._group.append(elements)

def _new_group(self):
self._group = []

def _store_and_reset_group(self):
if len(self._group) > 0:
self._tokens.append((GherkinParser.GROUP, self._group))
self._new_group()

def _add_text_token(self, text):
self._tokens.append((GherkinParser.TEXT, text))

def _finish(self):
self._store_and_reset_group()


class GherkinFormatter(object):

def __init__(self):
self._result = StringIO()

def format(self, parsed):
for token_type, token in parsed:
if token_type == GherkinParser.TEXT:
self._emit(token)
elif token_type == GherkinParser.GROUP:
self._format_group(token)
else:
raise Exception('unsupported token type %s' % token_type)

result = self._result.getvalue()
self._result.close()

return result

def _format_group(self, group):
widths = []
for i in range(len(group[0])):
width = 0
for j in range(len(group)):
width = max(len(group[j][i]), width)
widths.append(width)

for line in group:
buf = "|"

for idx, col in enumerate(line):
width = widths[idx]
padding = width - len(col)
buf += ' '
buf += col
buf += ' ' * padding
buf += ' |'

self._emit(buf)

def _emit(self, text):
self._result.write(text)
self._result.write('\n')
2 changes: 2 additions & 0 deletions requirements.pip
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
nose>=1.2.1
sure>=1.1.4
14 changes: 14 additions & 0 deletions sublime-gherkin-formatter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import sublime, sublime_plugin

from gherkin import GherkinParser, GherkinFormatter

class FormatGherkinCommand(sublime_plugin.TextCommand):

def run(self, edit):
entire_buffer = sublime.Region(0, self.view.size())

parser = GherkinParser(self.view.substr(entire_buffer))
parsed = parser.parse()
result = GherkinFormatter().format(parsed)

self.view.replace(edit, entire_buffer, result)
Empty file added tests/__init__.py
Empty file.
146 changes: 146 additions & 0 deletions tests/gherkin_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import sure
import unittest

from gherkin import GherkinParser, GherkinFormatter


class GherkinParserTestCase(unittest.TestCase):

def test_it_should_parse_non_example_tables_as_text(self):
feature = "\
Feature: As a tester\n\
I want my non example table text to remain intact\n\
So that I don't want kill the author of this plugin"

tokens = GherkinParser(feature).parse()
tokens.should.have.length_of(3)
tokens[0].should.equal((GherkinParser.TEXT, 'Feature: As a tester'))
tokens[1].should.equal((GherkinParser.TEXT, 'I want my non example table text to remain intact'))
tokens[2].should.equal((GherkinParser.TEXT, 'So that I don\'t want kill the author of this plugin'))

def test_it_should_parse_example_groups(self):
example_text = "\
|cat breeds|country|\n\
|Bengal|United States|\n\
|Burmese|Burma|"

tokens = GherkinParser(example_text).parse()
tokens.should.have.length_of(1)

group = tokens[0]
group[0].should.equal(GherkinParser.GROUP)

examples = group[1]
examples.should.have.length_of(3)
examples[0].should.equal(['cat breeds', 'country'])
examples[1].should.equal(['Bengal', 'United States'])
examples[2].should.equal(['Burmese', 'Burma'])

def test_it_should_parse_text_and_groups(self):
feature = "\
Feature: As a crazy cat person\n\
I want to write a list of cat breeds\n\
So that my codez is odd\n\
\n\
|cat breeds|country|\n\
|Manx|Isle of Man|\n\
|Octocat|The Web|\n"

tokens = GherkinParser(feature).parse()
tokens.should.have.length_of(5)

tokens[0].should.equal((GherkinParser.TEXT, 'Feature: As a crazy cat person'))
tokens[1].should.equal((GherkinParser.TEXT, 'I want to write a list of cat breeds'))
tokens[2].should.equal((GherkinParser.TEXT, 'So that my codez is odd'))
tokens[3].should.equal((GherkinParser.TEXT, ''))

group = tokens[4]
group[0].should.equal(GherkinParser.GROUP)

examples = group[1]
examples.should.have.length_of(3)
examples[0].should.equal(['cat breeds', 'country'])
examples[1].should.equal(['Manx', 'Isle of Man'])
examples[2].should.equal(['Octocat', 'The Web'])

def test_it_should_parse_multiple_text_and_group_sections(self):
feature = "\
foo\n\
|exampleA|\n\
bar\n\
|example1|example2|\n"

tokens = GherkinParser(feature).parse()
tokens.should.have.length_of(4)

tokens[0].should.equal((GherkinParser.TEXT, 'foo'))

group = tokens[1]
group[0].should.equal(GherkinParser.GROUP)
group[1].should.equal([['exampleA']])

tokens[2].should.equal((GherkinParser.TEXT, 'bar'))

group = tokens[3]
group[0].should.equal(GherkinParser.GROUP)
group[1].should.equal([['example1', 'example2']])


class GherkinFormatterTestCase(unittest.TestCase):

def test_it_should_strip_leading_whitespace_of_text(self):
feature = "\
Feature: As a tester\n\
I want my non example table text to remain intact\n\
So that I don't want kill the author of this plugin\n"

parsed = GherkinParser(feature).parse()
text = GherkinFormatter().format(parsed)

text.should.equal(_strip_leading_whitespace(feature))

def test_it_should_format_example_group_columns_to_widest_value(self):
examples = "\
|cat breeds|country|\n\
|Manx|Isle of Man|\n\
|Octocat|The Web|\n"

parsed = GherkinParser(examples).parse()
text = GherkinFormatter().format(parsed)

lines = text.split('\n')
lines.should.have.length_of(4)
lines[0].should.equal('| cat breeds | country |')
lines[1].should.equal('| Manx | Isle of Man |')
lines[2].should.equal('| Octocat | The Web |')
lines[3].should.equal('')

def test_it_should_format_combinations_of_text_and_example_groups(self):
feature = "\
Feature: As a tester\n\
I want my non example table text to remain intact\n\
So that I don't want kill the author of this plugin\n\
\n\
|cat breeds|country|\n\
|Manx|Isle of Man|\n\
|Octocat|The Web|\n"

parsed = GherkinParser(feature).parse()
text = GherkinFormatter().format(parsed)

lines = text.split('\n')
lines.should.have.length_of(8)
lines[0].should.equal('Feature: As a tester')
lines[1].should.equal('I want my non example table text to remain intact')
lines[2].should.equal('So that I don\'t want kill the author of this plugin')
lines[3].should.equal('')
lines[4].should.equal('| cat breeds | country |')
lines[5].should.equal('| Manx | Isle of Man |')
lines[6].should.equal('| Octocat | The Web |')
lines[7].should.equal('')


def _strip_leading_whitespace(text):
lines = map(lambda l: l.lstrip(), text.split("\n"))
return '\n'.join(lines)

0 comments on commit a90cc44

Please sign in to comment.