Repository: jeanralphaviles/comment_parser

Issue:
Extract comments from string?
Is there any way of extract comments from a string? I tried this:
 
`comment_parser.extract_comments(a_lis  mime='text/x-java-source'')`

However, I got:

```
~/anaconda3/envs/lib/python3.6/site-packages/comment_parser/comment_parser.py in extract_comments(filename, mime)
     78     except common.Error as exception:
     79         raise ParseError(str(exception))
---> 80     return parser.extract_comments(filename)
     81 
     82 

~/anaconda3/envs/lib/python3.6/site-packages/comment_parser/parsers/c_parser.py in extract_comments(filename)
     73         return comments
     74     except OSError as exception:
---> 75         raise common.FileError(str(exception))
     76 

FileError: [Errno 36] File name too long:```

I guess it would be very handy to be able to extract the comments from a string. In my case, `a_list` is  a list which contains the code in a string format.

Discussion:

Patch:
diff --git a/README.md b/README.md
index e152862..47bfe3e 100644
--- a/README.md
+++ b/README.md
@@ -32,8 +32,10 @@ To use, simply run:
 >>> from comment_parser import comment_parser
 >>> # Returns a list of comment_parser.parsers.common.Comments
 >>> comment_parser.extract_comments('/path/to/source_file')
+>>> # Or
+>>> comment_parser.extract_comments_from_str('...')
 ```
-### extract_comments signature
+### extract_comments signatures
 
 ```python
 def extract_comments(filename, mime=None):
@@ -50,6 +52,24 @@ def extract_comments(filename, mime=None):
     Raises:
         UnsupportedError: If filename is of an unsupported MIME type.
     """
+    pass
+
+
+def extract_comments_from_str(code, mime=None):
+    """Extracts and returns comments from the given source string.
+
+    Args:
+        code: String containing code to extract comments from.
+        mime: Optional MIME type for code (str). Note some MIME types accepted
+            don't comply with RFC2045. If not given, an attempt to deduce the
+            MIME type will occur.
+    Returns:
+        Python list of parsers.common.Comment in the order that they appear in
+            the source code.
+    Raises:
+        UnsupportedError: If code is of an unsupported MIME type.
+    """
+    pass
 ```
 ### Comments Interface
 
diff --git a/comment_parser/comment_parser.py b/comment_parser/comment_parser.py
index 6b1cbdb..2396415 100755
--- a/comment_parser/comment_parser.py
+++ b/comment_parser/comment_parser.py
@@ -66,18 +66,35 @@ def extract_comments(filename, mime=None):
     Raises:
         UnsupportedError: If filename is of an unsupported MIME type.
     """
+    with open(filename, 'r') as code:
+        return extract_comments_from_str(code.read(), mime)
+
+
+def extract_comments_from_str(code, mime=None):
+    """Extracts and returns comments from the given source string.
+
+    Args:
+        code: String containing code to extract comments from.
+        mime: Optional MIME type for code (str). Note some MIME types accepted
+            don't comply with RFC2045. If not given, an attempt to deduce the
+            MIME type will occur.
+    Returns:
+        Python list of parsers.common.Comment in the order that they appear in
+            the source code.
+    Raises:
+        UnsupportedError: If code is of an unsupported MIME type.
+    """
     if not mime:
-        mime = magic.from_file(filename, mime=True)
+        mime = magic.from_buffer(code, mime=True)
         if type(mime) == bytes:
             mime = mime.decode('utf-8')
     if mime not in MIME_MAP:
-        raise UnsupportedError(
-            'Unsupported MIME type %s for file %s' % (mime, filename))
+        raise UnsupportedError('Unsupported MIME type %s' % mime)
     try:
         parser = MIME_MAP[mime]
-    except common.Error as exception:
-        raise ParseError(str(exception))
-    return parser.extract_comments(filename)
+    except common.Error as e:
+        raise ParseError(str(e))
+    return parser.extract_comments(code)
 
 
 def main(argv):
diff --git a/comment_parser/parsers/c_parser.py b/comment_parser/parsers/c_parser.py
index 938e3ca..06c359b 100644
--- a/comment_parser/parsers/c_parser.py
+++ b/comment_parser/parsers/c_parser.py
@@ -8,10 +8,13 @@ Works with:
     Java
 """
 
+import re
+from bisect import bisect_left
 from comment_parser.parsers import common as common
 
-def extract_comments(filename):
-    """Extracts a list of comments from the given C family source file.
+
+def extract_comments(code):
+    """Extracts a list of comments from the given C family source code.
 
     Comments are represented with the Comment class found in the common module.
     C family comments come in two forms, single and multi-line comments.
@@ -24,53 +27,42 @@ def extract_comments(filename):
     consideration.
 
     Args:
-        filename: String name of the file to extract comments from.
+        code: String containing code to extract comments from.
     Returns:
-        Python list of common.Comment in the order that they appear in the file.
+        Python list of common.Comment in the order that they appear in the code.
     Raises:
-        common.FileError: File was unable to be open or read.
         common.UnterminatedCommentError: Encountered an unterminated multi-line
             comment.
     """
-    try:
-        import re
-        from bisect import bisect_left
-
-        pattern = r"""
-            (?P<literal> (\"([^\"\n])*\")+) |
-            (?P<single> //(?P<single_content>.*)?$) |
-            (?P<multi> /\*(?P<multi_content>(.|\n)*?)?\*/) |
-            (?P<error> /\*(.*)?)
-        """
-
-        compiled = re.compile(pattern, re.VERBOSE | re.MULTILINE)
-
-        with open(filename, 'r') as source_file:
-            content = source_file.read()
+    pattern = r"""
+        (?P<literal> (\"([^\"\n])*\")+) |
+        (?P<single> //(?P<single_content>.*)?$) |
+        (?P<multi> /\*(?P<multi_content>(.|\n)*?)?\*/) |
+        (?P<error> /\*(.*)?)
+    """
 
