Skip to content

Commit

Permalink
feat(history): Add angular parser
Browse files Browse the repository at this point in the history
This adds a parser that follows the angular specification. The parser is
not hooked into the history evaluation yet. However, it will become the
default parser of commit messages when the evaluator starts using
exchangeable parsers.

Related to #17
  • Loading branch information
relekang committed Aug 18, 2015
1 parent ad7c9c6 commit 91e4f0f
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 2 deletions.
3 changes: 3 additions & 0 deletions semantic_release/errors.py
Expand Up @@ -7,3 +7,6 @@ class SemanticReleaseBaseError(Exception):

class ImproperConfigurationError(SemanticReleaseBaseError):
pass

class UnknownCommitMessageStyle(SemanticReleaseBaseError):
pass
Expand Up @@ -3,8 +3,8 @@
import semver
from invoke import run

from .settings import config
from .vcs_helpers import get_commit_log
from ..settings import config
from ..vcs_helpers import get_commit_log

LEVELS = {
1: 'patch',
Expand Down
57 changes: 57 additions & 0 deletions semantic_release/history/parser_angular.py
@@ -0,0 +1,57 @@
import re

from semantic_release.errors import UnknownCommitMessageStyle

re_parser = re.compile(
r'(?P<type>feat|fix|docs|style|refactor|test|chore)'
r'\((?P<scope>[\w _\-]+)\): '
r'(?P<subject>[^\n]+)'
r'(?:\n\n'
r'(?P<body>[^\n]+))?'
r'(?:\n\n'
r'(?P<footer>[^\n]+))?'
)

TYPES = {
'feat': 'feature',
'fix': 'fix',
'test': 'test',
'docs': 'documentation',
'style': 'style',
'refactor': 'refactor',
'chore': 'chore',
}

def parse_commit_message(message):
"""
Parses a commit message according to the angular commit guidelines specification.
:param message: A string of a commit message.
:return: A tuple of (level to bump, type of change, scope of change, a tuple with descriptions)
"""

if not re_parser.match(message):
raise UnknownCommitMessageStyle(
'Unable to parse the given commit message: {}'.format(message)
)

parsed = re_parser.match(message)
level_bump = 4
if parsed.group('body') and 'BREAKING CHANGE' in parsed.group('body'):
level_bump = 1

if parsed.group('footer') and 'BREAKING CHANGE' in parsed.group('footer'):
level_bump = 1

if parsed.group('type') == 'feat':
level_bump = min([level_bump, 2])

if parsed.group('type') == 'fix':
level_bump = min([level_bump, 2])

return (
level_bump,
TYPES[parsed.group('type')],
parsed.group('scope'),
(parsed.group('subject'), parsed.group('body'), parsed.group('footer'))
)
69 changes: 69 additions & 0 deletions tests/test_parsers.py
@@ -0,0 +1,69 @@
from unittest import TestCase

from semantic_release.errors import UnknownCommitMessageStyle
from semantic_release.history.parser_angular import parse_commit_message as angular_parser


class AngularCommitParserTests(TestCase):

text = 'This is an long explanatory part of a commit message. It should give ' \
'some insight to the fix this commit adds to the codebase.'
footer = 'Closes #400'

def test_parser_raises_unknown_message_style(self):
self.assertRaises(UnknownCommitMessageStyle, angular_parser, '')

def test_parser_return_correct_bump_level(self):
self.assertEqual(
angular_parser('feat(parsers): Add new parser pattern\n\nBREAKING CHANGE:')[0],
1
)
self.assertEqual(
angular_parser('feat(parsers): Add new parser pattern\n\n'
'New pattern is awesome\n\nBREAKING CHANGE:')[0],
1
)
self.assertEqual(angular_parser('feat(parser): Add emoji parser')[0], 2)
self.assertEqual(angular_parser('fix(parser): Fix regex in angular parser')[0], 2)
self.assertEqual(angular_parser('test(parser): Add a test for angular parser')[0], 4)

def test_parser_return_type_from_commit_message(self):
self.assertEqual(angular_parser('feat(parser): ...')[1], 'feature')
self.assertEqual(angular_parser('fix(parser): ...')[1], 'fix')
self.assertEqual(angular_parser('test(parser): ...')[1], 'test')
self.assertEqual(angular_parser('docs(parser): ...')[1], 'documentation')
self.assertEqual(angular_parser('style(parser): ...')[1], 'style')
self.assertEqual(angular_parser('refactor(parser): ...')[1], 'refactor')
self.assertEqual(angular_parser('chore(parser): ...')[1], 'chore')

def test_parser_return_scope_from_commit_message(self):
self.assertEqual(angular_parser('chore(parser): ...')[2], 'parser')
self.assertEqual(angular_parser('chore(a part): ...')[2], 'a part')
self.assertEqual(angular_parser('chore(a_part): ...')[2], 'a_part')
self.assertEqual(angular_parser('chore(a-part): ...')[2], 'a-part')

def test_parser_return_subject_from_commit_message(self):
self.assertEqual(
angular_parser('feat(parser): Add emoji parser')[3][0],
'Add emoji parser'
)
self.assertEqual(
angular_parser('fix(parser): Fix regex in angular parser')[3][0],
'Fix regex in angular parser'
)
self.assertEqual(
angular_parser('test(parser): Add a test for angular parser')[3][0],
'Add a test for angular parser'
)

def test_parser_return_text_from_commit_message(self):
self.assertEqual(
angular_parser('fix(parser): Fix regex in angular parser\n\n{}'.format(self.text))[3][1],
self.text
)

def test_parser_return_footer_from_commit_message(self):
self.assertEqual(
angular_parser('fix(tox): Fix env \n\n{t.text}\n\n{t.footer}'.format(t=self))[3][2],
self.footer
)

0 comments on commit 91e4f0f

Please sign in to comment.