From 6a598bd10b7ee192a801ac1f483f5ef3e8676cec Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Fri, 13 May 2016 14:47:49 +0200 Subject: [PATCH 01/17] Remove __setitem__ method from TagEntry. This method is buggy and I don't see any use case where a user would like to change those values from python code. This method is buggy cause : - Assigning string python object to 'char *' C variable do not copy but directly use the inner data array of the string. Once the string python object is garbage collected, the 'char *' points to garbage data. - Assigning to the fields items is buggy cause the for loop set only the first item of the list (and after having set it to NULL) This method has never work and so probably no one use it. There will be no problem if we remove it. --- src/_readtags.pyx | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/src/_readtags.pyx b/src/_readtags.pyx index 73d7e0b..70af4f5 100644 --- a/src/_readtags.pyx +++ b/src/_readtags.pyx @@ -30,33 +30,6 @@ cdef class TagEntry: self.c_entry.fields.list = NULL - def __setitem__(self, key, item): - if key == 'name': - self.c_entry.name = item - elif key == 'file': - self.c_entry.file = item - elif key == 'pattern': - self.c_entry.address.pattern = item - elif key == 'lineNumber': - self.c_entry.address.lineNumber = item - elif key == 'kind': - self.c_entry.kind = item - elif key == 'fileScope': - self.c_entry.fileScope = item - elif key == 'fields': - # fields.list is allocated by readtags.c - if self.c_entry.fields.count != len(item): - return - - fields = item - if self.c_entry.fields.list != NULL: - free(self.c_entry.fields.list) - self.c_entry.fields.list = NULL - - for k, v in fields.iteritems(): - self.c_entry.fields.list.key = k - self.c_entry.fields.list.value = v - def __getitem__(self, key): cdef char* result if key == 'name': From 820264c8c77a07aacca1bdd65ece6556b2fa6c46 Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Fri, 13 May 2016 14:51:23 +0200 Subject: [PATCH 02/17] Remove __cinit__ method from TagEntry. The fields struct is already initialized by C code before parsing a tag line (in the tags file) and setting the tagEntry. We do not need to do it ourselves. --- src/_readtags.pyx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/_readtags.pyx b/src/_readtags.pyx index 70af4f5..a9c5961 100644 --- a/src/_readtags.pyx +++ b/src/_readtags.pyx @@ -25,11 +25,6 @@ include "readtags.pxi" cdef class TagEntry: cdef tagEntry c_entry - def __cinit__(self): - self.c_entry.fields.count = 0 - self.c_entry.fields.list = NULL - - def __getitem__(self, key): cdef char* result if key == 'name': From 664a7d2868860f4f721502020c250f16338f0b58 Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Fri, 13 May 2016 14:59:31 +0200 Subject: [PATCH 03/17] Respect 'const' qualifier. Most 'char *' attributes, function arguments or return values are const. Keep it in Cython. In __getitem__, result should be 'const char*' now, but Cython is smart enough to infer it itself. So no need to be explicit. --- src/_readtags.pyx | 1 - src/readtags.pxi | 36 ++++++++++++++++++------------------ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/_readtags.pyx b/src/_readtags.pyx index a9c5961..cc882d6 100644 --- a/src/_readtags.pyx +++ b/src/_readtags.pyx @@ -26,7 +26,6 @@ cdef class TagEntry: cdef tagEntry c_entry def __getitem__(self, key): - cdef char* result if key == 'name': return self.c_entry.name elif key == 'file': diff --git a/src/readtags.pxi b/src/readtags.pxi index a674b7b..b6f91f0 100644 --- a/src/readtags.pxi +++ b/src/readtags.pxi @@ -36,10 +36,10 @@ cdef extern from "readtags.h": int error_number ctypedef struct programType "program": - char *author - char *name - char *url - char *version + const char *author + const char *name + const char *url + const char *version ctypedef struct tagFileInfo: statusType status @@ -48,11 +48,11 @@ cdef extern from "readtags.h": ctypedef struct tagExtensionField: - char* key - char* value + const char* key + const char* value ctypedef struct addressType "address": - char* pattern + const char* pattern unsigned long lineNumber ctypedef struct fieldsType: @@ -60,12 +60,12 @@ cdef extern from "readtags.h": tagExtensionField *list ctypedef struct tagEntry: - char* name - char* file + const char* name + const char* file addressType address - char* kind + const char* kind short fileScope fieldsType fields @@ -75,12 +75,12 @@ cdef extern from "readtags.h": TagSuccess - tagFile* ctagsOpen "tagsOpen" (char *filePath, tagFileInfo *info) - tagResult ctagsSetSortType "tagsSetSortType" (tagFile* file, tagSortType type) - tagResult ctagsFirst "tagsFirst" (tagFile *file, tagEntry *entry) -#C++: char *ctagsField "tagsField" (tagEntry *entry, char *key) except +MemoryError - char *ctagsField "tagsField" (tagEntry *entry, char *key) - tagResult ctagsFind "tagsFind" (tagFile *file, tagEntry *entry, char *name, int options) - tagResult ctagsNext "tagsNext" (tagFile *file, tagEntry *entry) + tagFile* ctagsOpen "tagsOpen" (const char *const filePath, tagFileInfo *const info) + tagResult ctagsSetSortType "tagsSetSortType" (tagFile *const file, const tagSortType type) + tagResult ctagsFirst "tagsFirst" (tagFile *const file, tagEntry *const entry) +#C++: const char *ctagsField "tagsField" (const tagEntry *const entry, const char *const key) except +MemoryError + const char *ctagsField "tagsField" (const tagEntry *const entry, const char *const key) + tagResult ctagsFind "tagsFind" (tagFile *const file, tagEntry *const entry, const char *const name, const int options) + tagResult ctagsNext "tagsNext" (tagFile *const file, tagEntry *const entry) tagResult ctagsFindNext "tagsFindNext" (tagFile *file, tagEntry *entry) - tagResult ctagsClose "tagsClose" (tagFile *file) + tagResult ctagsClose "tagsClose" (tagFile *const file) From cc5a2ae052e9d4c87aa88c3c5e25191f4d215447 Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Fri, 13 May 2016 17:49:08 +0200 Subject: [PATCH 04/17] We need to encode the string to pass it as const char* to ctagsField. --- src/_readtags.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_readtags.pyx b/src/_readtags.pyx index cc882d6..4ad876a 100644 --- a/src/_readtags.pyx +++ b/src/_readtags.pyx @@ -45,7 +45,7 @@ cdef class TagEntry: else: # It will crash if we mix NULL/0/None # don't mix comparison of type - result = ctagsField(&self.c_entry, key) + result = ctagsField(&self.c_entry, key.encode()) if result == NULL: return None From 93ec2bc0a856507e874a87937418c7f49765c3f0 Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Fri, 13 May 2016 17:47:33 +0200 Subject: [PATCH 05/17] Raise KeyError if the key is not present in the tag. Behave more link a dict, raise a KeyError instead of returning None. --- README.md | 23 ++++++++++++++++------- src/_readtags.pyx | 8 +++++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 13a481d..0f58f56 100644 --- a/README.md +++ b/README.md @@ -71,19 +71,28 @@ entry = TagEntry() status = tagFile.first(entry) if status: - # Available TagEntry keys: + # The following TagEntry keys are always available: # name - name of tag # file - path of source file containing definition of tag - # pattern - pattern for locating source line (None if no pattern) - # lineNumber - line number in source file of tag definition (may be zero if not known) - # kind - kind of tag (none if not known) + # pattern - pattern for locating source line + # (None if no pattern, this should no huppen with a correct + # tag file) # fileScope - is tag of file-limited scope? + # + # Other keys will be assumed as an extensionkey and will raise a + # KeyError if no such key is found. + # Other keys include : + # lineNumber - line number in source file of tag definition + # kind - kind of tag - # Note: other keys will be assumed as an extension key and will - # return None if no such key is found - print entry['name'] print entry['kind'] + try: + entry['lineNumber'] + except KeyError: + print "Entry has no lineNumber" + else: + print "Entry has a lineNumber" ``` **Finding a Tag Entry** diff --git a/src/_readtags.pyx b/src/_readtags.pyx index 4ad876a..2f334d2 100644 --- a/src/_readtags.pyx +++ b/src/_readtags.pyx @@ -35,10 +35,12 @@ cdef class TagEntry: return None return self.c_entry.address.pattern elif key == 'lineNumber': - return self.c_entry.address.lineNumber + if not self.c_entry.address.lineNumber: + raise KeyError(key) + return self.c_entry.address.lineNumber elif key == 'kind': if self.c_entry.kind == NULL: - return None + raise KeyError(key) return self.c_entry.kind elif key == 'fileScope': return self.c_entry.fileScope @@ -47,7 +49,7 @@ cdef class TagEntry: # don't mix comparison of type result = ctagsField(&self.c_entry, key.encode()) if result == NULL: - return None + raise KeyError(key) return result From f8433de91dec3b562f6d573a3025d8d924763e31 Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Tue, 17 May 2016 15:05:20 +0200 Subject: [PATCH 06/17] Make TagEntry behave like a Mapping. Use collections.abc.Mapping to let TagEntry be usable has a Mapping. cdef class cannot inherit from a Python class. So rename it to cTagEntry and use classic class TagEntry to do the Mixin. # Conflicts: # README.rst --- README.md | 6 ++++++ src/_readtags.pyx | 32 ++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0f58f56..4f0d602 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,12 @@ if status: print "Entry has no lineNumber" else: print "Entry has a lineNumber" + + # TagEntry are Mapping. It is possible to loop for all the keys in the + # entry as it is done with a dict. + + for k, v in entry.items(): + print k, "value is", v ``` **Finding a Tag Entry** diff --git a/src/_readtags.pyx b/src/_readtags.pyx index 2f334d2..80f2430 100644 --- a/src/_readtags.pyx +++ b/src/_readtags.pyx @@ -20,9 +20,9 @@ along with Python-Ctags. If not, see . include "stdlib.pxi" include "readtags.pxi" +from collections.abc import Mapping - -cdef class TagEntry: +cdef class cTagEntry: cdef tagEntry c_entry def __getitem__(self, key): @@ -53,6 +53,26 @@ cdef class TagEntry: return result + def __iter__(self): + yield from ('name', 'file', 'pattern', 'fileScope') + if self.c_entry.address.lineNumber: + yield 'lineNumber' + if self.c_entry.kind != NULL: + yield 'kind' + for index in range(self.c_entry.fields.count): + key = self.c_entry.fields.list[index].key + yield key.decode() + + def __len__(self): + return (4 # Number of fields always present. + + bool(self.c_entry.address.lineNumber) # Do we have lineNumber ? + + bool(self.c_entry.kind != NULL) # Do we have kind ? + + self.c_entry.fields.count # Number of extra fields. + ) + +class TagEntry(cTagEntry, Mapping): + pass + cdef class CTags: cdef tagFile* file cdef tagFileInfo info @@ -101,15 +121,15 @@ cdef class CTags: def setSortType(self, tagSortType type): return ctagsSetSortType(self.file, type) - def first(self, TagEntry entry): + def first(self, cTagEntry entry): return ctagsFirst(self.file, &entry.c_entry) - def find(self, TagEntry entry, char* name, int options): + def find(self, cTagEntry entry, char* name, int options): return ctagsFind(self.file, &entry.c_entry, name, options) - def findNext(self, TagEntry entry): + def findNext(self, cTagEntry entry): return ctagsFindNext(self.file, &entry.c_entry) - def next(self, TagEntry entry): + def next(self, cTagEntry entry): return ctagsNext(self.file, &entry.c_entry) From 1157b6d67fa42061db6276faeac7b1a557bf826c Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Mon, 16 May 2016 11:57:37 +0200 Subject: [PATCH 07/17] Raise KeyError if item is not in the Ctags file. --- README.md | 27 +++++++++++++++++++-------- src/_readtags.pyx | 35 ++++++++++++++++------------------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 4f0d602..0d4e598 100644 --- a/README.md +++ b/README.md @@ -47,14 +47,25 @@ except: # opened - was the tag file successfully opened? # error_number - errno value when 'opened' is false # format - format of tag file (1 = original, 2 = extended) -# sort - how is the tag file sorted? -# author - name of author of generating program (may be empy string) -# name - name of program (may be empy string) -# url - URL of distribution (may be empy string) -# version - program version (may be empty string) - -print tagFile['name'] -print tagFile['author'] +# sort - how is the tag file sorted? +# +# Other keys may be available: +# author - name of author of generating program +# name - name of program +# url - URL of distribution +# version - program version +# If one of them is not present a KeyError is raised. + +try: + print tagFile['name'] +except KeyError: + print "No 'name' in the tagfile" + +try: + print tagFile['author'] +except KeyError: + print "No 'author' in the tagfile" + print tagFile['format'] # Available sort type: diff --git a/src/_readtags.pyx b/src/_readtags.pyx index 80f2430..37d9251 100644 --- a/src/_readtags.pyx +++ b/src/_readtags.pyx @@ -86,30 +86,27 @@ cdef class CTags: ctagsClose(self.file) def __getitem__(self, key): + ret = None if key == 'opened': return self.info.status.opened - if key == 'error_number': + elif key == 'error_number': return self.info.status.error_number - if key == 'format': + elif key == 'format': return self.info.file.format - if key == 'sort': + elif key == 'sort': return self.info.file.sort - if key == 'author': - if self.info.program.author == NULL: - return '' - return self.info.program.author - if key == 'name': - if self.info.program.name == NULL: - return '' - return self.info.program.name - if key == 'url': - if self.info.program.url == NULL: - return '' - return self.info.program.url - if key == 'version': - if self.info.program.version == NULL: - return '' - return self.info.program.version + else: + if key == 'author': + ret = self.info.program.author + elif key == 'name': + ret = self.info.program.name + elif key == 'url': + ret = self.info.program.url + elif key == 'version': + ret = self.info.program.version + if ret is None: + raise KeyError(key) + return ret def open(self, filepath): From daacf115bbb74738e475e738fc759cadb6be77ea Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Tue, 17 May 2016 15:13:11 +0200 Subject: [PATCH 08/17] Raise Exception instead of returning status. The common Python idiom is to raise Exception instead of returning status values. Move code of open method in __cinit__. No more access to CTags attributes 'opened' and 'error_number'. This is no more needed with the raise of OSError. # Conflicts: # README.rst --- README.md | 95 +++++++++++++++++++++++++++-------------------- src/_readtags.pyx | 41 +++++++++++--------- 2 files changed, 78 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 0d4e598..f1fb44f 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,8 @@ import sys try: tagFile = CTags('tags') -except: +except OSError as err: + print err sys.exit(1) # Available file information keys: @@ -73,43 +74,46 @@ print tagFile['format'] # Note: use this only if you know how the tags file is sorted which is # specified when you generate the tag file -status = tagFile.setSortType(ctags.TAG_SORTED) +tagFile.setSortType(ctags.TAG_SORTED) ``` **Obtaining First Tag Entry** ```python entry = TagEntry() -status = tagFile.first(entry) - -if status: - # The following TagEntry keys are always available: - # name - name of tag - # file - path of source file containing definition of tag - # pattern - pattern for locating source line - # (None if no pattern, this should no huppen with a correct - # tag file) - # fileScope - is tag of file-limited scope? - # - # Other keys will be assumed as an extensionkey and will raise a - # KeyError if no such key is found. - # Other keys include : - # lineNumber - line number in source file of tag definition - # kind - kind of tag - - print entry['name'] - print entry['kind'] - try: - entry['lineNumber'] - except KeyError: - print "Entry has no lineNumber" - else: - print "Entry has a lineNumber" - - # TagEntry are Mapping. It is possible to loop for all the keys in the - # entry as it is done with a dict. - - for k, v in entry.items(): - print k, "value is", v +try: + tagFile.first(entry) +except: + print "Something wrong" + sys.exit(-1) + +# The following TagEntry keys are always available: +# name - name of tag +# file - path of source file containing definition of tag +# pattern - pattern for locating source line +# (None if no pattern, this should no huppen with a correct +# tag file) +# fileScope - is tag of file-limited scope? +# +# Other keys will be assumed as an extensionkey and will raise a +# KeyError if no such key is found. +# Other keys include : +# lineNumber - line number in source file of tag definition +# kind - kind of tag + +print entry['name'] +print entry['kind'] +try: + entry['lineNumber'] +except KeyError: + print "Entry has no lineNumber" +else: + print "Entry has a lineNumber" + +# TagEntry are Mapping. It is possible to loop for all the keys in the +# entry as it is done with a dict. + +for k, v in entry.items(): + print k, "value is", v ``` **Finding a Tag Entry** @@ -120,16 +124,27 @@ if status: # TAG_IGNORECASE - disable binary search # TAG_OBSERVECASE - case sensitive and allowed binary search to perform -if tagFile.find(entry, 'find', ctags.TAG_PARTIALMATCH | ctags.TAG_IGNORECASE): - print 'found' - print entry['lineNumber'] - print entry['pattern'] - print entry['kind'] +try: + tagFile.find(entry, 'find', ctags.TAG_PARTIALMATCH | ctags.TAG_IGNORECASE) +except: + print "Not found" + sys.exit(-1) + +print 'found' +print entry['lineNumber'] +print entry['pattern'] +print entry['kind'] # Find the next tag matching the name and options supplied to the # most recent call to tagFile.find(). (replace the entry if found) -status = tagFile.findNext(entry) +try: + tagFile.findNext(entry) +except: + ... # Step to the next tag in the file (replace entry if found) -status = tagFile.next(entry) +try: + tagFile.next(entry) +except: + ... ``` diff --git a/src/_readtags.pyx b/src/_readtags.pyx index 37d9251..27d1f89 100644 --- a/src/_readtags.pyx +++ b/src/_readtags.pyx @@ -17,6 +17,8 @@ You should have received a copy of the GNU General Public License along with Python-Ctags. If not, see . """ +cdef extern from "string.h": + char* strerror(int errnum) include "stdlib.pxi" include "readtags.pxi" @@ -78,7 +80,11 @@ cdef class CTags: cdef tagFileInfo info def __cinit__(self, filepath): - self.open(filepath) + self.file = ctagsOpen(filepath, &self.info) + if not self.file: + raise OSError(self.info.status.error_number, + strerror(self.info.status.error_number), + filepath) def __dealloc__(self): @@ -87,11 +93,7 @@ cdef class CTags: def __getitem__(self, key): ret = None - if key == 'opened': - return self.info.status.opened - elif key == 'error_number': - return self.info.status.error_number - elif key == 'format': + if key == 'format': return self.info.file.format elif key == 'sort': return self.info.file.sort @@ -108,25 +110,28 @@ cdef class CTags: raise KeyError(key) return ret - - def open(self, filepath): - self.file = ctagsOpen(filepath, &self.info) - - if not self.info.status.opened: - raise Exception('Invalid tag file') - def setSortType(self, tagSortType type): - return ctagsSetSortType(self.file, type) + success = ctagsSetSortType(self.file, type) + if not success: + raise RuntimeError() def first(self, cTagEntry entry): - return ctagsFirst(self.file, &entry.c_entry) + success = ctagsFirst(self.file, &entry.c_entry) + if not success: + raise RuntimeError() def find(self, cTagEntry entry, char* name, int options): - return ctagsFind(self.file, &entry.c_entry, name, options) + success = ctagsFind(self.file, &entry.c_entry, name, option) + if not success: + raise RuntimeError() def findNext(self, cTagEntry entry): - return ctagsFindNext(self.file, &entry.c_entry) + success = ctagsFindNext(self.file, &entry.c_entry) + if not success: + raise RuntimeError() def next(self, cTagEntry entry): - return ctagsNext(self.file, &entry.c_entry) + success = ctagsNext(self.file, &entry.c_entry) + if not success: + raise RuntimeError() From 404b4fae40a7d6bb5b59e201f073d2a6c2dcd55e Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Mon, 16 May 2016 14:23:42 +0200 Subject: [PATCH 09/17] Return a new entry instead of taking an entry as input to change it. Taking a argument as ouput is a C idiom. In python, we return the value. The entry is a plain dict, not anymore a class with Mapping API. More than the pythonic API, this fix also a bug : The C tagEntry share the same internal buffer to store the datas. ctags* function "just" changes the entries' pointers to the right place in the buffer. Using one of those functions will invalidate any other entries already returned. By using a dict we make a plain copy of the string and so entry are now independent of the internal state of the C code. --- README.md | 30 ++++++--------- src/_readtags.pyx | 88 ++++++++++++++----------------------------- src/ctags/__init__.py | 4 +- 3 files changed, 42 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index f1fb44f..2c00f0c 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ ctags --fields=afmikKlnsStz readtags.c readtags.h **Opening Tags File** ```python import ctags -from ctags import CTags, TagEntry +from ctags import CTags import sys try: @@ -79,14 +79,13 @@ tagFile.setSortType(ctags.TAG_SORTED) **Obtaining First Tag Entry** ```python -entry = TagEntry() try: - tagFile.first(entry) + entry = tagFile.first() except: print "Something wrong" sys.exit(-1) -# The following TagEntry keys are always available: +# The return entry is a dict. The following keys are always available: # name - name of tag # file - path of source file containing definition of tag # pattern - pattern for locating source line @@ -94,26 +93,19 @@ except: # tag file) # fileScope - is tag of file-limited scope? # -# Other keys will be assumed as an extensionkey and will raise a -# KeyError if no such key is found. +# The dict may contain other keys (extension keys). # Other keys include : # lineNumber - line number in source file of tag definition # kind - kind of tag print entry['name'] -print entry['kind'] +print entry['file'] try: entry['lineNumber'] except KeyError: print "Entry has no lineNumber" else: print "Entry has a lineNumber" - -# TagEntry are Mapping. It is possible to loop for all the keys in the -# entry as it is done with a dict. - -for k, v in entry.items(): - print k, "value is", v ``` **Finding a Tag Entry** @@ -125,7 +117,7 @@ for k, v in entry.items(): # TAG_OBSERVECASE - case sensitive and allowed binary search to perform try: - tagFile.find(entry, 'find', ctags.TAG_PARTIALMATCH | ctags.TAG_IGNORECASE) + entry = tagFile.find('find', ctags.TAG_PARTIALMATCH | ctags.TAG_IGNORECASE) except: print "Not found" sys.exit(-1) @@ -136,15 +128,17 @@ print entry['pattern'] print entry['kind'] # Find the next tag matching the name and options supplied to the -# most recent call to tagFile.find(). (replace the entry if found) +# most recent call to tagFile.find(). +# Raise if no entry is found. try: - tagFile.findNext(entry) + entry = tagFile.findNext() except: ... -# Step to the next tag in the file (replace entry if found) +# Step to the next tag in the file. +# Raise if no entry is found. try: - tagFile.next(entry) + entry = tagFile.next() except: ... ``` diff --git a/src/_readtags.pyx b/src/_readtags.pyx index 27d1f89..fb95394 100644 --- a/src/_readtags.pyx +++ b/src/_readtags.pyx @@ -22,62 +22,27 @@ cdef extern from "string.h": include "stdlib.pxi" include "readtags.pxi" -from collections.abc import Mapping -cdef class cTagEntry: - cdef tagEntry c_entry - - def __getitem__(self, key): - if key == 'name': - return self.c_entry.name - elif key == 'file': - return self.c_entry.file - elif key == 'pattern': - if self.c_entry.address.pattern == NULL: - return None - return self.c_entry.address.pattern - elif key == 'lineNumber': - if not self.c_entry.address.lineNumber: - raise KeyError(key) - return self.c_entry.address.lineNumber - elif key == 'kind': - if self.c_entry.kind == NULL: - raise KeyError(key) - return self.c_entry.kind - elif key == 'fileScope': - return self.c_entry.fileScope - else: - # It will crash if we mix NULL/0/None - # don't mix comparison of type - result = ctagsField(&self.c_entry, key.encode()) - if result == NULL: - raise KeyError(key) - - return result - - def __iter__(self): - yield from ('name', 'file', 'pattern', 'fileScope') - if self.c_entry.address.lineNumber: - yield 'lineNumber' - if self.c_entry.kind != NULL: - yield 'kind' - for index in range(self.c_entry.fields.count): - key = self.c_entry.fields.list[index].key - yield key.decode() - - def __len__(self): - return (4 # Number of fields always present. - + bool(self.c_entry.address.lineNumber) # Do we have lineNumber ? - + bool(self.c_entry.kind != NULL) # Do we have kind ? - + self.c_entry.fields.count # Number of extra fields. - ) - -class TagEntry(cTagEntry, Mapping): - pass +cdef create_tagEntry(const tagEntry* const c_entry): + cdef dict ret = {} + ret['name'] = c_entry.name + ret['file'] = c_entry.file + ret['fileScope'] = c_entry.fileScope + if c_entry.address.pattern != NULL: + ret['pattern'] = c_entry.address.pattern + if c_entry.address.lineNumber: + ret['lineNumber'] = c_entry.address.lineNumber + if c_entry.kind != NULL: + ret['kind'] = c_entry.kind + for index in range(c_entry.fields.count): + key = c_entry.fields.list[index].key + ret[key.decode()] = c_entry.fields.list[index].value + return ret cdef class CTags: cdef tagFile* file cdef tagFileInfo info + cdef tagEntry c_entry def __cinit__(self, filepath): self.file = ctagsOpen(filepath, &self.info) @@ -87,7 +52,6 @@ cdef class CTags: filepath) def __dealloc__(self): - if self.file: ctagsClose(self.file) @@ -115,23 +79,27 @@ cdef class CTags: if not success: raise RuntimeError() - def first(self, cTagEntry entry): - success = ctagsFirst(self.file, &entry.c_entry) + def first(self): + success = ctagsFirst(self.file, &self.c_entry) if not success: raise RuntimeError() + return create_tagEntry(&self.c_entry) - def find(self, cTagEntry entry, char* name, int options): - success = ctagsFind(self.file, &entry.c_entry, name, option) + def find(self, bytes name, int options): + success = ctagsFind(self.file, &self.c_entry, name, options) if not success: raise RuntimeError() + return create_tagEntry(&self.c_entry) - def findNext(self, cTagEntry entry): - success = ctagsFindNext(self.file, &entry.c_entry) + def findNext(self): + success = ctagsFindNext(self.file, &self.c_entry) if not success: raise RuntimeError() + return create_tagEntry(&self.c_entry) - def next(self, cTagEntry entry): - success = ctagsNext(self.file, &entry.c_entry) + def next(self): + success = ctagsNext(self.file, &self.c_entry) if not success: raise RuntimeError() + return create_tagEntry(&self.c_entry) diff --git a/src/ctags/__init__.py b/src/ctags/__init__.py index 2a9ef36..9bf7b87 100644 --- a/src/ctags/__init__.py +++ b/src/ctags/__init__.py @@ -20,8 +20,8 @@ """ -from ._readtags import TagEntry, CTags -__all__ = ['TagEntry', 'CTags'] +from ._readtags import CTags +__all__ = ['CTags'] # sortType TAG_UNSORTED=0 From b8627b820af9e07be7f314ca0ba8531ba956cb17 Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Mon, 16 May 2016 14:34:11 +0200 Subject: [PATCH 10/17] Use generators to explore the CTags file. We do not use first/next/find* methods anymore. '*next' methods are depends of the context set by other methods. Hiding those methods in a high level API ensure that they are not badly used. 'first' and 'find' can respectively replace by next(ctagsfile.all_tags()) and next(ctagsfile.find_tags(...)) --- README.md | 48 ++++++++++++++++--------------------- src/_readtags.pyx | 61 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 78 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 2c00f0c..b4ebfac 100644 --- a/README.md +++ b/README.md @@ -77,15 +77,13 @@ print tagFile['format'] tagFile.setSortType(ctags.TAG_SORTED) ``` -**Obtaining First Tag Entry** +**Listing Tag Entries** ```python -try: - entry = tagFile.first() -except: - print "Something wrong" - sys.exit(-1) +# A generator of all tags in the file can be obtain with: +all_tags = tagFile.all_tags() -# The return entry is a dict. The following keys are always available: +# The generator yield a dict for each entry. +# The following keys are always available for a entry: # name - name of tag # file - path of source file containing definition of tag # pattern - pattern for locating source line @@ -98,34 +96,30 @@ except: # lineNumber - line number in source file of tag definition # kind - kind of tag -print entry['name'] -print entry['file'] -try: - entry['lineNumber'] -except KeyError: - print "Entry has no lineNumber" -else: - print "Entry has a lineNumber" +for entry in all_tags: + print entry['name'] + print entry['file'] + try: + entry['lineNumber'] + except KeyError: + print "Entry has no lineNumber" + else: + print "Entry has a lineNumber" ``` -**Finding a Tag Entry** -```python +**Finding Tag Entries** +```python # Available options: # TAG_PARTIALMATCH - begin with # TAG_FULLMATCH - full length matching # TAG_IGNORECASE - disable binary search # TAG_OBSERVECASE - case sensitive and allowed binary search to perform -try: - entry = tagFile.find('find', ctags.TAG_PARTIALMATCH | ctags.TAG_IGNORECASE) -except: - print "Not found" - sys.exit(-1) - -print 'found' -print entry['lineNumber'] -print entry['pattern'] -print entry['kind'] +found_tags = tagFile.find_tags('find', ctags.TAG_PARTIALMATCH | ctags.TAG_IGNORECASE) +for entry in found_tags: + print entry['lineNumber'] + print entry['pattern'] + print entry['kind'] # Find the next tag matching the name and options supplied to the # most recent call to tagFile.find(). diff --git a/src/_readtags.pyx b/src/_readtags.pyx index fb95394..994b1b5 100644 --- a/src/_readtags.pyx +++ b/src/_readtags.pyx @@ -43,6 +43,7 @@ cdef class CTags: cdef tagFile* file cdef tagFileInfo info cdef tagEntry c_entry + cdef object current_id def __cinit__(self, filepath): self.file = ctagsOpen(filepath, &self.info) @@ -79,27 +80,79 @@ cdef class CTags: if not success: raise RuntimeError() - def first(self): + cdef first(self): success = ctagsFirst(self.file, &self.c_entry) if not success: raise RuntimeError() return create_tagEntry(&self.c_entry) - def find(self, bytes name, int options): + cdef find(self, bytes name, int options): success = ctagsFind(self.file, &self.c_entry, name, options) if not success: raise RuntimeError() return create_tagEntry(&self.c_entry) - def findNext(self): + cdef findNext(self): success = ctagsFindNext(self.file, &self.c_entry) if not success: raise RuntimeError() return create_tagEntry(&self.c_entry) - def next(self): + cdef next(self): success = ctagsNext(self.file, &self.c_entry) if not success: raise RuntimeError() return create_tagEntry(&self.c_entry) + def find_tags(self, bytes name, int options): + """ Find tags corresponding to name in the tag file. + @name : a bytes array to search to. + @options : A option flags for the search. + @return : A iterator on all tags corresponding to the search. + + WARNING: Only one iterator can run on a tag file. + If you use another iterator (by calling all_tags or find_tags), + any previous iterator will be invalidate and raise a RuntimeError. + """ + try: + first = self.find(name, options) + self.current_id = first + yield first + except KeyError: + raise StopIteration from None + + while True: + if self.current_id is not first: + raise RuntimeError("Only one search/list generator at a time") + try: + other = self.findNext() + except RuntimeError: + raise StopIteration from None + else: + yield other + + def all_tags(self): + """ List all tags in the tag file. + @return : A iterator on all tags in the file. + + WARNING: Only one iterator can run on a tag file. + If you use another iterator (by calling all_tags or find_tags), + any previous iterator will be invalidate and raise a RuntimeError. + """ + try: + first = self.first() + self.current_id = first + yield first + except KeyError: + raise StopIteration from None + + while True: + if self.current_id is not first: + raise RuntimeError("Only one search/list generator at a time") + try: + other = self.next() + except RuntimeError: + raise StopIteration from None + else: + yield other + From 540b4bfd9dda55f4b823652dc507d5cbe9c60621 Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Tue, 17 May 2016 14:15:14 +0200 Subject: [PATCH 11/17] Fix tests to use new api --- tests/test_ctags.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_ctags.py b/tests/test_ctags.py index c90a6e1..8a43afc 100644 --- a/tests/test_ctags.py +++ b/tests/test_ctags.py @@ -12,22 +12,20 @@ def setUp(self): file_path = os.path.join(src_dir, 'examples', 'tags') self.ctags = ctags.CTags(file_path.encode(sys.getfilesystemencoding())) def test_tag_entry(self): - entry = ctags.TagEntry() self.ctags.setSortType(ctags.TAG_SORTED) - self.ctags.first(entry) + entry = next(self.ctags.all_tags()) entry_info = [entry[_] - for _ in ('file', 'name', 'pattern', 'kind', b'language') + for _ in ('file', 'name', 'pattern', 'kind', 'language') ] self.assertEqual( entry_info, [b'../_readtags.c', b'DL_EXPORT', b'10', b'macro', b'C'] ) def test_tag_find(self): - entry = ctags.TagEntry() self.ctags.setSortType(ctags.TAG_SORTED) - self.ctags.find(entry, b'find', ctags.TAG_PARTIALMATCH | ctags.TAG_IGNORECASE) + entry = next(self.ctags.find_tags(b'find', ctags.TAG_PARTIALMATCH | ctags.TAG_IGNORECASE)) entry_info = [entry[_] - for _ in ('file', 'name', 'pattern', 'kind', b'language') + for _ in ('file', 'name', 'pattern', 'kind', 'language') ] self.assertEqual( entry_info, From 01796217c9e0c0188ce64b297802953d467c6b78 Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Tue, 17 May 2016 17:23:20 +0200 Subject: [PATCH 12/17] first and find method raise RuntimeError, not KeyError. --- src/_readtags.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_readtags.pyx b/src/_readtags.pyx index 994b1b5..9ab9432 100644 --- a/src/_readtags.pyx +++ b/src/_readtags.pyx @@ -118,7 +118,7 @@ cdef class CTags: first = self.find(name, options) self.current_id = first yield first - except KeyError: + except RuntimeError: raise StopIteration from None while True: @@ -143,7 +143,7 @@ cdef class CTags: first = self.first() self.current_id = first yield first - except KeyError: + except RuntimeError: raise StopIteration from None while True: From 8f73c8cdebed80902f9a012a4adb7ee6eb041c84 Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Tue, 17 May 2016 18:37:35 +0200 Subject: [PATCH 13/17] Accept bytes and str as tag file path. --- src/_readtags.pyx | 3 +++ tests/test_ctags.py | 12 +++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/_readtags.pyx b/src/_readtags.pyx index 9ab9432..b7b5aa8 100644 --- a/src/_readtags.pyx +++ b/src/_readtags.pyx @@ -22,6 +22,7 @@ cdef extern from "string.h": include "stdlib.pxi" include "readtags.pxi" +import sys cdef create_tagEntry(const tagEntry* const c_entry): cdef dict ret = {} @@ -46,6 +47,8 @@ cdef class CTags: cdef object current_id def __cinit__(self, filepath): + if isinstance(filepath, unicode): + filepath = (filepath).encode(sys.getfilesystemencoding()) self.file = ctagsOpen(filepath, &self.info) if not self.file: raise OSError(self.info.status.error_number, diff --git a/tests/test_ctags.py b/tests/test_ctags.py index 8a43afc..8650f1c 100644 --- a/tests/test_ctags.py +++ b/tests/test_ctags.py @@ -7,10 +7,20 @@ from unittest import TestCase import ctags +class TestCTagsOpen(TestCase): + def setUp(self): + self.file_path = os.path.join(src_dir, 'examples', 'tags') + + def test_open_str(self): + ctags.CTags(self.file_path) + + def test_open_bytes(self): + ctags.CTags(self.file_path.encode(sys.getfilesystemencoding())) + class TestCTagsParse(TestCase): def setUp(self): file_path = os.path.join(src_dir, 'examples', 'tags') - self.ctags = ctags.CTags(file_path.encode(sys.getfilesystemencoding())) + self.ctags = ctags.CTags(file_path) def test_tag_entry(self): self.ctags.setSortType(ctags.TAG_SORTED) entry = next(self.ctags.all_tags()) From f8b15eb408c3858457faf8c54a96e0161d39b0e1 Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Tue, 17 May 2016 18:37:53 +0200 Subject: [PATCH 14/17] Add few tests. --- tests/test_ctags.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_ctags.py b/tests/test_ctags.py index 8650f1c..b2af839 100644 --- a/tests/test_ctags.py +++ b/tests/test_ctags.py @@ -42,3 +42,19 @@ def test_tag_find(self): [b'../readtags.c', b'find', b'/^static tagResult find (tagFile ' b'*const file, tagEntry *const entry,$/', b'function', b'C'] ) + + def test_tag_find_partial_nocase(self): + for entry in self.ctags.find_tags(b'tag', ctags.TAG_PARTIALMATCH | ctags.TAG_IGNORECASE): + self.assertTrue(entry['name'].lower().startswith(b'tag')) + + def test_tag_find_nocase(self): + for entry in self.ctags.find_tags(b'tag', ctags.TAG_IGNORECASE): + self.assertEqual(entry['name'].lower(), b'tag') + + def test_tag_find_partial(self): + for entry in self.ctags.find_tags(b'tag', ctags.TAG_PARTIALMATCH): + self.assertTrue(entry['name'].startswith(b'tag')) + + def test_tag_find_noflag(self): + for entry in self.ctags.find_tags(b'tag', 0): + self.assertEqual(entry['name'], b'tag') From f741351961a0a019388badc1cb3eac07c5723d30 Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Tue, 17 May 2016 18:40:12 +0200 Subject: [PATCH 15/17] Update README (switch to python3 syntax for print) --- README.md | 41 +++++++++++++---------------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index b4ebfac..f0c9aa4 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ import sys try: tagFile = CTags('tags') except OSError as err: - print err + print(err) sys.exit(1) # Available file information keys: @@ -58,16 +58,16 @@ except OSError as err: # If one of them is not present a KeyError is raised. try: - print tagFile['name'] + print(tagFile['name']) except KeyError: - print "No 'name' in the tagfile" + print("No 'name' in the tagfile") try: - print tagFile['author'] + print(tagFile['author']) except KeyError: - print "No 'author' in the tagfile" + print("No 'author' in the tagfile") -print tagFile['format'] +print(tagFile['format']) # Available sort type: # TAG_UNSORTED, TAG_SORTED, TAG_FOLDSORTED @@ -97,14 +97,14 @@ all_tags = tagFile.all_tags() # kind - kind of tag for entry in all_tags: - print entry['name'] - print entry['file'] + print(entry['name']) + print(entry['file']) try: entry['lineNumber'] except KeyError: - print "Entry has no lineNumber" + print("Entry has no lineNumber") else: - print "Entry has a lineNumber" + print("Entry has a lineNumber") ``` **Finding Tag Entries** @@ -117,22 +117,7 @@ for entry in all_tags: found_tags = tagFile.find_tags('find', ctags.TAG_PARTIALMATCH | ctags.TAG_IGNORECASE) for entry in found_tags: - print entry['lineNumber'] - print entry['pattern'] - print entry['kind'] + print(entry['lineNumber']) + print(entry['pattern']) + print(entry['kind']) -# Find the next tag matching the name and options supplied to the -# most recent call to tagFile.find(). -# Raise if no entry is found. -try: - entry = tagFile.findNext() -except: - ... - -# Step to the next tag in the file. -# Raise if no entry is found. -try: - entry = tagFile.next() -except: - ... -``` From 43b268859668ae25721dbbde3db0ac7e85abc8af Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Sat, 4 Jun 2016 14:38:50 +0200 Subject: [PATCH 16/17] Add a encoding arg to the CTags file to automatically decode file content. Encoding argument to CTags file is used to automatically encode/decode content of the CTags file. --- README.md | 17 ++++++++++++ src/_readtags.pyx | 64 ++++++++++++++++++++++++++++----------------- tests/test_ctags.py | 46 ++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index f0c9aa4..b82afb5 100644 --- a/README.md +++ b/README.md @@ -121,3 +121,20 @@ for entry in found_tags: print(entry['pattern']) print(entry['kind']) + +# File Encoding. + +By default, CTags return unicode strings using the 'utf8' encoding. +This can be changed by providing a custom encoding at CTags creation : + +```python +tagFile = CTags('tags', encoding='latin1') +``` + +If None is provided as encoding, no encoding is done and entries will contain +bytes instead of string. + +This is also possible to provide a encoding_errors. +It will be passed to the encode function as the 'errors' argument. +See the definition of the encode function to know how to use this argument. +By default, encoding_errors is 'strict'. diff --git a/src/_readtags.pyx b/src/_readtags.pyx index b7b5aa8..d7d2f9f 100644 --- a/src/_readtags.pyx +++ b/src/_readtags.pyx @@ -24,29 +24,17 @@ include "stdlib.pxi" include "readtags.pxi" import sys -cdef create_tagEntry(const tagEntry* const c_entry): - cdef dict ret = {} - ret['name'] = c_entry.name - ret['file'] = c_entry.file - ret['fileScope'] = c_entry.fileScope - if c_entry.address.pattern != NULL: - ret['pattern'] = c_entry.address.pattern - if c_entry.address.lineNumber: - ret['lineNumber'] = c_entry.address.lineNumber - if c_entry.kind != NULL: - ret['kind'] = c_entry.kind - for index in range(c_entry.fields.count): - key = c_entry.fields.list[index].key - ret[key.decode()] = c_entry.fields.list[index].value - return ret + cdef class CTags: cdef tagFile* file cdef tagFileInfo info cdef tagEntry c_entry cdef object current_id + cdef object encoding + cdef unicode encoding_errors - def __cinit__(self, filepath): + def __cinit__(self, filepath, encoding='utf8', encoding_errors='strict'): if isinstance(filepath, unicode): filepath = (filepath).encode(sys.getfilesystemencoding()) self.file = ctagsOpen(filepath, &self.info) @@ -54,6 +42,13 @@ cdef class CTags: raise OSError(self.info.status.error_number, strerror(self.info.status.error_number), filepath) + self.encoding = encoding + self.encoding_errors = encoding_errors + + cdef decode(self, bytes bytes_array): + if not self.encoding: + return bytes_array + return bytes_array.decode(self.encoding, self.encoding_errors) def __dealloc__(self): if self.file: @@ -76,38 +71,54 @@ cdef class CTags: ret = self.info.program.version if ret is None: raise KeyError(key) - return ret + return self.decode(ret) def setSortType(self, tagSortType type): success = ctagsSetSortType(self.file, type) if not success: raise RuntimeError() + cdef create_tagEntry(self, const tagEntry* const c_entry): + cdef dict ret = {} + ret['name'] = self.decode(c_entry.name) + ret['file'] = self.decode(c_entry.file) + ret['fileScope'] = c_entry.fileScope + if c_entry.address.pattern != NULL: + ret['pattern'] = self.decode(c_entry.address.pattern) + if c_entry.address.lineNumber: + ret['lineNumber'] = c_entry.address.lineNumber + if c_entry.kind != NULL: + ret['kind'] = self.decode(c_entry.kind) + for index in range(c_entry.fields.count): + key = c_entry.fields.list[index].key + ret[key.decode()] = self.decode(c_entry.fields.list[index].value) + return ret + cdef first(self): success = ctagsFirst(self.file, &self.c_entry) if not success: raise RuntimeError() - return create_tagEntry(&self.c_entry) + return self.create_tagEntry(&self.c_entry) cdef find(self, bytes name, int options): success = ctagsFind(self.file, &self.c_entry, name, options) if not success: raise RuntimeError() - return create_tagEntry(&self.c_entry) + return self.create_tagEntry(&self.c_entry) cdef findNext(self): success = ctagsFindNext(self.file, &self.c_entry) if not success: raise RuntimeError() - return create_tagEntry(&self.c_entry) + return self.create_tagEntry(&self.c_entry) cdef next(self): success = ctagsNext(self.file, &self.c_entry) if not success: raise RuntimeError() - return create_tagEntry(&self.c_entry) + return self.create_tagEntry(&self.c_entry) - def find_tags(self, bytes name, int options): + def find_tags(self, name, int options): """ Find tags corresponding to name in the tag file. @name : a bytes array to search to. @options : A option flags for the search. @@ -115,8 +126,13 @@ cdef class CTags: WARNING: Only one iterator can run on a tag file. If you use another iterator (by calling all_tags or find_tags), - any previous iterator will be invalidate and raise a RuntimeError. + any previous iterator will be invalidated and raise a RuntimeError. """ + if isinstance(name, unicode): + if self.encoding is None: + raise ValueError("%r is a unicode string and you do not provide" + "a encoding"%name) + name = (name).encode(self.encoding) try: first = self.find(name, options) self.current_id = first @@ -140,7 +156,7 @@ cdef class CTags: WARNING: Only one iterator can run on a tag file. If you use another iterator (by calling all_tags or find_tags), - any previous iterator will be invalidate and raise a RuntimeError. + any previous iterator will be invalidated and raise a RuntimeError. """ try: first = self.first() diff --git a/tests/test_ctags.py b/tests/test_ctags.py index b2af839..0348d8e 100644 --- a/tests/test_ctags.py +++ b/tests/test_ctags.py @@ -21,6 +21,52 @@ class TestCTagsParse(TestCase): def setUp(self): file_path = os.path.join(src_dir, 'examples', 'tags') self.ctags = ctags.CTags(file_path) + def test_tag_entry(self): + self.ctags.setSortType(ctags.TAG_SORTED) + entry = next(self.ctags.all_tags()) + entry_info = [entry[_] + for _ in ('file', 'name', 'pattern', 'kind', 'language') + ] + self.assertEqual( + entry_info, + ['../_readtags.c', 'DL_EXPORT', '10', 'macro', 'C'] + ) + def test_tag_find(self): + self.ctags.setSortType(ctags.TAG_SORTED) + entry = next(self.ctags.find_tags('find', ctags.TAG_PARTIALMATCH | ctags.TAG_IGNORECASE)) + entry_info = [entry[_] + for _ in ('file', 'name', 'pattern', 'kind', 'language') + ] + self.assertEqual( + entry_info, + ['../readtags.c', 'find', '/^static tagResult find (tagFile ' + '*const file, tagEntry *const entry,$/', 'function', 'C'] + ) + + def test_tag_find_partial_nocase(self): + for entry in self.ctags.find_tags('tag', ctags.TAG_PARTIALMATCH | ctags.TAG_IGNORECASE): + self.assertTrue(entry['name'].lower().startswith('tag')) + + def test_tag_find_nocase(self): + for entry in self.ctags.find_tags('tag', ctags.TAG_IGNORECASE): + self.assertEqual(entry['name'].lower(), 'tag') + + def test_tag_find_partial(self): + for entry in self.ctags.find_tags('tag', ctags.TAG_PARTIALMATCH): + self.assertTrue(entry['name'].startswith('tag')) + + def test_tag_find_noflag(self): + for entry in self.ctags.find_tags('tag', 0): + self.assertEqual(entry['name'], 'tag') + + def test_tag_find_bytes(self): + for entry in self.ctags.find_tags(b'tag', 0): + self.assertEqual(entry['name'], 'tag') + +class TestCTagsParseNoEncoding(TestCase): + def setUp(self): + file_path = os.path.join(src_dir, 'examples', 'tags') + self.ctags = ctags.CTags(file_path, encoding=None) def test_tag_entry(self): self.ctags.setSortType(ctags.TAG_SORTED) entry = next(self.ctags.all_tags()) From aaac75714463a85bc61c1392ea27301e12404599 Mon Sep 17 00:00:00 2001 From: Matthieu Gautier Date: Sat, 4 Jun 2016 14:49:13 +0200 Subject: [PATCH 17/17] Use str instead of unicode to be compatible with python2. --- src/_readtags.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_readtags.pyx b/src/_readtags.pyx index d7d2f9f..d99ff58 100644 --- a/src/_readtags.pyx +++ b/src/_readtags.pyx @@ -32,7 +32,7 @@ cdef class CTags: cdef tagEntry c_entry cdef object current_id cdef object encoding - cdef unicode encoding_errors + cdef str encoding_errors def __cinit__(self, filepath, encoding='utf8', encoding_errors='strict'): if isinstance(filepath, unicode):