-        lines_indexes = []
-        for match in re.finditer(r"$", content, re.M):
-            lines_indexes.append(match.start())
+    compiled = re.compile(pattern, re.VERBOSE | re.MULTILINE)
 
-        comments = []
-        for match in compiled.finditer(content):
-            kind = match.lastgroup
+    lines_indexes = []
+    for match in re.finditer(r"$", code, re.M):
+        lines_indexes.append(match.start())
 
-            start_character = match.start()
-            line_no = bisect_left(lines_indexes, start_character)
+    comments = []
+    for match in compiled.finditer(code):
+        kind = match.lastgroup
 
-            if kind == "single":
-                comment_content = match.group("single_content")
-                comment = common.Comment(comment_content, line_no + 1)
-                comments.append(comment)
-            elif kind == "multi":
-                comment_content = match.group("multi_content")
-                comment = common.Comment(comment_content, line_no + 1, multiline=True)
-                comments.append(comment)
-            elif kind == "error":
-                raise common.UnterminatedCommentError()
+        start_character = match.start()
+        line_no = bisect_left(lines_indexes, start_character)
 
-        return comments
-    except OSError as exception:
-        raise common.FileError(str(exception))
+        if kind == "single":
+            comment_content = match.group("single_content")
+            comment = common.Comment(comment_content, line_no + 1)
+            comments.append(comment)
+        elif kind == "multi":
+            comment_content = match.group("multi_content")
+            comment = common.Comment(comment_content, line_no + 1, multiline=True)
+            comments.append(comment)
+        elif kind == "error":
+            raise common.UnterminatedCommentError()
 
+    return comments
diff --git a/comment_parser/parsers/go_parser.py b/comment_parser/parsers/go_parser.py
index e328e86..32e37b5 100644
--- a/comment_parser/parsers/go_parser.py
+++ b/comment_parser/parsers/go_parser.py
@@ -1,11 +1,11 @@
 #!/usr/bin/python
-"""This module provides methods for parsing comments from Go Source files."""
+"""This module provides methods for parsing comments from Go source code."""
 
 from comment_parser.parsers import common as common
 
 
-def extract_comments(filename):
-    """Extracts a list of comments from the given Go source file.
+def extract_comments(code):
+    """Extracts a list of comments from the given Go source code.
 
     Comments are represented with the Comment class found in the common module.
     Go comments come in two forms, single and multi-line comments.
@@ -19,92 +19,86 @@ def extract_comments(filename):
     https://golang.org/ref/spec#Comments
 
     Args:
-        filename: String name of the file to extract comments from.
+        code: String containing code to extract comments from.
     Returns:
-        Python list of common.Comment in the order that they appear in the file.
+        Python list of common.Comment in the order that they appear in the code.
     Raises:
-        common.FileError: File was unable to be open or read.
         common.UnterminatedCommentError: Encountered an unterminated multi-line
             comment.
     """
-    try:
-        with open(filename, 'r') as source_file:
-            state = 0
-            current_comment = ''
-            comments = []
-            line_counter = 1
-            comment_start = 1
-            string_char = ''
-            while True:
-                char = source_file.read(1)
-                if not char:
-                    if state is 3 or state is 4:
-                        raise common.UnterminatedCommentError()
-                    if state is 2:
-                        # Was in single-line comment. Create comment.
-                        comment = common.Comment(current_comment, line_counter)
-                        comments.append(comment)
-                    return comments
-                if state is 0:
-                    # Waiting for comment start character or beginning of
-                    # string or rune literal.
-                    if char == '/':
-                        state = 1
-                    elif char == '"' or char == "'" or char == '`':
-                        string_char = char
-                        state = 5
-                elif state is 1:
-                    # Found comment start character, classify next character and
-                    # determine if single or multi-line comment.
-                    if char == '/':
-                        state = 2
-                    elif char == '*':
-                        comment_start = line_counter
-                        state = 3
-                    else:
-                        state = 0
-                elif state is 2:
-                    # In single-line comment, read characters util EOL.
-                    if char == '\n':
-                        comment = common.Comment(current_comment, line_counter)
-                        comments.append(comment)
-                        current_comment = ''
-                        state = 0
-                    else:
-                        current_comment += char
-                elif state is 3:
-                    # In multi-line comment, add characters until '*' is
-                    # encountered.
-                    if char == '*':
-                        state = 4
-                    else:
-                        current_comment += char
-                elif state is 4:
-                    # In multi-line comment with asterisk found. Determine if
-                    # comment is ending.
-                    if char == '/':
-                        comment = common.Comment(
-                            current_comment, comment_start, multiline=True)
-                        comments.append(comment)
-                        current_comment = ''
-                        state = 0
-                    else:
-                        current_comment += '*'
-                        # Care for multiple '*' in a row
-                        if char != '*':
-                            current_comment += char
-                            state = 3
-                elif state is 5:
-                    # In string literal, expect literal end or escape character.
-                    if char == string_char:
-                        state = 0
-                    elif char == '\\':
-                        state = 6
-                elif state is 6:
-                    # In string literal, escaping current char.
-                    state = 5
-                if char == '\n':
-                    line_counter += 1
-    except OSError as exception:
-        raise common.FileError(str(exception))
+    state = 0
+    current_comment = ''
+    comments = []
+    line_counter = 1
+    comment_start = 1
+    string_char = ''
+    for char in code:
+        if state is 0:
+            # Waiting for comment start character or beginning of
+            # string or rune literal.
+            if char == '/':
+                state = 1
+            elif char == '"' or char == "'" or char == '`':
+                string_char = char
+                state = 5
+        elif state is 1:
+            # Found comment start character, classify next character and
+            # determine if single or multi-line comment.
+            if char == '/':
+                state = 2
+            elif char == '*':
+                comment_start = line_counter
+                state = 3
+            else:
+                state = 0
+        elif state is 2:
+            # In single-line comment, read characters util EOL.
+            if char == '\n':
+                comment = common.Comment(current_comment, line_counter)
+                comments.append(comment)
+                current_comment = ''
+                state = 0
+            else:
+                current_comment += char
+        elif state is 3:
+            # In multi-line comment, add characters until '*' is
+            # encountered.
+            if char == '*':
+                state = 4
+            else:
+                current_comment += char
+        elif state is 4:
+            # In multi-line comment with asterisk found. Determine if
+            # comment is ending.
+            if char == '/':
+                comment = common.Comment(
+                    current_comment, comment_start, multiline=True)
+                comments.append(comment)
+                current_comment = ''
+                state = 0
+            else:
+                current_comment += '*'
+                # Care for multiple '*' in a row
+                if char != '*':
+                    current_comment += char
+                    state = 3
+        elif state is 5:
+            # In string literal, expect literal end or escape character.
+            if char == string_char:
+                state = 0
+            elif char == '\\':
+                state = 6
+        elif state is 6:
+            # In string literal, escaping current char.
+            state = 5
+        if char == '\n':
+            line_counter += 1
 
+    # EOF.
+    if state is 3 or state is 4:
+        raise common.UnterminatedCommentError()
+    if state is 2:
+        # Was in single-line comment. Create comment.
+        comment = common.Comment(current_comment, line_counter)
+        comments.append(comment)
+    return comments
diff --git a/comment_parser/parsers/html_parser.py b/comment_parser/parsers/html_parser.py
index 793a35d..2596405 100644
--- a/comment_parser/parsers/html_parser.py
+++ b/comment_parser/parsers/html_parser.py
@@ -6,63 +6,54 @@ Works with:
     XML
 """
 
