├── Default (OSX).sublime-keymap ├── Default (Linux).sublime-keymap ├── Default (Windows).sublime-keymap ├── LICENSE ├── Default.sublime-commands ├── readme.md ├── Main.sublime-menu ├── CaseConversion.sublime-settings ├── case_conversion.py └── case_parse.py /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+alt+c", "ctrl+alt+s"], "command": "convert_to_snake"}, 3 | { "keys": ["ctrl+alt+c", "ctrl+alt+shift+s"], "command": "convert_to_screaming_snake"}, 4 | { "keys": ["ctrl+alt+c", "ctrl+alt+c"], "command": "convert_to_camel"}, 5 | { "keys": ["ctrl+alt+c", "ctrl+alt+p"], "command": "convert_to_pascal"}, 6 | { "keys": ["ctrl+alt+c", "ctrl+alt+d"], "command": "convert_to_dot"}, 7 | { "keys": ["ctrl+alt+c", "ctrl+alt+h"], "command": "convert_to_dash"}, 8 | { "keys": ["ctrl+alt+c", "ctrl+alt+w"], "command": "convert_to_separate_words"}, 9 | { "keys": ["ctrl+alt+c", "ctrl+alt+/"], "command": "convert_to_slash"}, 10 | { "keys": ["ctrl+alt+c", "ctrl+alt+b"], "command": "convert_to_back_slash"}, 11 | { "keys": ["ctrl+shift+-"], "command": "toggle_snake_camel_pascal"} 12 | ] 13 | -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+alt+c", "ctrl+alt+s"], "command": "convert_to_snake"}, 3 | { "keys": ["ctrl+alt+c", "ctrl+alt+shift+s"], "command": "convert_to_screaming_snake"}, 4 | { "keys": ["ctrl+alt+c", "ctrl+alt+c"], "command": "convert_to_camel"}, 5 | { "keys": ["ctrl+alt+c", "ctrl+alt+p"], "command": "convert_to_pascal"}, 6 | { "keys": ["ctrl+alt+c", "ctrl+alt+d"], "command": "convert_to_dot"}, 7 | { "keys": ["ctrl+alt+c", "ctrl+alt+h"], "command": "convert_to_dash"}, 8 | { "keys": ["ctrl+alt+c", "ctrl+alt+w"], "command": "convert_to_separate_words"}, 9 | { "keys": ["ctrl+alt+c", "ctrl+alt+/"], "command": "convert_to_slash"}, 10 | { "keys": ["ctrl+alt+c", "ctrl+alt+b"], "command": "convert_to_back_slash"}, 11 | { "keys": ["ctrl+shift+-"], "command": "toggle_snake_camel_pascal"} 12 | ] 13 | -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+alt+c", "ctrl+alt+s"], "command": "convert_to_snake"}, 3 | { "keys": ["ctrl+alt+c", "ctrl+alt+shift+s"], "command": "convert_to_screaming_snake"}, 4 | { "keys": ["ctrl+alt+c", "ctrl+alt+c"], "command": "convert_to_camel"}, 5 | { "keys": ["ctrl+alt+c", "ctrl+alt+p"], "command": "convert_to_pascal"}, 6 | { "keys": ["ctrl+alt+c", "ctrl+alt+d"], "command": "convert_to_dot"}, 7 | { "keys": ["ctrl+alt+c", "ctrl+alt+h"], "command": "convert_to_dash"}, 8 | { "keys": ["ctrl+alt+c", "ctrl+alt+w"], "command": "convert_to_separate_words"}, 9 | { "keys": ["ctrl+alt+c", "ctrl+alt+/"], "command": "convert_to_slash"}, 10 | { "keys": ["ctrl+alt+c", "ctrl+alt+b"], "command": "convert_to_back_slash"}, 11 | { "keys": ["ctrl+shift+-"], "command": "toggle_snake_camel_pascal"} 12 | ] 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012-2015 Davis Clark 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Convert Case: PascalCase", 4 | "command": "convert_to_pascal" 5 | }, 6 | { 7 | "caption": "Convert Case: camelCase", 8 | "command": "convert_to_camel" 9 | }, 10 | { 11 | "caption": "Convert Case: snake_case", 12 | "command": "convert_to_snake" 13 | }, 14 | { 15 | "caption": "Convert Case: SCREAMING_SNAKE_CASE", 16 | "command": "convert_to_screaming_snake" 17 | }, 18 | { 19 | "caption": "Convert Case: dot.case", 20 | "command": "convert_to_dot" 21 | }, 22 | { 23 | "caption": "Convert Case: dash-case", 24 | "command": "convert_to_dash" 25 | }, 26 | { 27 | "caption": "Convert Case: separate␣words", 28 | "command": "convert_to_separate_words" 29 | }, 30 | { 31 | "caption": "Convert Case: separate/with/slash", 32 | "command": "convert_to_slash" 33 | }, 34 | { 35 | "caption": "Convert Case: separate\\with\\backslash", 36 | "command": "convert_to_back_slash" 37 | }, 38 | { 39 | "caption": "Convert Case: Toggle Case", 40 | "command": "toggle_snake_camel_pascal" 41 | 42 | } 43 | ] 44 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Case Conversion 2 | Case Conversion is a plugin for Sublime Text. It converts the current word/token between pascal, 3 | camel, snake, screaming snake, dot, dash (hyphen), forward slash `/`, backslash `\` cases, and separated words. 4 | 5 | ## Keybindings 6 | - To snake_case: "ctrl+alt+c", "ctrl+alt+s" 7 | - To SCREAMING_SNAKE_CASE: "ctrl+alt+c", "ctrl+alt+shift+s" 8 | - To camelCase: "ctrl+alt+c", "ctrl+alt+c" 9 | - To PascalCase: "ctrl+alt+c", "ctrl+alt+p" 10 | - To dot.case: "ctrl+alt+c", "ctrl+alt+d" 11 | - To dash-case: "ctrl+alt+c", "ctrl+alt+h" 12 | - To separate words: "ctrl+alt+c", "ctrl+alt+w" 13 | - To separate with forward slashes: "ctrl+alt+c", "ctrl+alt+/" 14 | - To separate with backslashes: "ctrl+alt+c", "ctrl+alt+b" 15 | - To toggle between snake_case, camelCase and PascalCase: "ctrl+shift+-" 16 | 17 | ## Install 18 | #### Package Control 19 | Open the Command Palette, type ***`pci`*** to bring up **`Package Control: Install Package`**, hit Enter, 20 | then search for `Case Conversion`. 21 | 22 | #### Git Clone 23 | Clone this repository in to the Sublime Text "Packages" directory, which is located where ever the 24 | "Preferences" -> "Browse Packages" option in Sublime takes you. 25 | 26 | ## Contributors 27 | - Davis Clark 28 | - Scott Bessler 29 | - Curtis Gibby 30 | - Matt Morrison 31 | - Gavin Higham 32 | 33 | ## License 34 | Copyright (C) 2012-2015 Davis Clark 35 | 36 | Permission is hereby granted, free of charge, to any person obtaining a copy of 37 | this software and associated documentation files (the "Software"), to deal in 38 | the Software without restriction, including without limitation the rights to 39 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 40 | of the Software, and to permit persons to whom the Software is furnished to do 41 | so, subject to the following conditions: 42 | 43 | The above copyright notice and this permission notice shall be included in all 44 | copies or substantial portions of the Software. 45 | 46 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 47 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 48 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 49 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 50 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 51 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 52 | SOFTWARE. 53 | 54 | 55 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/jdc0589/caseconversion/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 56 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "edit", 4 | "children": 5 | [ 6 | { 7 | "id": "convert_case", 8 | "children": 9 | [ 10 | { "command": "convert_to_snake", "caption": "snake_case" }, 11 | { "command": "convert_to_screaming_snake", "caption": "SCREAMING_SNAKE_CASE" }, 12 | { "command": "convert_to_camel", "caption": "camelCase" }, 13 | { "command": "convert_to_pascal", "caption": "PascalCase" }, 14 | { "command": "convert_to_dot", "caption": "dot.case" }, 15 | { "command": "convert_to_dash", "caption": "dash-case" }, 16 | { "command": "convert_to_separate_words", "caption": "separate␣words" }, 17 | { "command": "convert_to_slash", "caption": "separate/with/slash" }, 18 | { "command": "convert_to_back_slash", "caption": "separate\\with\\backslash" }, 19 | { "command": "toggle_snake_camel_pascal", "caption": "Toggle Case" } 20 | ] 21 | } 22 | ] 23 | }, 24 | { 25 | "caption": "Preferences", 26 | "mnemonic": "n", 27 | "id": "preferences", 28 | "children": 29 | [ 30 | { 31 | "caption": "Package Settings", 32 | "mnemonic": "P", 33 | "id": "package-settings", 34 | "children": 35 | [ 36 | { 37 | "caption": "Case Conversion", 38 | "children": 39 | [ 40 | { 41 | "command": "edit_settings", 42 | "args": { 43 | "base_file": "${packages}/Case Conversion/CaseConversion.sublime-settings", 44 | "default": "// Settings in here override those in \"Case Conversion/CaseConversion.sublime-settings\",\n\n{\n\t$0\n}\n" 45 | }, 46 | "caption": "Settings" 47 | }, 48 | { 49 | "command": "edit_settings", 50 | "args": { 51 | "base_file": "${packages}/Case Conversion/Default ($platform).sublime-keymap", 52 | "default": "// Settings in here override those in \"Case Conversion/Default.sublime-settings\",\n\n{\n\t$0\n}\n" 53 | }, 54 | "caption": "Key Bindings" 55 | } 56 | ] 57 | } 58 | ] 59 | } 60 | ] 61 | } 62 | ] 63 | -------------------------------------------------------------------------------- /CaseConversion.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // `detect_acronyms` will cause certain words in variable names to be 3 | // marked as acronyms, making them upper-case ("URL") instead of 4 | // capitalized ("Url"). 5 | 6 | // When variables are parsed, upper-case letters count as word boundaries. 7 | // That means words which would be considered acronyms are instead 8 | // separated into individual letters. For example, converting "BaseURL" to 9 | // snake_case will produce "base_u_r_l". 10 | 11 | // If `detect_acronyms` is enabled, runs of single upper-case characters 12 | // will be combined into single words. How these are detected depends on 13 | // the `use_acronyms_list` setting. In general, this means "BaseURL" would 14 | // be converted into "base_url". 15 | 16 | // If `detect_acronyms` is disabled, no attempts to combine upper-case 17 | // characters will be made. 18 | "detect_acronyms": true, 19 | 20 | 21 | // `use_acronyms_list` causes a more robust way to detect acronyms to be 22 | // used, by searching for words from a predefined list. 23 | 24 | // If `use_acronyms_list` is disabled, then a basic detection method is 25 | // used. That is, runs of upper-case letters are detected and combined 26 | // into single words. There are two drawbacks to this. The first is that 27 | // two supposed acronyms that are adjacent will be counted as one word 28 | // (e.g. "GetHTTPURLPath" would be divided into [Get, HTTPURL, Path]). 29 | 30 | // The second drawback is that acronyms converted to lower-case cannot be 31 | // converted back to their original upper-case. For example, "BaseURL" to 32 | // "base_url" to "BaseUrl". 33 | 34 | // If `use_acronyms_list` is enabled, then each run of upper-case letters 35 | // is compared with words in the `acronyms` list, and any matches are 36 | // counted as words. This means adjacent acronyms will be detected (e.g. 37 | // "GetHTTPURLPath" will be divided into [Get, HTTP, URL, Path]). 38 | 39 | // Acronyms are also detected among words, so converting from lower-case 40 | // will produce correctly upper-cased acronyms. For example, "BaseURL" to 41 | // "base_url" to "BaseURL". 42 | "use_acronyms_list": true, 43 | 44 | 45 | // `acronyms` is a list of words that are to be considered acronyms. 46 | 47 | // Valid acronyms contain only upper and lower-case letters, and digits. 48 | // Invalid words are ignored, and valid words are converted to upper-case. 49 | 50 | // Order matters; words earlier in the list will be selected before words 51 | // later in the list. For example, if "UI" were to be put before "GUI", 52 | // then "GUI" would never be selected, because the "UI" in "GUI" would 53 | // always be selected first. 54 | 55 | // Note that if the list is empty, no acronyms will be detected, so 56 | // variables will be treated as if `detect_acronyms` was disabled. 57 | "acronyms": [ 58 | "HTML", 59 | "CSS", 60 | "HTTP", 61 | "URL", 62 | "GUI", 63 | "UI", 64 | "ID" 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /case_conversion.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import re 4 | import sys 5 | 6 | PYTHON = sys.version_info[0] 7 | 8 | if 3 == PYTHON: 9 | # Python 3 and ST3 10 | from . import case_parse 11 | else: 12 | # Python 2 and ST2 13 | import case_parse 14 | 15 | 16 | SETTINGS_FILE = "CaseConversion.sublime-settings" 17 | 18 | 19 | def to_snake_case(text, detectAcronyms, acronyms): 20 | words, case, sep = case_parse.parseVariable(text, detectAcronyms, acronyms) 21 | return '_'.join([w.lower() for w in words]) 22 | 23 | 24 | def to_screaming_snake_case(text, detectAcronyms, acronyms): 25 | words, case, sep = case_parse.parseVariable(text, detectAcronyms, acronyms) 26 | return '_'.join([w.upper() for w in words]) 27 | 28 | 29 | def to_pascal_case(text, detectAcronyms, acronyms): 30 | words, case, sep = case_parse.parseVariable(text, detectAcronyms, acronyms) 31 | return ''.join(words) 32 | 33 | 34 | def to_camel_case(text, detectAcronyms, acronyms): 35 | words, case, sep = case_parse.parseVariable(text, detectAcronyms, acronyms) 36 | words[0] = words[0].lower() 37 | return ''.join(words) 38 | 39 | 40 | def to_dot_case(text, detectAcronyms, acronyms): 41 | words, case, sep = case_parse.parseVariable(text, detectAcronyms, acronyms) 42 | return '.'.join([w.lower() for w in words]) 43 | 44 | 45 | def to_dash_case(text, detectAcronyms, acronyms): 46 | words, case, sep = case_parse.parseVariable(text, detectAcronyms, acronyms) 47 | return '-'.join([w.lower() for w in words]) 48 | 49 | 50 | def to_slash(text, detectAcronyms, acronyms): 51 | words, case, sep = case_parse.parseVariable(text, detectAcronyms, acronyms, True) 52 | return '/'.join(words) 53 | 54 | def to_backslash(text, detectAcronyms, acronyms): 55 | words, case, sep = case_parse.parseVariable(text, detectAcronyms, acronyms, True) 56 | return '\\'.join(words) 57 | 58 | 59 | def to_separate_words(text, detectAcronyms, acronyms): 60 | words, case, sep = case_parse.parseVariable(text, detectAcronyms, acronyms, True) 61 | return ' '.join(words) 62 | 63 | 64 | def toggle_case(text, detectAcronyms, acronyms): 65 | words, case, sep = case_parse.parseVariable(text, detectAcronyms, acronyms) 66 | if case == 'pascal' and not sep: 67 | return to_snake_case(text, detectAcronyms, acronyms) 68 | elif case == 'lower' and sep == '_': 69 | return to_camel_case(text, detectAcronyms, acronyms) 70 | elif case == 'camel' and not sep: 71 | return to_pascal_case(text, detectAcronyms, acronyms) 72 | else: 73 | return text 74 | 75 | 76 | def run_on_selections(view, edit, func): 77 | settings = sublime.load_settings(SETTINGS_FILE) 78 | detectAcronyms = settings.get("detect_acronyms", True) 79 | useList = settings.get("use_acronyms_list", True) 80 | if useList: 81 | acronyms = settings.get("acronyms", []) 82 | else: 83 | acronyms = False 84 | 85 | for s in view.sel(): 86 | region = s if s else view.word(s) 87 | 88 | text = view.substr(region) 89 | # Preserve leading and trailing whitespace 90 | leading = text[:len(text)-len(text.lstrip())] 91 | trailing = text[len(text.rstrip()):] 92 | new_text = leading + func(text.strip(), detectAcronyms, acronyms) + trailing 93 | if new_text != text: 94 | view.replace(edit, region, new_text) 95 | 96 | 97 | class ToggleSnakeCamelPascalCommand(sublime_plugin.TextCommand): 98 | def run(self, edit): 99 | run_on_selections(self.view, edit, toggle_case) 100 | 101 | 102 | class ConvertToSnakeCommand(sublime_plugin.TextCommand): 103 | def run(self, edit): 104 | run_on_selections(self.view, edit, to_snake_case) 105 | 106 | 107 | class ConvertToScreamingSnakeCommand(sublime_plugin.TextCommand): 108 | def run(self, edit): 109 | run_on_selections(self.view, edit, to_screaming_snake_case) 110 | 111 | 112 | class ConvertToCamel(sublime_plugin.TextCommand): 113 | def run(self, edit): 114 | run_on_selections(self.view, edit, to_camel_case) 115 | 116 | 117 | class ConvertToPascal(sublime_plugin.TextCommand): 118 | def run(self, edit): 119 | run_on_selections(self.view, edit, to_pascal_case) 120 | 121 | 122 | class ConvertToDot(sublime_plugin.TextCommand): 123 | def run(self, edit): 124 | run_on_selections(self.view, edit, to_dot_case) 125 | 126 | 127 | class ConvertToDash(sublime_plugin.TextCommand): 128 | def run(self, edit): 129 | run_on_selections(self.view, edit, to_dash_case) 130 | 131 | 132 | class ConvertToSeparateWords(sublime_plugin.TextCommand): 133 | def run(self, edit): 134 | run_on_selections(self.view, edit, to_separate_words) 135 | 136 | 137 | class ConvertToSlash(sublime_plugin.TextCommand): 138 | def run(self, edit): 139 | run_on_selections(self.view, edit, to_slash ) 140 | 141 | class ConvertToBackSlash(sublime_plugin.TextCommand): 142 | def run(self, edit): 143 | run_on_selections(self.view, edit, to_backslash ) 144 | -------------------------------------------------------------------------------- /case_parse.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | 4 | PYTHON = sys.version_info[0] 5 | if 3 == PYTHON: xrange = range 6 | 7 | 8 | """ 9 | Parses a variable into a list of words. 10 | 11 | Also returns the case type, which can be one of the following: 12 | 13 | - upper: All words are upper-case. 14 | - lower: All words are lower-case. 15 | - pascal: All words are title-case or upper-case. Note that the variable may still have separators. 16 | - camel: First word is lower-case, the rest are title-case or upper-case. Variable may still have separators. 17 | - mixed: Any other mixing of word casing. Never occurs if there are no separators. 18 | - unknown: Variable contains no words. 19 | 20 | Also returns the first separator character, or False if there isn't one. 21 | """ 22 | def parseVariable(var, detectAcronyms=True, acronyms=[], preserveCase=False): 23 | # TODO: include unicode characters. 24 | lower = re.compile('^[a-z0-9]$') 25 | upper = re.compile('^[A-Z]$') 26 | sep = re.compile('^[^a-zA-Z0-9]$') 27 | notsep = re.compile('^[a-zA-Z0-9]$') 28 | 29 | words = [] 30 | hasSep = False 31 | 32 | # Index of current character. Initially 1 because we don't want to check 33 | # if the 0th character is a boundary. 34 | i = 1 35 | # Index of first character in a sequence 36 | s = 0 37 | # Previous character. 38 | p = var[0:1] 39 | 40 | # Treat an all-caps variable as lower-case, so that every letter isn't 41 | # counted as a boundary. 42 | wasUpper = False 43 | if var.isupper(): 44 | var = var.lower() 45 | wasUpper = True 46 | 47 | # Iterate over each character, checking for boundaries, or places where 48 | # the variable should divided. 49 | while i <= len(var): 50 | c = var[i:i+1] 51 | 52 | split = False 53 | if i < len(var): 54 | # Detect upper-case letter as boundary. 55 | if upper.match(c): 56 | split = True 57 | # Detect transition from separator to not separator. 58 | elif notsep.match(c) and sep.match(p): 59 | split = True 60 | # Detect transition not separator to separator. 61 | elif sep.match(c) and notsep.match(p): 62 | split = True 63 | else: 64 | # The loop goes one extra iteration so that it can handle the 65 | # remaining text after the last boundary. 66 | split = True 67 | 68 | if split: 69 | if notsep.match(p): 70 | words.append(var[s:i]) 71 | else: 72 | # Variable contains at least one separator. 73 | # Use the first one as the variable's primary separator. 74 | if not hasSep: hasSep = var[s:s+1] 75 | 76 | # Use None to indicate a separator in the word list. 77 | words.append(None) 78 | # If separators weren't included in the list, then breaks 79 | # between upper-case sequences ("AAA_BBB") would be 80 | # disregarded; the letter-run detector would count them as one 81 | # sequence ("AAABBB"). 82 | s = i 83 | 84 | i = i + 1 85 | p = c 86 | 87 | if detectAcronyms: 88 | if acronyms: 89 | # Use advanced acronym detection with list 90 | 91 | # Sanitize acronyms list by discarding invalid acronyms and 92 | # normalizing valid ones to upper-case. 93 | validacronym = re.compile('^[a-zA-Z0-9]+$') 94 | unsafeacronyms = acronyms 95 | acronyms = [] 96 | for a in unsafeacronyms: 97 | if validacronym.match(a): 98 | acronyms.append(a.upper()) 99 | else: 100 | print("Case Conversion: acronym '%s' was discarded for being invalid" % a) 101 | 102 | # Check a run of words represented by the range [s, i]. Should 103 | # return last index of new word groups. 104 | def checkAcronym(s, i): 105 | # Combine each letter into single string. 106 | acstr = ''.join(words[s:i]) 107 | 108 | # List of ranges representing found acronyms. 109 | rangeList = [] 110 | # Set of remaining letters. 111 | notRange = set(range(len(acstr))) 112 | 113 | # Search for each acronym in acstr. 114 | for acronym in acronyms: 115 | #TODO: Sanitize acronyms to include only letters. 116 | rac = re.compile(acronym) 117 | 118 | # Loop so that all instances of the acronym are found, instead 119 | # of just the first. 120 | n = 0 121 | while True: 122 | m = rac.search(acstr, n) 123 | if not m: break 124 | 125 | a, b = m.start(), m.end() 126 | n = b 127 | 128 | # Make sure found acronym doesn't overlap with others. 129 | ok = True 130 | for r in rangeList: 131 | if a < r[1] and b > r[0]: 132 | ok = False 133 | break 134 | 135 | if ok: 136 | rangeList.append((a, b)) 137 | for j in xrange(a, b): 138 | notRange.remove(j) 139 | 140 | # Add remaining letters as ranges. 141 | for nr in notRange: 142 | rangeList.append((nr, nr+1)) 143 | 144 | # No ranges will overlap, so it's safe to sort by lower bound, 145 | # which sort() will do by default. 146 | rangeList.sort() 147 | 148 | # Remove original letters in word list. 149 | for j in xrange(s, i): del words[s] 150 | 151 | # Replace them with new word grouping. 152 | for j in xrange(len(rangeList)): 153 | r = rangeList[j] 154 | words.insert(s+j, acstr[r[0]:r[1]]) 155 | 156 | return s+len(rangeList)-1 157 | else: 158 | # Fallback to simple acronym detection. 159 | def checkAcronym(s, i): 160 | # Combine each letter into a single string. 161 | acronym = ''.join(words[s:i]) 162 | 163 | # Remove original letters in word list. 164 | for j in xrange(s, i): del words[s] 165 | 166 | # Replace them with new word grouping. 167 | words.insert(s,''.join(acronym)) 168 | 169 | return s 170 | 171 | # Letter-run detector 172 | 173 | # Index of current word. 174 | i = 0 175 | # Index of first letter in run. 176 | s = None 177 | 178 | # Find runs of single upper-case letters. 179 | while i < len(words): 180 | word = words[i] 181 | if word != None and upper.match(word): 182 | if s == None: s = i 183 | elif s != None: 184 | i = checkAcronym(s, i) + 1 185 | s = None 186 | 187 | i += 1 188 | 189 | if s != None: 190 | checkAcronym(s, i) 191 | 192 | # Separators are no longer needed, so they can be removed. They *should* 193 | # be removed, since it's supposed to be a *word* list. 194 | words = [w for w in words if w != None] 195 | 196 | # Determine case type. 197 | caseType = 'unknown' 198 | if wasUpper: 199 | caseType = 'upper' 200 | elif var.islower(): 201 | caseType = 'lower' 202 | elif len(words) > 0: 203 | camelCase = words[0].islower() 204 | pascalCase = words[0].istitle() or words[0].isupper() 205 | 206 | if camelCase or pascalCase: 207 | for word in words[1:]: 208 | c = word.istitle() or word.isupper() 209 | camelCase &= c 210 | pascalCase &= c 211 | if not c: break 212 | 213 | if camelCase: 214 | caseType = 'camel' 215 | elif pascalCase: 216 | caseType = 'pascal' 217 | else: 218 | caseType = 'mixed' 219 | 220 | if preserveCase: 221 | if wasUpper: 222 | words = [w.upper() for w in words] 223 | else: 224 | # Normalize case of each word to PascalCase. From there, other cases 225 | # can be worked out easily. 226 | for i in xrange(len(words)): 227 | if detectAcronyms: 228 | if acronyms: 229 | if words[i].upper() in acronyms: 230 | # Convert known acronyms to upper-case. 231 | words[i] = words[i].upper() 232 | else: 233 | # Capitalize everything else. 234 | words[i] = words[i].capitalize() 235 | else: 236 | # Fallback behavior: Preserve case on upper-case words. 237 | if not words[i].isupper(): 238 | words[i] = words[i].capitalize() 239 | else: 240 | words[i] = words[i].capitalize() 241 | 242 | return words, caseType, hasSep 243 | --------------------------------------------------------------------------------