diff --git a/.travis.yml b/.travis.yml index 8131d86..1c8ec97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,9 @@ matrix: python: "2.7" - env: TOXENV=py27-twisted15 python: "2.7" - - env: TOXENV=py35-twisted15 + - env: TOXENV=py27-twisted16 + python: "2.7" + - env: TOXENV=py35-twisted16 python: "3.5" - env: TOXENV=pypy-twisted12 python: "pypy" @@ -21,11 +23,12 @@ matrix: python: "pypy" - env: TOXENV=pypy-twisted15 python: "pypy" - - env: TOXENV=pypy3-twisted15 + - env: TOXENV=pypy-twisted16 + python: "pypy" + - env: TOXENV=pypy3-twisted16 python: "pypy3" allow_failures: - - env: TOXENV=py35-twisted15 - - env: TOXENV=pypy3-twisted15 + - env: TOXENV=pypy3-twisted16 install: - pip install tox coveralls pep8 pyflakes diff --git a/BermiInflector/Inflector.py b/BermiInflector/Inflector.py index 018ad47..56264e6 100644 --- a/BermiInflector/Inflector.py +++ b/BermiInflector/Inflector.py @@ -1,130 +1,130 @@ -#!/usr/bin/env python - -# Copyright (c) 2006 Bermi Ferrer Martinez -# -# bermi a-t bermilabs - com -# See the end of this file for the free software, open source license (BSD-style). - -import re -from Rules.English import English -from Rules.Spanish import Spanish - -class Inflector : - """ - Inflector for pluralizing and singularizing nouns. - - It provides methods for helping on creating programs - based on naming conventions like on Ruby on Rails. - """ - - def __init__( self, Inflector = English ) : - assert callable(Inflector), "Inflector should be a callable obj" - self.Inflector = apply(Inflector); - - def pluralize(self, word) : - '''Pluralizes nouns.''' - return self.Inflector.pluralize(word) - - def singularize(self, word) : - '''Singularizes nouns.''' - return self.Inflector.singularize(word) - - def conditionalPlural(self, numer_of_records, word) : - '''Returns the plural form of a word if first parameter is greater than 1''' - return self.Inflector.conditionalPlural(numer_of_records, word) - - def titleize(self, word, uppercase = '') : - '''Converts an underscored or CamelCase word into a sentence. - The titleize function converts text like "WelcomePage", - "welcome_page" or "welcome page" to this "Welcome Page". - If the "uppercase" parameter is set to 'first' it will only - capitalize the first character of the title.''' - return self.Inflector.titleize(word, uppercase) - - def camelize(self, word): - ''' Returns given word as CamelCased - Converts a word like "send_email" to "SendEmail". It - will remove non alphanumeric character from the word, so - "who's online" will be converted to "WhoSOnline"''' - return self.Inflector.camelize(word) - - def underscore(self, word) : - ''' Converts a word "into_it_s_underscored_version" - Convert any "CamelCased" or "ordinary Word" into an - "underscored_word". - This can be really useful for creating friendly URLs.''' - return self.Inflector.underscore(word) - - def humanize(self, word, uppercase = '') : - '''Returns a human-readable string from word - Returns a human-readable string from word, by replacing - underscores with a space, and by upper-casing the initial - character by default. - If you need to uppercase all the words you just have to - pass 'all' as a second parameter.''' - return self.Inflector.humanize(word, uppercase) - - - def variablize(self, word) : - '''Same as camelize but first char is lowercased - Converts a word like "send_email" to "sendEmail". It - will remove non alphanumeric character from the word, so - "who's online" will be converted to "whoSOnline"''' - return self.Inflector.variablize(word) - - def tableize(self, class_name) : - ''' Converts a class name to its table name according to rails - naming conventions. Example. Converts "Person" to "people" ''' - return self.Inflector.tableize(class_name) - - def classify(self, table_name) : - '''Converts a table name to its class name according to rails - naming conventions. Example: Converts "people" to "Person" ''' - return self.Inflector.classify(table_name) - - def ordinalize(self, number) : - '''Converts number to its ordinal form. - This method converts 13 to 13th, 2 to 2nd ...''' - return self.Inflector.ordinalize(number) - - - def unaccent(self, text) : - '''Transforms a string to its unaccented version. - This might be useful for generating "friendly" URLs''' - return self.Inflector.unaccent(text) - - def urlize(self, text) : - '''Transform a string its unaccented and underscored - version ready to be inserted in friendly URLs''' - return self.Inflector.urlize(text) - - - def demodulize(self, module_name) : - return self.Inflector.demodulize(module_name) - - def modulize(self, module_description) : - return self.Inflector.modulize(module_description) - - def foreignKey(self, class_name, separate_class_name_and_id_with_underscore = 1) : - ''' Returns class_name in underscored form, with "_id" tacked on at the end. - This is for use in dealing with the database.''' - return self.Inflector.foreignKey(class_name, separate_class_name_and_id_with_underscore) - - - - -# Copyright (c) 2006 Bermi Ferrer Martinez -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software to deal in this software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of this software, and to permit -# persons to whom this software is furnished to do so, subject to the following -# condition: -# -# THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THIS SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THIS SOFTWARE. \ No newline at end of file +#!/usr/bin/env python + +# Copyright (c) 2006 Bermi Ferrer Martinez +# +# bermi a-t bermilabs - com +# See the end of this file for the free software, open source license (BSD-style). + +from __future__ import absolute_import +import re +from .Rules.English import English + +class Inflector : + """ + Inflector for pluralizing and singularizing nouns. + + It provides methods for helping on creating programs + based on naming conventions like on Ruby on Rails. + """ + + def __init__( self, Inflector = English ) : + assert callable(Inflector), "Inflector should be a callable obj" + self.Inflector = Inflector() + + def pluralize(self, word) : + '''Pluralizes nouns.''' + return self.Inflector.pluralize(word) + + def singularize(self, word) : + '''Singularizes nouns.''' + return self.Inflector.singularize(word) + + def conditionalPlural(self, numer_of_records, word) : + '''Returns the plural form of a word if first parameter is greater than 1''' + return self.Inflector.conditionalPlural(numer_of_records, word) + + def titleize(self, word, uppercase = '') : + '''Converts an underscored or CamelCase word into a sentence. + The titleize function converts text like "WelcomePage", + "welcome_page" or "welcome page" to this "Welcome Page". + If the "uppercase" parameter is set to 'first' it will only + capitalize the first character of the title.''' + return self.Inflector.titleize(word, uppercase) + + def camelize(self, word): + ''' Returns given word as CamelCased + Converts a word like "send_email" to "SendEmail". It + will remove non alphanumeric character from the word, so + "who's online" will be converted to "WhoSOnline"''' + return self.Inflector.camelize(word) + + def underscore(self, word) : + ''' Converts a word "into_it_s_underscored_version" + Convert any "CamelCased" or "ordinary Word" into an + "underscored_word". + This can be really useful for creating friendly URLs.''' + return self.Inflector.underscore(word) + + def humanize(self, word, uppercase = '') : + '''Returns a human-readable string from word + Returns a human-readable string from word, by replacing + underscores with a space, and by upper-casing the initial + character by default. + If you need to uppercase all the words you just have to + pass 'all' as a second parameter.''' + return self.Inflector.humanize(word, uppercase) + + + def variablize(self, word) : + '''Same as camelize but first char is lowercased + Converts a word like "send_email" to "sendEmail". It + will remove non alphanumeric character from the word, so + "who's online" will be converted to "whoSOnline"''' + return self.Inflector.variablize(word) + + def tableize(self, class_name) : + ''' Converts a class name to its table name according to rails + naming conventions. Example. Converts "Person" to "people" ''' + return self.Inflector.tableize(class_name) + + def classify(self, table_name) : + '''Converts a table name to its class name according to rails + naming conventions. Example: Converts "people" to "Person" ''' + return self.Inflector.classify(table_name) + + def ordinalize(self, number) : + '''Converts number to its ordinal form. + This method converts 13 to 13th, 2 to 2nd ...''' + return self.Inflector.ordinalize(number) + + + def unaccent(self, text) : + '''Transforms a string to its unaccented version. + This might be useful for generating "friendly" URLs''' + return self.Inflector.unaccent(text) + + def urlize(self, text) : + '''Transform a string its unaccented and underscored + version ready to be inserted in friendly URLs''' + return self.Inflector.urlize(text) + + + def demodulize(self, module_name) : + return self.Inflector.demodulize(module_name) + + def modulize(self, module_description) : + return self.Inflector.modulize(module_description) + + def foreignKey(self, class_name, separate_class_name_and_id_with_underscore = 1) : + ''' Returns class_name in underscored form, with "_id" tacked on at the end. + This is for use in dealing with the database.''' + return self.Inflector.foreignKey(class_name, separate_class_name_and_id_with_underscore) + + + + +# Copyright (c) 2006 Bermi Ferrer Martinez +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software to deal in this software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of this software, and to permit +# persons to whom this software is furnished to do so, subject to the following +# condition: +# +# THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THIS SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THIS SOFTWARE. diff --git a/BermiInflector/Rules/Base.py b/BermiInflector/Rules/Base.py index 99f3d3b..c3614d6 100644 --- a/BermiInflector/Rules/Base.py +++ b/BermiInflector/Rules/Base.py @@ -1,156 +1,158 @@ -#!/usr/bin/env python - -# Copyright (c) 2006 Bermi Ferrer Martinez -# bermi a-t bermilabs - com -# See the end of this file for the free software, open source license (BSD-style). - -import re - -class Base: - '''Locale inflectors must inherit from this base class inorder to provide - the basic Inflector functionality''' - - def conditionalPlural(self, numer_of_records, word) : - '''Returns the plural form of a word if first parameter is greater than 1''' - - if numer_of_records > 1 : - return self.pluralize(word) - else : - return word - - - def titleize(self, word, uppercase = '') : - '''Converts an underscored or CamelCase word into a English sentence. - The titleize function converts text like "WelcomePage", - "welcome_page" or "welcome page" to this "Welcome Page". - If second parameter is set to 'first' it will only - capitalize the first character of the title.''' - - if(uppercase == 'first'): - return self.humanize(self.underscore(word)).capitalize() - else : - return self.humanize(self.underscore(word)).title() - - - def camelize(self, word): - ''' Returns given word as CamelCased - Converts a word like "send_email" to "SendEmail". It - will remove non alphanumeric character from the word, so - "who's online" will be converted to "WhoSOnline"''' - return ''.join(w[0].upper() + w[1:] for w in re.sub('[^A-Z^a-z^0-9^:]+', ' ', word).split(' ')) - - def underscore(self, word) : - ''' Converts a word "into_it_s_underscored_version" - Convert any "CamelCased" or "ordinary Word" into an - "underscored_word". - This can be really useful for creating friendly URLs.''' - - return re.sub('[^A-Z^a-z^0-9^\/]+','_', \ - re.sub('([a-z\d])([A-Z])','\\1_\\2', \ - re.sub('([A-Z]+)([A-Z][a-z])','\\1_\\2', re.sub('::', '/',word)))).lower() - - - def humanize(self, word, uppercase = '') : - '''Returns a human-readable string from word - Returns a human-readable string from word, by replacing - underscores with a space, and by upper-casing the initial - character by default. - If you need to uppercase all the words you just have to - pass 'all' as a second parameter.''' - - if(uppercase == 'first'): - return re.sub('_id$', '', word).replace('_',' ').capitalize() - else : - return re.sub('_id$', '', word).replace('_',' ').title() - - - def variablize(self, word) : - '''Same as camelize but first char is lowercased - Converts a word like "send_email" to "sendEmail". It - will remove non alphanumeric character from the word, so - "who's online" will be converted to "whoSOnline"''' - word = self.camelize(word) - return word[0].lower()+word[1:] - - def tableize(self, class_name) : - ''' Converts a class name to its table name according to rails - naming conventions. Example. Converts "Person" to "people" ''' - return self.pluralize(self.underscore(class_name)) - - - def classify(self, table_name) : - '''Converts a table name to its class name according to rails - naming conventions. Example: Converts "people" to "Person" ''' - return self.camelize(self.singularize(table_name)) - - - def ordinalize(self, number) : - '''Converts number to its ordinal English form. - This method converts 13 to 13th, 2 to 2nd ...''' - tail = 'th' - if number % 100 == 11 or number % 100 == 12 or number % 100 == 13: - tail = 'th' - elif number % 10 == 1 : - tail = 'st' - elif number % 10 == 2 : - tail = 'nd' - elif number % 10 == 3 : - tail = 'rd' - - return str(number)+tail - - - def unaccent(self, text) : - '''Transforms a string to its unaccented version. - This might be useful for generating "friendly" URLs''' - find = u'\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF' - replace = u'AAAAAAACEEEEIIIIDNOOOOOOUUUUYTsaaaaaaaceeeeiiiienoooooouuuuyty' - return self.string_replace(text, find, replace) - - def string_replace (self, word, find, replace) : - '''This function returns a copy of word, translating - all occurrences of each character in find to the - corresponding character in replace''' - for k in range(0,len(find)) : - word = re.sub(find[k], replace[k], word) - - return word - - def urlize(self, text) : - '''Transform a string its unaccented and underscored - version ready to be inserted in friendly URLs''' - return re.sub('^_|_$','',self.underscore(self.unaccent(text))) - - - def demodulize(self, module_name) : - return self.humanize(self.underscore(re.sub('^.*::','',module_name))) - - def modulize(self, module_description) : - return self.camelize(self.singularize(module_description)) - - def foreignKey(self, class_name, separate_class_name_and_id_with_underscore = 1) : - ''' Returns class_name in underscored form, with "_id" tacked on at the end. - This is for use in dealing with the database.''' - if separate_class_name_and_id_with_underscore : - tail = '_id' - else : - tail = 'id' - return self.underscore(self.demodulize(class_name))+tail; - - - -# Copyright (c) 2006 Bermi Ferrer Martinez -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software to deal in this software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of this software, and to permit -# persons to whom this software is furnished to do so, subject to the following -# condition: -# -# THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THIS SOFTWARE OR THE USE OR OTHER DEALINGS IN +#!/usr/bin/env python + +# Copyright (c) 2006 Bermi Ferrer Martinez +# bermi a-t bermilabs - com +# See the end of this file for the free software, open source license (BSD-style). + +from __future__ import absolute_import +import re +from six.moves import range + +class Base: + '''Locale inflectors must inherit from this base class inorder to provide + the basic Inflector functionality''' + + def conditionalPlural(self, numer_of_records, word) : + '''Returns the plural form of a word if first parameter is greater than 1''' + + if numer_of_records > 1 : + return self.pluralize(word) + else : + return word + + + def titleize(self, word, uppercase = '') : + '''Converts an underscored or CamelCase word into a English sentence. + The titleize function converts text like "WelcomePage", + "welcome_page" or "welcome page" to this "Welcome Page". + If second parameter is set to 'first' it will only + capitalize the first character of the title.''' + + if(uppercase == 'first'): + return self.humanize(self.underscore(word)).capitalize() + else : + return self.humanize(self.underscore(word)).title() + + + def camelize(self, word): + ''' Returns given word as CamelCased + Converts a word like "send_email" to "SendEmail". It + will remove non alphanumeric character from the word, so + "who's online" will be converted to "WhoSOnline"''' + return ''.join(w[0].upper() + w[1:] for w in re.sub('[^A-Z^a-z^0-9^:]+', ' ', word).split(' ')) + + def underscore(self, word) : + ''' Converts a word "into_it_s_underscored_version" + Convert any "CamelCased" or "ordinary Word" into an + "underscored_word". + This can be really useful for creating friendly URLs.''' + + return re.sub('[^A-Z^a-z^0-9^\/]+','_', \ + re.sub('([a-z\d])([A-Z])','\\1_\\2', \ + re.sub('([A-Z]+)([A-Z][a-z])','\\1_\\2', re.sub('::', '/',word)))).lower() + + + def humanize(self, word, uppercase = '') : + '''Returns a human-readable string from word + Returns a human-readable string from word, by replacing + underscores with a space, and by upper-casing the initial + character by default. + If you need to uppercase all the words you just have to + pass 'all' as a second parameter.''' + + if(uppercase == 'first'): + return re.sub('_id$', '', word).replace('_',' ').capitalize() + else : + return re.sub('_id$', '', word).replace('_',' ').title() + + + def variablize(self, word) : + '''Same as camelize but first char is lowercased + Converts a word like "send_email" to "sendEmail". It + will remove non alphanumeric character from the word, so + "who's online" will be converted to "whoSOnline"''' + word = self.camelize(word) + return word[0].lower()+word[1:] + + def tableize(self, class_name) : + ''' Converts a class name to its table name according to rails + naming conventions. Example. Converts "Person" to "people" ''' + return self.pluralize(self.underscore(class_name)) + + + def classify(self, table_name) : + '''Converts a table name to its class name according to rails + naming conventions. Example: Converts "people" to "Person" ''' + return self.camelize(self.singularize(table_name)) + + + def ordinalize(self, number) : + '''Converts number to its ordinal English form. + This method converts 13 to 13th, 2 to 2nd ...''' + tail = 'th' + if number % 100 == 11 or number % 100 == 12 or number % 100 == 13: + tail = 'th' + elif number % 10 == 1 : + tail = 'st' + elif number % 10 == 2 : + tail = 'nd' + elif number % 10 == 3 : + tail = 'rd' + + return str(number)+tail + + + def unaccent(self, text) : + '''Transforms a string to its unaccented version. + This might be useful for generating "friendly" URLs''' + find = u'\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF' + replace = u'AAAAAAACEEEEIIIIDNOOOOOOUUUUYTsaaaaaaaceeeeiiiienoooooouuuuyty' + return self.string_replace(text, find, replace) + + def string_replace (self, word, find, replace) : + '''This function returns a copy of word, translating + all occurrences of each character in find to the + corresponding character in replace''' + for k in range(0,len(find)) : + word = re.sub(find[k], replace[k], word) + + return word + + def urlize(self, text) : + '''Transform a string its unaccented and underscored + version ready to be inserted in friendly URLs''' + return re.sub('^_|_$','',self.underscore(self.unaccent(text))) + + + def demodulize(self, module_name) : + return self.humanize(self.underscore(re.sub('^.*::','',module_name))) + + def modulize(self, module_description) : + return self.camelize(self.singularize(module_description)) + + def foreignKey(self, class_name, separate_class_name_and_id_with_underscore = 1) : + ''' Returns class_name in underscored form, with "_id" tacked on at the end. + This is for use in dealing with the database.''' + if separate_class_name_and_id_with_underscore : + tail = '_id' + else : + tail = 'id' + return self.underscore(self.demodulize(class_name))+tail; + + + +# Copyright (c) 2006 Bermi Ferrer Martinez +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software to deal in this software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of this software, and to permit +# persons to whom this software is furnished to do so, subject to the following +# condition: +# +# THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THIS SOFTWARE OR THE USE OR OTHER DEALINGS IN # THIS SOFTWARE. \ No newline at end of file diff --git a/BermiInflector/Rules/English.py b/BermiInflector/Rules/English.py index 25d78ee..ad8f297 100644 --- a/BermiInflector/Rules/English.py +++ b/BermiInflector/Rules/English.py @@ -1,156 +1,158 @@ -#!/usr/bin/env python - -# Copyright (c) 2006 Bermi Ferrer Martinez -# bermi a-t bermilabs - com -# -# See the end of this file for the free software, open source license (BSD-style). - -import re -from Base import Base - -class English (Base): - """ - Inflector for pluralize and singularize English nouns. - - This is the default Inflector for the Inflector obj - """ - - def pluralize(self, word) : - '''Pluralizes English nouns.''' - - rules = [ - ['(?i)(quiz)$' , '\\1zes'], - ['^(?i)(ox)$' , '\\1en'], - ['(?i)([m|l])ouse$' , '\\1ice'], - ['(?i)(matr|vert|ind)ix|ex$' , '\\1ices'], - ['(?i)(x|ch|ss|sh)$' , '\\1es'], - ['(?i)([^aeiouy]|qu)ies$' , '\\1y'], - ['(?i)([^aeiouy]|qu)y$' , '\\1ies'], - ['(?i)(hive)$' , '\\1s'], - ['(?i)(?:([^f])fe|([lr])f)$' , '\\1\\2ves'], - ['(?i)sis$' , 'ses'], - ['(?i)([ti])um$' , '\\1a'], - ['(?i)(buffal|tomat)o$' , '\\1oes'], - ['(?i)(bu)s$' , '\\1ses'], - ['(?i)(alias|status)' , '\\1es'], - ['(?i)(octop|vir)us$' , '\\1i'], - ['(?i)(ax|test)is$' , '\\1es'], - ['(?i)s$' , 's'], - ['(?i)$' , 's'] - ] - - uncountable_words = ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep'] - - irregular_words = { - 'person' : 'people', - 'man' : 'men', - 'child' : 'children', - 'sex' : 'sexes', - 'move' : 'moves' - } - - lower_cased_word = word.lower(); - - for uncountable_word in uncountable_words: - if lower_cased_word[-1*len(uncountable_word):] == uncountable_word : - return word - - for irregular in irregular_words.keys(): - match = re.search('('+irregular+')$',word, re.IGNORECASE) - if match: - return re.sub('(?i)'+irregular+'$', match.expand('\\1')[0]+irregular_words[irregular][1:], word) - - for rule in range(len(rules)): - match = re.search(rules[rule][0], word, re.IGNORECASE) - if match : - groups = match.groups() - for k in range(0,len(groups)) : - if groups[k] == None : - rules[rule][1] = rules[rule][1].replace('\\'+str(k+1), '') - - return re.sub(rules[rule][0], rules[rule][1], word) - - return word - - - def singularize (self, word) : - '''Singularizes English nouns.''' - - rules = [ - ['(?i)(quiz)zes$' , '\\1'], - ['(?i)(matr)ices$' , '\\1ix'], - ['(?i)(vert|ind)ices$' , '\\1ex'], - ['(?i)^(ox)en' , '\\1'], - ['(?i)(alias|status)es$' , '\\1'], - ['(?i)([octop|vir])i$' , '\\1us'], - ['(?i)(cris|ax|test)es$' , '\\1is'], - ['(?i)(shoe)s$' , '\\1'], - ['(?i)(o)es$' , '\\1'], - ['(?i)(bus)es$' , '\\1'], - ['(?i)([m|l])ice$' , '\\1ouse'], - ['(?i)(x|ch|ss|sh)es$' , '\\1'], - ['(?i)(m)ovies$' , '\\1ovie'], - ['(?i)(s)eries$' , '\\1eries'], - ['(?i)([^aeiouy]|qu)ies$' , '\\1y'], - ['(?i)([lr])ves$' , '\\1f'], - ['(?i)(tive)s$' , '\\1'], - ['(?i)(hive)s$' , '\\1'], - ['(?i)([^f])ves$' , '\\1fe'], - ['(?i)(^analy)ses$' , '\\1sis'], - ['(?i)((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$' , '\\1\\2sis'], - ['(?i)([ti])a$' , '\\1um'], - ['(?i)(n)ews$' , '\\1ews'], - ['(?i)s$' , ''], - ]; - - uncountable_words = ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep','sms']; - - irregular_words = { - 'people' : 'person', - 'men' : 'man', - 'children' : 'child', - 'sexes' : 'sex', - 'moves' : 'move' - } - - lower_cased_word = word.lower(); - - for uncountable_word in uncountable_words: - if lower_cased_word[-1*len(uncountable_word):] == uncountable_word : - return word - - for irregular in irregular_words.keys(): - match = re.search('('+irregular+')$',word, re.IGNORECASE) - if match: - return re.sub('(?i)'+irregular+'$', match.expand('\\1')[0]+irregular_words[irregular][1:], word) - - - for rule in range(len(rules)): - match = re.search(rules[rule][0], word, re.IGNORECASE) - if match : - groups = match.groups() - for k in range(0,len(groups)) : - if groups[k] == None : - rules[rule][1] = rules[rule][1].replace('\\'+str(k+1), '') - - return re.sub(rules[rule][0], rules[rule][1], word) - - return word - - - -# Copyright (c) 2006 Bermi Ferrer Martinez -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software to deal in this software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of this software, and to permit -# persons to whom this software is furnished to do so, subject to the following -# condition: -# -# THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THIS SOFTWARE OR THE USE OR OTHER DEALINGS IN +#!/usr/bin/env python + +# Copyright (c) 2006 Bermi Ferrer Martinez +# bermi a-t bermilabs - com +# +# See the end of this file for the free software, open source license (BSD-style). + +from __future__ import absolute_import +import re +from .Base import Base +from six.moves import range + +class English (Base): + """ + Inflector for pluralize and singularize English nouns. + + This is the default Inflector for the Inflector obj + """ + + def pluralize(self, word) : + '''Pluralizes English nouns.''' + + rules = [ + ['(?i)(quiz)$' , '\\1zes'], + ['^(?i)(ox)$' , '\\1en'], + ['(?i)([m|l])ouse$' , '\\1ice'], + ['(?i)(matr|vert|ind)ix|ex$' , '\\1ices'], + ['(?i)(x|ch|ss|sh)$' , '\\1es'], + ['(?i)([^aeiouy]|qu)ies$' , '\\1y'], + ['(?i)([^aeiouy]|qu)y$' , '\\1ies'], + ['(?i)(hive)$' , '\\1s'], + ['(?i)(?:([^f])fe|([lr])f)$' , '\\1\\2ves'], + ['(?i)sis$' , 'ses'], + ['(?i)([ti])um$' , '\\1a'], + ['(?i)(buffal|tomat)o$' , '\\1oes'], + ['(?i)(bu)s$' , '\\1ses'], + ['(?i)(alias|status)' , '\\1es'], + ['(?i)(octop|vir)us$' , '\\1i'], + ['(?i)(ax|test)is$' , '\\1es'], + ['(?i)s$' , 's'], + ['(?i)$' , 's'] + ] + + uncountable_words = ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep'] + + irregular_words = { + 'person' : 'people', + 'man' : 'men', + 'child' : 'children', + 'sex' : 'sexes', + 'move' : 'moves' + } + + lower_cased_word = word.lower(); + + for uncountable_word in uncountable_words: + if lower_cased_word[-1*len(uncountable_word):] == uncountable_word : + return word + + for irregular in irregular_words.keys(): + match = re.search('('+irregular+')$',word, re.IGNORECASE) + if match: + return re.sub('(?i)'+irregular+'$', match.expand('\\1')[0]+irregular_words[irregular][1:], word) + + for rule in range(len(rules)): + match = re.search(rules[rule][0], word, re.IGNORECASE) + if match : + groups = match.groups() + for k in range(0,len(groups)) : + if groups[k] == None : + rules[rule][1] = rules[rule][1].replace('\\'+str(k+1), '') + + return re.sub(rules[rule][0], rules[rule][1], word) + + return word + + + def singularize (self, word) : + '''Singularizes English nouns.''' + + rules = [ + ['(?i)(quiz)zes$' , '\\1'], + ['(?i)(matr)ices$' , '\\1ix'], + ['(?i)(vert|ind)ices$' , '\\1ex'], + ['(?i)^(ox)en' , '\\1'], + ['(?i)(alias|status)es$' , '\\1'], + ['(?i)([octop|vir])i$' , '\\1us'], + ['(?i)(cris|ax|test)es$' , '\\1is'], + ['(?i)(shoe)s$' , '\\1'], + ['(?i)(o)es$' , '\\1'], + ['(?i)(bus)es$' , '\\1'], + ['(?i)([m|l])ice$' , '\\1ouse'], + ['(?i)(x|ch|ss|sh)es$' , '\\1'], + ['(?i)(m)ovies$' , '\\1ovie'], + ['(?i)(s)eries$' , '\\1eries'], + ['(?i)([^aeiouy]|qu)ies$' , '\\1y'], + ['(?i)([lr])ves$' , '\\1f'], + ['(?i)(tive)s$' , '\\1'], + ['(?i)(hive)s$' , '\\1'], + ['(?i)([^f])ves$' , '\\1fe'], + ['(?i)(^analy)ses$' , '\\1sis'], + ['(?i)((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$' , '\\1\\2sis'], + ['(?i)([ti])a$' , '\\1um'], + ['(?i)(n)ews$' , '\\1ews'], + ['(?i)s$' , ''], + ]; + + uncountable_words = ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep','sms']; + + irregular_words = { + 'people' : 'person', + 'men' : 'man', + 'children' : 'child', + 'sexes' : 'sex', + 'moves' : 'move' + } + + lower_cased_word = word.lower(); + + for uncountable_word in uncountable_words: + if lower_cased_word[-1*len(uncountable_word):] == uncountable_word : + return word + + for irregular in irregular_words.keys(): + match = re.search('('+irregular+')$',word, re.IGNORECASE) + if match: + return re.sub('(?i)'+irregular+'$', match.expand('\\1')[0]+irregular_words[irregular][1:], word) + + + for rule in range(len(rules)): + match = re.search(rules[rule][0], word, re.IGNORECASE) + if match : + groups = match.groups() + for k in range(0,len(groups)) : + if groups[k] == None : + rules[rule][1] = rules[rule][1].replace('\\'+str(k+1), '') + + return re.sub(rules[rule][0], rules[rule][1], word) + + return word + + + +# Copyright (c) 2006 Bermi Ferrer Martinez +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software to deal in this software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of this software, and to permit +# persons to whom this software is furnished to do so, subject to the following +# condition: +# +# THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THIS SOFTWARE OR THE USE OR OTHER DEALINGS IN # THIS SOFTWARE. \ No newline at end of file diff --git a/BermiInflector/Rules/Spanish.py b/BermiInflector/Rules/Spanish.py deleted file mode 100644 index 7b0d3b3..0000000 --- a/BermiInflector/Rules/Spanish.py +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright (c) 2006 Bermi Ferrer Martinez -# Copyright (c) 2006 Carles Sadurní Anguita -# -# bermi a-t bermilabs - com -# -# See the end of this file for the free software, open source license (BSD-style). - -import re -from Base import Base - -class Spanish (Base): - ''' - Inflector for pluralize and singularize Spanish nouns. - ''' - - def pluralize(self, word) : - '''Pluralizes Spanish nouns.''' - rules = [ - ['(?i)([aeiou])x$', '\\1x'], # This could fail if the word is oxytone. - ['(?i)([áéíóú])([ns])$', '|1\\2es'], - ['(?i)(^[bcdfghjklmnñpqrstvwxyz]*)an$', '\\1anes'], # clan->clanes - ['(?i)([áéíóú])s$', '|1ses'], - ['(?i)(^[bcdfghjklmnñpqrstvwxyz]*)([aeiou])([ns])$', '\\1\\2\\3es'], # tren->trenes - ['(?i)([aeiouáéó])$', '\\1s'], # casa->casas, padre->padres, papá->papás - ['(?i)([aeiou])s$', '\\1s'], # atlas->atlas, virus->virus, etc. - ['(?i)([éí])(s)$', '|1\\2es'], # inglés->ingleses - ['(?i)z$', 'ces'], # luz->luces - ['(?i)([íú])$', '\\1es'], # ceutí->ceutíes, tabú->tabúes - ['(?i)(ng|[wckgtp])$', '\\1s'], # Anglicismos como puenting, frac, crack, show (En que casos podría fallar esto?) - ['(?i)$', 'es'] # ELSE +es (v.g. árbol->árboles) - ] - - uncountable_words = ['tijeras','gafas', 'vacaciones','víveres','déficit'] - ''' In fact these words have no singular form: you cannot say neither - "una gafa" nor "un vívere". So we should change the variable name to - onlyplural or something alike.''' - - irregular_words = { - 'país' : 'países', - 'champú' : 'champús', - 'jersey' : 'jerséis', - 'carácter' : 'caracteres', - 'espécimen' : 'especímenes', - 'menú' : 'menús', - 'régimen' : 'regímenes', - 'curriculum' : 'currículos', - 'ultimátum' : 'ultimatos', - 'memorándum' : 'memorandos', - 'referéndum' : 'referendos' - } - - lower_cased_word = word.lower(); - - for uncountable_word in uncountable_words: - if lower_cased_word[-1*len(uncountable_word):] == uncountable_word : - return word - - for irregular in irregular_words.keys(): - match = re.search('(?i)('+irregular+')$',word, re.IGNORECASE) - if match: - return re.sub('(?i)'+irregular+'$', match.expand('\\1')[0]+irregular_words[irregular][1:], word) - - - for rule in range(len(rules)): - match = re.search(rules[rule][0], word, re.IGNORECASE) - - if match : - groups = match.groups() - replacement = rules[rule][1] - if re.match('\|', replacement) : - for k in range(1, len(groups)) : - replacement = replacement.replace('|'+str(k), self.string_replace(groups[k-1], 'ÁÉÍÓÚáéíóú', 'AEIOUaeiou')) - - result = re.sub(rules[rule][0], replacement, word) - # Esto acentua los sustantivos que al pluralizarse se convierten en esdrújulos como esmóquines, jóvenes... - match = re.search('(?i)([aeiou]).{1,3}([aeiou])nes$',result) - - if match and len(match.groups()) > 1 and not re.search('(?i)[áéíóú]', word) : - result = result.replace(match.group(0), self.string_replace(match.group(1), 'AEIOUaeiou', 'ÁÉÍÓÚáéíóú') + match.group(0)[1:]) - - return result - - return word - - - def singularize (self, word) : - '''Singularizes Spanish nouns.''' - - rules = [ - ['(?i)^([bcdfghjklmnñpqrstvwxyz]*)([aeiou])([ns])es$', '\\1\\2\\3'], - ['(?i)([aeiou])([ns])es$', '~1\\2'], - ['(?i)oides$', 'oide'], # androides->androide - ['(?i)(ces)$/i', 'z'], - ['(?i)(sis|tis|xis)+$', '\\1'], # crisis, apendicitis, praxis - ['(?i)(é)s$', '\\1'], # bebés->bebé - ['(?i)([^e])s$', '\\1'], # casas->casa - ['(?i)([bcdfghjklmnñprstvwxyz]{2,}e)s$', '\\1'], # cofres->cofre - ['(?i)([ghñpv]e)s$', '\\1'], # 24-01 llaves->llave - ['(?i)es$', ''] # ELSE remove _es_ monitores->monitor - ]; - - uncountable_words = ['paraguas','tijeras', 'gafas', 'vacaciones', 'víveres','lunes','martes','miércoles','jueves','viernes','cumpleaños','virus','atlas','sms'] - - irregular_words = { - 'jersey':'jerséis', - 'espécimen':'especímenes', - 'carácter':'caracteres', - 'régimen':'regímenes', - 'menú':'menús', - 'régimen':'regímenes', - 'curriculum' : 'currículos', - 'ultimátum' : 'ultimatos', - 'memorándum' : 'memorandos', - 'referéndum' : 'referendos', - 'sándwich' : 'sándwiches' - } - - lower_cased_word = word.lower(); - - for uncountable_word in uncountable_words: - if lower_cased_word[-1*len(uncountable_word):] == uncountable_word : - return word - - for irregular in irregular_words.keys(): - match = re.search('('+irregular+')$',word, re.IGNORECASE) - if match: - return re.sub('(?i)'+irregular+'$', match.expand('\\1')[0]+irregular_words[irregular][1:], word) - - for rule in range(len(rules)): - match = re.search(rules[rule][0], word, re.IGNORECASE) - if match : - groups = match.groups() - replacement = rules[rule][1] - if re.match('~', replacement) : - for k in range(1, len(groups)) : - replacement = replacement.replace('~'+str(k), self.string_replace(groups[k-1], 'AEIOUaeiou', 'ÁÉÍÓÚáéíóú')) - - result = re.sub(rules[rule][0], replacement, word) - # Esta es una posible solución para el problema de dobles acentos. Un poco guarrillo pero funciona - match = re.search('(?i)([áéíóú]).*([áéíóú])',result) - - if match and len(match.groups()) > 1 and not re.search('(?i)[áéíóú]', word) : - result = self.string_replace(result, 'ÁÉÍÓÚáéíóú', 'AEIOUaeiou') - - return result - - return word - - -# Copyright (c) 2006 Bermi Ferrer Martinez -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software to deal in this software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of this software, and to permit -# persons to whom this software is furnished to do so, subject to the following -# condition: -# -# THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THIS SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THIS SOFTWARE. \ No newline at end of file diff --git a/BermiInflector/tests.py b/BermiInflector/tests.py index 1525c50..08d916c 100644 --- a/BermiInflector/tests.py +++ b/BermiInflector/tests.py @@ -1,129 +1,130 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright (c) 2006 Bermi Ferrer Martinez -# -# bermi a-t bermilabs - com -# -import unittest -from Inflector import Inflector, English, Spanish - -class EnglishInflectorTestCase(unittest.TestCase): - singular_to_plural = { - "search" : "searches", - "switch" : "switches", - "fix" : "fixes", - "box" : "boxes", - "process" : "processes", - "address" : "addresses", - "case" : "cases", - "stack" : "stacks", - "wish" : "wishes", - "fish" : "fish", - - "category" : "categories", - "query" : "queries", - "ability" : "abilities", - "agency" : "agencies", - "movie" : "movies", - - "archive" : "archives", - - "index" : "indices", - - "wife" : "wives", - "safe" : "saves", - "half" : "halves", - - "move" : "moves", - - "salesperson" : "salespeople", - "person" : "people", - - "spokesman" : "spokesmen", - "man" : "men", - "woman" : "women", - - "basis" : "bases", - "diagnosis" : "diagnoses", - - "datum" : "data", - "medium" : "media", - "analysis" : "analyses", - - "node_child" : "node_children", - "child" : "children", - - "experience" : "experiences", - "day" : "days", - - "comment" : "comments", - "foobar" : "foobars", - "newsletter" : "newsletters", - - "old_news" : "old_news", - "news" : "news", - - "series" : "series", - "species" : "species", - - "quiz" : "quizzes", - - "perspective" : "perspectives", - - "ox" : "oxen", - "photo" : "photos", - "buffalo" : "buffaloes", - "tomato" : "tomatoes", - "dwarf" : "dwarves", - "elf" : "elves", - "information" : "information", - "equipment" : "equipment", - "bus" : "buses", - "status" : "statuses", - "mouse" : "mice", - - "louse" : "lice", - "house" : "houses", - "octopus" : "octopi", - "virus" : "viri", - "alias" : "aliases", - "portfolio" : "portfolios", - - "vertex" : "vertices", - "matrix" : "matrices", - - "axis" : "axes", - "testis" : "testes", - "crisis" : "crises", - - "rice" : "rice", - "shoe" : "shoes", - - "horse" : "horses", - "prize" : "prizes", - "edge" : "edges" - } - def setUp(self): - self.inflector = Inflector(English) - - def tearDown(self): - self.inflector = None - - def test_pluralize(self) : - for singular in self.singular_to_plural.keys() : - assert self.inflector.pluralize(singular) == self.singular_to_plural[singular], \ - 'English Inlector pluralize(%s) should produce "%s" and NOT "%s"' % (singular, self.singular_to_plural[singular], self.inflector.pluralize(singular)) - - def test_singularize(self) : - for singular in self.singular_to_plural.keys() : - assert self.inflector.singularize(self.singular_to_plural[singular]) == singular, \ - 'English Inlector singularize(%s) should produce "%s" and NOT "%s"' % (self.singular_to_plural[singular], singular, self.inflector.singularize(self.singular_to_plural[singular])) - - - -InflectorTestSuite = unittest.TestSuite() -InflectorTestSuite.addTest(EnglishInflectorTestCase("test_pluralize")) -InflectorTestSuite.addTest(EnglishInflectorTestCase("test_singularize")) -runner = unittest.TextTestRunner() -runner.run(InflectorTestSuite) +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2006 Bermi Ferrer Martinez +# +# bermi a-t bermilabs - com +# +from __future__ import absolute_import +import unittest +from .Inflector import Inflector, English, Spanish + +class EnglishInflectorTestCase(unittest.TestCase): + singular_to_plural = { + "search" : "searches", + "switch" : "switches", + "fix" : "fixes", + "box" : "boxes", + "process" : "processes", + "address" : "addresses", + "case" : "cases", + "stack" : "stacks", + "wish" : "wishes", + "fish" : "fish", + + "category" : "categories", + "query" : "queries", + "ability" : "abilities", + "agency" : "agencies", + "movie" : "movies", + + "archive" : "archives", + + "index" : "indices", + + "wife" : "wives", + "safe" : "saves", + "half" : "halves", + + "move" : "moves", + + "salesperson" : "salespeople", + "person" : "people", + + "spokesman" : "spokesmen", + "man" : "men", + "woman" : "women", + + "basis" : "bases", + "diagnosis" : "diagnoses", + + "datum" : "data", + "medium" : "media", + "analysis" : "analyses", + + "node_child" : "node_children", + "child" : "children", + + "experience" : "experiences", + "day" : "days", + + "comment" : "comments", + "foobar" : "foobars", + "newsletter" : "newsletters", + + "old_news" : "old_news", + "news" : "news", + + "series" : "series", + "species" : "species", + + "quiz" : "quizzes", + + "perspective" : "perspectives", + + "ox" : "oxen", + "photo" : "photos", + "buffalo" : "buffaloes", + "tomato" : "tomatoes", + "dwarf" : "dwarves", + "elf" : "elves", + "information" : "information", + "equipment" : "equipment", + "bus" : "buses", + "status" : "statuses", + "mouse" : "mice", + + "louse" : "lice", + "house" : "houses", + "octopus" : "octopi", + "virus" : "viri", + "alias" : "aliases", + "portfolio" : "portfolios", + + "vertex" : "vertices", + "matrix" : "matrices", + + "axis" : "axes", + "testis" : "testes", + "crisis" : "crises", + + "rice" : "rice", + "shoe" : "shoes", + + "horse" : "horses", + "prize" : "prizes", + "edge" : "edges" + } + def setUp(self): + self.inflector = Inflector(English) + + def tearDown(self): + self.inflector = None + + def test_pluralize(self) : + for singular in self.singular_to_plural.keys() : + assert self.inflector.pluralize(singular) == self.singular_to_plural[singular], \ + 'English Inlector pluralize(%s) should produce "%s" and NOT "%s"' % (singular, self.singular_to_plural[singular], self.inflector.pluralize(singular)) + + def test_singularize(self) : + for singular in self.singular_to_plural.keys() : + assert self.inflector.singularize(self.singular_to_plural[singular]) == singular, \ + 'English Inlector singularize(%s) should produce "%s" and NOT "%s"' % (self.singular_to_plural[singular], singular, self.inflector.singularize(self.singular_to_plural[singular])) + + + +InflectorTestSuite = unittest.TestSuite() +InflectorTestSuite.addTest(EnglishInflectorTestCase("test_pluralize")) +InflectorTestSuite.addTest(EnglishInflectorTestCase("test_singularize")) +runner = unittest.TextTestRunner() +runner.run(InflectorTestSuite) diff --git a/setup.py b/setup.py index d36b5f5..ee2e5b8 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +from __future__ import absolute_import from setuptools import setup, find_packages from twistar import version @@ -13,5 +14,5 @@ url="http://findingscience.com/twistar", packages=find_packages(), requires=["twisted.enterprise.adbapi"], - install_requires=['twisted >= 12.1'] + install_requires=['twisted >= 12.1','six'] ) diff --git a/tox.ini b/tox.ini index 5b0b8b8..563ee1c 100644 --- a/tox.ini +++ b/tox.ini @@ -2,23 +2,21 @@ [tox] envlist = - py26-twisted12 - py26-twisted13 - py26-twisted14 - py26-twisted15 py27-twisted12 py27-twisted13 py27-twisted14 py27-twisted15 - py35-twisted15 + py35-twisted16 pypy-twisted12 pypy-twisted13 pypy-twisted14 pypy-twisted15 - pypy3-twisted15 + pypy-twisted16 + pypy3-twisted16 [testenv] deps = + six coverage twisted>=15.0, <16.0 commands = @@ -27,48 +25,63 @@ commands = [testenv:py26-twisted12] deps = + six coverage twisted>=12.0, <13.0 [testenv:py26-twisted13] deps = + six coverage twisted>=13.0, <14.0 [testenv:py26-twisted14] deps = + six coverage twisted>=14.0, <15.0 [testenv:py26-twisted15] deps = + six coverage twisted>=15.0, <15.5 [testenv:py27-twisted12] deps = + six coverage twisted>=12.0, <13.0 [testenv:py27-twisted13] deps = + six coverage twisted>=13.0, <14.0 [testenv:py27-twisted14] deps = + six coverage twisted>=14.0, <15.0 [testenv:py27-twisted15] deps = + six coverage twisted>=15.0, <16.0 -[testenv:py35-twisted15] +[testenv:py27-twisted16] deps = + six coverage - twisted>=15.0, <16.0 + twisted>=16.0, <17.0 + +[testenv:py35-twisted16] +deps = + six + coverage + twisted>=16.0, <17.0 [testenv:pypy-twisted12] deps = @@ -90,7 +103,12 @@ deps = coverage twisted>=15.0, <16.0 -[testenv:pypy3-twisted15] +[testenv:pypy-twisted16] deps = coverage - twisted>=15.0, <16.0 + twisted>=16.0, <17.0 + +[testenv:pypy3-twisted16] +deps = + coverage + twisted>=16.0, <17.0 diff --git a/twistar/__init__.py b/twistar/__init__.py index cef26e3..85478a2 100644 --- a/twistar/__init__.py +++ b/twistar/__init__.py @@ -6,5 +6,6 @@ @author: Brian Muller U{bamuller@gmail.com} """ +from __future__ import absolute_import version_info = (1, 6) version = '.'.join(map(str, version_info)) diff --git a/twistar/dbconfig/base.py b/twistar/dbconfig/base.py index d2e45cb..58adbd4 100644 --- a/twistar/dbconfig/base.py +++ b/twistar/dbconfig/base.py @@ -2,12 +2,14 @@ Base module for interfacing with databases. """ +from __future__ import absolute_import from twisted.python import log from twisted.internet import defer from twistar.registry import Registry from twistar.exceptions import ImaginaryTableError, CannotRefreshError from twistar.utils import joinWheres +from six.moves import range class InteractionBase(object): @@ -28,15 +30,6 @@ def __init__(self): self.txn = None - def logEncode(self, s, encoding='utf-8'): - """ - Encode the given string if necessary for printing to logs. - """ - if isinstance(s, unicode): - return s.encode(encoding) - return str(s) - - def log(self, query, args, kwargs): """ Log the query and any args or kwargs using C{twisted.python.log.msg} if @@ -46,7 +39,7 @@ def log(self, query, args, kwargs): return log.msg("TWISTAR query: %s" % query) if len(args) > 0: - log.msg("TWISTAR args: %s" % ",".join(map(self.logEncode, *args))) + log.msg("TWISTAR args: %s" % ",".join(args)) elif len(kwargs) > 0: log.msg("TWISTAR kargs: %s" % str(kwargs)) @@ -188,11 +181,11 @@ def insert(self, tablename, vals, txn=None): # if we have a transaction use it if txn is not None: - self.executeTxn(txn, q, vals.values()) + self.executeTxn(txn, q, list(vals.values())) return self.getLastInsertID(txn) def _insert(txn, q, vals): - self.executeTxn(txn, q, vals.values()) + self.executeTxn(txn, q, list(vals.values())) return self.getLastInsertID(txn) return self.runInteraction(_insert, q, vals) @@ -205,7 +198,7 @@ def escapeColNames(self, colnames): @return: A C{List} of string escaped column names. """ - return map(lambda x: "`%s`" % x, colnames) + return ["`%s`" % x for x in colnames] def insertMany(self, tablename, vals): @@ -223,7 +216,7 @@ def insertMany(self, tablename, vals): params = ",".join([self.insertArgsToString(val) for val in vals]) args = [] for val in vals: - args = args + val.values() + args = args + list(val.values()) q = "INSERT INTO %s (%s) VALUES %s" % (tablename, colnames, params) return self.executeOperation(q, args) @@ -409,9 +402,9 @@ def updateArgsToString(self, args): @return: A tuple of the form C{('name = %s, othername = %s, ...', argvalues)}. """ - colnames = self.escapeColNames(args.keys()) + colnames = self.escapeColNames(list(args.keys())) setstring = ",".join([key + " = %s" for key in colnames]) - return (setstring, args.values()) + return (setstring, list(args.values())) def count(self, tablename, where=None): diff --git a/twistar/dbconfig/mysql.py b/twistar/dbconfig/mysql.py index 4960137..052c1b6 100644 --- a/twistar/dbconfig/mysql.py +++ b/twistar/dbconfig/mysql.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import MySQLdb from twisted.enterprise import adbapi @@ -23,7 +24,7 @@ class ReconnectingMySQLConnectionPool(adbapi.ConnectionPool): def _runInteraction(self, interaction, *args, **kw): try: return adbapi.ConnectionPool._runInteraction(self, interaction, *args, **kw) - except MySQLdb.OperationalError, e: + except MySQLdb.OperationalError as e: if e[0] not in (2006, 2013): raise log.err("Lost connection to MySQL, retrying operation. If no errors follow, retry was successful.") diff --git a/twistar/dbconfig/postgres.py b/twistar/dbconfig/postgres.py index 1b7e13b..9d584ed 100644 --- a/twistar/dbconfig/postgres.py +++ b/twistar/dbconfig/postgres.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from twistar.dbconfig.base import InteractionBase @@ -18,7 +19,7 @@ def insertArgsToString(self, vals): def escapeColNames(self, colnames): - return map(lambda x: '"%s"' % x, colnames) + return ['"%s"' % x for x in colnames] def count(self, tablename, where=None): diff --git a/twistar/dbconfig/pyodbc.py b/twistar/dbconfig/pyodbc.py index 38194c8..1981c71 100644 --- a/twistar/dbconfig/pyodbc.py +++ b/twistar/dbconfig/pyodbc.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from twistar.dbconfig.base import InteractionBase @@ -13,7 +14,7 @@ def whereToString(self, where): def updateArgsToString(self, args): colnames = self.escapeColNames(args.keys()) setstring = ",".join([key + " = ?" for key in colnames]) - return (setstring, args.values()) + return (setstring, list(args.values())) def insertArgsToString(self, vals): diff --git a/twistar/dbconfig/sqlite.py b/twistar/dbconfig/sqlite.py index 9575c50..af3fcce 100644 --- a/twistar/dbconfig/sqlite.py +++ b/twistar/dbconfig/sqlite.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from twistar.registry import Registry from twistar.dbconfig.base import InteractionBase @@ -13,7 +14,7 @@ def whereToString(self, where): def updateArgsToString(self, args): colnames = self.escapeColNames(args.keys()) setstring = ",".join([key + " = ?" for key in colnames]) - return (setstring, args.values()) + return (setstring, list(args.values())) def insertArgsToString(self, vals): diff --git a/twistar/dbobject.py b/twistar/dbobject.py index cdfc7f2..8fcf2f7 100644 --- a/twistar/dbobject.py +++ b/twistar/dbobject.py @@ -1,6 +1,7 @@ """ Code relating to the base L{DBObject} object. """ +from __future__ import absolute_import from twisted.internet import defer from twistar.registry import Registry @@ -10,6 +11,7 @@ from twistar.validation import Validator, Errors from BermiInflector.Inflector import Inflector +import six class DBObject(Validator): @@ -82,7 +84,7 @@ def updateAttrs(self, kwargs): @param kwargs: A C{dict} whose keys will be turned into properties and whose values will then be assigned to those properties. """ - for k, v in kwargs.iteritems(): + for k, v in six.iteritems(kwargs): setattr(self, k, v) @@ -297,7 +299,7 @@ def loadRelations(self, *relations): """ if len(relations) == 0: klass = object.__getattribute__(self, "__class__") - allrelations = klass.RELATIONSHIP_CACHE.keys() + allrelations = list(klass.RELATIONSHIP_CACHE.keys()) if len(allrelations) == 0: return defer.succeed({}) return self.loadRelations(*allrelations) diff --git a/twistar/registry.py b/twistar/registry.py index d19882f..9de3ffd 100644 --- a/twistar/registry.py +++ b/twistar/registry.py @@ -2,6 +2,7 @@ Module handling global registration of variables and classes. """ +from __future__ import absolute_import from twisted.python import reflect from twistar.exceptions import ClassNotRegisteredError diff --git a/twistar/relationships.py b/twistar/relationships.py index 326cfb6..2709314 100644 --- a/twistar/relationships.py +++ b/twistar/relationships.py @@ -2,6 +2,7 @@ Module descripting different types of object relationships. """ +from __future__ import absolute_import from twisted.internet import defer from BermiInflector.Inflector import Inflector diff --git a/twistar/tests/mysql_config.py b/twistar/tests/mysql_config.py index 25c22a5..5bf986f 100644 --- a/twistar/tests/mysql_config.py +++ b/twistar/tests/mysql_config.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from twisted.enterprise import adbapi from twistar.registry import Registry diff --git a/twistar/tests/postgres_config.py b/twistar/tests/postgres_config.py index 124b0ca..869e203 100644 --- a/twistar/tests/postgres_config.py +++ b/twistar/tests/postgres_config.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from twisted.enterprise import adbapi from twistar.registry import Registry diff --git a/twistar/tests/sqlite_config.py b/twistar/tests/sqlite_config.py index 4d32e82..4931034 100644 --- a/twistar/tests/sqlite_config.py +++ b/twistar/tests/sqlite_config.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from twisted.enterprise import adbapi from twisted.internet import defer diff --git a/twistar/tests/test_dbconfig.py b/twistar/tests/test_dbconfig.py index a14cb2b..292c598 100644 --- a/twistar/tests/test_dbconfig.py +++ b/twistar/tests/test_dbconfig.py @@ -1,10 +1,12 @@ +from __future__ import absolute_import from twisted.trial import unittest from twisted.internet.defer import inlineCallbacks from twistar.registry import Registry from twistar.dbconfig.base import InteractionBase -from utils import User, Picture, Avatar, initDB, tearDownDB, Coltest +from .utils import User, Picture, Avatar, initDB, tearDownDB, Coltest +from six.moves import range class DBConfigTest(unittest.TestCase): @@ -178,5 +180,6 @@ def test_unicode_logging(self): ustr = '\xc3\xa8' InteractionBase().log(ustr, [ustr], {ustr: ustr}) + InteractionBase().log(ustr, [], {ustr: ustr}) InteractionBase.LOG = False diff --git a/twistar/tests/test_dbobject.py b/twistar/tests/test_dbobject.py index d7bf1ab..568f3dd 100644 --- a/twistar/tests/test_dbobject.py +++ b/twistar/tests/test_dbobject.py @@ -1,10 +1,12 @@ +from __future__ import absolute_import from twisted.trial import unittest from twisted.internet.defer import inlineCallbacks from twistar.exceptions import ImaginaryTableError from twistar.registry import Registry -from utils import User, Avatar, Picture, tearDownDB, initDB, FakeObject, DBObject +from .utils import User, Avatar, Picture, tearDownDB, initDB, FakeObject, DBObject +from six.moves import range class DBObjectTest(unittest.TestCase): @@ -59,11 +61,11 @@ def test_findOrCreate(self): def test_creation(self): # test creating blank object u = yield User().save() - self.assertTrue(type(u.id) == int or type(u.id) == long) + self.assertTrue(type(u.id) == int or type(u.id) == int) # test creating object with props that don't correspond to columns u = yield User(a_fake_column="blech").save() - self.assertTrue(type(u.id) == int or type(u.id) == long) + self.assertTrue(type(u.id) == int or type(u.id) == int) # Test table doesn't exist f = FakeObject(blah="something") @@ -173,7 +175,7 @@ def test_refresh(self): @inlineCallbacks def test_validation(self): User.validatesPresenceOf('first_name', message='cannot be blank, fool.') - User.validatesLengthOf('last_name', range=xrange(1, 101)) + User.validatesLengthOf('last_name', range=range(1, 101)) User.validatesUniquenessOf('first_name') u = User() diff --git a/twistar/tests/test_relationships.py b/twistar/tests/test_relationships.py index 6bee894..3290935 100644 --- a/twistar/tests/test_relationships.py +++ b/twistar/tests/test_relationships.py @@ -1,10 +1,12 @@ +from __future__ import absolute_import from twisted.trial import unittest from twisted.internet.defer import inlineCallbacks from twistar.exceptions import ReferenceNotSavedError -from utils import Boy, Girl, tearDownDB, initDB, Registry, Comment, Category -from utils import User, Avatar, Picture, FavoriteColor, Nickname, Blogpost +from .utils import Boy, Girl, tearDownDB, initDB, Registry, Comment, Category +from .utils import User, Avatar, Picture, FavoriteColor, Nickname, Blogpost +from six.moves import range class RelationshipTest(unittest.TestCase): diff --git a/twistar/tests/test_transactions.py b/twistar/tests/test_transactions.py index 8629f99..5d4700c 100644 --- a/twistar/tests/test_transactions.py +++ b/twistar/tests/test_transactions.py @@ -1,10 +1,11 @@ +from __future__ import absolute_import from twisted.trial import unittest from twisted.internet.defer import inlineCallbacks from twistar.utils import transaction from twistar.exceptions import TransactionError -from utils import initDB, tearDownDB, Registry, Transaction +from .utils import initDB, tearDownDB, Registry, Transaction class TransactionTest(unittest.TestCase): diff --git a/twistar/tests/test_utils.py b/twistar/tests/test_utils.py index 7644cc7..817a401 100644 --- a/twistar/tests/test_utils.py +++ b/twistar/tests/test_utils.py @@ -1,9 +1,10 @@ +from __future__ import absolute_import from twisted.trial import unittest from twisted.internet.defer import inlineCallbacks from twistar import utils -from utils import User, initDB, tearDownDB +from .utils import User, initDB, tearDownDB from collections import OrderedDict diff --git a/twistar/tests/utils.py b/twistar/tests/utils.py index 3c54bc6..6459df6 100644 --- a/twistar/tests/utils.py +++ b/twistar/tests/utils.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import +from __future__ import print_function from twistar.dbobject import DBObject from twistar.registry import Registry @@ -5,18 +7,18 @@ DBTYPE = os.environ.get('DBTYPE', 'sqlite') if DBTYPE == 'mysql': - print "Using MySQL for tests" - import mysql_config + print("Using MySQL for tests") + from . import mysql_config initDB = mysql_config.initDB tearDownDB = mysql_config.tearDownDB elif DBTYPE == 'postgres': - print "Using PostgreSQL for tests" - import postgres_config + print("Using PostgreSQL for tests") + from . import postgres_config initDB = postgres_config.initDB tearDownDB = postgres_config.tearDownDB else: - print "Using SQLite for tests" - import sqlite_config + print("Using SQLite for tests") + from . import sqlite_config initDB = sqlite_config.initDB tearDownDB = sqlite_config.initDB diff --git a/twistar/utils.py b/twistar/utils.py index c2efbdb..a06f6e8 100644 --- a/twistar/utils.py +++ b/twistar/utils.py @@ -2,10 +2,14 @@ General catchall for functions that don't make sense as methods. """ +from __future__ import absolute_import from twisted.internet import defer, threads, reactor from twistar.registry import Registry from twistar.exceptions import TransactionError +import six +from six.moves import range +from functools import reduce def transaction(interaction): @@ -22,7 +26,7 @@ def _transaction(txn, args, kwargs): result = threads.blockingCallFromThread(reactor, interaction, txn, *args, **kwargs) config.txn = None return result - except Exception, e: + except Exception as e: config.txn = None raise TransactionError(str(e)) @@ -68,11 +72,11 @@ def dictToWhere(attrs, joiner="AND"): return None wheres = [] - for key, value in attrs.iteritems(): + for key, value in six.iteritems(attrs): comparator = 'is' if value is None else '=' wheres.append("(%s %s ?)" % (key, comparator)) - return [(" %s " % joiner).join(wheres)] + attrs.values() + return [(" %s " % joiner).join(wheres)] + list(attrs.values()) def joinWheres(wone, wtwo, joiner="AND"): @@ -130,5 +134,5 @@ def handle(results, names): rvalue[names[index]] = results[index][1] return rvalue - dl = defer.DeferredList(d.values()) - return dl.addCallback(handle, d.keys()) + dl = defer.DeferredList(list(d.values())) + return dl.addCallback(handle, list(d.keys())) diff --git a/twistar/validation.py b/twistar/validation.py index ccaf6c1..29b6b61 100644 --- a/twistar/validation.py +++ b/twistar/validation.py @@ -2,9 +2,11 @@ Package providing validation support for L{DBObject}s. """ +from __future__ import absolute_import from twisted.internet import defer from BermiInflector.Inflector import Inflector from twistar.utils import joinWheres, deferredDict +import six def presenceOf(obj, names, kwargs): @@ -226,7 +228,7 @@ def isEmpty(self): Returns C{True} if there are any errors associated with any properties, C{False} otherwise. """ - for value in self.itervalues(): + for value in six.itervalues(self): if len(value) > 0: return False return True @@ -249,7 +251,7 @@ def __str__(self): Return all errors as a single string. """ s = [] - for values in self.itervalues(): + for values in six.itervalues(self): for value in values: s.append(value) if len(s) == 0: @@ -261,4 +263,4 @@ def __len__(self): """ Get the sum of all errors for all properties. """ - return sum([len(value) for value in self.itervalues()]) + return sum([len(value) for value in six.itervalues(self)])