+import re
+from bisect import bisect_left
 from comment_parser.parsers import common as common
 
-def extract_comments(filename):
-    """Extracts a list of comments from the given HTML family source file.
+def extract_comments(code):
+    """Extracts a list of comments from the given HTML family source code.
 
     Comments are represented with the Comment class found in the common module.
     HTML family comments come in one form, comprising all text within '<!--' and
     '-->' markers. Comments cannot be nested.
 
     Args:
-        filename: String name of the file to extract comments from.
+        code: String containing code to extract comments from.
     Returns:
-        Python list of common.Comment in the order that they appear in the file.
+        Python list of common.Comment in the order that they appear in the code..
     Raises:
-        common.FileError: File was unable to be open or read.
         common.UnterminatedCommentError: Encountered an unterminated multi-line
             comment.
     """
-    try:
-        import re
-        from bisect import bisect_left
-
-        pattern = r"""
-            (?P<literal> (\"([^\"\n])*\")+) |
-            (?P<single> <!--(?P<single_content>.*?)-->) |
-            (?P<multi> <!--(?P<multi_content>(.|\n)*?)?-->) |
-            (?P<error> <!--(.*)?)
-        """
-
-        compiled = re.compile(pattern, re.VERBOSE | re.MULTILINE)
-
-        with open(filename, 'r') as source_file:
-            content = source_file.read()
-
-        lines_indexes = []
-        for match in re.finditer(r"$", content, re.M):
-            lines_indexes.append(match.start())
-
-        comments = []
-        for match in compiled.finditer(content):
-            kind = match.lastgroup
-
-            start_character = match.start()
-            line_no = bisect_left(lines_indexes, start_character)
-
-            if kind == "single":
-                comment_content = match.group("single_content")
-                comment = common.Comment(comment_content, line_no + 1)
-                comments.append(comment)
-            elif kind == "multi":
-                comment_content = match.group("multi_content")
-                comment = common.Comment(
-                    comment_content, line_no + 1, multiline=True)
-                comments.append(comment)
-            elif kind == "error":
-                raise common.UnterminatedCommentError()
-
-        return comments
-    except OSError as exception:
-        raise common.FileError(str(exception))
+    pattern = r"""
+        (?P<literal> (\"([^\"\n])*\")+) |
+        (?P<single> <!--(?P<single_content>.*?)-->) |
+        (?P<multi> <!--(?P<multi_content>(.|\n)*?)?-->) |
+        (?P<error> <!--(.*)?)
+    """
+    compiled = re.compile(pattern, re.VERBOSE | re.MULTILINE)
+
+    lines_indexes = []
+    for match in re.finditer(r"$", code, re.M):
+        lines_indexes.append(match.start())
+
+    comments = []
+    for match in compiled.finditer(code):
+        kind = match.lastgroup
+
+        start_character = match.start()
+        line_no = bisect_left(lines_indexes, start_character)
+
+        if kind == "single":
+            comment_content = match.group("single_content")
+            comment = common.Comment(comment_content, line_no + 1)
+            comments.append(comment)
+        elif kind == "multi":
+            comment_content = match.group("multi_content")
+            comment = common.Comment(
+                comment_content, line_no + 1, multiline=True)
+            comments.append(comment)
+        elif kind == "error":
+            raise common.UnterminatedCommentError()
+
+    return comments
diff --git a/comment_parser/parsers/js_parser.py b/comment_parser/parsers/js_parser.py
index f4f0abd..546cabb 100644
--- a/comment_parser/parsers/js_parser.py
+++ b/comment_parser/parsers/js_parser.py
@@ -1,11 +1,11 @@
 #!/usr/bin/python
-"""This module provides methods for parsing comments from Javascript files."""
+"""This module provides methods for parsing comments from Javascript code."""
 
 from comment_parser.parsers import common as common
 
 
-def extract_comments(filename):
-    """Extracts a list of comments from the given Javascript source file.
+def extract_comments(code):
+    """Extracts a list of comments from the given Javascript source code.
 
     Comments are represented with the Comment class found in the common module.
     Javascript comments come in two forms, single and multi-line comments.
@@ -14,95 +14,89 @@ def extract_comments(filename):
             multiple lines of code. If a multi-line comment does not terminate
             before EOF is reached, then an exception is raised.
     This module takes quoted strings into account when extracting comments from
-    source files.
+    source code.
 
     Args:
-        filename: String name of the file to extract comments from.
+        code: String containing code to extract comments from.
     Returns:
-        Python list of common.Comment in the order that they appear in the file.
+        Python list of common.Comment in the order that they appear in the code.
     Raises:
-        common.FileError: File was unable to be open or read.
         common.UnterminatedCommentError: Encountered an unterminated multi-line
             comment.
     """
-    try:
-        with open(filename, 'r') as source_file:
-            state = 0
-            current_comment = ''
-            comments = []
-            line_counter = 1
-            comment_start = 1
-            string_char = ''
-            while True:
-                char = source_file.read(1)
-                if not char:
-                    if state is 3 or state is 4:
-                        raise common.UnterminatedCommentError()
-                    if state is 2:
-                        # Was in single-line comment. Create comment.
-                        comment = common.Comment(current_comment, line_counter)
-                        comments.append(comment)
-                    return comments
-                if state is 0:
-                    # Waiting for comment start character or beginning of
-                    # string.
-                    if char == '/':
-                        state = 1
-                    elif char == '"' or char == "'":
-                        string_char = char
-                        state = 5
-                elif state is 1:
-                    # Found comment start character, classify next character and
-                    # determine if single or multi-line comment.
-                    if char == '/':
-                        state = 2
-                    elif char == '*':
-                        comment_start = line_counter
-                        state = 3
-                    else:
-                        state = 0
-                elif state is 2:
-                    # In single-line comment, read characters util EOL.
-                    if char == '\n':
-                        comment = common.Comment(current_comment, line_counter)
-                        comments.append(comment)
-                        current_comment = ''
-                        state = 0
-                    else:
-                        current_comment += char
-                elif state is 3:
-                    # In multi-line comment, add characters until '*' is
-                    # encountered.
-                    if char == '*':
-                        state = 4
-                    else:
-                        current_comment += char
-                elif state is 4:
-                    # In multi-line comment with asterisk found. Determine if
-                    # comment is ending.
-                    if char == '/':
-                        comment = common.Comment(
-                            current_comment, comment_start, multiline=True)
-                        comments.append(comment)
-                        current_comment = ''
-                        state = 0
-                    else:
-                        current_comment += '*'
-                        # Care for multiple '*' in a row
-                        if char != '*':
-                            current_comment += char
-                            state = 3
-                elif state is 5:
-                    # In string literal, expect literal end or escape character.
-                    if char == string_char:
-                        state = 0
-                    elif char == '\\':
-                        state = 6
-                elif state is 6:
-                    # In string literal, escaping current char.
-                    state = 5
-                if char == '\n':
-                    line_counter += 1
-    except OSError as exception:
-        raise common.FileError(str(exception))
+    state = 0
+    current_comment = ''
+    comments = []
+    line_counter = 1
+    comment_start = 1
+    string_char = ''
+    for char in code:
+        if state is 0:
+            # Waiting for comment start character or beginning of
+            # string.
+            if char == '/':
+                state = 1
+            elif char == '"' or char == "'":
+                string_char = char
+                state = 5
+        elif state is 1:
+            # Found comment start character, classify next character and
+            # determine if single or multi-line comment.
+            if char == '/':
+                state = 2
+            elif char == '*':
+                comment_start = line_counter
+                state = 3
+            else:
+                state = 0
+        elif state is 2:
+            # In single-line comment, read characters util EOL.
+            if char == '\n':
+                comment = common.Comment(current_comment, line_counter)
+                comments.append(comment)
+                current_comment = ''
+                state = 0
+            else:
+                current_comment += char
+        elif state is 3:
+            # In multi-line comment, add characters until '*' is
+            # encountered.
+            if char == '*':
+                state = 4
+            else:
+                current_comment += char
+        elif state is 4:
+            # In multi-line comment with asterisk found. Determine if
+            # comment is ending.
+            if char == '/':
+                comment = common.Comment(
+                    current_comment, comment_start, multiline=True)
+                comments.append(comment)
+                current_comment = ''
+                state = 0
+            else:
+                current_comment += '*'
+                # Care for multiple '*' in a row
+                if char != '*':
+                    current_comment += char
+                    state = 3
+        elif state is 5:
+            # In string literal, expect literal end or escape character.
+            if char == string_char:
+                state = 0
+            elif char == '\\':
+                state = 6
+        elif state is 6:
+            # In string literal, escaping current char.
+            state = 5
+        if char == '\n':
+            line_counter += 1
 
+    # EOF.
+    if state is 3 or state is 4:
+        raise common.UnterminatedCommentError()
+    if state is 2:
+        # Was in single-line comment. Create comment.
+        comment = common.Comment(current_comment, line_counter)
+        comments.append(comment)
+    return comments
diff --git a/comment_parser/parsers/shell_parser.py b/comment_parser/parsers/shell_parser.py
index 0babe3d..21e1328 100644
--- a/comment_parser/parsers/shell_parser.py
+++ b/comment_parser/parsers/shell_parser.py
@@ -4,7 +4,7 @@
 from comment_parser.parsers import common as common
 
 
-def extract_comments(filename):
+def extract_comments(code):
     """Extracts a list of comments from the given shell script.
 
     Comments are represented with the Comment class found in the common module.
@@ -15,61 +15,53 @@ def extract_comments(filename):
     immediately preceeded by a backslash '\'
 
     Args:
-        filename: String name of the file to extract comments from.
+        code: String containing code to extract comments from.
     Returns:
-        Python list of common.Comment in the order that they appear in the file.
-    Raises:
-        common.FileError: File was unable to be open or read.
+        Python list of common.Comment in the order that they appear in the code.
     """
-    try:
-        with open(filename, 'r') as source_file:
+    state = 0
+    string_char = ''
+    current_comment = ''
+    comments = []
+    line_counter = 1
+    for char in code:
+        if state is 0:
+            # Waiting for comment start character, beginning of string,
+            # or escape character.
+            if char == '#':
+                state = 1
+            elif char == '"' or char == "'":
+                string_char = char
+                state = 2
+            elif char == '\\':
+                state = 4
+        elif state is 1:
+            # Found comment start character. Read comment until EOL.
+            if char == '\n':
+                comment = common.Comment(current_comment, line_counter)
+                comments.append(comment)
+                current_comment = ''
+                state = 0
+            else:
+                current_comment += char
+        elif state is 2:
+            # In string literal, wait for string end or escape char.
+            if char == string_char:
+                state = 0
+            elif char == '\\':
+                state = 3
+        elif state is 3:
+            # Escaping current char, inside of string.
+            state = 2
+        elif state is 4:
+            # Escaping current char, outside of string.
             state = 0
-            string_char = ''
-            current_comment = ''
-            comments = []
-            line_counter = 1
-            while True:
-                char = source_file.read(1)
-                if not char:
-                    # EOF
-                    if state is 1:
-                        # Was in single line comment. Create comment.
-                        comment = common.Comment(current_comment, line_counter)
-                        comments.append(comment)
-                    return comments
-                if state is 0:
-                    # Waiting for comment start character, beginning of string,
-                    # or escape character.
-                    if char == '#':
-                        state = 1
-                    elif char == '"' or char == "'":
-                        string_char = char
-                        state = 2
-                    elif char == '\\':
-                        state = 4
-                elif state is 1:
-                    # Found comment start character. Read comment until EOL.
-                    if char == '\n':
-                        comment = common.Comment(current_comment, line_counter)
-                        comments.append(comment)
-                        current_comment = ''
-                        state = 0
-                    else:
-                        current_comment += char
-                elif state is 2:
-                    # In string literal, wait for string end or escape char.
-                    if char == string_char:
-                        state = 0
-                    elif char == '\\':
-                        state = 3
-                elif state is 3:
-                    # Escaping current char, inside of string.
-                    state = 2
-                elif state is 4:
-                    # Escaping current char, outside of string.
-                    state = 0
-                if char == '\n':
-                    line_counter += 1
-    except OSError as exception:
-        raise common.FileError(str(exception))
+        if char == '\n':
+            line_counter += 1
 
+    # EOF.
+    if state is 1:
+        # Was in single line comment. Create comment.
+        comment = common.Comment(current_comment, line_counter)
+        comments.append(comment)
+    return comments
diff --git a/setup.py b/setup.py
index 201884f..a9665dd 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@ def readme():
 
 setup(
     name='comment_parser',
-    version='1.0.9',
+    version='1.1.0',
     description='Parse comments from various source files.',
     classifiers=[
       'Development Status :: 5 - Production/Stable',


Test patch:
diff --git a/comment_parser/parsers/tests/c_parser_test.py b/comment_parser/parsers/tests/c_parser_test.py
index b8e2495..ecc118c 100644
--- a/comment_parser/parsers/tests/c_parser_test.py
+++ b/comment_parser/parsers/tests/c_parser_test.py
@@ -5,68 +5,53 @@ from comment_parser.parsers import common as common
 from comment_parser.parsers import c_parser as c_parser
 
 import unittest
-import builtins
-from unittest import mock
-from io import StringIO
 
 class CParserTest(unittest.TestCase):
 
-    @mock.patch.object(builtins, 'open')
-    def ExtractComments(self, text, mock_open):
-        mock_file = StringIO(text)
-        mock_open.return_value = mock_file
-        return c_parser.extract_comments('filename')
-
     def testSimpleMain(self):
-        text = "// this is a comment\nint main() {\nreturn 0;\n}\n"
-        comments = self.ExtractComments(text)
-        expected = [common.Comment(text[2:20], 1, multiline=False)]
+        code = "// this is a comment\nint main() {\nreturn 0;\n}\n"
+        comments = c_parser.extract_comments(code)
+        expected = [common.Comment(code[2:20], 1, multiline=False)]
         self.assertEqual(comments, expected)
 
     def testSingleLineComment(self):
-        text = '// single line comment'
-        comments = self.ExtractComments(text)
-        expected = [common.Comment(text[2:], 1, multiline=False)]
+        code = '// single line comment'
+        comments = c_parser.extract_comments(code)
+        expected = [common.Comment(code[2:], 1, multiline=False)]
         self.assertEqual(comments, expected)
 
     def testSingleLineCommentInStringLiteral(self):
-        text = 'char* msg = "// this is not a comment"'
-        comments = self.ExtractComments(text)
+        code = 'char* msg = "// this is not a comment"'
+        comments = c_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testMultiLineComment(self):
-        text = '/* multiline\ncomment */'
-        comments = self.ExtractComments(text)
-        expected = [common.Comment(text[2:-2], 1, multiline=True)]
+        code = '/* multiline\ncomment */'
+        comments = c_parser.extract_comments(code)
+        expected = [common.Comment(code[2:-2], 1, multiline=True)]
         self.assertEqual(comments, expected)
 
     def testMultiLineCommentWithStars(self):
-        text = "/***************/"
-        comments = self.ExtractComments(text)
-        expected = [common.Comment(text[2:-2], 1, multiline=True)]
+        code = "/***************/"
+        comments = c_parser.extract_comments(code)
+        expected = [common.Comment(code[2:-2], 1, multiline=True)]
         self.assertEqual(comments, expected)
 
     def testMultiLineCommentInStringLiteral(self):
-        text = 'char* msg = "/* This is not a\\nmultiline comment */"'
-        comments = self.ExtractComments(text)
+        code = 'char* msg = "/* This is not a\\nmultiline comment */"'
+        comments = c_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testMultiLineCommentUnterminated(self):
-        text = 'int a = 1; /* Unterminated\\n comment'
+        code = 'int a = 1; /* Unterminated\\n comment'
         self.assertRaises(
-            common.UnterminatedCommentError, self.ExtractComments, text)
+            common.UnterminatedCommentError, c_parser.extract_comments, code)
 
     def testMultipleMultilineComments(self):
-        text = '/* abc */ /* 123 */'
-        comments = self.ExtractComments(text)
+        code = '/* abc */ /* 123 */'
+        comments = c_parser.extract_comments(code)
         expected = [
             common.Comment(' abc ', 1, multiline=True),
             common.Comment(' 123 ', 1, multiline=True),
         ]
         self.assertEqual(comments, expected)
-
-    @mock.patch.object(builtins, 'open')
-    def testExtractCommentsFileError(self, mock_open):
-        mock_open.side_effect = FileNotFoundError()
-        self.assertRaises(common.FileError, c_parser.extract_comments, '')
-
diff --git a/comment_parser/parsers/tests/go_parser_test.py b/comment_parser/parsers/tests/go_parser_test.py
index 4a2096e..df619c9 100644
--- a/comment_parser/parsers/tests/go_parser_test.py
+++ b/comment_parser/parsers/tests/go_parser_test.py
@@ -1,77 +1,62 @@
 #!/usr/bin/python
 """Tests for comment_parser.parsers.go_parser.py"""
 
+import unittest
 from comment_parser.parsers import common as common
 from comment_parser.parsers import go_parser as go_parser
 
-import unittest
-import builtins
-from unittest import mock
-from io import StringIO
 
 class GoParserTest(unittest.TestCase):
 
-    @mock.patch.object(builtins, 'open')
-    def ExtractComments(self, text, mock_open):
-        mock_file = StringIO(text)
-        mock_open.return_value = mock_file
-        return go_parser.extract_comments('filename')
-
     def testSingleLineComment(self):
-        text = '// single line comment'
-        comments = self.ExtractComments(text)
-        expected = [common.Comment(text[2:], 1, multiline=False)]
+        code = '// single line comment'
+        comments = go_parser.extract_comments(code)
+        expected = [common.Comment(code[2:], 1, multiline=False)]
         self.assertEqual(comments, expected)
 
     def testSingleLineCommentInRuneLiteral(self):
-        text = "msg := '// this is not a comment'"
-        comments = self.ExtractComments(text)
+        code = "msg := '// this is not a comment'"
+        comments = go_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testSingleLineCommentInBackTickedLiteral(self):
-        text = "msg := `// this is not a comment`"
-        comments = self.ExtractComments(text)
+        code = "msg := `// this is not a comment`"
+        comments = go_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testSingleLineCommentInDoubleQuotedStringLiteral(self):
-        text = 'msg := "// this is not a comment"'
-        comments = self.ExtractComments(text)
+        code = 'msg := "// this is not a comment"'
+        comments = go_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testMultiLineComment(self):
-        text = '/* multiline\ncomment */'
-        comments = self.ExtractComments(text)
-        expected = [common.Comment(text[2:-2], 1, multiline=True)]
+        code = '/* multiline\ncomment */'
+        comments = go_parser.extract_comments(code)
+        expected = [common.Comment(code[2:-2], 1, multiline=True)]
         self.assertEqual(comments, expected)
 
     def testMultiLineCommentWithStars(self):
-        text = "/***************/"
-        comments = self.ExtractComments(text)
-        expected = [common.Comment(text[2:-2], 1, multiline=True)]
+        code = "/***************/"
+        comments = go_parser.extract_comments(code)
+        expected = [common.Comment(code[2:-2], 1, multiline=True)]
         self.assertEqual(comments, expected)
 
     def testMultiLineCommentInRuneLiteral(self):
-        text = "msg := '/* This is not a\\nmultiline comment */'"
-        comments = self.ExtractComments(text)
+        code = "msg := '/* This is not a\\nmultiline comment */'"
+        comments = go_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testMultiLineCommentInDoubleQuotedStringLiteral(self):
-        text = 'msg := "/* This is not a\\nmultiline comment */"'
-        comments = self.ExtractComments(text)
+        code = 'msg := "/* This is not a\\nmultiline comment */"'
+        comments = go_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testMultiLineCommentInBackTickedLiteral(self):
-        text = 'msg := `/* This is not a\\nmultiline comment */`'
-        comments = self.ExtractComments(text)
+        code = 'msg := `/* This is not a\\nmultiline comment */`'
+        comments = go_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testMultiLineCommentUnterminated(self):
-        text = 'a := 1 /* Unterminated\\n comment'
+        code = 'a := 1 /* Unterminated\\n comment'
         self.assertRaises(
-            common.UnterminatedCommentError, self.ExtractComments, text)
-
-    @mock.patch.object(builtins, 'open')
-    def testExtractCommentsFileError(self, mock_open):
-        mock_open.side_effect = FileNotFoundError()
-        self.assertRaises(common.FileError, go_parser.extract_comments, '')
-
+            common.UnterminatedCommentError, go_parser.extract_comments, code)
diff --git a/comment_parser/parsers/tests/html_parser_test.py b/comment_parser/parsers/tests/html_parser_test.py
index 9fc4736..6562b7d 100644
--- a/comment_parser/parsers/tests/html_parser_test.py
+++ b/comment_parser/parsers/tests/html_parser_test.py
@@ -1,39 +1,28 @@
 #!/usr/bin/python
 """Tests for comment_parser.parsers.html_parser.py"""
 
-import builtins
 import unittest
-from io import StringIO
-from unittest import mock
-
 from comment_parser.parsers import common
 from comment_parser.parsers import html_parser
 
 
-
 class ShellParserTest(unittest.TestCase):
 
-    @mock.patch.object(builtins, 'open')
-    def ExtractComments(self, text, mock_open):
-        mock_file = StringIO(text)
-        mock_open.return_value = mock_file
-        return html_parser.extract_comments('filename')
-
     def testComment(self):
-        text = '<!--comment-->'
-        comments = self.ExtractComments(text)
+        code = '<!--comment-->'
+        comments = html_parser.extract_comments(code)
         expected = [common.Comment('comment', 1, multiline=False)]
         self.assertEqual(comments, expected)
 
     def testMultilineComment(self):
-        text = '<!--multi-line\ncomment-->'
-        comments = self.ExtractComments(text)
+        code = '<!--multi-line\ncomment-->'
+        comments = html_parser.extract_comments(code)
         expected = [common.Comment('multi-line\ncomment', 1, multiline=True)]
         self.assertEqual(comments, expected)
 
     def testTwoSeparateSingleComment(self):
-        text = '<!--comment1-->\n<!--comment2-->'
-        comments = self.ExtractComments(text)
+        code = '<!--comment1-->\n<!--comment2-->'
+        comments = html_parser.extract_comments(code)
         expected = [
             common.Comment('comment1', 1, multiline=False),
             common.Comment('comment2', 2, multiline=False),
@@ -41,20 +30,20 @@ class ShellParserTest(unittest.TestCase):
         self.assertEqual(comments, expected)
 
     def testLayeredComment(self):
-        text = '<!-- comment<!-- -->'
-        comments = self.ExtractComments(text)
+        code = '<!-- comment<!-- -->'
+        comments = html_parser.extract_comments(code)
         expected = [common.Comment(' comment<!-- ', 1, multiline=False)]
         self.assertEqual(comments, expected)
 
     def testNonGreedyComment(self):
-        text = '<!--comment--> not a comment -->'
-        comments = self.ExtractComments(text)
+        code = '<!--comment--> not a comment -->'
+        comments = html_parser.extract_comments(code)
         expected = [common.Comment('comment', 1, multiline=False)]
         self.assertEqual(comments, expected)
 
     def testSideBySideComment(self):
-        text = '<!--comment1--> ... <!--comment2-->'
-        comments = self.ExtractComments(text)
+        code = '<!--comment1--> ... <!--comment2-->'
+        comments = html_parser.extract_comments(code)
         expected = [
             common.Comment('comment1', 1, multiline=False),
             common.Comment('comment2', 1, multiline=False),
@@ -62,11 +51,11 @@ class ShellParserTest(unittest.TestCase):
         self.assertEqual(comments, expected)
 
     def testUnterminatedComment(self):
-        text = '<!--invalid'
+        code = '<!--invalid'
         self.assertRaises(
-            common.UnterminatedCommentError, self.ExtractComments, text)
+            common.UnterminatedCommentError, html_parser.extract_comments, code)
 
     def testLonelyTerminator(self):
-        text = 'not a comment-->'
-        comments = self.ExtractComments(text)
+        code = 'not a comment-->'
+        comments = html_parser.extract_comments(code)
         self.assertEqual(comments, [])
diff --git a/comment_parser/parsers/tests/js_parser_test.py b/comment_parser/parsers/tests/js_parser_test.py
index 4bc507d..663239a 100644
--- a/comment_parser/parsers/tests/js_parser_test.py
+++ b/comment_parser/parsers/tests/js_parser_test.py
@@ -1,67 +1,52 @@
 #!/usr/bin/python
 """Tests for comment_parser.parsers.js_parser.py"""
 
+import unittest
 from comment_parser.parsers import common as common
 from comment_parser.parsers import js_parser as js_parser
 
-import unittest
-import builtins
-from unittest import mock
-from io import StringIO
 
 class JsParserTest(unittest.TestCase):
 
-    @mock.patch.object(builtins, 'open')
-    def ExtractComments(self, text, mock_open):
-        mock_file = StringIO(text)
-        mock_open.return_value = mock_file
-        return js_parser.extract_comments('filename')
-
     def testSingleLineComment(self):
-        text = '// single line comment'
-        comments = self.ExtractComments(text)
-        expected = [common.Comment(text[2:], 1, multiline=False)]
+        code = '// single line comment'
+        comments = js_parser.extract_comments(code)
+        expected = [common.Comment(code[2:], 1, multiline=False)]
         self.assertEqual(comments, expected)
 
     def testSingleLineCommentInSingleQuotedStringLiteral(self):
-        text = "msg = '// this is not a comment'"
-        comments = self.ExtractComments(text)
+        code = "msg = '// this is not a comment'"
+        comments = js_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testSingleLineCommentInDoubleQuotedStringLiteral(self):
-        text = 'msg = "// this is not a comment"'
-        comments = self.ExtractComments(text)
+        code = 'msg = "// this is not a comment"'
+        comments = js_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testMultiLineComment(self):
-        text = '/* multiline\ncomment */'
-        comments = self.ExtractComments(text)
-        expected = [common.Comment(text[2:-2], 1, multiline=True)]
+        code = '/* multiline\ncomment */'
+        comments = js_parser.extract_comments(code)
+        expected = [common.Comment(code[2:-2], 1, multiline=True)]
         self.assertEqual(comments, expected)
 
     def testMultiLineCommentWithStars(self):
-        text = "/***************/"
-        comments = self.ExtractComments(text)
-        expected = [common.Comment(text[2:-2], 1, multiline=True)]
+        code = "/***************/"
+        comments = js_parser.extract_comments(code)
+        expected = [common.Comment(code[2:-2], 1, multiline=True)]
         self.assertEqual(comments, expected)
 
     def testMultiLineCommentInSingleQuotedStringLiteral(self):
-        text = "msg = '/* This is not a\\nmultiline comment */'"
-        comments = self.ExtractComments(text)
+        code = "msg = '/* This is not a\\nmultiline comment */'"
+        comments = js_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testMultiLineCommentInDoubleQuotedStringLiteral(self):
-        text = 'msg = "/* This is not a\\nmultiline comment */"'
-        comments = self.ExtractComments(text)
+        code = 'msg = "/* This is not a\\nmultiline comment */"'
+        comments = js_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testMultiLineCommentUnterminated(self):
-        text = 'a = 1 /* Unterminated\\n comment'
+        code = 'a = 1 /* Unterminated\\n comment'
         self.assertRaises(
-            common.UnterminatedCommentError, self.ExtractComments, text)
-
-    @mock.patch.object(builtins, 'open')
-    def testExtractCommentsFileError(self, mock_open):
-        mock_open.side_effect = FileNotFoundError()
-        self.assertRaises(common.FileError, js_parser.extract_comments, '')
-
+            common.UnterminatedCommentError, js_parser.extract_comments, code)
diff --git a/comment_parser/parsers/tests/shell_parser_test.py b/comment_parser/parsers/tests/shell_parser_test.py
index f8d78fe..6a8ab85 100644
--- a/comment_parser/parsers/tests/shell_parser_test.py
+++ b/comment_parser/parsers/tests/shell_parser_test.py
@@ -1,63 +1,53 @@
 #!/usr/bin/python
 """Tests for comment_parser.parsers.shell_parser.py"""
 
+import unittest
 from comment_parser.parsers import common as common
 from comment_parser.parsers import shell_parser
 
-import builtins
-import unittest
-from io import StringIO
-from unittest import mock
-
 
 class ShellParserTest(unittest.TestCase):
 
-    @mock.patch.object(builtins, 'open')
-    def ExtractComments(self, text, mock_open):
-        mock_file = StringIO(text)
-        mock_open.return_value = mock_file
-        return shell_parser.extract_comments('filename')
-
     def testComment(self):
-        text = '# comment'
-        comments = self.ExtractComments(text)
-        expected = [common.Comment(text[1:], 1, multiline=False)]
+        code = '# comment'
+        comments = shell_parser.extract_comments(code)
+        expected = [common.Comment(code[1:], 1, multiline=False)]
         self.assertEqual(comments, expected)
 
     def testEscapedComment(self):
-        text = '\# not a comment'
-        comments = self.ExtractComments(text)
+        code = '\# not a comment'
+        comments = shell_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testCommentInSingleQuotedString(self):
-        text = "'this is # not a comment'"
-        comments = self.ExtractComments(text)
+        code = "'this is # not a comment'"
+        comments = shell_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testCommentInDoubleQuotedString(self):
-        text = '"this is # not a comment"'
-        comments = self.ExtractComments(text)
+        code = '"this is # not a comment"'
+        comments = shell_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testNestedStringSingleOutside(self):
-        text = "'this is \"# not a comment\"'"
-        comments = self.ExtractComments(text)
+        code = "'this is \"# not a comment\"'"
+        comments = shell_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testNestedStringDoubleOutside(self):
-        text = '"this is \'# not a comment\'"'
-        comments = self.ExtractComments(text)
+        code = '"this is \'# not a comment\'"'
+        comments = shell_parser.extract_comments(code)
         self.assertEqual(comments, [])
 
     def testEscapedSingleQuote(self):
-        text = "\\'# this is a comment"
-        comments = self.ExtractComments(text)
-        expected = [common.Comment(text[3:], 1, multiline=False)]
+        code = "\\'# this is a comment"
+        comments = shell_parser.extract_comments(code)
+        expected = [common.Comment(code[3:], 1, multiline=False)]
         self.assertEqual(comments, expected)
 
     def testEscapedDoubleQuote(self):
-        text = '\\"# this is a comment'
-        comments = self.ExtractComments(text)
-        expected = [common.Comment(text[3:], 1, multiline=False)]
+        code = '\\"# this is a comment'
+        comments = shell_parser.extract_comments(code)
+        expected = [common.Comment(code[3:], 1, multiline=False)]
         self.assertEqual(comments, expected)