├── __init__.py ├── libs ├── __init__.py ├── eol │ ├── __init__.py │ └── LICENSE.txt ├── polib │ ├── __init__.py │ └── LICENSE.txt ├── chardet │ ├── cli │ │ ├── __init__.py │ │ └── chardetect.py │ ├── compat.py │ ├── chardetect.py │ ├── __init__.py │ ├── constants.py │ ├── euckrprober.py │ ├── euctwprober.py │ ├── gb2312prober.py │ ├── big5prober.py │ ├── cp949prober.py │ ├── charsetprober.py │ ├── mbcsgroupprober.py │ ├── codingstatemachine.py │ ├── utf8prober.py │ ├── escprober.py │ ├── sbcsgroupprober.py │ ├── mbcharsetprober.py │ ├── eucjpprober.py │ ├── sjisprober.py │ ├── charsetgroupprober.py │ ├── sbcharsetprober.py │ ├── latin1prober.py │ ├── universaldetector.py │ ├── escsm.py │ └── chardistribution.py ├── kodi │ ├── __init__.py │ └── kodi.py ├── yattag │ ├── MANIFEST.in │ ├── license │ │ └── COPYING │ ├── __init__.py │ ├── README.rst │ └── indentation.py ├── window.py ├── sublimelogger.py ├── include.py ├── imageparser.py ├── adbdevice.py ├── skin.py └── addon.py ├── requirements.txt ├── kodidevkit ├── .gitignore ├── messages ├── 2.0.5.txt ├── 2.5.3.txt ├── 2.5.8.txt ├── 2.0.8.txt ├── 2.2.1.txt ├── 2.6.2.txt ├── 2.0.7.txt ├── 2.5.1.txt ├── 2.0.6.txt ├── 2.5.5.txt ├── 2.5.2.txt ├── 2.5.7.txt ├── 2.5.4.txt ├── 2.5.9.txt ├── 2.6.1.txt ├── 2.6.0.txt ├── 2.5.6.txt ├── 2.0.9.txt ├── 2.2.0.txt ├── 2.5.0.txt └── install.txt ├── info.sublime-snippet ├── group.sublime-snippet ├── includecontent.sublime-snippet ├── Python.sublime-settings ├── KodiSkinXML.sublime-settings ├── tests.py ├── dependencies.json ├── button.sublime-snippet ├── image.sublime-snippet ├── videowindow.sublime-snippet ├── .travis.yml ├── label.sublime-snippet ├── textbox.sublime-snippet ├── fadelabel.sublime-snippet ├── multiimage.sublime-snippet ├── kodi-skin.sublime-build ├── messages.json ├── Context.sublime-menu ├── Default.sublime-keymap ├── KodiLog.sublime-syntax ├── Default.sublime-commands ├── kodidevkit.sublime-settings ├── remote.py ├── script.py ├── Main.sublime-menu ├── kodidevkit.sublime-completions ├── data ├── krypton │ └── windows.json └── leia │ └── windows.json ├── README.md ├── Gettext.tmLanguage └── KodiSkinXML.sublime-syntax /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/eol/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/polib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/chardet/cli/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | lxml>=4.1.0 2 | requests 3 | -------------------------------------------------------------------------------- /kodidevkit: -------------------------------------------------------------------------------- 1 | /home/phil/.config/sublime-text-3/Packages/KodiDevKit 2 | -------------------------------------------------------------------------------- /libs/kodi/__init__.py: -------------------------------------------------------------------------------- 1 | from .kodi import Kodi 2 | kodi = Kodi() 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | -------------------------------------------------------------------------------- /messages/2.0.5.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.0.5 2 | ================= 3 | 4 | ## First release 5 | -------------------------------------------------------------------------------- /libs/yattag/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include license/COPYING 3 | include license/lgpl-2.1.txt 4 | -------------------------------------------------------------------------------- /messages/2.5.3.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.5.3 2 | ================= 3 | 4 | - quick follow-up fix for init error 5 | -------------------------------------------------------------------------------- /messages/2.5.8.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.5.8 2 | ================= 3 | 4 | - Hotfix for broken loading of data files 5 | -------------------------------------------------------------------------------- /messages/2.0.8.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.0.8 2 | ================= 3 | 4 | - fixed loading external resources, 3rd try 5 | -------------------------------------------------------------------------------- /messages/2.2.1.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.2.1 2 | ================= 3 | 4 | - fixed bug related to module filename casing 5 | 6 | -------------------------------------------------------------------------------- /messages/2.6.2.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.6.2 2 | ================= 3 | 4 | - fix building of themes 5 | - leia adjustments 6 | -------------------------------------------------------------------------------- /messages/2.0.7.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.0.7 2 | ================= 3 | 4 | - fixed loading external resources, 2nd try 5 | - fix for panel output 6 | -------------------------------------------------------------------------------- /messages/2.5.1.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.5.1 2 | ================= 3 | 4 | - some test fixes and updates 5 | - fix "replace-label" functionality 6 | -------------------------------------------------------------------------------- /info.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | iinfo 4 | text.xml 5 | 6 | -------------------------------------------------------------------------------- /messages/2.0.6.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.0.6 2 | ================= 3 | 4 | - Code rework 5 | - use mdpopups for tooltips 6 | - fixed loading external resources 7 | -------------------------------------------------------------------------------- /messages/2.5.5.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.5.5 2 | ================= 3 | 4 | - improve skin sanity checks 5 | - open userdata log (windows-only) 6 | - snippet work 7 | -------------------------------------------------------------------------------- /messages/2.5.2.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.5.2 2 | ================= 3 | 4 | - updated skin validation XML 5 | - fix autocompletion 6 | - add support for core color file 7 | -------------------------------------------------------------------------------- /messages/2.5.7.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.5.7 2 | ================= 3 | 4 | - improved check when to show tooltip with image dimensions 5 | - allow constants for int attributes 6 | -------------------------------------------------------------------------------- /messages/2.5.4.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.5.4 2 | ================= 3 | 4 | - improve skin sanity checks, update control definitions in controls.xml 5 | - correctly deal with some errors / exceptions 6 | -------------------------------------------------------------------------------- /messages/2.5.9.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.5.9 2 | ================= 3 | 4 | - improved Evaluate-Math-Expression command 5 | - improved tooltips 6 | - show texture paths for autocompletion 7 | - misc fixes 8 | -------------------------------------------------------------------------------- /messages/2.6.1.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.6.1 2 | ================= 3 | 4 | - add missing dependency 5 | - improved / fixed control sanity checks 6 | - added dependency search 7 | - autocompletion improvements 8 | -------------------------------------------------------------------------------- /group.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 4 | ${1} 5 | 6 | ]]> 7 | cgroup 8 | text.xml 9 | 10 | -------------------------------------------------------------------------------- /includecontent.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 4 | ${2} 5 | 6 | ]]> 7 | includecontent 8 | text.xml 9 | 10 | -------------------------------------------------------------------------------- /messages/2.6.0.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.6.0 2 | ================= 3 | 4 | - fix non-workin settings for some parts 5 | - fix interaction with other add-ons concerning tooltips 6 | - some more preparations to support different versions 7 | -------------------------------------------------------------------------------- /Python.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "extensions": 3 | [ 4 | "py" 5 | ], 6 | "auto_complete_selector": "source", 7 | "auto_complete_triggers": 8 | [ 9 | {"selector": "source.python", "characters": "."}, 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /messages/2.5.6.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.5.6 2 | ================= 3 | 4 | - fixed: non-color files in colors folder causing issues 5 | - fix for multiple aspect ratio definitions pointing to same folder 6 | - fix for KodiSkinXml syntax definiton 7 | -------------------------------------------------------------------------------- /KodiSkinXML.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "extensions": 3 | [ 4 | "xml" 5 | ], 6 | "auto_complete_selector": "source, text", 7 | "auto_complete_triggers": 8 | [ 9 | {"selector": "text.xml", "characters": ".<[/"}, 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | # Copyright (C) 2017 - Philipp Temminghoff 4 | # This program is Free Software see LICENSE file for details 5 | 6 | import sys 7 | 8 | 9 | if __name__ == "__main__": 10 | sys.exit(0) 11 | -------------------------------------------------------------------------------- /dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "*": { 3 | "*": [ 4 | "lxml", 5 | "requests", 6 | "pygments", 7 | "python-markdown", 8 | "mdpopups", 9 | "python-jinja2", 10 | "markupsafe" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /button.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 4 | ${1:0} 5 | ${2:0} 6 | ${3:0} 7 | ${4:0} 8 | ${5} 9 | 10 | ]]> 11 | cbutton 12 | text.xml 13 | 14 | -------------------------------------------------------------------------------- /image.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 4 | ${1:0} 5 | ${2:0} 6 | ${3:stretch} 7 | ${4} 8 | ${5} 9 | 10 | ]]> 11 | cimage 12 | text.xml 13 | 14 | -------------------------------------------------------------------------------- /videowindow.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 4 | ${1:0} 5 | ${2:0} 6 | ${3:0} 7 | ${4:0} 8 | ${5} 9 | 10 | ]]> 11 | cvideowindow 12 | text.xml 13 | 14 | -------------------------------------------------------------------------------- /messages/2.0.9.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.0.9 2 | ================= 3 | 4 | - syntax highlighting for include tooltips 5 | - cleaned up window tooltips 6 | - fixed laggy tooltip in case Kodi isnt reachable via JSON 7 | - resolve symlinks for go-to-exception if possible 8 | - no line numbers for .po files anymore 9 | - do not show empty tooltips 10 | - several bugfixes 11 | -------------------------------------------------------------------------------- /messages/2.2.0.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.2.0 2 | ================= 3 | 4 | - new context menu command: open skin image 5 | - add syntax highlighting for several tooltips 6 | - extended metadata info for image tooltips, and cache that info 7 | - several bugfixes 8 | - improved autocompletion 9 | - polib upstream fixes 10 | - fix running tool from command line 11 | - reworked lot of code 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.3" 4 | before_install: 5 | - wget https://github.com/phil65/skin.estuary/archive/master.zip 6 | - unzip master.zip 7 | - sudo apt-get update -qq 8 | install: 9 | - sudo apt-get install -y python3-lxml 10 | - pip install -r requirements.txt 11 | script: python tests.py 12 | after_success: python script.py ./skin.estuary-master/ leia 13 | -------------------------------------------------------------------------------- /label.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 4 | ${1:0} 5 | ${2:0} 6 | ${3:left} 7 | 8 | ${5:white} 9 | ${6} 10 | ${7} 11 | 12 | ]]> 13 | clabel 14 | text.xml 15 | 16 | -------------------------------------------------------------------------------- /messages/2.5.0.txt: -------------------------------------------------------------------------------- 1 | Changes in 2.5.0 2 | ================= 3 | 4 | - major rework: several tests now based on XML templates (see https://github.com/phil65/KodiDevKit/blob/master/data/controls.xml) 5 | - major tooltip improvements 6 | - new Command: browse Kodi VFS 7 | - new Command: bump addon version 8 | - fixed language folder detection for core labels 9 | - lot of stuff I forgot 10 | 11 | -------------------------------------------------------------------------------- /textbox.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 4 | ${1:0} 5 | ${2:0} 6 | ${3:left} 7 | 8 | ${5:white} 9 | ${6} 10 | ${7} 11 | 12 | ]]> 13 | ctextbox 14 | text.xml 15 | 16 | -------------------------------------------------------------------------------- /fadelabel.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 4 | ${1:0} 5 | ${2:0} 6 | ${3:left} 7 | 8 | ${5:white} 9 | ${6} 10 | ${7} 11 | 12 | ]]> 13 | cfadelabel 14 | text.xml 15 | 16 | -------------------------------------------------------------------------------- /multiimage.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 4 | ${1:0} 5 | ${2:0} 6 | ${3:stretch} 7 | ${4} 8 | ${5:true} 9 | ${6} 10 | 11 | ]]> 12 | cmultiimage 13 | text.xml 14 | 15 | -------------------------------------------------------------------------------- /kodi-skin.sublime-build: -------------------------------------------------------------------------------- 1 | { 2 | "shell_cmd": "TexturePacker -dupecheck -input $folder/media -output $folder/testus.xbt", 3 | "selector": "text.xml", 4 | "variants": [ 5 | 6 | { "name": "List Python Files", 7 | "cmd": ["ls -l *.py"], 8 | "shell": true 9 | }, 10 | 11 | { "name": "Word Count (current file)", 12 | "cmd": ["wc", "$file"] 13 | }, 14 | 15 | { "name": "Run", 16 | "cmd": ["python", "-u", "$file"] 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /libs/yattag/license/COPYING: -------------------------------------------------------------------------------- 1 | Yattag (Python library) 2 | 3 | Copyright (C) 2014 Benjamin Le Forestier 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License version 2.1 as published by the Free Software Foundation. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this library; if not, write to the Free Software 16 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "install": "messages/install.txt", 3 | "2.0.5": "messages/2.0.5.txt", 4 | "2.0.6": "messages/2.0.6.txt", 5 | "2.0.7": "messages/2.0.7.txt", 6 | "2.0.8": "messages/2.0.8.txt", 7 | "2.0.9": "messages/2.0.9.txt", 8 | "2.2.0": "messages/2.2.0.txt", 9 | "2.2.1": "messages/2.2.1.txt", 10 | "2.5.0": "messages/2.5.0.txt", 11 | "2.5.1": "messages/2.5.1.txt", 12 | "2.5.2": "messages/2.5.2.txt", 13 | "2.5.3": "messages/2.5.3.txt", 14 | "2.5.4": "messages/2.5.4.txt", 15 | "2.5.5": "messages/2.5.5.txt", 16 | "2.5.6": "messages/2.5.6.txt", 17 | "2.5.7": "messages/2.5.7.txt", 18 | "2.5.8": "messages/2.5.8.txt", 19 | "2.5.9": "messages/2.5.9.txt", 20 | "2.6.0": "messages/2.6.0.txt", 21 | "2.6.1": "messages/2.6.1.txt", 22 | "2.6.2": "messages/2.6.2.txt", 23 | } 24 | -------------------------------------------------------------------------------- /libs/window.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | # Copyright (C) 2015 - Philipp Temminghoff 4 | # This program is Free Software see LICENSE file for details 5 | 6 | """ 7 | KodiDevKit is a tool to assist with Kodi skinning / scripting using Sublime Text 3 8 | """ 9 | 10 | from . import utils 11 | 12 | 13 | class Window(object): 14 | """ 15 | Class representing a Kodi Window 16 | """ 17 | 18 | def __init__(self, path): 19 | self.root = utils.get_root_from_file(path) 20 | 21 | def get_controls(self, control_type): 22 | """ 23 | yields all control nodes from window 24 | """ 25 | for node in self.root.xpath(".//control[@type='%s']" % control_type): 26 | yield node 27 | 28 | def xpath(self, *args, **kwargs): 29 | """ 30 | xpath function for the window xml 31 | """ 32 | return self.root.xpath(*args, **kwargs) 33 | -------------------------------------------------------------------------------- /messages/install.txt: -------------------------------------------------------------------------------- 1 | KodiDevKit (former SublimeKodi) - https://github.com/phil65/KodiDevKit 2 | ================= 3 | 4 | KodiDevKit finally made it to packagecontrol. :) This script contains lot of little helper 5 | functions to assist with skin and script development as well as custom syntax highlighting, 6 | remote uploading add-ons via ADB, syntax highlighting for Kodi log files and Skin XMLs, 7 | and much more. 8 | 9 | To get started, have a look at the settings file first. 10 | In order to use all features, you also need to install the Kodi plugin "Toolbox script" 11 | (script.toolbox) from the official Kodi repositories. 12 | That way this script can interact with Kodi via JSON-RPC. 13 | 14 | A quite up-to-date feature list can be found here: 15 | https://github.com/phil65/kodidevkit/blob/master/README.md 16 | 17 | Some calls are not documented yet, I hope to fix that in the next weeks. 18 | 19 | 20 | For further questions, visit the Kodi forums. ( http://forum.kodi.tv/showthread.php?tid=221682 ) 21 | 22 | Happy coding! 23 | -------------------------------------------------------------------------------- /Context.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | // { "caption": "-" }, 3 | { 4 | "command": "preview_image", 5 | "caption": "KodiDevKit: Preview image", 6 | "context": [{ "key": "selector", "operator": "equal", "operand": "text.xml" }] 7 | }, 8 | { 9 | "command": "go_to_online_help", 10 | "caption": "KodiDevKit: Show info from Kodi Wiki", 11 | "context": [{ "key": "selector", "operator": "equal", "operand": "text.xml" }] 12 | }, 13 | { 14 | "command": "move_to_language_file", 15 | "caption": "KodiDevKit: Move to language file", 16 | "context": [{ "key": "selector", "operator": "equal", "operand": "text.xml" }] 17 | }, 18 | { 19 | "command": "open_skin_image", 20 | "caption": "KodiDevKit: Open image", 21 | "context": [{ "key": "selector", "operator": "equal", "operand": "text.xml" }] 22 | }, 23 | 24 | ] 25 | -------------------------------------------------------------------------------- /libs/polib/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2015 David Jean Louis. 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 8 | furnished 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 THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /libs/eol/LICENSE.txt: -------------------------------------------------------------------------------- 1 | # This is the MIT license 2 | 3 | Copyright (c) 2010 ActiveState Software Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /libs/chardet/compat.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # Contributor(s): 3 | # Ian Cordasco - port to Python 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 2.1 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library; if not, write to the Free Software 17 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 18 | # 02110-1301 USA 19 | ######################### END LICENSE BLOCK ######################### 20 | 21 | import sys 22 | 23 | 24 | if sys.version_info < (3, 0): 25 | base_str = (str, unicode) 26 | else: 27 | base_str = (bytes, str) 28 | 29 | 30 | def wrap_ord(a): 31 | if sys.version_info < (3, 0) and isinstance(a, base_str): 32 | return ord(a) 33 | else: 34 | return a 35 | -------------------------------------------------------------------------------- /libs/chardet/chardetect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Script which takes one or more file paths and reports on their detected 4 | encodings 5 | 6 | Example:: 7 | 8 | % chardetect somefile someotherfile 9 | somefile: windows-1252 with confidence 0.5 10 | someotherfile: ascii with confidence 1.0 11 | 12 | If no paths are provided, it takes its input from stdin. 13 | 14 | """ 15 | from io import open 16 | from sys import argv, stdin 17 | 18 | from chardet.universaldetector import UniversalDetector 19 | 20 | 21 | def description_of(file, name='stdin'): 22 | """Return a string describing the probable encoding of a file.""" 23 | u = UniversalDetector() 24 | for line in file: 25 | u.feed(line) 26 | u.close() 27 | result = u.result 28 | if result['encoding']: 29 | return '%s: %s with confidence %s' % (name, 30 | result['encoding'], 31 | result['confidence']) 32 | else: 33 | return '%s: no result' % name 34 | 35 | 36 | def main(): 37 | if len(argv) <= 1: 38 | print(description_of(stdin)) 39 | else: 40 | for path in argv[1:]: 41 | with open(path, 'rb') as f: 42 | print(description_of(f, path)) 43 | 44 | 45 | if __name__ == '__main__': 46 | main() 47 | -------------------------------------------------------------------------------- /libs/chardet/__init__.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # This library is free software; you can redistribute it and/or 3 | # modify it under the terms of the GNU Lesser General Public 4 | # License as published by the Free Software Foundation; either 5 | # version 2.1 of the License, or (at your option) any later version. 6 | # 7 | # This library is distributed in the hope that it will be useful, 8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # Lesser General Public License for more details. 11 | # 12 | # You should have received a copy of the GNU Lesser General Public 13 | # License along with this library; if not, write to the Free Software 14 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 15 | # 02110-1301 USA 16 | ######################### END LICENSE BLOCK ######################### 17 | 18 | __version__ = "2.2.1" 19 | from sys import version_info 20 | 21 | 22 | def detect(aBuf): 23 | if ((version_info < (3, 0) and isinstance(aBuf, unicode)) or 24 | (version_info >= (3, 0) and not isinstance(aBuf, bytes))): 25 | raise ValueError('Expected a bytes object, not a unicode object') 26 | 27 | from . import universaldetector 28 | u = universaldetector.UniversalDetector() 29 | u.reset() 30 | u.feed(aBuf) 31 | u.close() 32 | return u.result 33 | -------------------------------------------------------------------------------- /Default.sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["shift+enter"], "command": "open_source_from_log", 3 | "context": [{ "key": "selector", "operator": "equal", "operand": "text.kodi.traceback" }] 4 | }, 5 | { "keys": ["ctrl+enter"], "command": "preview_image", 6 | "context": [{ "key": "selector", "operator": "equal", "operand": "text.xml" }] 7 | }, 8 | { "keys": ["ctrl+shift+m"], "command": "move_to_language_file", 9 | // "context": [{ "key": "selector", "operator": "equal", "operand": "text.xml" }] 10 | }, 11 | { "keys": ["ctrl+shift+x"], "command": "create_element_row", 12 | "context": [{ "key": "selector", "operator": "equal", "operand": "text.xml" }] 13 | }, 14 | { "keys": ["ctrl+shift+y"], "command": "evaluate_math_expression_prompt", 15 | }, 16 | { "keys": ["ctrl+shift+w"], "command": "go_to_online_help", 17 | "context": [{ "key": "selector", "operator": "equal", "operand": "text.xml" }] 18 | }, 19 | { "keys": ["shift+enter"], "command": "go_to_tag", 20 | "context": [{ "key": "selector", "operator": "equal", "operand": "text.xml" }] 21 | }, 22 | { "keys": ["shift+ctrl+enter"], "command": "switch_xml_folder", 23 | "context": [{ "key": "selector", "operator": "equal", "operand": "text.xml" }] 24 | }, 25 | { "keys": ["ctrl+shift+o"], "command": "show_overlay", 26 | "args": {"overlay": "command_palette", "text": "KodiDevKit"} }, 27 | ] 28 | -------------------------------------------------------------------------------- /libs/chardet/constants.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is Mozilla Universal charset detector code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 2001 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # Shy Shalom - original C code 12 | # 13 | # This library is free software; you can redistribute it and/or 14 | # modify it under the terms of the GNU Lesser General Public 15 | # License as published by the Free Software Foundation; either 16 | # version 2.1 of the License, or (at your option) any later version. 17 | # 18 | # This library is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 | # Lesser General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU Lesser General Public 24 | # License along with this library; if not, write to the Free Software 25 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 26 | # 02110-1301 USA 27 | ######################### END LICENSE BLOCK ######################### 28 | 29 | _debug = 0 30 | 31 | eDetecting = 0 32 | eFoundIt = 1 33 | eNotMe = 2 34 | 35 | eStart = 0 36 | eError = 1 37 | eItsMe = 2 38 | 39 | SHORTCUT_THRESHOLD = 0.95 40 | -------------------------------------------------------------------------------- /libs/chardet/euckrprober.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is mozilla.org code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 1998 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # 12 | # This library is free software; you can redistribute it and/or 13 | # modify it under the terms of the GNU Lesser General Public 14 | # License as published by the Free Software Foundation; either 15 | # version 2.1 of the License, or (at your option) any later version. 16 | # 17 | # This library is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | # Lesser General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU Lesser General Public 23 | # License along with this library; if not, write to the Free Software 24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 25 | # 02110-1301 USA 26 | ######################### END LICENSE BLOCK ######################### 27 | 28 | from .mbcharsetprober import MultiByteCharSetProber 29 | from .codingstatemachine import CodingStateMachine 30 | from .chardistribution import EUCKRDistributionAnalysis 31 | from .mbcssm import EUCKRSMModel 32 | 33 | 34 | class EUCKRProber(MultiByteCharSetProber): 35 | def __init__(self): 36 | MultiByteCharSetProber.__init__(self) 37 | self._mCodingSM = CodingStateMachine(EUCKRSMModel) 38 | self._mDistributionAnalyzer = EUCKRDistributionAnalysis() 39 | self.reset() 40 | 41 | def get_charset_name(self): 42 | return "EUC-KR" 43 | -------------------------------------------------------------------------------- /libs/chardet/euctwprober.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is mozilla.org code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 1998 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # 12 | # This library is free software; you can redistribute it and/or 13 | # modify it under the terms of the GNU Lesser General Public 14 | # License as published by the Free Software Foundation; either 15 | # version 2.1 of the License, or (at your option) any later version. 16 | # 17 | # This library is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | # Lesser General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU Lesser General Public 23 | # License along with this library; if not, write to the Free Software 24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 25 | # 02110-1301 USA 26 | ######################### END LICENSE BLOCK ######################### 27 | 28 | from .mbcharsetprober import MultiByteCharSetProber 29 | from .codingstatemachine import CodingStateMachine 30 | from .chardistribution import EUCTWDistributionAnalysis 31 | from .mbcssm import EUCTWSMModel 32 | 33 | class EUCTWProber(MultiByteCharSetProber): 34 | def __init__(self): 35 | MultiByteCharSetProber.__init__(self) 36 | self._mCodingSM = CodingStateMachine(EUCTWSMModel) 37 | self._mDistributionAnalyzer = EUCTWDistributionAnalysis() 38 | self.reset() 39 | 40 | def get_charset_name(self): 41 | return "EUC-TW" 42 | -------------------------------------------------------------------------------- /libs/chardet/gb2312prober.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is mozilla.org code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 1998 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # 12 | # This library is free software; you can redistribute it and/or 13 | # modify it under the terms of the GNU Lesser General Public 14 | # License as published by the Free Software Foundation; either 15 | # version 2.1 of the License, or (at your option) any later version. 16 | # 17 | # This library is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | # Lesser General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU Lesser General Public 23 | # License along with this library; if not, write to the Free Software 24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 25 | # 02110-1301 USA 26 | ######################### END LICENSE BLOCK ######################### 27 | 28 | from .mbcharsetprober import MultiByteCharSetProber 29 | from .codingstatemachine import CodingStateMachine 30 | from .chardistribution import GB2312DistributionAnalysis 31 | from .mbcssm import GB2312SMModel 32 | 33 | class GB2312Prober(MultiByteCharSetProber): 34 | def __init__(self): 35 | MultiByteCharSetProber.__init__(self) 36 | self._mCodingSM = CodingStateMachine(GB2312SMModel) 37 | self._mDistributionAnalyzer = GB2312DistributionAnalysis() 38 | self.reset() 39 | 40 | def get_charset_name(self): 41 | return "GB2312" 42 | -------------------------------------------------------------------------------- /libs/chardet/big5prober.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is Mozilla Communicator client code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 1998 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # 12 | # This library is free software; you can redistribute it and/or 13 | # modify it under the terms of the GNU Lesser General Public 14 | # License as published by the Free Software Foundation; either 15 | # version 2.1 of the License, or (at your option) any later version. 16 | # 17 | # This library is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | # Lesser General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU Lesser General Public 23 | # License along with this library; if not, write to the Free Software 24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 25 | # 02110-1301 USA 26 | ######################### END LICENSE BLOCK ######################### 27 | 28 | from .mbcharsetprober import MultiByteCharSetProber 29 | from .codingstatemachine import CodingStateMachine 30 | from .chardistribution import Big5DistributionAnalysis 31 | from .mbcssm import Big5SMModel 32 | 33 | 34 | class Big5Prober(MultiByteCharSetProber): 35 | def __init__(self): 36 | MultiByteCharSetProber.__init__(self) 37 | self._mCodingSM = CodingStateMachine(Big5SMModel) 38 | self._mDistributionAnalyzer = Big5DistributionAnalysis() 39 | self.reset() 40 | 41 | def get_charset_name(self): 42 | return "Big5" 43 | -------------------------------------------------------------------------------- /libs/chardet/cp949prober.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is mozilla.org code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 1998 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # 12 | # This library is free software; you can redistribute it and/or 13 | # modify it under the terms of the GNU Lesser General Public 14 | # License as published by the Free Software Foundation; either 15 | # version 2.1 of the License, or (at your option) any later version. 16 | # 17 | # This library is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | # Lesser General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU Lesser General Public 23 | # License along with this library; if not, write to the Free Software 24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 25 | # 02110-1301 USA 26 | ######################### END LICENSE BLOCK ######################### 27 | 28 | from .mbcharsetprober import MultiByteCharSetProber 29 | from .codingstatemachine import CodingStateMachine 30 | from .chardistribution import EUCKRDistributionAnalysis 31 | from .mbcssm import CP949SMModel 32 | 33 | 34 | class CP949Prober(MultiByteCharSetProber): 35 | def __init__(self): 36 | MultiByteCharSetProber.__init__(self) 37 | self._mCodingSM = CodingStateMachine(CP949SMModel) 38 | # NOTE: CP949 is a superset of EUC-KR, so the distribution should be 39 | # not different. 40 | self._mDistributionAnalyzer = EUCKRDistributionAnalysis() 41 | self.reset() 42 | 43 | def get_charset_name(self): 44 | return "CP949" 45 | -------------------------------------------------------------------------------- /libs/yattag/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | The SimpleDoc class generates xml or html documents using context managers (`with` blocks). 3 | The Doc class extends the SimpleDoc class. It adds the capability to render 4 | html forms with defaults values and errors. 5 | These two classes can be used to define html templates in a web application. 6 | 7 | Basic example 8 | ------------- 9 | 10 | Nested html tags, no need to close tags. 11 | 12 | .. code:: python 13 | 14 | from yattag import Doc 15 | 16 | doc, tag, text = Doc().tagtext() 17 | 18 | with tag('html'): 19 | with tag('body', id = 'hello'): 20 | with tag('h1'): 21 | text('Hello world!') 22 | 23 | print(doc.getvalue()) 24 | 25 | Html form rendering example with default values 26 | ----------------------------------------------- 27 | 28 | .. code:: python 29 | 30 | from yattag import Doc 31 | 32 | doc, tag, text = Doc( 33 | defaults = {'ingredient': ['chocolate', 'coffee']} 34 | ).tagtext() 35 | 36 | with tag('form', action = ""): 37 | with tag('label'): 38 | text("Select one or more ingredients") 39 | with doc.select(name = 'ingredient', multiple = "multiple"): 40 | for value, description in ( 41 | ("chocolate", "Dark chocolate"), 42 | ("almonds", "Roasted almonds"), 43 | ("honey", "Acacia honey"), 44 | ("coffee", "Ethiopian coffee") 45 | ): 46 | with doc.option(value = value): 47 | text(description) 48 | doc.stag('input', type = "submit", value = "Validate") 49 | 50 | print(doc.getvalue()) 51 | 52 | Full tutorial on yattag.org_ 53 | 54 | .. _yattag.org: http://www.yattag.org 55 | """ 56 | 57 | __author__ = "Benjamin Le Forestier (benjamin@leforestier.org)" 58 | __version__ = '1.6.0' 59 | 60 | from .simpledoc import SimpleDoc 61 | from .doc import Doc 62 | from .indentation import indent 63 | -------------------------------------------------------------------------------- /libs/sublimelogger.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | # Copyright (C) 2015 - Philipp Temminghoff 4 | # This program is Free Software see LICENSE file for details 5 | 6 | """ 7 | KodiDevKit is a plugin to assist with Kodi skinning / scripting using Sublime Text 3 8 | """ 9 | 10 | import sublime 11 | import logging 12 | 13 | 14 | class SublimeLogHandler(logging.StreamHandler): 15 | """ 16 | SublimeText Stream handler, outputs stream via console/panels/dialogs 17 | """ 18 | 19 | def __init__(self): 20 | super().__init__() 21 | formatter = logging.Formatter('[KodiDevKit] %(asctime)s: %(message)s', 22 | datefmt='%Y-%m-%d %H:%M:%S') 23 | self.setFormatter(formatter) 24 | 25 | def emit(self, record): 26 | levels = { 27 | logging.CRITICAL: self.message, 28 | logging.ERROR: self.info, 29 | logging.WARNING: self.info, 30 | logging.INFO: self.debug, 31 | logging.DEBUG: self.debug, 32 | logging.NOTSET: self.debug, 33 | } 34 | # if settings.get("debug_mode"): 35 | log = levels[record.levelno] 36 | log(record) 37 | 38 | def flush(self): 39 | pass 40 | 41 | def debug(self, record): 42 | # if settings.get("debug_mode"): 43 | print(self.format(record)) 44 | 45 | def info(self, record): 46 | wnd = sublime.active_window() 47 | wnd.run_command("log", {"label": self.format(record).strip()}) 48 | 49 | @staticmethod 50 | def message(record): 51 | """ 52 | shows text in message dialog 53 | """ 54 | sublime.message_dialog(record.msg) 55 | 56 | 57 | def config(): 58 | """ 59 | attach Sublime StreamHandler to logger 60 | """ 61 | logger = logging.getLogger() 62 | for hdlr in logger.handlers: # remove all old handlers 63 | logger.removeHandler(hdlr) 64 | logger.addHandler(SublimeLogHandler()) 65 | logger.setLevel(logging.INFO) 66 | -------------------------------------------------------------------------------- /libs/chardet/charsetprober.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is Mozilla Universal charset detector code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 2001 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # Shy Shalom - original C code 12 | # 13 | # This library is free software; you can redistribute it and/or 14 | # modify it under the terms of the GNU Lesser General Public 15 | # License as published by the Free Software Foundation; either 16 | # version 2.1 of the License, or (at your option) any later version. 17 | # 18 | # This library is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 | # Lesser General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU Lesser General Public 24 | # License along with this library; if not, write to the Free Software 25 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 26 | # 02110-1301 USA 27 | ######################### END LICENSE BLOCK ######################### 28 | 29 | from . import constants 30 | import re 31 | 32 | 33 | class CharSetProber: 34 | def __init__(self): 35 | pass 36 | 37 | def reset(self): 38 | self._mState = constants.eDetecting 39 | 40 | def get_charset_name(self): 41 | return None 42 | 43 | def feed(self, aBuf): 44 | pass 45 | 46 | def get_state(self): 47 | return self._mState 48 | 49 | def get_confidence(self): 50 | return 0.0 51 | 52 | def filter_high_bit_only(self, aBuf): 53 | aBuf = re.sub(b'([\x00-\x7F])+', b' ', aBuf) 54 | return aBuf 55 | 56 | def filter_without_english_letters(self, aBuf): 57 | aBuf = re.sub(b'([A-Za-z])+', b' ', aBuf) 58 | return aBuf 59 | 60 | def filter_with_english_letters(self, aBuf): 61 | # TODO 62 | return aBuf 63 | -------------------------------------------------------------------------------- /libs/chardet/mbcsgroupprober.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is Mozilla Universal charset detector code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 2001 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # Shy Shalom - original C code 12 | # Proofpoint, Inc. 13 | # 14 | # This library is free software; you can redistribute it and/or 15 | # modify it under the terms of the GNU Lesser General Public 16 | # License as published by the Free Software Foundation; either 17 | # version 2.1 of the License, or (at your option) any later version. 18 | # 19 | # This library is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 22 | # Lesser General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU Lesser General Public 25 | # License along with this library; if not, write to the Free Software 26 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 27 | # 02110-1301 USA 28 | ######################### END LICENSE BLOCK ######################### 29 | 30 | from .charsetgroupprober import CharSetGroupProber 31 | from .utf8prober import UTF8Prober 32 | from .sjisprober import SJISProber 33 | from .eucjpprober import EUCJPProber 34 | from .gb2312prober import GB2312Prober 35 | from .euckrprober import EUCKRProber 36 | from .cp949prober import CP949Prober 37 | from .big5prober import Big5Prober 38 | from .euctwprober import EUCTWProber 39 | 40 | 41 | class MBCSGroupProber(CharSetGroupProber): 42 | def __init__(self): 43 | CharSetGroupProber.__init__(self) 44 | self._mProbers = [ 45 | UTF8Prober(), 46 | SJISProber(), 47 | EUCJPProber(), 48 | GB2312Prober(), 49 | EUCKRProber(), 50 | CP949Prober(), 51 | Big5Prober(), 52 | EUCTWProber() 53 | ] 54 | self.reset() 55 | -------------------------------------------------------------------------------- /KodiLog.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | name: Kodi Log 4 | file_extensions: [log, LOG] 5 | scope: text.kodilog 6 | 7 | contexts: 8 | main: 9 | - match: ^([0-9]{2}):([0-9]{2}):([0-9]{2}) 10 | scope: keyword.date 11 | - match: \b(WARNING)\b 12 | scope: constant.character.escape 13 | - match: \b(ERROR)\b 14 | scope: invalid.illegal 15 | - match: \b(NOTICE)\b 16 | scope: entity.name.function 17 | - match: \b([a-z])+(\.\w+)+[:] 18 | scope: keyword.control.import 19 | - match: ([a-z]*):\/\/([\w\-_]+(?:(?:\.[\w\-_]+)+))([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])? 20 | scope: variable.parameter 21 | - match: '"' 22 | push: string 23 | - match: 'EXCEPTION Thrown \(PythonToCppException\) : -->Python callback/script returned the following error<--' 24 | push: exception 25 | scope: invalid.illegal 26 | - match: 'Exception in thread Thread-[0-9].*?' 27 | push: exception 28 | scope: invalid.illegal 29 | string: 30 | - meta_scope: string.quoted.double 31 | - match: '"' 32 | pop: true 33 | - meta_scope: string.quoted.double 34 | - match: ^([0-9]{2}):([0-9]{2}):([0-9]{2}) 35 | pop: true 36 | 37 | 38 | exception: 39 | - meta_scope: variable.parameter 40 | - match: 'Traceback \(most recent call last\):' 41 | push: traceback_content 42 | - match: '-->End of Python script error report<--' 43 | scope: invalid.illegal 44 | pop: true 45 | - match: ^([0-9]{2}):([0-9]{2}):([0-9]{2}) 46 | pop: true 47 | 48 | traceback_content: 49 | - meta_scope: variable.parameter2 50 | - match: 'File "(.*?)", line (\d*), in (.*)' 51 | scope: text.kodi.traceback 52 | captures: 53 | 1: string.quoted 54 | 2: constant.character.escape 55 | 3: storage.type.function 56 | - match: '(\b(?:[A-Za-z]+)Error: (.*)|SystemExit)$' 57 | scope: text.kodi.traceback 58 | captures: 59 | 1: storage.type.function 60 | pop: true 61 | - match: ^([0-9]{2}):([0-9]{2}):([0-9]{2}) 62 | scope: text.kodi.traceback 63 | pop: true 64 | -------------------------------------------------------------------------------- /libs/chardet/codingstatemachine.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is mozilla.org code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 1998 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # 12 | # This library is free software; you can redistribute it and/or 13 | # modify it under the terms of the GNU Lesser General Public 14 | # License as published by the Free Software Foundation; either 15 | # version 2.1 of the License, or (at your option) any later version. 16 | # 17 | # This library is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | # Lesser General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU Lesser General Public 23 | # License along with this library; if not, write to the Free Software 24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 25 | # 02110-1301 USA 26 | ######################### END LICENSE BLOCK ######################### 27 | 28 | from .constants import eStart 29 | from .compat import wrap_ord 30 | 31 | 32 | class CodingStateMachine: 33 | def __init__(self, sm): 34 | self._mModel = sm 35 | self._mCurrentBytePos = 0 36 | self._mCurrentCharLen = 0 37 | self.reset() 38 | 39 | def reset(self): 40 | self._mCurrentState = eStart 41 | 42 | def next_state(self, c): 43 | # for each byte we get its class 44 | # if it is first byte, we also get byte length 45 | # PY3K: aBuf is a byte stream, so c is an int, not a byte 46 | byteCls = self._mModel['classTable'][wrap_ord(c)] 47 | if self._mCurrentState == eStart: 48 | self._mCurrentBytePos = 0 49 | self._mCurrentCharLen = self._mModel['charLenTable'][byteCls] 50 | # from byte's class and stateTable, we get its next state 51 | curr_state = (self._mCurrentState * self._mModel['classFactor'] 52 | + byteCls) 53 | self._mCurrentState = self._mModel['stateTable'][curr_state] 54 | self._mCurrentBytePos += 1 55 | return self._mCurrentState 56 | 57 | def get_current_charlen(self): 58 | return self._mCurrentCharLen 59 | 60 | def get_coding_state_machine(self): 61 | return self._mModel['name'] 62 | -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | {"caption": "KodiDevKit: Set Kodi Folder", "command": "set_kodi_folder"}, 3 | {"caption": "KodiDevKit: Build Skin", "command": "build_addon", "args": { "pack_textures": true}}, 4 | {"caption": "KodiDevKit: Build Theme", "command": "build_theme"}, 5 | {"caption": "KodiDevKit: Remote Actions", "command": "remote_actions"}, 6 | {"caption": "KodiDevKit: Open Log", "command": "open_kodi_log"}, 7 | {"caption": "KodiDevKit: Open Userdata Log", "command": "open_alt_kodi_log"}, 8 | {"caption": "KodiDevKit: Bump addon version", "command": "bump_version"}, 9 | {"caption": "KodiDevKit: Choose color", "command": "color_picker"}, 10 | {"caption": "KodiDevKit: Reload Language Files", "command": "reload_kodi_language_files"}, 11 | {"caption": "KodiDevKit: Switch to Add-on", "command": "open_kodi_addon"}, 12 | {"caption": "KodiDevKit: Search for Label", "command": "search_for_label"}, 13 | {"caption": "KodiDevKit: Search for Image", "command": "search_for_image"}, 14 | {"caption": "KodiDevKit: Search for Font", "command": "search_for_font"}, 15 | {"caption": "KodiDevKit: Search for Dependency", "command": "show_dependencies"}, 16 | {"caption": "KodiDevKit: Search for Builtins", "command": "search_for_builtin"}, 17 | {"caption": "KodiDevKit: Search for Boolean Conditions", "command": "search_for_visible_condition"}, 18 | {"caption": "KodiDevKit: Search for JSON Methods / Types / Notifications", "command": "search_for_json"}, 19 | {"caption": "KodiDevKit: Search translated Strings of open File", "command": "search_file_for_labels"}, 20 | {"caption": "KodiDevKit: Check Variables", "command": "check_variables", "args": { "check_type": "variable"}}, 21 | {"caption": "KodiDevKit: Check Includes", "command": "check_variables", "args": { "check_type": "include"}}, 22 | {"caption": "KodiDevKit: Check Fonts", "command": "check_variables", "args": { "check_type": "font"}}, 23 | {"caption": "KodiDevKit: Check Labels", "command": "check_variables", "args": { "check_type": "label"}}, 24 | {"caption": "KodiDevKit: Browse Kodi VFS", "command": "browse_kodi_vfs"}, 25 | {"caption": "KodiDevKit: Check Control / Window IDs", "command": "check_variables", "args": { "check_type": "id"}}, 26 | {"caption": "KodiDevKit: Check for invalid Nodes / Attributes", "command": "check_variables", "args": { "check_type": "general"}}, 27 | {"caption": "KodiDevKit: JSON-RPC: Reload Skin", "command": "execute_builtin", "args": { "builtin": "ReloadSkin()" }}, 28 | {"caption": "KodiDevKit: JSON-RPC: Execute Builtin", "command": "execute_builtin_prompt"}, 29 | {"caption": "KodiDevKit: JSON-RPC: Display InfoLabels", "command": "get_info_labels_prompt"}, 30 | {"caption": "KodiDevKit: JSON-RPC: Display Booleans", "command": "get_info_booleans_prompt"}, 31 | {"caption": "KodiDevKit: JSON-RPC: Open active Window XML", "command": "open_active_window_xml_from_remote"}, 32 | ] 33 | -------------------------------------------------------------------------------- /libs/yattag/README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://travis-ci.org/leforestier/yattag.svg 2 | :target: https://travis-ci.org/leforestier/yattag 3 | 4 | Why use a template engine when you can generate HTML or XML documents with Python in a very readable way? 5 | 6 | ( full tutorial on yattag.org_ ) 7 | 8 | Basic example 9 | ------------- 10 | 11 | Nested html tags, no need to close tags. 12 | 13 | .. code:: python 14 | 15 | from yattag import Doc 16 | 17 | doc, tag, text = Doc().tagtext() 18 | 19 | with tag('html'): 20 | with tag('body', id = 'hello'): 21 | with tag('h1'): 22 | text('Hello world!') 23 | 24 | print(doc.getvalue()) 25 | 26 | 27 | Html form rendering 28 | ------------------- 29 | 30 | Yattag can fill your HTML forms with default values and error messages. 31 | Pass a ``defaults`` dictionnary of default values, and an ``errors`` dictionnary of error messages to the ``Doc`` constructor. 32 | Then, use the special ``input``, ``textarea``, ``select``, ``option`` methods when generating your documents. 33 | 34 | 35 | Example with default values 36 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 37 | 38 | .. code:: python 39 | 40 | from yattag import Doc 41 | 42 | doc, tag, text = Doc( 43 | defaults = {'ingredient': ['chocolate', 'coffee']} 44 | ).tagtext() 45 | 46 | with tag('form', action = ""): 47 | with tag('label'): 48 | text("Select one or more ingredients") 49 | with doc.select(name = 'ingredient', multiple = "multiple"): 50 | for value, description in ( 51 | ("chocolate", "Dark chocolate"), 52 | ("almonds", "Roasted almonds"), 53 | ("honey", "Acacia honey"), 54 | ("coffee", "Ethiopian coffee") 55 | ): 56 | with doc.option(value = value): 57 | text(description) 58 | doc.stag('input', type = "submit", value = "Validate") 59 | 60 | print(doc.getvalue()) 61 | 62 | Example with default values and errors 63 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 64 | 65 | .. code:: python 66 | 67 | from yattag import Doc 68 | 69 | doc, tag, text = Doc( 70 | defaults = { 71 | 'title': 'Untitled', 72 | 'contact_message': 'You just won the lottery!' 73 | }, 74 | errors = { 75 | 'contact_message': 'Your message looks like spam.' 76 | } 77 | ).tagtext() 78 | 79 | with tag('h1'): 80 | text('Contact form') 81 | with tag('form', action = ""): 82 | doc.input(name = 'title', type = 'text') 83 | with doc.textarea(name = 'contact_message'): 84 | pass 85 | doc.stag('input', type = 'submit', value = 'Send my message') 86 | 87 | print(doc.getvalue()) 88 | 89 | Full tutorial on yattag.org_ 90 | 91 | GitHub repo: https://github.com/leforestier/yattag 92 | 93 | .. _yattag.org: http://www.yattag.org 94 | 95 | 96 | -------------------------------------------------------------------------------- /libs/chardet/cli/chardetect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Script which takes one or more file paths and reports on their detected 4 | encodings 5 | 6 | Example:: 7 | 8 | % chardetect somefile someotherfile 9 | somefile: windows-1252 with confidence 0.5 10 | someotherfile: ascii with confidence 1.0 11 | 12 | If no paths are provided, it takes its input from stdin. 13 | 14 | """ 15 | 16 | from __future__ import absolute_import, print_function, unicode_literals 17 | 18 | import argparse 19 | import sys 20 | from io import open 21 | 22 | from chardet import __version__ 23 | from chardet.compat import PY2 24 | from chardet.universaldetector import UniversalDetector 25 | 26 | 27 | 28 | 29 | def description_of(lines, name='stdin'): 30 | """ 31 | Return a string describing the probable encoding of a file or 32 | list of strings. 33 | 34 | :param lines: The lines to get the encoding of. 35 | :type lines: Iterable of bytes 36 | :param name: Name of file or collection of lines 37 | :type name: str 38 | """ 39 | u = UniversalDetector() 40 | for line in lines: 41 | u.feed(line) 42 | u.close() 43 | result = u.result 44 | if PY2: 45 | name = name.decode(sys.getfilesystemencoding(), 'ignore') 46 | if result['encoding']: 47 | return '{0}: {1} with confidence {2}'.format(name, result['encoding'], 48 | result['confidence']) 49 | else: 50 | return '{0}: no result'.format(name) 51 | 52 | 53 | def main(argv=None): 54 | """ 55 | Handles command line arguments and gets things started. 56 | 57 | :param argv: List of arguments, as if specified on the command-line. 58 | If None, ``sys.argv[1:]`` is used instead. 59 | :type argv: list of str 60 | """ 61 | # Get command line arguments 62 | parser = argparse.ArgumentParser( 63 | description="Takes one or more file paths and reports their detected \ 64 | encodings") 65 | parser.add_argument('input', 66 | help='File whose encoding we would like to determine. \ 67 | (default: stdin)', 68 | type=argparse.FileType('rb'), nargs='*', 69 | default=[sys.stdin if PY2 else sys.stdin.buffer]) 70 | parser.add_argument('--version', action='version', 71 | version='%(prog)s {0}'.format(__version__)) 72 | args = parser.parse_args(argv) 73 | 74 | for f in args.input: 75 | if f.isatty(): 76 | print("You are running chardetect interactively. Press " + 77 | "CTRL-D twice at the start of a blank line to signal the " + 78 | "end of your input. If you want help, run chardetect " + 79 | "--help\n", file=sys.stderr) 80 | print(description_of(f, f.name)) 81 | 82 | 83 | if __name__ == '__main__': 84 | main() 85 | -------------------------------------------------------------------------------- /libs/chardet/utf8prober.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is mozilla.org code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 1998 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # 12 | # This library is free software; you can redistribute it and/or 13 | # modify it under the terms of the GNU Lesser General Public 14 | # License as published by the Free Software Foundation; either 15 | # version 2.1 of the License, or (at your option) any later version. 16 | # 17 | # This library is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | # Lesser General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU Lesser General Public 23 | # License along with this library; if not, write to the Free Software 24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 25 | # 02110-1301 USA 26 | ######################### END LICENSE BLOCK ######################### 27 | 28 | from . import constants 29 | from .charsetprober import CharSetProber 30 | from .codingstatemachine import CodingStateMachine 31 | from .mbcssm import UTF8SMModel 32 | 33 | ONE_CHAR_PROB = 0.5 34 | 35 | 36 | class UTF8Prober(CharSetProber): 37 | def __init__(self): 38 | CharSetProber.__init__(self) 39 | self._mCodingSM = CodingStateMachine(UTF8SMModel) 40 | self.reset() 41 | 42 | def reset(self): 43 | CharSetProber.reset(self) 44 | self._mCodingSM.reset() 45 | self._mNumOfMBChar = 0 46 | 47 | def get_charset_name(self): 48 | return "utf-8" 49 | 50 | def feed(self, aBuf): 51 | for c in aBuf: 52 | codingState = self._mCodingSM.next_state(c) 53 | if codingState == constants.eError: 54 | self._mState = constants.eNotMe 55 | break 56 | elif codingState == constants.eItsMe: 57 | self._mState = constants.eFoundIt 58 | break 59 | elif codingState == constants.eStart: 60 | if self._mCodingSM.get_current_charlen() >= 2: 61 | self._mNumOfMBChar += 1 62 | 63 | if self.get_state() == constants.eDetecting: 64 | if self.get_confidence() > constants.SHORTCUT_THRESHOLD: 65 | self._mState = constants.eFoundIt 66 | 67 | return self.get_state() 68 | 69 | def get_confidence(self): 70 | unlike = 0.99 71 | if self._mNumOfMBChar < 6: 72 | for i in range(0, self._mNumOfMBChar): 73 | unlike = unlike * ONE_CHAR_PROB 74 | return 1.0 - unlike 75 | else: 76 | return unlike 77 | -------------------------------------------------------------------------------- /kodidevkit.sublime-settings: -------------------------------------------------------------------------------- 1 | /* 2 | KodiDevKit default settings 3 | */ 4 | { 5 | 6 | /* 7 | * Kodi Path 8 | * 9 | * Path to your local Kodi installation (used to get labels) * 10 | * "\" chars have to be escaped! (\\) 11 | * 12 | */ 13 | "kodi_path": "", 14 | 15 | /* 16 | * Portable Mode 17 | * 18 | * Select whether you are running Kodi in portable mode (Windows Only) * 19 | */ 20 | "portable_mode": false, 21 | /* 22 | * Language folders to check 23 | * 24 | * Will make use of labels of chosen folders if available 25 | * 26 | */ 27 | "language_folders": ["resource.language.en_gb", "English"], 28 | /* 29 | * Kodi address 30 | * 31 | * Put the URL and port of the kodi instance you´re workin with here: 32 | * 33 | */ 34 | "kodi_address": "http://localhost:8080", 35 | /* 36 | * Kodi username 37 | * 38 | * username for JSON requests, needs proper kodi config 39 | * 40 | */ 41 | "kodi_username": "kodi", 42 | /* 43 | * Kodi password 44 | * 45 | * password for JSON requests, needs proper kodi config 46 | * 47 | */ 48 | "kodi_password": "", 49 | /* 50 | * Auto-reload skin on saving 51 | * 52 | * Choose if you want to auto-reload the skin after saving a skin xml 53 | * 54 | */ 55 | "auto_reload_skin": true, 56 | /* 57 | * Tooltip height 58 | * 59 | * Choose the height for the info tooltip 60 | * needs latest script.toolbox installed 61 | * 62 | */ 63 | "tooltip_height": 300, 64 | /* 65 | * Tooltip width 66 | * 67 | * Choose the width for the info tooltip 68 | * 69 | */ 70 | "tooltip_width": 1000, 71 | /* 72 | * Tooltip delay 73 | * 74 | * Choose the delay until the info tooltip shows up (in ms) 75 | * Choose -1 if you do not want tooltip at all 76 | * 77 | */ 78 | "tooltip_delay": 200, 79 | /* 80 | * Auto-Check XML 81 | * 82 | * Automatically check the xml for non-valid skin code on saving 83 | * 84 | * 85 | */ 86 | "auto_skin_check": true, 87 | /* 88 | * Debug mode 89 | * 90 | * Set this to on in order to log some debug info 91 | * 92 | * 93 | */ 94 | "debug_mode": false, 95 | /* 96 | * TextureChecker Path 97 | * 98 | * Allows building the skin from Command palette 99 | * "\" chars have to be escaped! (\\) 100 | * 101 | * 102 | */ 103 | "texturechecker_path": "", 104 | /* 105 | * Userdata folder on remote device (for ADB) 106 | * 107 | * 108 | * 109 | */ 110 | "remote_userdata_folder": "", 111 | /* 112 | * IP from from remote device 113 | * 114 | * 115 | * 116 | */ 117 | "remote_ip": "", 118 | } 119 | -------------------------------------------------------------------------------- /libs/include.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | # Copyright (C) 2015 - Philipp Temminghoff 4 | # This program is Free Software see LICENSE file for details 5 | 6 | from lxml import etree as ET 7 | import os 8 | 9 | 10 | class Include(dict): 11 | """ 12 | Represents an include-like kodi object 13 | """ 14 | 15 | constant_attribs = {"x", "y", "width", "height", "center", "max", "min", "w", "h", "time", 16 | "acceleration", "delay", "start", "end", "center", "border", "repeat"} 17 | constant_nodes = {"posx", "posy", "left", "centerleft", "right", "centerright", "top", "centertop", 18 | "bottom", "centerbottom", "width", "height", "offsetx", "offsety", "textoffsetx", 19 | "textoffsety", "textwidth", "spinposx", "spinposy", "spinwidth", "spinheight", 20 | "radioposx", "radioposy", "radiowidth", "radioheight", "sliderwidth", "sliderheight", 21 | "itemgap", "bordersize", "timeperimage", "fadetime", "pauseatend", "depth"} 22 | exp_nodes = {"visible", "enable", "usealttexture", "selected"} 23 | exp_attribs = {"condition"} 24 | 25 | def __init__(self, node, *args, **kwargs): 26 | super().__init__(*args, **kwargs) 27 | self.node = node 28 | self.file = kwargs.get("file") 29 | if self.node.getnext() is not None: 30 | self.length = self.node.getnext().sourceline - self.node.sourceline 31 | else: 32 | self.length = None 33 | 34 | def __getitem__(self, key): 35 | if key == "line": 36 | return self.line 37 | elif key == "type": 38 | return self.tag 39 | elif key == "name": 40 | return self.name 41 | elif key == "filename": 42 | return self.filename 43 | elif key == "file": 44 | return self.file 45 | elif key == "content": 46 | return ET.tostring(self.node, pretty_print=True, encoding="unicode") 47 | elif key == "length": 48 | return self.length 49 | return super().__getitem__(key) 50 | 51 | def get(self, key): 52 | return self.__getitem__(key) 53 | 54 | @property 55 | def line(self): 56 | """ 57 | returns xml source line 58 | """ 59 | return self.node.sourceline 60 | 61 | @property 62 | def tag(self): 63 | """ 64 | returns tag of include node 65 | """ 66 | return self.node.tag 67 | 68 | @property 69 | def content(self): 70 | """ 71 | returns include node text 72 | """ 73 | return self.node.text 74 | 75 | @property 76 | def name(self): 77 | """ 78 | returns name of include 79 | """ 80 | return self.node.attrib.get("name") 81 | 82 | @property 83 | def filename(self): 84 | """ 85 | returns filename of include parent file 86 | """ 87 | return os.path.basename(self.file) 88 | 89 | -------------------------------------------------------------------------------- /remote.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | # Copyright (C) 2015 - Philipp Temminghoff 4 | # This program is Free Software see LICENSE file for details 5 | 6 | """ 7 | KodiDevKit is a plugin to assist with Kodi skinning / scripting using Sublime Text 3 8 | """ 9 | import os 10 | import sublime 11 | import sublime_plugin 12 | from .libs.adbdevice import AdbDevice 13 | 14 | REMOTE = AdbDevice() 15 | SETTINGS_FILE = 'kodidevkit.sublime-settings' 16 | 17 | 18 | def plugin_loaded(): 19 | """ 20 | gets called when plugin is ready 21 | """ 22 | REMOTE.setup(sublime.load_settings(SETTINGS_FILE)) 23 | 24 | 25 | class RemoteActionsCommand(sublime_plugin.WindowCommand): 26 | """ 27 | Menu with all options related to ADB 28 | """ 29 | 30 | def __init__(self, *args, **kwargs): 31 | super().__init__(*args, **kwargs) 32 | self.settings = None 33 | 34 | def run(self): 35 | """ 36 | Show quick panel with all possible actions 37 | """ 38 | self.settings = sublime.load_settings(SETTINGS_FILE) 39 | active_device = "Set device: %s" % self.settings.get("remote_ip", "") 40 | listitems = [active_device, "Reconnect", "Send this add-on", 41 | "Get log", "Get Screenshot", "Clear cache", "Reboot"] 42 | self.window.show_quick_panel(items=listitems, 43 | on_select=self.on_done, 44 | selected_index=0) 45 | 46 | def on_done(self, index): 47 | """ 48 | callback for menu items, gets called with *index of selected items 49 | """ 50 | if index == -1: 51 | return None 52 | elif index == 0: 53 | self.window.show_input_panel("Set remote IP", 54 | self.settings.get("remote_ip", "192.168.0.1"), 55 | self.set_ip, 56 | None, 57 | None) 58 | elif index == 1: 59 | REMOTE.adb_reconnect_async() 60 | self.window.run_command("remote_actions") 61 | elif index == 2: 62 | variables = self.window.active_view().extract_variables() 63 | if "folder" in variables: 64 | REMOTE.push_to_box(variables["folder"]) 65 | elif index == 3: 66 | plugin_path = os.path.join(sublime.packages_path(), "KodiDevKit") 67 | REMOTE.get_log(self.open_file, plugin_path) 68 | elif index == 4: 69 | plugin_path = os.path.join(sublime.packages_path(), "KodiDevKit") 70 | REMOTE.get_screenshot(self.open_file, plugin_path) 71 | elif index == 5: 72 | REMOTE.clear_cache() 73 | elif index == 6: 74 | REMOTE.reboot() 75 | 76 | def open_file(self, path): 77 | """ 78 | used as callback, opens file with *path 79 | """ 80 | self.window.open_file(path) 81 | 82 | def set_ip(self, ip): 83 | """ 84 | set and save ip of adb device, return to actions menu when finished 85 | """ 86 | self.settings.set("remote_ip", ip) 87 | sublime.save_settings(SETTINGS_FILE) 88 | self.window.run_command("remote_actions") 89 | -------------------------------------------------------------------------------- /libs/chardet/escprober.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is mozilla.org code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 1998 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # 12 | # This library is free software; you can redistribute it and/or 13 | # modify it under the terms of the GNU Lesser General Public 14 | # License as published by the Free Software Foundation; either 15 | # version 2.1 of the License, or (at your option) any later version. 16 | # 17 | # This library is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | # Lesser General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU Lesser General Public 23 | # License along with this library; if not, write to the Free Software 24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 25 | # 02110-1301 USA 26 | ######################### END LICENSE BLOCK ######################### 27 | 28 | from . import constants 29 | from .escsm import (HZSMModel, ISO2022CNSMModel, ISO2022JPSMModel, 30 | ISO2022KRSMModel) 31 | from .charsetprober import CharSetProber 32 | from .codingstatemachine import CodingStateMachine 33 | from .compat import wrap_ord 34 | 35 | 36 | class EscCharSetProber(CharSetProber): 37 | def __init__(self): 38 | CharSetProber.__init__(self) 39 | self._mCodingSM = [ 40 | CodingStateMachine(HZSMModel), 41 | CodingStateMachine(ISO2022CNSMModel), 42 | CodingStateMachine(ISO2022JPSMModel), 43 | CodingStateMachine(ISO2022KRSMModel) 44 | ] 45 | self.reset() 46 | 47 | def reset(self): 48 | CharSetProber.reset(self) 49 | for codingSM in self._mCodingSM: 50 | if not codingSM: 51 | continue 52 | codingSM.active = True 53 | codingSM.reset() 54 | self._mActiveSM = len(self._mCodingSM) 55 | self._mDetectedCharset = None 56 | 57 | def get_charset_name(self): 58 | return self._mDetectedCharset 59 | 60 | def get_confidence(self): 61 | if self._mDetectedCharset: 62 | return 0.99 63 | else: 64 | return 0.00 65 | 66 | def feed(self, aBuf): 67 | for c in aBuf: 68 | # PY3K: aBuf is a byte array, so c is an int, not a byte 69 | for codingSM in self._mCodingSM: 70 | if not codingSM: 71 | continue 72 | if not codingSM.active: 73 | continue 74 | codingState = codingSM.next_state(wrap_ord(c)) 75 | if codingState == constants.eError: 76 | codingSM.active = False 77 | self._mActiveSM -= 1 78 | if self._mActiveSM <= 0: 79 | self._mState = constants.eNotMe 80 | return self.get_state() 81 | elif codingState == constants.eItsMe: 82 | self._mState = constants.eFoundIt 83 | self._mDetectedCharset = codingSM.get_coding_state_machine() # nopep8 84 | return self.get_state() 85 | 86 | return self.get_state() 87 | -------------------------------------------------------------------------------- /libs/chardet/sbcsgroupprober.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is Mozilla Universal charset detector code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 2001 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # Shy Shalom - original C code 12 | # 13 | # This library is free software; you can redistribute it and/or 14 | # modify it under the terms of the GNU Lesser General Public 15 | # License as published by the Free Software Foundation; either 16 | # version 2.1 of the License, or (at your option) any later version. 17 | # 18 | # This library is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 | # Lesser General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU Lesser General Public 24 | # License along with this library; if not, write to the Free Software 25 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 26 | # 02110-1301 USA 27 | ######################### END LICENSE BLOCK ######################### 28 | 29 | from .charsetgroupprober import CharSetGroupProber 30 | from .sbcharsetprober import SingleByteCharSetProber 31 | from .langcyrillicmodel import (Win1251CyrillicModel, Koi8rModel, 32 | Latin5CyrillicModel, MacCyrillicModel, 33 | Ibm866Model, Ibm855Model) 34 | from .langgreekmodel import Latin7GreekModel, Win1253GreekModel 35 | from .langbulgarianmodel import Latin5BulgarianModel, Win1251BulgarianModel 36 | from .langhungarianmodel import Latin2HungarianModel, Win1250HungarianModel 37 | from .langthaimodel import TIS620ThaiModel 38 | from .langhebrewmodel import Win1255HebrewModel 39 | from .hebrewprober import HebrewProber 40 | 41 | 42 | class SBCSGroupProber(CharSetGroupProber): 43 | def __init__(self): 44 | CharSetGroupProber.__init__(self) 45 | self._mProbers = [ 46 | SingleByteCharSetProber(Win1251CyrillicModel), 47 | SingleByteCharSetProber(Koi8rModel), 48 | SingleByteCharSetProber(Latin5CyrillicModel), 49 | SingleByteCharSetProber(MacCyrillicModel), 50 | SingleByteCharSetProber(Ibm866Model), 51 | SingleByteCharSetProber(Ibm855Model), 52 | SingleByteCharSetProber(Latin7GreekModel), 53 | SingleByteCharSetProber(Win1253GreekModel), 54 | SingleByteCharSetProber(Latin5BulgarianModel), 55 | SingleByteCharSetProber(Win1251BulgarianModel), 56 | SingleByteCharSetProber(Latin2HungarianModel), 57 | SingleByteCharSetProber(Win1250HungarianModel), 58 | SingleByteCharSetProber(TIS620ThaiModel), 59 | ] 60 | hebrewProber = HebrewProber() 61 | logicalHebrewProber = SingleByteCharSetProber(Win1255HebrewModel, 62 | False, hebrewProber) 63 | visualHebrewProber = SingleByteCharSetProber(Win1255HebrewModel, True, 64 | hebrewProber) 65 | hebrewProber.set_model_probers(logicalHebrewProber, visualHebrewProber) 66 | self._mProbers.extend([hebrewProber, logicalHebrewProber, 67 | visualHebrewProber]) 68 | 69 | self.reset() 70 | -------------------------------------------------------------------------------- /script.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | # Copyright (C) 2017 - Philipp Temminghoff 4 | # This program is Free Software see LICENSE file for details 5 | 6 | import os 7 | import sys 8 | import codecs 9 | import logging 10 | 11 | RESULTS_FILE = "results.txt" 12 | 13 | settings = {"kodi_path": "C:/Kodi", 14 | "portable_mode": True, 15 | "language_folders": ["resource.language.en_gb", "English"]} 16 | 17 | 18 | def check_tags(check_type): 19 | """ 20 | triggers of test of type "check_type", then formats and logs them 21 | """ 22 | errors = INFOS.get_check_listitems(check_type) 23 | for e in errors: 24 | logging.info(e["message"]) 25 | path = "/".join(e["file"].split(os.sep)[-2:]) 26 | logging.info("%s: line %s\n" % (path, e["line"])) 27 | 28 | 29 | if __name__ == "__main__": 30 | from libs import utils 31 | from libs.infoprovider import InfoProvider 32 | from libs import chardet 33 | from libs.eol import eol 34 | INFOS = InfoProvider() 35 | open(RESULTS_FILE, 'w').close() 36 | INFOS.load_settings(settings) 37 | INFOS.load_data() 38 | filehandler = logging.FileHandler("result.txt", mode="w") 39 | formatter = logging.Formatter('%(asctime)s - %(message)s', 40 | datefmt='%Y-%m-%d %H:%M:%S') 41 | filehandler.setFormatter(formatter) 42 | logger = logging.getLogger() 43 | logger.addHandler(filehandler) 44 | project_folder = sys.argv[1] if len(sys.argv) >= 2 else input("Enter Path to skin: ") 45 | INFOS.init_addon(project_folder) 46 | if len(sys.argv) < 3: 47 | repo = input('Enter Kodi version (%s): ' % " / ".join([item["name"] for item in INFOS.addon.RELEASES])) 48 | else: 49 | repo = sys.argv[2] 50 | INFOS.check_xml_files() 51 | for path in INFOS.addon.get_xml_files(): 52 | if utils.check_bom(path): 53 | logging.info("found BOM. File: " + path) 54 | try: 55 | with codecs.open(path, "rb", encoding='utf-8', errors="strict") as f: 56 | text = f.read() 57 | except Exception: 58 | logging.info("Error when trying to read %s as UTF-8" % path) 59 | with codecs.open(path, "rb", errors="ignore") as f: 60 | rawdata = f.read() 61 | encoding = chardet.detect(rawdata) 62 | logging.info("detected encoding: %s" % encoding["encoding"]) 63 | with codecs.open(path, "rb", encoding=encoding["encoding"]) as f: 64 | text = f.read() 65 | result = eol.eol_info_from_path_patterns([project_folder], 66 | recursive=True, 67 | includes=[], 68 | excludes=['.svn', '.git']) 69 | for item in result: 70 | if item[1] == '\n' or None: 71 | continue 72 | elif item[1] == '\r': 73 | logging.info("MAC Line Endings detected in " + item[0]) 74 | else: 75 | logging.info("Windows Line Endings detected in " + item[0]) 76 | logging.info("ADDON DEPENDENCY CHECK") 77 | INFOS.check_dependencies() 78 | logging.info("INCLUDE CHECK") 79 | check_tags("include") 80 | logging.info("VARIABLE CHECK") 81 | check_tags("variable") 82 | logging.info("FONT CHECK") 83 | check_tags("font") 84 | logging.info("LABEL CHECK") 85 | check_tags("label") 86 | logging.info("ID CHECK") 87 | check_tags("id") 88 | logging.info("CHECK FOR COMMON MISTAKES") 89 | check_tags("general") 90 | -------------------------------------------------------------------------------- /libs/chardet/mbcharsetprober.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is Mozilla Universal charset detector code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 2001 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # Shy Shalom - original C code 12 | # Proofpoint, Inc. 13 | # 14 | # This library is free software; you can redistribute it and/or 15 | # modify it under the terms of the GNU Lesser General Public 16 | # License as published by the Free Software Foundation; either 17 | # version 2.1 of the License, or (at your option) any later version. 18 | # 19 | # This library is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 22 | # Lesser General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU Lesser General Public 25 | # License along with this library; if not, write to the Free Software 26 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 27 | # 02110-1301 USA 28 | ######################### END LICENSE BLOCK ######################### 29 | 30 | import sys 31 | from . import constants 32 | from .charsetprober import CharSetProber 33 | 34 | 35 | class MultiByteCharSetProber(CharSetProber): 36 | def __init__(self): 37 | CharSetProber.__init__(self) 38 | self._mDistributionAnalyzer = None 39 | self._mCodingSM = None 40 | self._mLastChar = [0, 0] 41 | 42 | def reset(self): 43 | CharSetProber.reset(self) 44 | if self._mCodingSM: 45 | self._mCodingSM.reset() 46 | if self._mDistributionAnalyzer: 47 | self._mDistributionAnalyzer.reset() 48 | self._mLastChar = [0, 0] 49 | 50 | def get_charset_name(self): 51 | pass 52 | 53 | def feed(self, aBuf): 54 | aLen = len(aBuf) 55 | for i in range(0, aLen): 56 | codingState = self._mCodingSM.next_state(aBuf[i]) 57 | if codingState == constants.eError: 58 | if constants._debug: 59 | sys.stderr.write(self.get_charset_name() 60 | + ' prober hit error at byte ' + str(i) 61 | + '\n') 62 | self._mState = constants.eNotMe 63 | break 64 | elif codingState == constants.eItsMe: 65 | self._mState = constants.eFoundIt 66 | break 67 | elif codingState == constants.eStart: 68 | charLen = self._mCodingSM.get_current_charlen() 69 | if i == 0: 70 | self._mLastChar[1] = aBuf[0] 71 | self._mDistributionAnalyzer.feed(self._mLastChar, charLen) 72 | else: 73 | self._mDistributionAnalyzer.feed(aBuf[i - 1:i + 1], 74 | charLen) 75 | 76 | self._mLastChar[0] = aBuf[aLen - 1] 77 | 78 | if self.get_state() == constants.eDetecting: 79 | if (self._mDistributionAnalyzer.got_enough_data() and 80 | (self.get_confidence() > constants.SHORTCUT_THRESHOLD)): 81 | self._mState = constants.eFoundIt 82 | 83 | return self.get_state() 84 | 85 | def get_confidence(self): 86 | return self._mDistributionAnalyzer.get_confidence() 87 | -------------------------------------------------------------------------------- /libs/chardet/eucjpprober.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is mozilla.org code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 1998 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # 12 | # This library is free software; you can redistribute it and/or 13 | # modify it under the terms of the GNU Lesser General Public 14 | # License as published by the Free Software Foundation; either 15 | # version 2.1 of the License, or (at your option) any later version. 16 | # 17 | # This library is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | # Lesser General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU Lesser General Public 23 | # License along with this library; if not, write to the Free Software 24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 25 | # 02110-1301 USA 26 | ######################### END LICENSE BLOCK ######################### 27 | 28 | import sys 29 | from . import constants 30 | from .mbcharsetprober import MultiByteCharSetProber 31 | from .codingstatemachine import CodingStateMachine 32 | from .chardistribution import EUCJPDistributionAnalysis 33 | from .jpcntx import EUCJPContextAnalysis 34 | from .mbcssm import EUCJPSMModel 35 | 36 | 37 | class EUCJPProber(MultiByteCharSetProber): 38 | def __init__(self): 39 | MultiByteCharSetProber.__init__(self) 40 | self._mCodingSM = CodingStateMachine(EUCJPSMModel) 41 | self._mDistributionAnalyzer = EUCJPDistributionAnalysis() 42 | self._mContextAnalyzer = EUCJPContextAnalysis() 43 | self.reset() 44 | 45 | def reset(self): 46 | MultiByteCharSetProber.reset(self) 47 | self._mContextAnalyzer.reset() 48 | 49 | def get_charset_name(self): 50 | return "EUC-JP" 51 | 52 | def feed(self, aBuf): 53 | aLen = len(aBuf) 54 | for i in range(0, aLen): 55 | # PY3K: aBuf is a byte array, so aBuf[i] is an int, not a byte 56 | codingState = self._mCodingSM.next_state(aBuf[i]) 57 | if codingState == constants.eError: 58 | if constants._debug: 59 | sys.stderr.write(self.get_charset_name() 60 | + ' prober hit error at byte ' + str(i) 61 | + '\n') 62 | self._mState = constants.eNotMe 63 | break 64 | elif codingState == constants.eItsMe: 65 | self._mState = constants.eFoundIt 66 | break 67 | elif codingState == constants.eStart: 68 | charLen = self._mCodingSM.get_current_charlen() 69 | if i == 0: 70 | self._mLastChar[1] = aBuf[0] 71 | self._mContextAnalyzer.feed(self._mLastChar, charLen) 72 | self._mDistributionAnalyzer.feed(self._mLastChar, charLen) 73 | else: 74 | self._mContextAnalyzer.feed(aBuf[i - 1:i + 1], charLen) 75 | self._mDistributionAnalyzer.feed(aBuf[i - 1:i + 1], 76 | charLen) 77 | 78 | self._mLastChar[0] = aBuf[aLen - 1] 79 | 80 | if self.get_state() == constants.eDetecting: 81 | if (self._mContextAnalyzer.got_enough_data() and 82 | (self.get_confidence() > constants.SHORTCUT_THRESHOLD)): 83 | self._mState = constants.eFoundIt 84 | 85 | return self.get_state() 86 | 87 | def get_confidence(self): 88 | contxtCf = self._mContextAnalyzer.get_confidence() 89 | distribCf = self._mDistributionAnalyzer.get_confidence() 90 | return max(contxtCf, distribCf) 91 | -------------------------------------------------------------------------------- /libs/chardet/sjisprober.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is mozilla.org code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 1998 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # 12 | # This library is free software; you can redistribute it and/or 13 | # modify it under the terms of the GNU Lesser General Public 14 | # License as published by the Free Software Foundation; either 15 | # version 2.1 of the License, or (at your option) any later version. 16 | # 17 | # This library is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | # Lesser General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU Lesser General Public 23 | # License along with this library; if not, write to the Free Software 24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 25 | # 02110-1301 USA 26 | ######################### END LICENSE BLOCK ######################### 27 | 28 | import sys 29 | from .mbcharsetprober import MultiByteCharSetProber 30 | from .codingstatemachine import CodingStateMachine 31 | from .chardistribution import SJISDistributionAnalysis 32 | from .jpcntx import SJISContextAnalysis 33 | from .mbcssm import SJISSMModel 34 | from . import constants 35 | 36 | 37 | class SJISProber(MultiByteCharSetProber): 38 | def __init__(self): 39 | MultiByteCharSetProber.__init__(self) 40 | self._mCodingSM = CodingStateMachine(SJISSMModel) 41 | self._mDistributionAnalyzer = SJISDistributionAnalysis() 42 | self._mContextAnalyzer = SJISContextAnalysis() 43 | self.reset() 44 | 45 | def reset(self): 46 | MultiByteCharSetProber.reset(self) 47 | self._mContextAnalyzer.reset() 48 | 49 | def get_charset_name(self): 50 | return "SHIFT_JIS" 51 | 52 | def feed(self, aBuf): 53 | aLen = len(aBuf) 54 | for i in range(0, aLen): 55 | codingState = self._mCodingSM.next_state(aBuf[i]) 56 | if codingState == constants.eError: 57 | if constants._debug: 58 | sys.stderr.write(self.get_charset_name() 59 | + ' prober hit error at byte ' + str(i) 60 | + '\n') 61 | self._mState = constants.eNotMe 62 | break 63 | elif codingState == constants.eItsMe: 64 | self._mState = constants.eFoundIt 65 | break 66 | elif codingState == constants.eStart: 67 | charLen = self._mCodingSM.get_current_charlen() 68 | if i == 0: 69 | self._mLastChar[1] = aBuf[0] 70 | self._mContextAnalyzer.feed(self._mLastChar[2 - charLen:], 71 | charLen) 72 | self._mDistributionAnalyzer.feed(self._mLastChar, charLen) 73 | else: 74 | self._mContextAnalyzer.feed(aBuf[i + 1 - charLen:i + 3 75 | - charLen], charLen) 76 | self._mDistributionAnalyzer.feed(aBuf[i - 1:i + 1], 77 | charLen) 78 | 79 | self._mLastChar[0] = aBuf[aLen - 1] 80 | 81 | if self.get_state() == constants.eDetecting: 82 | if (self._mContextAnalyzer.got_enough_data() and 83 | (self.get_confidence() > constants.SHORTCUT_THRESHOLD)): 84 | self._mState = constants.eFoundIt 85 | 86 | return self.get_state() 87 | 88 | def get_confidence(self): 89 | contxtCf = self._mContextAnalyzer.get_confidence() 90 | distribCf = self._mDistributionAnalyzer.get_confidence() 91 | return max(contxtCf, distribCf) 92 | -------------------------------------------------------------------------------- /libs/imageparser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | # Copyright (C) 2015 4 | # http://stackoverflow.com/questions/8032642/how-to-obtain-image-size-using-standard-python-class-without-using-external-lib#20380514 5 | 6 | import struct 7 | import imghdr 8 | import re 9 | import functools 10 | import os 11 | 12 | 13 | @functools.lru_cache(maxsize=128) 14 | def get_image_info(fname): 15 | '''Determine the image type of fhandle and return its size. 16 | from draco''' 17 | with open(fname, 'rb') as fhandle: 18 | head = fhandle.read(32) 19 | if len(head) != 32: 20 | return 21 | if imghdr.what(fname) == 'png': 22 | check = struct.unpack('>i', head[4:8])[0] 23 | if check != 0x0d0a1a0a: 24 | return 25 | info = [("Type", "png"), 26 | ("Dimensions", "%sx%s" % (struct.unpack('>ii', head[16:24])))] 27 | elif imghdr.what(fname) == 'gif': 28 | info = [("Type", "gif"), 29 | ("Dimensions", "%sx%s" % (struct.unpack('H', fhandle.read(2))[0] - 2 42 | # We are at a SOFn block 43 | fhandle.seek(1, 1) # Skip `precision' byte. 44 | height, width = struct.unpack('>HH', fhandle.read(4)) 45 | info = [("Type", "jpeg"), 46 | ("Dimensions", "%sx%s" % (width, height)), 47 | ("Progressive", is_progressive(fname))] 48 | except Exception: # IGNORE:W0703 49 | return 50 | elif imghdr.what(fname) == 'pgm': 51 | header, width, height, maxval = re.search( 52 | b"(^P5\s(?:\s*#.*[\r\n])*" 53 | b"(\d+)\s(?:\s*#.*[\r\n])*" 54 | b"(\d+)\s(?:\s*#.*[\r\n])*" 55 | b"(\d+)\s(?:\s*#.*[\r\n]\s)*)", head).groups() 56 | info = [("Type", "pgm"), 57 | ("Dimensions", "%sx%s" % (width, height)), 58 | ("maxval", maxval), 59 | ("Header", header)] 60 | elif imghdr.what(fname) == 'bmp': 61 | _, width, height, depth = re.search( 62 | b"((\d+)\sx\s" 63 | b"(\d+)\sx\s" 64 | b"(\d+))", str).groups() 65 | info = [("Type", "bmp"), 66 | ("Dimensions", "%sx%s" % (width, height)), 67 | ("Depth", depth)] 68 | else: 69 | return 70 | info.append(("File size", "%.2f kb" % (os.path.getsize(fname) / 1024))) 71 | return info 72 | 73 | 74 | @functools.lru_cache(maxsize=128) 75 | def is_progressive(filename): 76 | with open(filename, "rb") as f: 77 | while True: 78 | block_start = struct.unpack('B', f.read(1))[0] 79 | if block_start != 0xff: 80 | raise ValueError('Invalid char code ' + block_start + ' - not a JPEG file: ' + filename) 81 | 82 | block_type = struct.unpack('B', f.read(1))[0] 83 | if block_type == 0xd8: # Start Of Image 84 | continue 85 | elif block_type == 0xc0: # Start of baseline frame 86 | return False 87 | elif block_type == 0xc2: # Start of progressive frame 88 | return True 89 | elif block_type >= 0xd0 and block_type <= 0xd7: # Restart 90 | continue 91 | elif block_type == 0xd9: # End Of Image 92 | break 93 | else: # Variable-size block, just skip it 94 | block_size = struct.unpack('2B', f.read(2)) 95 | block_size = block_size[0] * 256 + block_size[1] - 2 96 | f.seek(block_size, 1) 97 | return False 98 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Preferences", 4 | "id": "preferences", 5 | "mnemonic": "n", 6 | "children": 7 | [ 8 | { 9 | "caption": "Package Settings", 10 | "id": "package-settings", 11 | "mnemonic": "P", 12 | "children": 13 | [ 14 | { 15 | "caption": "KodiDevKit", 16 | "children": 17 | [ 18 | { 19 | "caption": "Settings – Default", 20 | "command": "open_file", "args": 21 | { 22 | "file": "${packages}/KodiDevKit/kodidevkit.sublime-settings" 23 | } 24 | }, 25 | { 26 | "caption": "Settings – User", 27 | "command": "open_file", "args": 28 | { 29 | "file": "${packages}/User/kodidevkit.sublime-settings" 30 | } 31 | }, 32 | // { 33 | // "caption": "-" 34 | // }, 35 | // { 36 | // "caption": "Key Bindings – Default", 37 | // "command": "open_file", "args": 38 | // { 39 | // "file": "${packages}/KodiDevKit/Default (OSX).sublime-keymap", 40 | // "platform": "OSX" 41 | // } 42 | // }, 43 | // { 44 | // "caption": "Key Bindings – User", 45 | // "command": "open_file", "args": 46 | // { 47 | // "file": "${packages}/User/Default (OSX).sublime-keymap", 48 | // "platform": "OSX" 49 | // } 50 | // }, 51 | // { 52 | // "caption": "Key Bindings – Default", 53 | // "command": "open_file", "args": 54 | // { 55 | // "file": "${packages}/KodiDevKit/Default (Linux).sublime-keymap", 56 | // "platform": "Linux" 57 | // } 58 | // }, 59 | // { 60 | // "caption": "Key Bindings – User", 61 | // "command": "open_file", "args": 62 | // { 63 | // "file": "${packages}/User/Default (Linux).sublime-keymap", 64 | // "platform": "Linux" 65 | // } 66 | // }, 67 | // { 68 | // "caption": "Key Bindings – Default", 69 | // "command": "open_file", "args": 70 | // { 71 | // "file": "${packages}/KodiDevKit/Default (Windows).sublime-keymap", 72 | // "platform": "Windows" 73 | // } 74 | // }, 75 | // { 76 | // "caption": "Key Bindings – User", 77 | // "command": "open_file", "args": 78 | // { 79 | // "file": "${packages}/User/Default (Windows).sublime-keymap", 80 | // "platform": "Windows" 81 | // } 82 | // } 83 | ] 84 | } 85 | ] 86 | } 87 | ] 88 | } 89 | ] 90 | -------------------------------------------------------------------------------- /kodidevkit.sublime-completions: -------------------------------------------------------------------------------- 1 | { 2 | "scope": "text.xml - source - meta.tag, punctuation.definition.tag.begin", 3 | 4 | "completions": 5 | [ 6 | { "trigger": "align", "contents": "align>$1" }, 7 | { "trigger": "aligny", "contents": "aligny>$1" }, 8 | { "trigger": "animation", "contents": "animation effect=\"$1\" time=\"$2\" tween=\"$3\" easing=\"$4\" start=\"$5\" end=\"$6\">$0" }, 9 | { "trigger": "aspectratio", "contents": "aspectratio>$1" }, 10 | { "trigger": "bottom", "contents": "bottom>$1" }, 11 | { "trigger": "button", "contents": "button>$1" }, 12 | { "trigger": "center", "contents": "center>$1" }, 13 | { "trigger": "content", "contents": "content>$1" }, 14 | { "trigger": "control", "contents": "control type=\"$1${1/(b$)|(fa$)|(fi$)|(g$)|(i$)|(la$)|(li$)|(mo$)|(mu$)|(pa$)|(pr$)|(ra$)|(re$)|(sc$)|(se$)|(sl$)|(sp$)|(te$)|(to$)|(v$)|(w$)|.*/?1:utton:?2:delabel:?3:xedlist:?4:rouplist:?5:mage:?6:bel:?7:st:?8:ver:?9:ltiimage:?10:nel:?11:ogress:?12:diobutton:?13:size:?14:rollbar:?15:lectbutton:?16:ider:?17:incontrol:?18:xtbox:?19:gglebutton:?20:ideowindow:?21:raplist/i}\" id=\"$2\">$0" }, 15 | { "trigger": "controls", "contents": "controls>$1" }, 16 | { "trigger": "description", "contents": "description>$1" }, 17 | { "trigger": "font", "contents": "font>$1" }, 18 | { "trigger": "height", "contents": "height>$1" }, 19 | { "trigger": "hinttext", "contents": "hinttext>$1" }, 20 | { "trigger": "icon", "contents": "icon>$1" }, 21 | { "trigger": "info", "contents": "info>$1" }, 22 | { "trigger": "imagepath", "contents": "imagepath>$1" }, 23 | { "trigger": "include", "contents": "include>$1" }, 24 | { "trigger": "includes", "contents": "includes>$1" }, 25 | { "trigger": "item", "contents": "item id=\"$1\">$0" }, 26 | { "trigger": "label", "contents": "label>$1" }, 27 | { "trigger": "label2", "contents": "label2>$1" }, 28 | { "trigger": "left", "contents": "left>$1" }, 29 | { "trigger": "onback", "contents": "onback>$1" }, 30 | { "trigger": "onclick", "contents": "onclick>$1" }, 31 | { "trigger": "ondown", "contents": "ondown>$1" }, 32 | { "trigger": "onfocus", "contents": "onfocus>$1" }, 33 | { "trigger": "onleft", "contents": "onleft>$1" }, 34 | { "trigger": "onright", "contents": "onright>$1" }, 35 | { "trigger": "onunfocus", "contents": "onunfocus>$1" }, 36 | { "trigger": "onup", "contents": "onup>$1" }, 37 | { "trigger": "orientation", "contents": "orientation>$1${1/(v$)|(h$)|.*/?1:ertical:?2:orizontal/i}" }, 38 | { "trigger": "pagecontrol", "contents": "pagecontrol>$1" }, 39 | { "trigger": "right", "contents": "right>$1" }, 40 | { "trigger": "scrolltime", "contents": "scrolltime>$1" }, 41 | { "trigger": "textcolor", "contents": "textcolor>$1" }, 42 | { "trigger": "texture", "contents": "texture>$1" }, 43 | { "trigger": "thumb", "contents": "thumb>$1" }, 44 | { "trigger": "timeperimage", "contents": "timeperimage>$1" }, 45 | { "trigger": "top", "contents": "top>$1" }, 46 | { "trigger": "value", "contents": "value>$1" }, 47 | { "trigger": "variable", "contents": "variable>$1" }, 48 | { "trigger": "visible", "contents": "visible>$1" }, 49 | { "trigger": "width", "contents": "width>$1" }, 50 | { "trigger": "window", "contents": "window id=\"$1\">$0" }, 51 | "Container.Content(addons)", 52 | "Container.Content(albums)", 53 | "Container.Content(artists)", 54 | "Container.Content(directors)", 55 | "Container.Content(episodes)", 56 | "Container.Content(files)", 57 | "Container.Content(genres)", 58 | "Container.Content(livetv)", 59 | "Container.Content(movies)", 60 | "Container.Content(musicvideos)", 61 | "Container.Content(playlists)", 62 | "Container.Content(plugins)", 63 | "Container.Content(seasons)", 64 | "Container.Content(sets)", 65 | "Container.Content(songs)", 66 | "Container.Content(studios)", 67 | "Container.Content(tags)", 68 | "Container.Content(tvshows)", 69 | "Container.Content(years)" 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /libs/chardet/charsetgroupprober.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is Mozilla Communicator client code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 1998 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # 12 | # This library is free software; you can redistribute it and/or 13 | # modify it under the terms of the GNU Lesser General Public 14 | # License as published by the Free Software Foundation; either 15 | # version 2.1 of the License, or (at your option) any later version. 16 | # 17 | # This library is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | # Lesser General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU Lesser General Public 23 | # License along with this library; if not, write to the Free Software 24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 25 | # 02110-1301 USA 26 | ######################### END LICENSE BLOCK ######################### 27 | 28 | from . import constants 29 | import sys 30 | from .charsetprober import CharSetProber 31 | 32 | 33 | class CharSetGroupProber(CharSetProber): 34 | def __init__(self): 35 | CharSetProber.__init__(self) 36 | self._mActiveNum = 0 37 | self._mProbers = [] 38 | self._mBestGuessProber = None 39 | 40 | def reset(self): 41 | CharSetProber.reset(self) 42 | self._mActiveNum = 0 43 | for prober in self._mProbers: 44 | if prober: 45 | prober.reset() 46 | prober.active = True 47 | self._mActiveNum += 1 48 | self._mBestGuessProber = None 49 | 50 | def get_charset_name(self): 51 | if not self._mBestGuessProber: 52 | self.get_confidence() 53 | if not self._mBestGuessProber: 54 | return None 55 | # self._mBestGuessProber = self._mProbers[0] 56 | return self._mBestGuessProber.get_charset_name() 57 | 58 | def feed(self, aBuf): 59 | for prober in self._mProbers: 60 | if not prober: 61 | continue 62 | if not prober.active: 63 | continue 64 | st = prober.feed(aBuf) 65 | if not st: 66 | continue 67 | if st == constants.eFoundIt: 68 | self._mBestGuessProber = prober 69 | return self.get_state() 70 | elif st == constants.eNotMe: 71 | prober.active = False 72 | self._mActiveNum -= 1 73 | if self._mActiveNum <= 0: 74 | self._mState = constants.eNotMe 75 | return self.get_state() 76 | return self.get_state() 77 | 78 | def get_confidence(self): 79 | st = self.get_state() 80 | if st == constants.eFoundIt: 81 | return 0.99 82 | elif st == constants.eNotMe: 83 | return 0.01 84 | bestConf = 0.0 85 | self._mBestGuessProber = None 86 | for prober in self._mProbers: 87 | if not prober: 88 | continue 89 | if not prober.active: 90 | if constants._debug: 91 | sys.stderr.write(prober.get_charset_name() 92 | + ' not active\n') 93 | continue 94 | cf = prober.get_confidence() 95 | if constants._debug: 96 | sys.stderr.write('%s confidence = %s\n' % 97 | (prober.get_charset_name(), cf)) 98 | if bestConf < cf: 99 | bestConf = cf 100 | self._mBestGuessProber = prober 101 | if not self._mBestGuessProber: 102 | return 0.0 103 | return bestConf 104 | # else: 105 | # self._mBestGuessProber = self._mProbers[0] 106 | # return self._mBestGuessProber.get_confidence() 107 | -------------------------------------------------------------------------------- /data/krypton/windows.json: -------------------------------------------------------------------------------- 1 | [["home", 0, "Home.xml"], 2 | ["programs", 1, "MyPrograms.xml"], 3 | ["pictures", 2, "MyPics.xml"], 4 | ["filemanager", 3, "FileManager.xml"], 5 | ["settings", 4, "Settings.xml"], 6 | ["systeminfo", 7, "SettingsSystemInfo.xml"], 7 | ["screencalibration", 11, "SettingsScreenCalibration.xml"], 8 | ["picturessettings", 12, "SettingsCategory.xml"], 9 | ["programssettings", 13, "SettingsCategory.xml"], 10 | ["musicsettings", 15, "SettingsCategory.xml"], 11 | ["systemsettings", 16, "SettingsCategory.xml"], 12 | ["videossettings", 17, "SettingsCategory.xml"], 13 | ["servicesettings", 18, "SettingsCategory.xml"], 14 | ["appearancesettings", 19, "SettingsCategory.xml"], 15 | ["interfacesettings", 19, "SettingsCategory.xml"], 16 | ["pvrsettings", 21, "SettingsCategory.xml"], 17 | ["videos", 25, "MyVideoNav.xml"], 18 | ["videoplaylist", 28, "MyPlaylist.xml"], 19 | ["loginscreen", 29, "LoginScreen.xml"], 20 | ["profiles", 34, "SettingsProfile.xml"], 21 | ["addonbrowser", 40, "AddonBrowser.xml"], 22 | ["yesnodialog", 100, "DialogConfirm.xml"], 23 | ["progressdialog", 101, "DialogConfirm.xml"], 24 | ["virtualkeyboard", 103, "DialogKeyboard.xml"], 25 | ["volumebar", 104, "DialogVolumeBar.xml"], 26 | ["contextmenu", 106, "DialogContextMenu.xml"], 27 | ["notification", 107, "DialogNotification.xml"], 28 | ["numericinput", 109, "DialogNumeric.xml"], 29 | ["shutdownmenu", 111, "DialogButtonMenu.xml"], 30 | ["playercontrols", 114, "PlayerControls.xml"], 31 | ["seekbar", 115, "DialogSeekBar.xml"], 32 | ["musicosd", 120, "MusicOSD.xml"], 33 | ["visualisationpresetlist", 122, "DialogSelect.xml"], 34 | ["osdvideosettings", 123, "DialogSettings.xml"], 35 | ["osdaudiosettings", 124, "DialogSettings.xml"], 36 | ["videobookmarks", 125, "VideoOSDBookmarks.xml"], 37 | ["filebrowser", 126, "FileBrowser.xml"], 38 | ["networksetup", 128, "DialogSettings.xml"], 39 | ["mediasource", 129, "DialogMediaSource.xml"], 40 | ["profilesettings", 130, "DialogSettings.xml"], 41 | ["locksettings", 131, "DialogSettings.xml"], 42 | ["contentsettings", 132, "DialogSettings.xml"], 43 | ["favourites", 134, "DialogFavourites.xml"], 44 | ["songinformation", 135, "DialogMusicInfo.xml"], 45 | ["smartplaylisteditor", 136, "SmartPlaylistEditor.xml"], 46 | ["smartplaylistrule", 137, "SmartPlaylistRule.xml"], 47 | ["busydialog", 138, "DialogBusy.xml"], 48 | ["pictureinfo", 139, "DialogPictureInfo.xml"], 49 | ["addonsettings", 140, "DialogAddonSettings.xml"], 50 | ["fullscreeninfo", 142, "DialogFullScreenInfo.xml"], 51 | ["sliderdialog", 145, "DialogSlider.xml"], 52 | ["addoninformation", 146, "DialogAddonInfo.xml"], 53 | ["textviewer", 147, "DialogTextViewer.xml"], 54 | ["peripherals", 149, "DialogSelect.xml"], 55 | ["peripheralsettings", 150, "DialogSettings.xml"], 56 | ["extendedprogressdialog", 151, "DialogExtendedProgressBar.xml"], 57 | ["mediafilter", 152, "DialogSettings.xml"], 58 | ["subtitlesearch", 153, "DialogSubtitles.xml"], 59 | ["musicplaylist", 500, "MyPlaylist.xml"], 60 | ["musicfiles", 501, "MyMusicNav.xml"], 61 | ["musiclibrary", 502, "MyMusicNav.xml"], 62 | ["musicplaylisteditor", 503, "MyMusicPlaylistEditor.xml"], 63 | ["tvchannels", 615, "MyPVRChannels.xml"], 64 | ["tvrecordings", 616, "MyPVRRecordings.xml"], 65 | ["tvguide", 617, "MyPVRGuide.xml"], 66 | ["tvtimers", 618, "MyPVRTimers.xml"], 67 | ["tvsearch", 619, "MyPVRSearch.xml"], 68 | ["radiochannels", 620, "MyPVRChannels.xml"], 69 | ["radiorecordings", 621, "MyPVRRecordings.xml"], 70 | ["radioguide", 622, "MyPVRGuide.xml"], 71 | ["radiotimers", 623, "MyPVRTimers.xml"], 72 | ["radiosearch", 624, "MyPVRSearch.xml"], 73 | ["pvrguideinfo", 602, "DialogPVRInfo.xml"], 74 | ["pvrrecordinginfo", 603, "DialogPVRInfo.xml"], 75 | ["pvrtimersetting", 604, "DialogPVRTimerSettings.xml"], 76 | ["pvrgroupmanager", 605, "DialogPVRGroupManager.xml"], 77 | ["pvrchannelmanager", 606, "DialogPVRChannelManager.xml"], 78 | ["pvrguidesearch", 607, "DialogPVRGuideSearch.xml"], 79 | ["pvrosdchannels", 610, "DialogPVRChannelsOSD.xml"], 80 | ["pvrosdguide", 611, "DialogPVRGuideOSD.xml"], 81 | ["selectdialog", 2000, "DialogSelect.xml"], 82 | ["musicinformation", 2001, "DialogAlbumInfo.xml"], 83 | ["okdialog", 2002, "DialogConfirm.xml"], 84 | ["movieinformation", 2003, "DialogVideoInfo.xml"], 85 | ["fullscreenvideo", 2005, "VideoFullScreen.xml"], 86 | ["visualisation", 2006, "MusicVisualisation.xml"], 87 | ["slideshow", 2007, "SlideShow.xml"], 88 | ["weather", 2600, "MyWeather.xml"], 89 | ["videoosd", 2901, "VideoOSD.xml"], 90 | ["startup", 2999, "Startup.xml"], 91 | ["skinsettings", 35, "SkinSettings.xml"], 92 | ["pointer", 105, "Pointer.xml"]] 93 | -------------------------------------------------------------------------------- /data/leia/windows.json: -------------------------------------------------------------------------------- 1 | [["home", 0, "Home.xml"], 2 | ["programs", 1, "MyPrograms.xml"], 3 | ["pictures", 2, "MyPics.xml"], 4 | ["filemanager", 3, "FileManager.xml"], 5 | ["settings", 4, "Settings.xml"], 6 | ["systeminfo", 7, "SettingsSystemInfo.xml"], 7 | ["screencalibration", 11, "SettingsScreenCalibration.xml"], 8 | ["picturessettings", 12, "SettingsCategory.xml"], 9 | ["programssettings", 13, "SettingsCategory.xml"], 10 | ["musicsettings", 15, "SettingsCategory.xml"], 11 | ["systemsettings", 16, "SettingsCategory.xml"], 12 | ["videossettings", 17, "SettingsCategory.xml"], 13 | ["servicesettings", 18, "SettingsCategory.xml"], 14 | ["appearancesettings", 19, "SettingsCategory.xml"], 15 | ["interfacesettings", 19, "SettingsCategory.xml"], 16 | ["pvrsettings", 21, "SettingsCategory.xml"], 17 | ["videos", 25, "MyVideoNav.xml"], 18 | ["videoplaylist", 28, "MyPlaylist.xml"], 19 | ["loginscreen", 29, "LoginScreen.xml"], 20 | ["profiles", 34, "SettingsProfile.xml"], 21 | ["addonbrowser", 40, "AddonBrowser.xml"], 22 | ["yesnodialog", 100, "DialogConfirm.xml"], 23 | ["progressdialog", 101, "DialogConfirm.xml"], 24 | ["virtualkeyboard", 103, "DialogKeyboard.xml"], 25 | ["volumebar", 104, "DialogVolumeBar.xml"], 26 | ["contextmenu", 106, "DialogContextMenu.xml"], 27 | ["notification", 107, "DialogNotification.xml"], 28 | ["numericinput", 109, "DialogNumeric.xml"], 29 | ["shutdownmenu", 111, "DialogButtonMenu.xml"], 30 | ["playercontrols", 114, "PlayerControls.xml"], 31 | ["seekbar", 115, "DialogSeekBar.xml"], 32 | ["musicosd", 120, "MusicOSD.xml"], 33 | ["visualisationpresetlist", 122, "DialogSelect.xml"], 34 | ["osdvideosettings", 123, "DialogSettings.xml"], 35 | ["osdaudiosettings", 124, "DialogSettings.xml"], 36 | ["videobookmarks", 125, "VideoOSDBookmarks.xml"], 37 | ["filebrowser", 126, "FileBrowser.xml"], 38 | ["networksetup", 128, "DialogSettings.xml"], 39 | ["mediasource", 129, "DialogMediaSource.xml"], 40 | ["profilesettings", 130, "DialogSettings.xml"], 41 | ["locksettings", 131, "DialogSettings.xml"], 42 | ["contentsettings", 132, "DialogSettings.xml"], 43 | ["favourites", 134, "DialogFavourites.xml"], 44 | ["songinformation", 135, "DialogMusicInfo.xml"], 45 | ["smartplaylisteditor", 136, "SmartPlaylistEditor.xml"], 46 | ["smartplaylistrule", 137, "SmartPlaylistRule.xml"], 47 | ["busydialog", 138, "DialogBusy.xml"], 48 | ["pictureinfo", 139, "DialogPictureInfo.xml"], 49 | ["addonsettings", 140, "DialogAddonSettings.xml"], 50 | ["fullscreeninfo", 142, "DialogFullScreenInfo.xml"], 51 | ["sliderdialog", 145, "DialogSlider.xml"], 52 | ["addoninformation", 146, "DialogAddonInfo.xml"], 53 | ["textviewer", 147, "DialogTextViewer.xml"], 54 | ["peripherals", 149, "DialogSelect.xml"], 55 | ["peripheralsettings", 150, "DialogSettings.xml"], 56 | ["extendedprogressdialog", 151, "DialogExtendedProgressBar.xml"], 57 | ["mediafilter", 152, "DialogSettings.xml"], 58 | ["subtitlesearch", 153, "DialogSubtitles.xml"], 59 | ["musicplaylist", 500, "MyPlaylist.xml"], 60 | ["musicfiles", 501, "MyMusicNav.xml"], 61 | ["musiclibrary", 502, "MyMusicNav.xml"], 62 | ["musicplaylisteditor", 503, "MyMusicPlaylistEditor.xml"], 63 | ["tvchannels", 615, "MyPVRChannels.xml"], 64 | ["tvrecordings", 616, "MyPVRRecordings.xml"], 65 | ["tvguide", 617, "MyPVRGuide.xml"], 66 | ["tvtimers", 618, "MyPVRTimers.xml"], 67 | ["tvsearch", 619, "MyPVRSearch.xml"], 68 | ["radiochannels", 620, "MyPVRChannels.xml"], 69 | ["radiorecordings", 621, "MyPVRRecordings.xml"], 70 | ["radioguide", 622, "MyPVRGuide.xml"], 71 | ["radiotimers", 623, "MyPVRTimers.xml"], 72 | ["radiosearch", 624, "MyPVRSearch.xml"], 73 | ["pvrguideinfo", 602, "DialogPVRInfo.xml"], 74 | ["pvrrecordinginfo", 603, "DialogPVRInfo.xml"], 75 | ["pvrtimersetting", 604, "DialogPVRTimerSettings.xml"], 76 | ["pvrgroupmanager", 605, "DialogPVRGroupManager.xml"], 77 | ["pvrchannelmanager", 606, "DialogPVRChannelManager.xml"], 78 | ["pvrguidesearch", 607, "DialogPVRGuideSearch.xml"], 79 | ["pvrosdchannels", 610, "DialogPVRChannelsOSD.xml"], 80 | ["pvrchannelguide", 611, "DialogPVRChannelGuide.xml"], 81 | ["games", 822, "MyGames.xml"], 82 | ["selectdialog", 2000, "DialogSelect.xml"], 83 | ["musicinformation", 2001, "DialogAlbumInfo.xml"], 84 | ["okdialog", 2002, "DialogConfirm.xml"], 85 | ["movieinformation", 2003, "DialogVideoInfo.xml"], 86 | ["fullscreenvideo", 2005, "VideoFullScreen.xml"], 87 | ["visualisation", 2006, "MusicVisualisation.xml"], 88 | ["slideshow", 2007, "SlideShow.xml"], 89 | ["weather", 2600, "MyWeather.xml"], 90 | ["videoosd", 2901, "VideoOSD.xml"], 91 | ["startup", 2999, "Startup.xml"], 92 | ["skinsettings", 35, "SkinSettings.xml"], 93 | ["pointer", 105, "Pointer.xml"]] 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KodiDevKit 2 | 3 | [![License](https://img.shields.io/badge/License-GPL%20v3%2B-blue.svg)](https://raw.githubusercontent.com/phil65/KodiDevKit/master/LICENSE) 4 | 5 | ST3 plugin to show translated Kodi labels in mouseover popup, quickly open the Kodi log, log syntax highlighting and much more. 6 | 7 | 8 | ### Requirements 9 | 10 | In order to get everything working you need to manage your Kodi add-ons as a project while havin the add-on root as the only project folder. 11 | To allow JSON-RPC interaction with Kodi you also need to install script toolbox ( https://github.com/phil65/script.toolbox ) as well as activating JSON control via Kodi settings. 12 | Also, don´t forget to set up your KodiDevKit settings! 13 | 14 | ### Feature list: 15 | 16 | ##### Kodi Log: 17 | 18 | - Open log from command palette 19 | - Jump to code where exception ocurred by pressing shift+enter when line with path is selected 20 | 21 | 22 | ##### Syntax Highlighting: 23 | 24 | - Added custom syntax highlighting for 25 | - Kodi language files 26 | - Kodi SkinXML files 27 | - Kodi log files 28 | 29 | 30 | ##### Tooltips: 31 | 32 | - Show english translation when label id is selected 33 | - Show additional translation of choice when label id is selected 34 | - Show actual color / color hex / alpha % for all color themes when color is selected 35 | - Show variable content 36 | - Show include content 37 | - Show font tag 38 | - Show constant value 39 | - Show value of selected Kodi InfoLabel in tooltip (by using JSON-RPC) 40 | - Show infos for selected image (image dimensions and file size) 41 | - Show window file name 42 | 43 | 44 | ##### JSON-RPC: (newest script.toolbox version needed) 45 | 46 | - Auto-reload skin after saving xml 47 | - Execute builtins from command palette 48 | - Reload skin 49 | - Display Kodi InfoLabel 50 | 51 | 52 | ##### Shortcuts: 53 | 54 | - Auto-completion for common Kodi snippets 55 | - builtins 56 | - boolean conditions 57 | - window names 58 | - include names 59 | - variable names 60 | - font names 61 | - constant names 62 | 63 | 64 | ##### Shortcuts: 65 | 66 | - Jump to include (shift+enter) 67 | - Jump to variable (shift+enter) 68 | - Jump to constant (shift+enter) 69 | - Jump to font (shift+enter) 70 | - Jump to label definition (shift+enter) 71 | - Jump to color definition (shift+enter) 72 | - Preview skin image (ctrl+enter) 73 | - Switch xml folder (ctrl+shift+enter) 74 | - Replicate code fragments (and insert asc. number starting with [offset]) (ctrl+shift+x) 75 | 76 | 77 | ##### Fuzzy searches: 78 | 79 | - Search through all skin labels 80 | - Search through all textures including preview 81 | - Search through all available fonts 82 | - Search through all translated strings ($LOCALIZE[id]) of currently open file 83 | - Search through all boolean conditions 84 | - Search through all builtins 85 | 86 | 87 | ##### Sanity checks: 88 | 89 | - Check for unused includes / invalid include references 90 | - Check for unused variables / invalid variable references 91 | - Check for unused fonts / invalid include font references 92 | - Check for unused labels / invalid label references 93 | - Check for invalid values / structure: 94 | - invalid nodes 95 | - invalid attributes 96 | - invalid attribute values 97 | - invalid node values 98 | - invalid multiple nodes 99 | - check for correct parantheses 100 | - check "empty" action calls 101 | 102 | 103 | ##### Context menu: 104 | 105 | - Move label to language file (creates entry in strings.po using the first free id and replaces selected text with $LOCALIZE[foo]) 106 | - Go to Kodi online wiki (opens corresponding online help page, only for control types atm) 107 | - Preview skin image 108 | 109 | 110 | ##### Misc: 111 | 112 | - Auto-check skin file on saving 113 | - SkinCheck can also be used from command line with "python script.py PATH_TO_ADDON" 114 | - requires Python 3.3 interpreter 115 | - Build skin with texturepacker from command palette 116 | - Jump-to-Addon command 117 | 118 | 119 | ##### Remote Actions 120 | 121 | - Quick access to common ADB commands: 122 | - set remote IP 123 | - connect to remote 124 | - push add-on to remote 125 | - pull log from remote 126 | - clear temp folder on remote 127 | - pull screenshot from remote 128 | - reboot remote 129 | 130 | ___ 131 | 132 | **Note:** *Sublime Text 2 is not supported. Also, KodiDevKit takes advantage of certain features of ST3 that have bugs in earlier ST3 releases or were implemented during betas. For the best experience, use the latest ST3 dev build. Minimum required version is build 3084.* 133 | 134 | Available on PackageControl: https://packagecontrol.io/packages/KodiDevKit 135 | -------------------------------------------------------------------------------- /libs/chardet/sbcharsetprober.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is Mozilla Universal charset detector code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 2001 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # Shy Shalom - original C code 12 | # 13 | # This library is free software; you can redistribute it and/or 14 | # modify it under the terms of the GNU Lesser General Public 15 | # License as published by the Free Software Foundation; either 16 | # version 2.1 of the License, or (at your option) any later version. 17 | # 18 | # This library is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 | # Lesser General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU Lesser General Public 24 | # License along with this library; if not, write to the Free Software 25 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 26 | # 02110-1301 USA 27 | ######################### END LICENSE BLOCK ######################### 28 | 29 | import sys 30 | from . import constants 31 | from .charsetprober import CharSetProber 32 | from .compat import wrap_ord 33 | 34 | SAMPLE_SIZE = 64 35 | SB_ENOUGH_REL_THRESHOLD = 1024 36 | POSITIVE_SHORTCUT_THRESHOLD = 0.95 37 | NEGATIVE_SHORTCUT_THRESHOLD = 0.05 38 | SYMBOL_CAT_ORDER = 250 39 | NUMBER_OF_SEQ_CAT = 4 40 | POSITIVE_CAT = NUMBER_OF_SEQ_CAT - 1 41 | #NEGATIVE_CAT = 0 42 | 43 | 44 | class SingleByteCharSetProber(CharSetProber): 45 | def __init__(self, model, reversed=False, nameProber=None): 46 | CharSetProber.__init__(self) 47 | self._mModel = model 48 | # TRUE if we need to reverse every pair in the model lookup 49 | self._mReversed = reversed 50 | # Optional auxiliary prober for name decision 51 | self._mNameProber = nameProber 52 | self.reset() 53 | 54 | def reset(self): 55 | CharSetProber.reset(self) 56 | # char order of last character 57 | self._mLastOrder = 255 58 | self._mSeqCounters = [0] * NUMBER_OF_SEQ_CAT 59 | self._mTotalSeqs = 0 60 | self._mTotalChar = 0 61 | # characters that fall in our sampling range 62 | self._mFreqChar = 0 63 | 64 | def get_charset_name(self): 65 | if self._mNameProber: 66 | return self._mNameProber.get_charset_name() 67 | else: 68 | return self._mModel['charsetName'] 69 | 70 | def feed(self, aBuf): 71 | if not self._mModel['keepEnglishLetter']: 72 | aBuf = self.filter_without_english_letters(aBuf) 73 | aLen = len(aBuf) 74 | if not aLen: 75 | return self.get_state() 76 | for c in aBuf: 77 | order = self._mModel['charToOrderMap'][wrap_ord(c)] 78 | if order < SYMBOL_CAT_ORDER: 79 | self._mTotalChar += 1 80 | if order < SAMPLE_SIZE: 81 | self._mFreqChar += 1 82 | if self._mLastOrder < SAMPLE_SIZE: 83 | self._mTotalSeqs += 1 84 | if not self._mReversed: 85 | i = (self._mLastOrder * SAMPLE_SIZE) + order 86 | model = self._mModel['precedenceMatrix'][i] 87 | else: # reverse the order of the letters in the lookup 88 | i = (order * SAMPLE_SIZE) + self._mLastOrder 89 | model = self._mModel['precedenceMatrix'][i] 90 | self._mSeqCounters[model] += 1 91 | self._mLastOrder = order 92 | 93 | if self.get_state() == constants.eDetecting: 94 | if self._mTotalSeqs > SB_ENOUGH_REL_THRESHOLD: 95 | cf = self.get_confidence() 96 | if cf > POSITIVE_SHORTCUT_THRESHOLD: 97 | if constants._debug: 98 | sys.stderr.write('%s confidence = %s, we have a' 99 | 'winner\n' % 100 | (self._mModel['charsetName'], cf)) 101 | self._mState = constants.eFoundIt 102 | elif cf < NEGATIVE_SHORTCUT_THRESHOLD: 103 | if constants._debug: 104 | sys.stderr.write('%s confidence = %s, below negative' 105 | 'shortcut threshhold %s\n' % 106 | (self._mModel['charsetName'], cf, 107 | NEGATIVE_SHORTCUT_THRESHOLD)) 108 | self._mState = constants.eNotMe 109 | 110 | return self.get_state() 111 | 112 | def get_confidence(self): 113 | r = 0.01 114 | if self._mTotalSeqs > 0: 115 | r = ((1.0 * self._mSeqCounters[POSITIVE_CAT]) / self._mTotalSeqs 116 | / self._mModel['mTypicalPositiveRatio']) 117 | r = r * self._mFreqChar / self._mTotalChar 118 | if r >= 1.0: 119 | r = 0.99 120 | return r 121 | -------------------------------------------------------------------------------- /Gettext.tmLanguage: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | comment 6 | 7 | fileTypes 8 | 9 | po 10 | potx 11 | 12 | foldingStartMarker 13 | #[ \.:,|] 14 | foldingStopMarker 15 | ^\s*$ 16 | keyEquivalent 17 | ^~G 18 | name 19 | Gettext 20 | patterns 21 | 22 | 23 | begin 24 | msgid\s+"" 25 | end 26 | ^$ 27 | name 28 | entity.name.section.header.po 29 | 30 | 31 | captures 32 | 33 | 1 34 | 35 | name 36 | constant.character.double-quote.po 37 | 38 | 2 39 | 40 | name 41 | string.quoted.double.po 42 | 43 | 3 44 | 45 | name 46 | constant.character.double-quote.po 47 | 48 | 49 | match 50 | ^msgid(?:_plural)?\s+(")(.*)(")\s*$ 51 | name 52 | keyword.control.msgid.po 53 | 54 | 55 | captures 56 | 57 | 1 58 | 59 | name 60 | constant.character.double-quote.po 61 | 62 | 2 63 | 64 | name 65 | string.quoted.double.po 66 | 67 | 3 68 | 69 | name 70 | constant.character.double-quote.po 71 | 72 | 73 | match 74 | ^msgstr(?:\[\d?\])?\s+(")(.*)(")\s*$ 75 | name 76 | keyword.control.msgstr.po 77 | 78 | 79 | captures 80 | 81 | 1 82 | 83 | name 84 | constant.character.double-quote.po 85 | 86 | 2 87 | 88 | name 89 | string.quoted.double.po 90 | 91 | 3 92 | 93 | name 94 | constant.character.double-quote.po 95 | 96 | 97 | match 98 | ^msgctxt(?:\[\d?\])?\s+(")(.*)(")\s*$ 99 | name 100 | keyword.control.msgctxt.po 101 | 102 | 103 | captures 104 | 105 | 1 106 | 107 | name 108 | constant.character.double-quote.po 109 | 110 | 2 111 | 112 | name 113 | string.quoted.double.po 114 | 115 | 3 116 | 117 | name 118 | constant.character.double-quote.po 119 | 120 | 121 | match 122 | ^(")(.+)(")\s*$ 123 | name 124 | string.quoted.double.po 125 | 126 | 127 | match 128 | ^#\s+(.*)\s*$ 129 | name 130 | comment.line.number-sign.po 131 | 132 | 133 | captures 134 | 135 | 1 136 | 137 | name 138 | keyword.other.flag.po 139 | 140 | 141 | match 142 | ^#,\s+((?:(?:fuzzy)|(?:no-)?(?:c|objc|sh|lisp|elisp|librep|scheme|smalltalk|java|csharp|awk|object-pascal|ycp|tcl|perl|perl-brace|php|gcc-internal|qt|boost)-format)(?:,\s*(?:(?:fuzzy)|(?:no-)?(?:c|objc|sh|lisp|elisp|librep|scheme|smalltalk|java|csharp|awk|object-pascal|ycp|tcl|perl|perl-brace|php|gcc-internal|qt|boost)-format))*)\s*$ 143 | name 144 | comment.line.number-sign.flag.po 145 | 146 | 147 | match 148 | ^#\.\s+(.*)\s*$ 149 | name 150 | comment.line.number-sign.extracted.po 151 | 152 | 153 | captures 154 | 155 | 1 156 | 157 | name 158 | constant.character.sourceref.po 159 | 160 | 3 161 | 162 | name 163 | constant.numeric.linenumber.po 164 | 165 | 166 | match 167 | ^#:\s+((.*))(:([\d;]*))\s*$ 168 | name 169 | comment.line.number-sign.reference.po 170 | 171 | 172 | match 173 | ^#|\s+(msgid|msgctxt)\s+(".*")\s*$ 174 | name 175 | comment.line.number-sign.previous.po 176 | 177 | 178 | comment 179 | a line that does not begin with # or ". Could improve this regexp 180 | match 181 | ^[^#"].+$ 182 | name 183 | invalid.illegal.po 184 | 185 | 186 | scopeName 187 | source.po 188 | uuid 189 | F07730BD-59BC-41D0-AC3F-4AB2DCB6C54A 190 | 191 | 192 | -------------------------------------------------------------------------------- /libs/chardet/latin1prober.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is Mozilla Universal charset detector code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 2001 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # Shy Shalom - original C code 12 | # 13 | # This library is free software; you can redistribute it and/or 14 | # modify it under the terms of the GNU Lesser General Public 15 | # License as published by the Free Software Foundation; either 16 | # version 2.1 of the License, or (at your option) any later version. 17 | # 18 | # This library is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 | # Lesser General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU Lesser General Public 24 | # License along with this library; if not, write to the Free Software 25 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 26 | # 02110-1301 USA 27 | ######################### END LICENSE BLOCK ######################### 28 | 29 | from .charsetprober import CharSetProber 30 | from .constants import eNotMe 31 | from .compat import wrap_ord 32 | 33 | FREQ_CAT_NUM = 4 34 | 35 | UDF = 0 # undefined 36 | OTH = 1 # other 37 | ASC = 2 # ascii capital letter 38 | ASS = 3 # ascii small letter 39 | ACV = 4 # accent capital vowel 40 | ACO = 5 # accent capital other 41 | ASV = 6 # accent small vowel 42 | ASO = 7 # accent small other 43 | CLASS_NUM = 8 # total classes 44 | 45 | Latin1_CharToClass = ( 46 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 00 - 07 47 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 08 - 0F 48 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 10 - 17 49 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 18 - 1F 50 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 20 - 27 51 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 28 - 2F 52 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 30 - 37 53 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 38 - 3F 54 | OTH, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 40 - 47 55 | ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 48 - 4F 56 | ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 50 - 57 57 | ASC, ASC, ASC, OTH, OTH, OTH, OTH, OTH, # 58 - 5F 58 | OTH, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 60 - 67 59 | ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 68 - 6F 60 | ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 70 - 77 61 | ASS, ASS, ASS, OTH, OTH, OTH, OTH, OTH, # 78 - 7F 62 | OTH, UDF, OTH, ASO, OTH, OTH, OTH, OTH, # 80 - 87 63 | OTH, OTH, ACO, OTH, ACO, UDF, ACO, UDF, # 88 - 8F 64 | UDF, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 90 - 97 65 | OTH, OTH, ASO, OTH, ASO, UDF, ASO, ACO, # 98 - 9F 66 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A0 - A7 67 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A8 - AF 68 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B0 - B7 69 | OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B8 - BF 70 | ACV, ACV, ACV, ACV, ACV, ACV, ACO, ACO, # C0 - C7 71 | ACV, ACV, ACV, ACV, ACV, ACV, ACV, ACV, # C8 - CF 72 | ACO, ACO, ACV, ACV, ACV, ACV, ACV, OTH, # D0 - D7 73 | ACV, ACV, ACV, ACV, ACV, ACO, ACO, ACO, # D8 - DF 74 | ASV, ASV, ASV, ASV, ASV, ASV, ASO, ASO, # E0 - E7 75 | ASV, ASV, ASV, ASV, ASV, ASV, ASV, ASV, # E8 - EF 76 | ASO, ASO, ASV, ASV, ASV, ASV, ASV, OTH, # F0 - F7 77 | ASV, ASV, ASV, ASV, ASV, ASO, ASO, ASO, # F8 - FF 78 | ) 79 | 80 | # 0 : illegal 81 | # 1 : very unlikely 82 | # 2 : normal 83 | # 3 : very likely 84 | Latin1ClassModel = ( 85 | # UDF OTH ASC ASS ACV ACO ASV ASO 86 | 0, 0, 0, 0, 0, 0, 0, 0, # UDF 87 | 0, 3, 3, 3, 3, 3, 3, 3, # OTH 88 | 0, 3, 3, 3, 3, 3, 3, 3, # ASC 89 | 0, 3, 3, 3, 1, 1, 3, 3, # ASS 90 | 0, 3, 3, 3, 1, 2, 1, 2, # ACV 91 | 0, 3, 3, 3, 3, 3, 3, 3, # ACO 92 | 0, 3, 1, 3, 1, 1, 1, 3, # ASV 93 | 0, 3, 1, 3, 1, 1, 3, 3, # ASO 94 | ) 95 | 96 | 97 | class Latin1Prober(CharSetProber): 98 | def __init__(self): 99 | CharSetProber.__init__(self) 100 | self.reset() 101 | 102 | def reset(self): 103 | self._mLastCharClass = OTH 104 | self._mFreqCounter = [0] * FREQ_CAT_NUM 105 | CharSetProber.reset(self) 106 | 107 | def get_charset_name(self): 108 | return "windows-1252" 109 | 110 | def feed(self, aBuf): 111 | aBuf = self.filter_with_english_letters(aBuf) 112 | for c in aBuf: 113 | charClass = Latin1_CharToClass[wrap_ord(c)] 114 | freq = Latin1ClassModel[(self._mLastCharClass * CLASS_NUM) 115 | + charClass] 116 | if freq == 0: 117 | self._mState = eNotMe 118 | break 119 | self._mFreqCounter[freq] += 1 120 | self._mLastCharClass = charClass 121 | 122 | return self.get_state() 123 | 124 | def get_confidence(self): 125 | if self.get_state() == eNotMe: 126 | return 0.01 127 | 128 | total = sum(self._mFreqCounter) 129 | if total < 0.01: 130 | confidence = 0.0 131 | else: 132 | confidence = ((self._mFreqCounter[3] / total) 133 | - (self._mFreqCounter[1] * 20.0 / total)) 134 | if confidence < 0.0: 135 | confidence = 0.0 136 | # lower the confidence of latin1 so that other more accurate 137 | # detector can take priority. 138 | confidence = confidence * 0.5 139 | return confidence 140 | -------------------------------------------------------------------------------- /libs/kodi/kodi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | # Copyright (C) 2015 - Philipp Temminghoff 4 | # This program is Free Software see LICENSE file for details 5 | 6 | import os 7 | import platform 8 | from .. import utils 9 | from urllib.request import Request, urlopen 10 | import json 11 | import base64 12 | import logging 13 | 14 | APP_NAME = "kodi" 15 | 16 | 17 | class Kodi(object): 18 | 19 | """ 20 | Class representing a kodi installation 21 | delivers core language files / paths / JSON answers / installed add-ons 22 | """ 23 | 24 | def __init__(self, settings=None): 25 | self.settings = settings 26 | self.po_files = [] 27 | self.colors = [] 28 | self.color_labels = [] 29 | self.json_url = None 30 | self.kodi_path = None 31 | self.userdata_folder = None 32 | 33 | @utils.run_async 34 | def request_async(self, method, params): 35 | """ 36 | send JSON command *data to Kodi in separate thread, 37 | also needs *settings for remote ip etc. 38 | """ 39 | return self.request(method, 40 | params) 41 | 42 | def request(self, method, params=None): 43 | """ 44 | send JSON command *data to Kodi, 45 | also needs *settings for remote ip etc. 46 | """ 47 | if not self.json_url: 48 | return None 49 | data = {"jsonrpc": "2.0", 50 | "method": method, 51 | "id": 1} 52 | if params: 53 | data["params"] = params 54 | credentials = '{}:{}'.format(self.settings.get("kodi_username", "kodi"), 55 | self.settings.get("kodi_password", "")) 56 | headers = {'Content-Type': 'application/json', 57 | 'Authorization': b'Basic ' + base64.b64encode(credentials.encode('UTF-8'))} 58 | request = Request(url=self.json_url + "/jsonrpc", 59 | data=json.dumps(data).encode('utf-8'), 60 | headers=headers) 61 | try: 62 | result = urlopen(request).read() 63 | result = json.loads(result.decode("utf-8")) 64 | utils.prettyprint(result) 65 | return result 66 | except Exception: 67 | return None 68 | 69 | def get_colors(self): 70 | """ 71 | create color list by parsing core color file 72 | """ 73 | self.colors = [] 74 | if not os.path.exists(self.color_file_path): 75 | return False 76 | root = utils.get_root_from_file(self.color_file_path) 77 | for node in root.findall("color"): 78 | color = {"name": node.attrib["name"], 79 | "line": node.sourceline, 80 | "content": node.text, 81 | "file": self.color_file_path} 82 | self.colors.append(color) 83 | logging.info("found color file %s including %i colors" % (self.color_file_path, len(self.colors))) 84 | self.color_labels = {i["name"] for i in self.colors} 85 | 86 | def get_userdata_folder(self): 87 | """ 88 | return userdata folder based on platform and portable setting 89 | """ 90 | if platform.system() == "Linux": 91 | return os.path.join(os.path.expanduser("~"), ".%s" % APP_NAME) 92 | elif platform.system() == "Windows": 93 | if self.settings.get("portable_mode"): 94 | return os.path.join(self.kodi_path, "portable_data") 95 | else: 96 | return os.path.join(os.getenv('APPDATA'), APP_NAME) 97 | elif platform.system() == "Darwin": 98 | return os.path.join(os.path.expanduser("~"), "Application Support", APP_NAME, "userdata") 99 | 100 | @property 101 | def user_addons_path(self): 102 | """ 103 | get path to userdata addon dir 104 | """ 105 | return os.path.join(self.userdata_folder, "addons") 106 | 107 | @property 108 | def core_addons_path(self): 109 | """ 110 | get path to core addon dir 111 | """ 112 | return os.path.join(self.kodi_path, "addons") 113 | 114 | @property 115 | def color_file_path(self): 116 | """ 117 | get path to core color xml 118 | """ 119 | return os.path.join(self.kodi_path, "system", "colors.xml") 120 | 121 | @property 122 | def default_skin_path(self): 123 | """ 124 | get path to userdata addon dir 125 | """ 126 | return os.path.join(self.user_addons_path, "skin.estuary", "xml") 127 | 128 | def get_userdata_addons(self): 129 | """ 130 | get list of folders from userdata addon dir 131 | """ 132 | if not os.path.exists(self.user_addons_path): 133 | return [] 134 | return [f for f in os.listdir(self.user_addons_path) if not os.path.isfile(f)] 135 | 136 | def load_settings(self, settings): 137 | """ 138 | init instance with *settings 139 | """ 140 | self.settings = settings 141 | self.json_url = self.settings.get("kodi_address", "http://localhost:8080") 142 | self.kodi_path = self.settings.get("kodi_path") 143 | self.userdata_folder = self.get_userdata_folder() 144 | self.get_colors() 145 | self.update_labels() 146 | 147 | def update_labels(self): 148 | """ 149 | get core po files 150 | """ 151 | po_files = self.get_po_files(self.user_addons_path) 152 | languages = {i.language for i in po_files} 153 | core_po_files = self.get_po_files(self.core_addons_path) 154 | core_po_files = [i for i in core_po_files if i.language not in languages] 155 | self.po_files = po_files + core_po_files 156 | 157 | def get_po_files(self, folder): 158 | """ 159 | get list with pofile objects 160 | """ 161 | po_files = [] 162 | folders = self.settings.get("language_folders", ["resource.language.en_gb", "English"]) 163 | for item in folders: 164 | path = utils.check_paths([os.path.join(folder, item, "strings.po"), 165 | os.path.join(folder, item, "resources", "strings.po")]) 166 | if path: 167 | po_file = utils.get_po_file(path) 168 | if po_file: 169 | po_file.language = item 170 | po_files.append(po_file) 171 | return po_files 172 | -------------------------------------------------------------------------------- /libs/chardet/universaldetector.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is Mozilla Universal charset detector code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 2001 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # Shy Shalom - original C code 12 | # 13 | # This library is free software; you can redistribute it and/or 14 | # modify it under the terms of the GNU Lesser General Public 15 | # License as published by the Free Software Foundation; either 16 | # version 2.1 of the License, or (at your option) any later version. 17 | # 18 | # This library is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 | # Lesser General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU Lesser General Public 24 | # License along with this library; if not, write to the Free Software 25 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 26 | # 02110-1301 USA 27 | ######################### END LICENSE BLOCK ######################### 28 | 29 | from . import constants 30 | import sys 31 | import codecs 32 | from .latin1prober import Latin1Prober # windows-1252 33 | from .mbcsgroupprober import MBCSGroupProber # multi-byte character sets 34 | from .sbcsgroupprober import SBCSGroupProber # single-byte character sets 35 | from .escprober import EscCharSetProber # ISO-2122, etc. 36 | import re 37 | 38 | MINIMUM_THRESHOLD = 0.20 39 | ePureAscii = 0 40 | eEscAscii = 1 41 | eHighbyte = 2 42 | 43 | 44 | class UniversalDetector: 45 | def __init__(self): 46 | self._highBitDetector = re.compile(b'[\x80-\xFF]') 47 | self._escDetector = re.compile(b'(\033|~{)') 48 | self._mEscCharSetProber = None 49 | self._mCharSetProbers = [] 50 | self.reset() 51 | 52 | def reset(self): 53 | self.result = {'encoding': None, 'confidence': 0.0} 54 | self.done = False 55 | self._mStart = True 56 | self._mGotData = False 57 | self._mInputState = ePureAscii 58 | self._mLastChar = b'' 59 | if self._mEscCharSetProber: 60 | self._mEscCharSetProber.reset() 61 | for prober in self._mCharSetProbers: 62 | prober.reset() 63 | 64 | def feed(self, aBuf): 65 | if self.done: 66 | return 67 | 68 | aLen = len(aBuf) 69 | if not aLen: 70 | return 71 | 72 | if not self._mGotData: 73 | # If the data starts with BOM, we know it is UTF 74 | if aBuf[:3] == codecs.BOM: 75 | # EF BB BF UTF-8 with BOM 76 | self.result = {'encoding': "UTF-8", 'confidence': 1.0} 77 | elif aBuf[:4] == codecs.BOM_UTF32_LE: 78 | # FF FE 00 00 UTF-32, little-endian BOM 79 | self.result = {'encoding': "UTF-32LE", 'confidence': 1.0} 80 | elif aBuf[:4] == codecs.BOM_UTF32_BE: 81 | # 00 00 FE FF UTF-32, big-endian BOM 82 | self.result = {'encoding': "UTF-32BE", 'confidence': 1.0} 83 | elif aBuf[:4] == b'\xFE\xFF\x00\x00': 84 | # FE FF 00 00 UCS-4, unusual octet order BOM (3412) 85 | self.result = { 86 | 'encoding': "X-ISO-10646-UCS-4-3412", 87 | 'confidence': 1.0 88 | } 89 | elif aBuf[:4] == b'\x00\x00\xFF\xFE': 90 | # 00 00 FF FE UCS-4, unusual octet order BOM (2143) 91 | self.result = { 92 | 'encoding': "X-ISO-10646-UCS-4-2143", 93 | 'confidence': 1.0 94 | } 95 | elif aBuf[:2] == codecs.BOM_LE: 96 | # FF FE UTF-16, little endian BOM 97 | self.result = {'encoding': "UTF-16LE", 'confidence': 1.0} 98 | elif aBuf[:2] == codecs.BOM_BE: 99 | # FE FF UTF-16, big endian BOM 100 | self.result = {'encoding': "UTF-16BE", 'confidence': 1.0} 101 | 102 | self._mGotData = True 103 | if self.result['encoding'] and (self.result['confidence'] > 0.0): 104 | self.done = True 105 | return 106 | 107 | if self._mInputState == ePureAscii: 108 | if self._highBitDetector.search(aBuf): 109 | self._mInputState = eHighbyte 110 | elif ((self._mInputState == ePureAscii) and 111 | self._escDetector.search(self._mLastChar + aBuf)): 112 | self._mInputState = eEscAscii 113 | 114 | self._mLastChar = aBuf[-1:] 115 | 116 | if self._mInputState == eEscAscii: 117 | if not self._mEscCharSetProber: 118 | self._mEscCharSetProber = EscCharSetProber() 119 | if self._mEscCharSetProber.feed(aBuf) == constants.eFoundIt: 120 | self.result = {'encoding': self._mEscCharSetProber.get_charset_name(), 121 | 'confidence': self._mEscCharSetProber.get_confidence()} 122 | self.done = True 123 | elif self._mInputState == eHighbyte: 124 | if not self._mCharSetProbers: 125 | self._mCharSetProbers = [MBCSGroupProber(), SBCSGroupProber(), 126 | Latin1Prober()] 127 | for prober in self._mCharSetProbers: 128 | if prober.feed(aBuf) == constants.eFoundIt: 129 | self.result = {'encoding': prober.get_charset_name(), 130 | 'confidence': prober.get_confidence()} 131 | self.done = True 132 | break 133 | 134 | def close(self): 135 | if self.done: 136 | return 137 | if not self._mGotData: 138 | if constants._debug: 139 | sys.stderr.write('no data received!\n') 140 | return 141 | self.done = True 142 | 143 | if self._mInputState == ePureAscii: 144 | self.result = {'encoding': 'ascii', 'confidence': 1.0} 145 | return self.result 146 | 147 | if self._mInputState == eHighbyte: 148 | proberConfidence = None 149 | maxProberConfidence = 0.0 150 | maxProber = None 151 | for prober in self._mCharSetProbers: 152 | if not prober: 153 | continue 154 | proberConfidence = prober.get_confidence() 155 | if proberConfidence > maxProberConfidence: 156 | maxProberConfidence = proberConfidence 157 | maxProber = prober 158 | if maxProber and (maxProberConfidence > MINIMUM_THRESHOLD): 159 | self.result = {'encoding': maxProber.get_charset_name(), 160 | 'confidence': maxProber.get_confidence()} 161 | return self.result 162 | 163 | if constants._debug: 164 | sys.stderr.write('no probers hit minimum threshhold\n') 165 | for prober in self._mCharSetProbers[0].mProbers: 166 | if not prober: 167 | continue 168 | sys.stderr.write('%s confidence = %s\n' % 169 | (prober.get_charset_name(), 170 | prober.get_confidence())) 171 | -------------------------------------------------------------------------------- /libs/adbdevice.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | # Copyright (C) 2015 - Philipp Temminghoff 4 | # This program is Free Software see LICENSE file for details 5 | 6 | """ 7 | KodiDevKit is a plugin to assist with Kodi skinning / scripting using Sublime Text 3 8 | """ 9 | 10 | import subprocess 11 | from . import utils 12 | import os 13 | import logging 14 | 15 | DEFAULT_SETTINGS = {"remote_ip": "localhost"} 16 | 17 | 18 | class AdbDevice(object): 19 | 20 | """ 21 | Class to communicate with Android Devices via ADB. 22 | """ 23 | 24 | def __init__(self): 25 | self.is_busy = False 26 | self.connected = False 27 | self.remote_ip = None 28 | self.userdata_folder = None 29 | self.settings = None 30 | self.setup(DEFAULT_SETTINGS) 31 | 32 | def setup(self, settings): 33 | """ 34 | set up device object with *settings 35 | """ 36 | self.settings = settings 37 | self.userdata_folder = self.settings.get("remote_userdata_folder") 38 | self.remote_ip = self.settings.get("remote_ip") 39 | 40 | @staticmethod 41 | def cmd(program, args): 42 | """ 43 | call *program from cmd with *args 44 | """ 45 | command = [program] 46 | for arg in args: 47 | command.append(arg) 48 | logging.warning(" ".join(command)) 49 | try: 50 | output = subprocess.check_output(command, 51 | shell=True, 52 | stderr=subprocess.STDOUT) 53 | logging.warning(output.decode("utf-8").replace('\r', '').replace('\n', '')) 54 | except subprocess.CalledProcessError as e: 55 | logging.warning("%s\nErrorCode: %s" % (e, str(e.returncode))) 56 | except Exception as e: 57 | logging.warning(e) 58 | # proc = subprocess.Popen(['echo', '"to stdout"'], 59 | # stdout=subprocess.PIPE) 60 | # stdout_value = proc.communicate()[0] 61 | 62 | # @utils.check_busy 63 | def adb_connect(self, server_ip): 64 | """ 65 | connect to *server_ip via adb 66 | """ 67 | self.remote_ip = server_ip 68 | logging.warning("Connect to remote with server_ip %s" % server_ip) 69 | self.cmd("adb", ["connect", server_ip]) 70 | self.connected = True 71 | 72 | @utils.run_async 73 | @utils.check_busy 74 | def adb_connect_async(self, server_ip): 75 | """ 76 | async connect to device with *server_ip 77 | """ 78 | self.adb_connect(server_ip) 79 | 80 | @utils.check_busy 81 | def adb_reconnect(self, server_ip=""): 82 | """ 83 | disconnect and connect device with *server_ip 84 | """ 85 | if not server_ip: 86 | server_ip = self.remote_ip 87 | self.adb_disconnect() 88 | self.adb_connect(server_ip) 89 | 90 | @utils.run_async 91 | def adb_reconnect_async(self, server_ip=""): 92 | """ 93 | disconnect and connect device with *server_ip, async 94 | """ 95 | self.adb_reconnect(server_ip) 96 | 97 | # @utils.check_busy 98 | def adb_disconnect(self): 99 | """ 100 | disconnect adb devices 101 | """ 102 | logging.warning("Disconnect from remote") 103 | self.cmd("adb", ["disconnect"]) 104 | self.connected = False 105 | 106 | @utils.run_async 107 | @utils.check_busy 108 | def adb_disconnect_async(self): 109 | """ 110 | disconnect adb devices, async 111 | """ 112 | self.adb_disconnect() 113 | 114 | @utils.check_busy 115 | def adb_push(self, source, target): 116 | """ 117 | push local *source to *target folder on device 118 | """ 119 | if not target.endswith('/'): 120 | target += '/' 121 | self.cmd("adb", ["push", source.replace('\\', '/'), target.replace('\\', '/')]) 122 | 123 | @utils.run_async 124 | @utils.check_busy 125 | def adb_push_async(self, source, target): 126 | """ 127 | push local *source to *target folder on device, async 128 | """ 129 | self.adb_push(source, target) 130 | 131 | @utils.check_busy 132 | def adb_pull(self, path, target): 133 | """ 134 | pull data from device *path to local *target 135 | """ 136 | self.cmd("adb", ["pull", path, target]) 137 | 138 | @utils.run_async 139 | @utils.check_busy 140 | def adb_pull_async(self, path, target): 141 | """ 142 | pull data from device *path to local *target, async 143 | """ 144 | self.adb_pull(path, target) 145 | 146 | @utils.run_async 147 | @utils.check_busy 148 | def adb_restart_server(self): 149 | """ 150 | restart adb server 151 | """ 152 | pass 153 | 154 | @utils.run_async 155 | @utils.check_busy 156 | def push_to_box(self, addon, all_file=False): 157 | """ 158 | push addon with path *addon to remote. set all_file to True to also push textures 159 | """ 160 | logging.warning("push %s to remote" % addon) 161 | for root, _, files in os.walk(addon): 162 | # ignore git files 163 | if ".git" in root.split(os.sep): 164 | continue 165 | if not all_file and os.path.basename(root) not in ['1080i', '720p']: 166 | continue 167 | target = '{}/addons/{}{}'.format(self.userdata_folder, 168 | os.path.basename(addon), 169 | root.replace(addon, "").replace('\\', '/')) 170 | self.cmd("adb", ["shell", "mkdir", target]) 171 | for f in files: 172 | if f.endswith(('.pyc', '.pyo')): 173 | continue 174 | self.cmd("adb", ["push", 175 | os.path.join(root, f).replace('\\', '/'), 176 | target.replace('\\', '/')]) 177 | logging.warning("All files pushed") 178 | 179 | @utils.run_async 180 | def get_log(self, open_function, target): 181 | """ 182 | download log to *target and open with *open_function 183 | """ 184 | logging.warning("Pull logs from remote") 185 | self.adb_pull("%s/temp/xbmc.log" % self.userdata_folder, target) 186 | # self.adb_pull("%stemp/xbmc.old.log" % self.userdata_folder) 187 | logging.warning("Finished pulling logs") 188 | open_function(os.path.join(target, "xbmc.log")) 189 | 190 | @utils.run_async 191 | @utils.check_busy 192 | def get_screenshot(self, f_open, target): 193 | """ 194 | create screenshot, pull to *target, clean up 195 | """ 196 | logging.warning("Pull screenshot from remote") 197 | self.cmd("adb", ["shell", "screencap", "-p", "/sdcard/screen.png"]) 198 | self.cmd("adb", ["pull", "/sdcard/screen.png", target]) 199 | self.cmd("adb", ["shell", "rm", "/sdcard/screen.png"]) 200 | # self.adb_pull("%stemp/xbmc.old.log" % self.userdata_folder) 201 | logging.warning("Finished pulling screenshot") 202 | f_open(os.path.join(target, "screen.png")) 203 | 204 | @utils.run_async 205 | @utils.check_busy 206 | def clear_cache(self): 207 | """ 208 | clear temp folder from userdata folder on remote 209 | """ 210 | self.cmd("adb", ["shell", "rm", "-rf", os.path.join(self.userdata_folder, "temp")]) 211 | 212 | @utils.run_async 213 | def reboot(self): 214 | """ 215 | complete device reboot 216 | """ 217 | self.cmd("adb", ["reboot"]) 218 | -------------------------------------------------------------------------------- /KodiSkinXML.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | name: KodiSkinXML 4 | file_extensions: 5 | - xml 6 | first_line_match: '^<\?xml ' 7 | scope: text.xml 8 | contexts: 9 | main: 10 | # xml header ? 11 | - match: '(<\?)\s*([-_a-zA-Z0-9]+)' 12 | captures: 13 | 1: punctuation.definition.tag.begin.xml 14 | 2: entity.name.tag.xml 15 | push: 16 | - meta_scope: meta.tag.preprocessor.xml 17 | - match: (\?>) 18 | pop: true 19 | - match: " ([a-zA-Z-]+)" 20 | scope: entity.other.attribute-name.xml 21 | - include: doublequotedString 22 | # comments 23 | - match: "" 29 | pop: true 30 | - match: '(<)?([-_a-zA-Z0-9:]+)(?=(\s[^>]*)?>)' 31 | captures: 32 | 1: punctuation.definition.tag.begin.xml 33 | 2: entity.name.tag.xml 34 | 3: punctuation.separator.namespace.xml 35 | 4: entity.name.tag.localname.xml 36 | push: 37 | - meta_scope: meta.tag.no-content.xml 38 | - match: "(>)(<)(/)?([-_a-zA-Z0-9:]+)(>)" 39 | captures: 40 | 1: punctuation.definition.tag.end.xml 41 | 2: punctuation.definition.tag.begin.xml meta.scope.between-tag-pair.xml 42 | 3: punctuation.definition.tag.begin.xml 43 | 4: entity.name.tag.xml 44 | 5: entity.name.tag.localname.xml 45 | 6: punctuation.definition.tag.end.xml 46 | pop: true 47 | - include: tagStuff 48 | 49 | - match: '() 58 | captures: 59 | 1: punctuation.definition.tag.end.xml.xxx 60 | pop: true 61 | - include: tagStuff 62 | - include: entity 63 | - include: bare-ampersand 64 | 65 | # xml tags 66 | - match: "() 73 | captures: 74 | 1: punctuation.definition.tag.end.xml 75 | pop: true 76 | - include: tagStuff 77 | - include: entity 78 | - include: bare-ampersand 79 | - include: koditranslation 80 | - include: kodiaddontranslation 81 | - include: kodinumber 82 | - include: kodiinfolabel 83 | - include: kodiparam 84 | - include: kodivariable 85 | - include: kodiexpression 86 | - include: kodiincludeparam 87 | - include: brackets 88 | - include: formatter 89 | 90 | bare-ampersand: 91 | - match: "&" 92 | scope: invalid.illegal.bad-ampersand.xml 93 | 94 | doublequotedString: 95 | - match: '"' 96 | captures: 97 | 0: punctuation.definition.string.begin.xml 98 | push: 99 | - meta_scope: string.quoted.double.xml 100 | - match: '"' 101 | captures: 102 | 0: punctuation.definition.string.end.xml 103 | pop: true 104 | - include: entity 105 | - include: bare-ampersand 106 | - include: koditranslation 107 | - include: kodiaddontranslation 108 | - include: kodiinfolabel 109 | - include: kodiparam 110 | - include: kodivariable 111 | - include: kodiincludeparam 112 | - include: brackets 113 | - include: formatter 114 | entity: 115 | - match: "(&)([:a-zA-Z_][:a-zA-Z0-9_.-]*|#[0-9]+|#x[0-9a-fA-F]+)(;)" 116 | scope: constant.character.entity.xml 117 | captures: 118 | 1: punctuation.definition.constant.xml 119 | 3: punctuation.definition.constant.xml 120 | 121 | tagStuff: 122 | - match: " (?:([-_a-zA-Z0-9]+)((:)))?([-_a-zA-Z0-9]+)=" 123 | captures: 124 | 1: entity.other.attribute-name.namespace.xml 125 | 2: entity.other.attribute-name.xml 126 | 3: punctuation.separator.namespace.xml 127 | 4: entity.other.attribute-name.localname.xml 128 | - include: doublequotedString 129 | 130 | koditranslation: 131 | - match: '(\$)(LOCALIZE)(\[)([0-9]+)(\])' 132 | name: variable.parameter text.kodi.translation.xml 133 | scope: variable.parameter entity.kodi.label 134 | 135 | 136 | kodiaddontranslation: 137 | - match: '(\$)(ADDON)(\[)(.*?)([0-9]+)(\])' 138 | name: variable.parameter text.kodi.translation.xml 139 | scope: variable.parameter entity.kodi.label 140 | 141 | kodinumber: 142 | - match: '(\$)(NUMBER)(\[)([0-9]+)(\])' 143 | name: variable.parameter text.kodi.number.xml 144 | scope: variable.parameter entity.kodi.number 145 | 146 | kodiparam: 147 | - match: '(\$)(PARAM)(\[)(.*?)(\])' 148 | name: variable.parameter text.kodi.param.xml 149 | scope: entity.kodi.param 150 | 151 | kodiinfolabel: 152 | - match: '(\$)((?:ESC)?INFO)(\[)' 153 | name: variable.parameter text.kodi.infolabel.xml 154 | captures: 155 | 1: entity.kodi.infolabel.dollar 156 | 2: entity.kodi.infolabel.info 157 | 3: entity.kodi.infolabel.begin 158 | push: 159 | - meta_scope: support.function kodi.infolabel 160 | - match: '(\])' 161 | captures: 162 | 1: entity.kodi.infolabel.end 163 | pop: true 164 | - include: koditranslation 165 | - include: kodiparam 166 | - include: kodiaddontranslation 167 | - include: formatter 168 | - include: brackets 169 | 170 | 171 | kodiincludeparam: 172 | - match: '(\$)(PARAM)(\[)' 173 | name: variable.parameter text.kodi.includeparam.xml 174 | captures: 175 | 1: entity.kodi.includeparam.dollar 176 | 2: entity.kodi.includeparam.param 177 | 3: entity.kodi.includeparam.begin 178 | push: 179 | - meta_scope: support.function kodi.includeparam 180 | - match: '(\])' 181 | captures: 182 | 1: entity.kodi.includeparam.end 183 | pop: true 184 | 185 | 186 | formatter: 187 | - match: '(\[)([A-Za-z0-9\/= ]*?)(\])' 188 | name: invalid.deprecated 189 | captures: 190 | '1': kodi.formatter.begin invalid.deprecated 191 | '2': kodi.formatter.content invalid.deprecated 192 | '3': kodi.formatter.end invalid.deprecated 193 | 194 | kodivariable: 195 | - match: '(\$)((?:ESC)?VAR)(\[)' 196 | name: variable.parameter text.kodi.variable.xml 197 | captures: 198 | 1: entity.kodi.variable.dollar 199 | 2: entity.kodi.variable.var 200 | 3: entity.kodi.variable.begin 201 | push: 202 | - meta_scope: storage.type kodi.variable 203 | - match: '(\])' 204 | captures: 205 | 1: entity.kodi.variable.end 206 | pop: true 207 | - include: formatter 208 | 209 | kodiexpression: 210 | - match: '(\$)(EXP)(\[)' 211 | name: expression.parameter text.kodi.expression.xml 212 | captures: 213 | 1: entity.kodi.expression.dollar 214 | 2: entity.kodi.expression.exp 215 | 3: entity.kodi.expression.begin 216 | push: 217 | - meta_scope: storage.type kodi.expression 218 | - match: '(\])' 219 | captures: 220 | 1: entity.kodi.expression.end 221 | pop: true 222 | 223 | brackets: 224 | - match: '(\()' 225 | name: constant.other.allcaps 226 | captures: 227 | 1: constant.other.allcaps 228 | push: 229 | - meta_scope: constant.other.allcaps 230 | - match: '(\))' 231 | captures: 232 | 1: constant.other.allcaps 233 | pop: true 234 | - include: brackets 235 | - include: koditranslation 236 | 237 | 238 | # brackets: 239 | # - match: \( 240 | # push: closingbracket 241 | # - match: \) 242 | # scope: invalid.illegal.stray-bracket-end 243 | 244 | # closingbracket: 245 | # - meta_content_scope: constant.other.allcaps 246 | # - match: \) 247 | # pop: true 248 | # - include: brackets 249 | -------------------------------------------------------------------------------- /libs/chardet/escsm.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is mozilla.org code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 1998 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # 12 | # This library is free software; you can redistribute it and/or 13 | # modify it under the terms of the GNU Lesser General Public 14 | # License as published by the Free Software Foundation; either 15 | # version 2.1 of the License, or (at your option) any later version. 16 | # 17 | # This library is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | # Lesser General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU Lesser General Public 23 | # License along with this library; if not, write to the Free Software 24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 25 | # 02110-1301 USA 26 | ######################### END LICENSE BLOCK ######################### 27 | 28 | from .constants import eStart, eError, eItsMe 29 | 30 | HZ_cls = ( 31 | 1,0,0,0,0,0,0,0, # 00 - 07 32 | 0,0,0,0,0,0,0,0, # 08 - 0f 33 | 0,0,0,0,0,0,0,0, # 10 - 17 34 | 0,0,0,1,0,0,0,0, # 18 - 1f 35 | 0,0,0,0,0,0,0,0, # 20 - 27 36 | 0,0,0,0,0,0,0,0, # 28 - 2f 37 | 0,0,0,0,0,0,0,0, # 30 - 37 38 | 0,0,0,0,0,0,0,0, # 38 - 3f 39 | 0,0,0,0,0,0,0,0, # 40 - 47 40 | 0,0,0,0,0,0,0,0, # 48 - 4f 41 | 0,0,0,0,0,0,0,0, # 50 - 57 42 | 0,0,0,0,0,0,0,0, # 58 - 5f 43 | 0,0,0,0,0,0,0,0, # 60 - 67 44 | 0,0,0,0,0,0,0,0, # 68 - 6f 45 | 0,0,0,0,0,0,0,0, # 70 - 77 46 | 0,0,0,4,0,5,2,0, # 78 - 7f 47 | 1,1,1,1,1,1,1,1, # 80 - 87 48 | 1,1,1,1,1,1,1,1, # 88 - 8f 49 | 1,1,1,1,1,1,1,1, # 90 - 97 50 | 1,1,1,1,1,1,1,1, # 98 - 9f 51 | 1,1,1,1,1,1,1,1, # a0 - a7 52 | 1,1,1,1,1,1,1,1, # a8 - af 53 | 1,1,1,1,1,1,1,1, # b0 - b7 54 | 1,1,1,1,1,1,1,1, # b8 - bf 55 | 1,1,1,1,1,1,1,1, # c0 - c7 56 | 1,1,1,1,1,1,1,1, # c8 - cf 57 | 1,1,1,1,1,1,1,1, # d0 - d7 58 | 1,1,1,1,1,1,1,1, # d8 - df 59 | 1,1,1,1,1,1,1,1, # e0 - e7 60 | 1,1,1,1,1,1,1,1, # e8 - ef 61 | 1,1,1,1,1,1,1,1, # f0 - f7 62 | 1,1,1,1,1,1,1,1, # f8 - ff 63 | ) 64 | 65 | HZ_st = ( 66 | eStart,eError, 3,eStart,eStart,eStart,eError,eError,# 00-07 67 | eError,eError,eError,eError,eItsMe,eItsMe,eItsMe,eItsMe,# 08-0f 68 | eItsMe,eItsMe,eError,eError,eStart,eStart, 4,eError,# 10-17 69 | 5,eError, 6,eError, 5, 5, 4,eError,# 18-1f 70 | 4,eError, 4, 4, 4,eError, 4,eError,# 20-27 71 | 4,eItsMe,eStart,eStart,eStart,eStart,eStart,eStart,# 28-2f 72 | ) 73 | 74 | HZCharLenTable = (0, 0, 0, 0, 0, 0) 75 | 76 | HZSMModel = {'classTable': HZ_cls, 77 | 'classFactor': 6, 78 | 'stateTable': HZ_st, 79 | 'charLenTable': HZCharLenTable, 80 | 'name': "HZ-GB-2312"} 81 | 82 | ISO2022CN_cls = ( 83 | 2,0,0,0,0,0,0,0, # 00 - 07 84 | 0,0,0,0,0,0,0,0, # 08 - 0f 85 | 0,0,0,0,0,0,0,0, # 10 - 17 86 | 0,0,0,1,0,0,0,0, # 18 - 1f 87 | 0,0,0,0,0,0,0,0, # 20 - 27 88 | 0,3,0,0,0,0,0,0, # 28 - 2f 89 | 0,0,0,0,0,0,0,0, # 30 - 37 90 | 0,0,0,0,0,0,0,0, # 38 - 3f 91 | 0,0,0,4,0,0,0,0, # 40 - 47 92 | 0,0,0,0,0,0,0,0, # 48 - 4f 93 | 0,0,0,0,0,0,0,0, # 50 - 57 94 | 0,0,0,0,0,0,0,0, # 58 - 5f 95 | 0,0,0,0,0,0,0,0, # 60 - 67 96 | 0,0,0,0,0,0,0,0, # 68 - 6f 97 | 0,0,0,0,0,0,0,0, # 70 - 77 98 | 0,0,0,0,0,0,0,0, # 78 - 7f 99 | 2,2,2,2,2,2,2,2, # 80 - 87 100 | 2,2,2,2,2,2,2,2, # 88 - 8f 101 | 2,2,2,2,2,2,2,2, # 90 - 97 102 | 2,2,2,2,2,2,2,2, # 98 - 9f 103 | 2,2,2,2,2,2,2,2, # a0 - a7 104 | 2,2,2,2,2,2,2,2, # a8 - af 105 | 2,2,2,2,2,2,2,2, # b0 - b7 106 | 2,2,2,2,2,2,2,2, # b8 - bf 107 | 2,2,2,2,2,2,2,2, # c0 - c7 108 | 2,2,2,2,2,2,2,2, # c8 - cf 109 | 2,2,2,2,2,2,2,2, # d0 - d7 110 | 2,2,2,2,2,2,2,2, # d8 - df 111 | 2,2,2,2,2,2,2,2, # e0 - e7 112 | 2,2,2,2,2,2,2,2, # e8 - ef 113 | 2,2,2,2,2,2,2,2, # f0 - f7 114 | 2,2,2,2,2,2,2,2, # f8 - ff 115 | ) 116 | 117 | ISO2022CN_st = ( 118 | eStart, 3,eError,eStart,eStart,eStart,eStart,eStart,# 00-07 119 | eStart,eError,eError,eError,eError,eError,eError,eError,# 08-0f 120 | eError,eError,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,# 10-17 121 | eItsMe,eItsMe,eItsMe,eError,eError,eError, 4,eError,# 18-1f 122 | eError,eError,eError,eItsMe,eError,eError,eError,eError,# 20-27 123 | 5, 6,eError,eError,eError,eError,eError,eError,# 28-2f 124 | eError,eError,eError,eItsMe,eError,eError,eError,eError,# 30-37 125 | eError,eError,eError,eError,eError,eItsMe,eError,eStart,# 38-3f 126 | ) 127 | 128 | ISO2022CNCharLenTable = (0, 0, 0, 0, 0, 0, 0, 0, 0) 129 | 130 | ISO2022CNSMModel = {'classTable': ISO2022CN_cls, 131 | 'classFactor': 9, 132 | 'stateTable': ISO2022CN_st, 133 | 'charLenTable': ISO2022CNCharLenTable, 134 | 'name': "ISO-2022-CN"} 135 | 136 | ISO2022JP_cls = ( 137 | 2,0,0,0,0,0,0,0, # 00 - 07 138 | 0,0,0,0,0,0,2,2, # 08 - 0f 139 | 0,0,0,0,0,0,0,0, # 10 - 17 140 | 0,0,0,1,0,0,0,0, # 18 - 1f 141 | 0,0,0,0,7,0,0,0, # 20 - 27 142 | 3,0,0,0,0,0,0,0, # 28 - 2f 143 | 0,0,0,0,0,0,0,0, # 30 - 37 144 | 0,0,0,0,0,0,0,0, # 38 - 3f 145 | 6,0,4,0,8,0,0,0, # 40 - 47 146 | 0,9,5,0,0,0,0,0, # 48 - 4f 147 | 0,0,0,0,0,0,0,0, # 50 - 57 148 | 0,0,0,0,0,0,0,0, # 58 - 5f 149 | 0,0,0,0,0,0,0,0, # 60 - 67 150 | 0,0,0,0,0,0,0,0, # 68 - 6f 151 | 0,0,0,0,0,0,0,0, # 70 - 77 152 | 0,0,0,0,0,0,0,0, # 78 - 7f 153 | 2,2,2,2,2,2,2,2, # 80 - 87 154 | 2,2,2,2,2,2,2,2, # 88 - 8f 155 | 2,2,2,2,2,2,2,2, # 90 - 97 156 | 2,2,2,2,2,2,2,2, # 98 - 9f 157 | 2,2,2,2,2,2,2,2, # a0 - a7 158 | 2,2,2,2,2,2,2,2, # a8 - af 159 | 2,2,2,2,2,2,2,2, # b0 - b7 160 | 2,2,2,2,2,2,2,2, # b8 - bf 161 | 2,2,2,2,2,2,2,2, # c0 - c7 162 | 2,2,2,2,2,2,2,2, # c8 - cf 163 | 2,2,2,2,2,2,2,2, # d0 - d7 164 | 2,2,2,2,2,2,2,2, # d8 - df 165 | 2,2,2,2,2,2,2,2, # e0 - e7 166 | 2,2,2,2,2,2,2,2, # e8 - ef 167 | 2,2,2,2,2,2,2,2, # f0 - f7 168 | 2,2,2,2,2,2,2,2, # f8 - ff 169 | ) 170 | 171 | ISO2022JP_st = ( 172 | eStart, 3,eError,eStart,eStart,eStart,eStart,eStart,# 00-07 173 | eStart,eStart,eError,eError,eError,eError,eError,eError,# 08-0f 174 | eError,eError,eError,eError,eItsMe,eItsMe,eItsMe,eItsMe,# 10-17 175 | eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eError,eError,# 18-1f 176 | eError, 5,eError,eError,eError, 4,eError,eError,# 20-27 177 | eError,eError,eError, 6,eItsMe,eError,eItsMe,eError,# 28-2f 178 | eError,eError,eError,eError,eError,eError,eItsMe,eItsMe,# 30-37 179 | eError,eError,eError,eItsMe,eError,eError,eError,eError,# 38-3f 180 | eError,eError,eError,eError,eItsMe,eError,eStart,eStart,# 40-47 181 | ) 182 | 183 | ISO2022JPCharLenTable = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) 184 | 185 | ISO2022JPSMModel = {'classTable': ISO2022JP_cls, 186 | 'classFactor': 10, 187 | 'stateTable': ISO2022JP_st, 188 | 'charLenTable': ISO2022JPCharLenTable, 189 | 'name': "ISO-2022-JP"} 190 | 191 | ISO2022KR_cls = ( 192 | 2,0,0,0,0,0,0,0, # 00 - 07 193 | 0,0,0,0,0,0,0,0, # 08 - 0f 194 | 0,0,0,0,0,0,0,0, # 10 - 17 195 | 0,0,0,1,0,0,0,0, # 18 - 1f 196 | 0,0,0,0,3,0,0,0, # 20 - 27 197 | 0,4,0,0,0,0,0,0, # 28 - 2f 198 | 0,0,0,0,0,0,0,0, # 30 - 37 199 | 0,0,0,0,0,0,0,0, # 38 - 3f 200 | 0,0,0,5,0,0,0,0, # 40 - 47 201 | 0,0,0,0,0,0,0,0, # 48 - 4f 202 | 0,0,0,0,0,0,0,0, # 50 - 57 203 | 0,0,0,0,0,0,0,0, # 58 - 5f 204 | 0,0,0,0,0,0,0,0, # 60 - 67 205 | 0,0,0,0,0,0,0,0, # 68 - 6f 206 | 0,0,0,0,0,0,0,0, # 70 - 77 207 | 0,0,0,0,0,0,0,0, # 78 - 7f 208 | 2,2,2,2,2,2,2,2, # 80 - 87 209 | 2,2,2,2,2,2,2,2, # 88 - 8f 210 | 2,2,2,2,2,2,2,2, # 90 - 97 211 | 2,2,2,2,2,2,2,2, # 98 - 9f 212 | 2,2,2,2,2,2,2,2, # a0 - a7 213 | 2,2,2,2,2,2,2,2, # a8 - af 214 | 2,2,2,2,2,2,2,2, # b0 - b7 215 | 2,2,2,2,2,2,2,2, # b8 - bf 216 | 2,2,2,2,2,2,2,2, # c0 - c7 217 | 2,2,2,2,2,2,2,2, # c8 - cf 218 | 2,2,2,2,2,2,2,2, # d0 - d7 219 | 2,2,2,2,2,2,2,2, # d8 - df 220 | 2,2,2,2,2,2,2,2, # e0 - e7 221 | 2,2,2,2,2,2,2,2, # e8 - ef 222 | 2,2,2,2,2,2,2,2, # f0 - f7 223 | 2,2,2,2,2,2,2,2, # f8 - ff 224 | ) 225 | 226 | ISO2022KR_st = ( 227 | eStart, 3,eError,eStart,eStart,eStart,eError,eError,# 00-07 228 | eError,eError,eError,eError,eItsMe,eItsMe,eItsMe,eItsMe,# 08-0f 229 | eItsMe,eItsMe,eError,eError,eError, 4,eError,eError,# 10-17 230 | eError,eError,eError,eError, 5,eError,eError,eError,# 18-1f 231 | eError,eError,eError,eItsMe,eStart,eStart,eStart,eStart,# 20-27 232 | ) 233 | 234 | ISO2022KRCharLenTable = (0, 0, 0, 0, 0, 0) 235 | 236 | ISO2022KRSMModel = {'classTable': ISO2022KR_cls, 237 | 'classFactor': 6, 238 | 'stateTable': ISO2022KR_st, 239 | 'charLenTable': ISO2022KRCharLenTable, 240 | 'name': "ISO-2022-KR"} 241 | 242 | # flake8: noqa 243 | -------------------------------------------------------------------------------- /libs/chardet/chardistribution.py: -------------------------------------------------------------------------------- 1 | ######################## BEGIN LICENSE BLOCK ######################## 2 | # The Original Code is Mozilla Communicator client code. 3 | # 4 | # The Initial Developer of the Original Code is 5 | # Netscape Communications Corporation. 6 | # Portions created by the Initial Developer are Copyright (C) 1998 7 | # the Initial Developer. All Rights Reserved. 8 | # 9 | # Contributor(s): 10 | # Mark Pilgrim - port to Python 11 | # 12 | # This library is free software; you can redistribute it and/or 13 | # modify it under the terms of the GNU Lesser General Public 14 | # License as published by the Free Software Foundation; either 15 | # version 2.1 of the License, or (at your option) any later version. 16 | # 17 | # This library is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | # Lesser General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU Lesser General Public 23 | # License along with this library; if not, write to the Free Software 24 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 25 | # 02110-1301 USA 26 | ######################### END LICENSE BLOCK ######################### 27 | 28 | from .euctwfreq import (EUCTWCharToFreqOrder, EUCTW_TABLE_SIZE, 29 | EUCTW_TYPICAL_DISTRIBUTION_RATIO) 30 | from .euckrfreq import (EUCKRCharToFreqOrder, EUCKR_TABLE_SIZE, 31 | EUCKR_TYPICAL_DISTRIBUTION_RATIO) 32 | from .gb2312freq import (GB2312CharToFreqOrder, GB2312_TABLE_SIZE, 33 | GB2312_TYPICAL_DISTRIBUTION_RATIO) 34 | from .big5freq import (Big5CharToFreqOrder, BIG5_TABLE_SIZE, 35 | BIG5_TYPICAL_DISTRIBUTION_RATIO) 36 | from .jisfreq import (JISCharToFreqOrder, JIS_TABLE_SIZE, 37 | JIS_TYPICAL_DISTRIBUTION_RATIO) 38 | from .compat import wrap_ord 39 | 40 | ENOUGH_DATA_THRESHOLD = 1024 41 | SURE_YES = 0.99 42 | SURE_NO = 0.01 43 | MINIMUM_DATA_THRESHOLD = 3 44 | 45 | 46 | class CharDistributionAnalysis: 47 | def __init__(self): 48 | # Mapping table to get frequency order from char order (get from 49 | # GetOrder()) 50 | self._mCharToFreqOrder = None 51 | self._mTableSize = None # Size of above table 52 | # This is a constant value which varies from language to language, 53 | # used in calculating confidence. See 54 | # http://www.mozilla.org/projects/intl/UniversalCharsetDetection.html 55 | # for further detail. 56 | self._mTypicalDistributionRatio = None 57 | self.reset() 58 | 59 | def reset(self): 60 | """reset analyser, clear any state""" 61 | # If this flag is set to True, detection is done and conclusion has 62 | # been made 63 | self._mDone = False 64 | self._mTotalChars = 0 # Total characters encountered 65 | # The number of characters whose frequency order is less than 512 66 | self._mFreqChars = 0 67 | 68 | def feed(self, aBuf, aCharLen): 69 | """feed a character with known length""" 70 | if aCharLen == 2: 71 | # we only care about 2-bytes character in our distribution analysis 72 | order = self.get_order(aBuf) 73 | else: 74 | order = -1 75 | if order >= 0: 76 | self._mTotalChars += 1 77 | # order is valid 78 | if order < self._mTableSize: 79 | if 512 > self._mCharToFreqOrder[order]: 80 | self._mFreqChars += 1 81 | 82 | def get_confidence(self): 83 | """return confidence based on existing data""" 84 | # if we didn't receive any character in our consideration range, 85 | # return negative answer 86 | if self._mTotalChars <= 0 or self._mFreqChars <= MINIMUM_DATA_THRESHOLD: 87 | return SURE_NO 88 | 89 | if self._mTotalChars != self._mFreqChars: 90 | r = (self._mFreqChars / ((self._mTotalChars - self._mFreqChars) 91 | * self._mTypicalDistributionRatio)) 92 | if r < SURE_YES: 93 | return r 94 | 95 | # normalize confidence (we don't want to be 100% sure) 96 | return SURE_YES 97 | 98 | def got_enough_data(self): 99 | # It is not necessary to receive all data to draw conclusion. 100 | # For charset detection, certain amount of data is enough 101 | return self._mTotalChars > ENOUGH_DATA_THRESHOLD 102 | 103 | def get_order(self, aBuf): 104 | # We do not handle characters based on the original encoding string, 105 | # but convert this encoding string to a number, here called order. 106 | # This allows multiple encodings of a language to share one frequency 107 | # table. 108 | return -1 109 | 110 | 111 | class EUCTWDistributionAnalysis(CharDistributionAnalysis): 112 | def __init__(self): 113 | CharDistributionAnalysis.__init__(self) 114 | self._mCharToFreqOrder = EUCTWCharToFreqOrder 115 | self._mTableSize = EUCTW_TABLE_SIZE 116 | self._mTypicalDistributionRatio = EUCTW_TYPICAL_DISTRIBUTION_RATIO 117 | 118 | def get_order(self, aBuf): 119 | # for euc-TW encoding, we are interested 120 | # first byte range: 0xc4 -- 0xfe 121 | # second byte range: 0xa1 -- 0xfe 122 | # no validation needed here. State machine has done that 123 | first_char = wrap_ord(aBuf[0]) 124 | if first_char >= 0xC4: 125 | return 94 * (first_char - 0xC4) + wrap_ord(aBuf[1]) - 0xA1 126 | else: 127 | return -1 128 | 129 | 130 | class EUCKRDistributionAnalysis(CharDistributionAnalysis): 131 | def __init__(self): 132 | CharDistributionAnalysis.__init__(self) 133 | self._mCharToFreqOrder = EUCKRCharToFreqOrder 134 | self._mTableSize = EUCKR_TABLE_SIZE 135 | self._mTypicalDistributionRatio = EUCKR_TYPICAL_DISTRIBUTION_RATIO 136 | 137 | def get_order(self, aBuf): 138 | # for euc-KR encoding, we are interested 139 | # first byte range: 0xb0 -- 0xfe 140 | # second byte range: 0xa1 -- 0xfe 141 | # no validation needed here. State machine has done that 142 | first_char = wrap_ord(aBuf[0]) 143 | if first_char >= 0xB0: 144 | return 94 * (first_char - 0xB0) + wrap_ord(aBuf[1]) - 0xA1 145 | else: 146 | return -1 147 | 148 | 149 | class GB2312DistributionAnalysis(CharDistributionAnalysis): 150 | def __init__(self): 151 | CharDistributionAnalysis.__init__(self) 152 | self._mCharToFreqOrder = GB2312CharToFreqOrder 153 | self._mTableSize = GB2312_TABLE_SIZE 154 | self._mTypicalDistributionRatio = GB2312_TYPICAL_DISTRIBUTION_RATIO 155 | 156 | def get_order(self, aBuf): 157 | # for GB2312 encoding, we are interested 158 | # first byte range: 0xb0 -- 0xfe 159 | # second byte range: 0xa1 -- 0xfe 160 | # no validation needed here. State machine has done that 161 | first_char, second_char = wrap_ord(aBuf[0]), wrap_ord(aBuf[1]) 162 | if (first_char >= 0xB0) and (second_char >= 0xA1): 163 | return 94 * (first_char - 0xB0) + second_char - 0xA1 164 | else: 165 | return -1 166 | 167 | 168 | class Big5DistributionAnalysis(CharDistributionAnalysis): 169 | def __init__(self): 170 | CharDistributionAnalysis.__init__(self) 171 | self._mCharToFreqOrder = Big5CharToFreqOrder 172 | self._mTableSize = BIG5_TABLE_SIZE 173 | self._mTypicalDistributionRatio = BIG5_TYPICAL_DISTRIBUTION_RATIO 174 | 175 | def get_order(self, aBuf): 176 | # for big5 encoding, we are interested 177 | # first byte range: 0xa4 -- 0xfe 178 | # second byte range: 0x40 -- 0x7e , 0xa1 -- 0xfe 179 | # no validation needed here. State machine has done that 180 | first_char, second_char = wrap_ord(aBuf[0]), wrap_ord(aBuf[1]) 181 | if first_char >= 0xA4: 182 | if second_char >= 0xA1: 183 | return 157 * (first_char - 0xA4) + second_char - 0xA1 + 63 184 | else: 185 | return 157 * (first_char - 0xA4) + second_char - 0x40 186 | else: 187 | return -1 188 | 189 | 190 | class SJISDistributionAnalysis(CharDistributionAnalysis): 191 | def __init__(self): 192 | CharDistributionAnalysis.__init__(self) 193 | self._mCharToFreqOrder = JISCharToFreqOrder 194 | self._mTableSize = JIS_TABLE_SIZE 195 | self._mTypicalDistributionRatio = JIS_TYPICAL_DISTRIBUTION_RATIO 196 | 197 | def get_order(self, aBuf): 198 | # for sjis encoding, we are interested 199 | # first byte range: 0x81 -- 0x9f , 0xe0 -- 0xfe 200 | # second byte range: 0x40 -- 0x7e, 0x81 -- oxfe 201 | # no validation needed here. State machine has done that 202 | first_char, second_char = wrap_ord(aBuf[0]), wrap_ord(aBuf[1]) 203 | if (first_char >= 0x81) and (first_char <= 0x9F): 204 | order = 188 * (first_char - 0x81) 205 | elif (first_char >= 0xE0) and (first_char <= 0xEF): 206 | order = 188 * (first_char - 0xE0 + 31) 207 | else: 208 | return -1 209 | order = order + second_char - 0x40 210 | if second_char > 0x7F: 211 | order = -1 212 | return order 213 | 214 | 215 | class EUCJPDistributionAnalysis(CharDistributionAnalysis): 216 | def __init__(self): 217 | CharDistributionAnalysis.__init__(self) 218 | self._mCharToFreqOrder = JISCharToFreqOrder 219 | self._mTableSize = JIS_TABLE_SIZE 220 | self._mTypicalDistributionRatio = JIS_TYPICAL_DISTRIBUTION_RATIO 221 | 222 | def get_order(self, aBuf): 223 | # for euc-JP encoding, we are interested 224 | # first byte range: 0xa0 -- 0xfe 225 | # second byte range: 0xa1 -- 0xfe 226 | # no validation needed here. State machine has done that 227 | char = wrap_ord(aBuf[0]) 228 | if char >= 0xA0: 229 | return 94 * (char - 0xA1) + wrap_ord(aBuf[1]) - 0xa1 230 | else: 231 | return -1 232 | -------------------------------------------------------------------------------- /libs/skin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | # Copyright (C) 2015 - Philipp Temminghoff 4 | # This program is Free Software see LICENSE file for details 5 | 6 | """ 7 | KodiDevKit is a tool to assist with Kodi skinning / scripting using Sublime Text 3 8 | """ 9 | 10 | import os 11 | from distutils.version import StrictVersion 12 | import logging 13 | 14 | from . import utils 15 | from . import addon 16 | from lxml import etree as ET 17 | from .include import Include 18 | 19 | 20 | class Skin(addon.Addon): 21 | 22 | """ 23 | Class representing a Kodi skin. 24 | """ 25 | 26 | LANG_START_ID = 31000 27 | LANG_OFFSET = 0 28 | 29 | def __init__(self, *args, **kwargs): 30 | """ 31 | parses includes / colors / fonts, addon.xml 32 | """ 33 | super().__init__(*args, **kwargs) 34 | self.type = "skin" 35 | api_version = self.root.find(".//import[@addon='xbmc.gui']").attrib.get("version") 36 | logging.info(api_version) 37 | for item in self.RELEASES: 38 | if StrictVersion(api_version) <= StrictVersion(item["gui_version"]): 39 | self.api_version = item["name"] 40 | break 41 | self.update_include_list() 42 | self.get_colors() 43 | self.get_fonts() 44 | 45 | def load_xml_folders(self): 46 | """ 47 | get all xml folders from addon.xml 48 | """ 49 | self.xml_folders = {node.attrib["folder"] for node in self.root.findall('.//res')} 50 | 51 | @property 52 | def lang_path(self): 53 | """ 54 | returns the skin language folder path 55 | """ 56 | return os.path.join(self.path, "language") 57 | 58 | @property 59 | def theme_path(self): 60 | """ 61 | returns the skin theme folder path 62 | """ 63 | return os.path.join(self.path, "themes") 64 | 65 | @property 66 | def primary_lang_folder(self): 67 | """ 68 | returns the primary lang folder, as chosen in settings 69 | """ 70 | lang_folder = self.settings.get("language_folders")[0] 71 | lang_path = os.path.join(self.path, "language", lang_folder) 72 | if not os.path.exists(lang_path): 73 | os.makedirs(lang_path) 74 | return lang_path 75 | 76 | @property 77 | def default_xml_folder(self): 78 | """ 79 | returns the fallback xml folder as a string 80 | """ 81 | folder = self.root.find(".//res[@default='true']") 82 | if folder: 83 | return folder.attrib["folder"] 84 | return None 85 | 86 | @property 87 | def media_path(self): 88 | """ 89 | returns the skin media folder path 90 | """ 91 | return os.path.join(self.path, "media") 92 | 93 | def get_colors(self): 94 | """ 95 | create color list by parsing all color files 96 | """ 97 | self.colors = [] 98 | color_path = os.path.join(self.path, "colors") if self.path else "" 99 | if not self.xml_file or not os.path.exists(color_path): 100 | return False 101 | for path in os.listdir(color_path): 102 | file_path = os.path.join(color_path, path) 103 | root = utils.get_root_from_file(file_path) 104 | if root is None: 105 | logging.info("Invalid color file: {}".format(file_path)) 106 | continue 107 | for node in root.findall("color"): 108 | color = {"name": node.attrib["name"], 109 | "line": node.sourceline, 110 | "content": node.text, 111 | "file": file_path} 112 | self.colors.append(color) 113 | logging.info("found color file %s including %i colors" % (path, len(self.colors))) 114 | self.color_labels = {i["name"] for i in self.colors} 115 | 116 | def get_fonts(self): 117 | """ 118 | create font dict by parsing first fontset 119 | """ 120 | if not self.xml_file or not self.xml_folders: 121 | return False 122 | self.fonts = {} 123 | for folder in self.xml_folders: 124 | paths = [os.path.join(self.path, folder, "Font.xml"), 125 | os.path.join(self.path, folder, "font.xml")] 126 | self.font_file = utils.check_paths(paths) 127 | if not self.font_file: 128 | return False 129 | self.fonts[folder] = [] 130 | root = utils.get_root_from_file(self.font_file) 131 | for node in root.find("fontset").findall("font"): 132 | font = {"name": node.find("name").text, 133 | "size": node.find("size").text, 134 | "line": node.sourceline, 135 | "content": ET.tostring(node, pretty_print=True, encoding="unicode"), 136 | "file": self.font_file, 137 | "filename": node.find("filename").text} 138 | self.fonts[folder].append(font) 139 | 140 | def get_media_files(self): 141 | """ 142 | yields relative paths of all files in "media" directory 143 | """ 144 | for path, _, files in os.walk(self.media_path): 145 | if "studio" in path or "recordlabel" in path: 146 | continue 147 | for filename in files: 148 | img_path = os.path.join(path, filename) 149 | img_path = img_path.replace(self.media_path, "").replace("\\", "/") 150 | img_path.lstrip() 151 | if img_path.startswith("/"): 152 | img_path = img_path[1:] 153 | yield img_path 154 | 155 | def update_include_list(self): 156 | """ 157 | create include list by parsing all include files starting with includes.xml 158 | """ 159 | self.includes = {} 160 | for folder in self.xml_folders: 161 | xml_folder = os.path.join(self.path, folder) 162 | paths = [os.path.join(xml_folder, "Includes.xml"), 163 | os.path.join(xml_folder, "includes.xml")] 164 | self.include_files[folder] = [] 165 | self.includes[folder] = [] 166 | include_file = utils.check_paths(paths) 167 | self.update_includes(include_file) 168 | logging.info("Include List: %i nodes found in '%s' folder." % (len(self.includes[folder]), folder)) 169 | 170 | def update_includes(self, xml_file): 171 | """ 172 | recursive, walks through include files and updates include list and include file list 173 | """ 174 | if not os.path.exists(xml_file): 175 | logging.info("Could not find include file " + xml_file) 176 | return None 177 | folder = xml_file.split(os.sep)[-2] 178 | logging.info("found include file: " + xml_file) 179 | if not os.path.exists(xml_file): 180 | logging.info("%s does not exist" % xml_file) 181 | return [] 182 | root = utils.get_root_from_file(xml_file) 183 | if root is None: 184 | return [] 185 | self.include_files[folder].append(xml_file) 186 | xpath = ".//" + " | .//".join(["include", "variable", "constant", "expression"]) 187 | for node in root.xpath(xpath): 188 | if "name" in node.attrib: 189 | include = Include(node=node, 190 | file=xml_file) 191 | self.includes[folder].append(include) 192 | for node in root.findall("include"): 193 | if "file" in node.attrib and node.attrib["file"] != "script-skinshortcuts-includes.xml": 194 | xml_file = os.path.join(self.path, folder, node.attrib["file"]) 195 | self.update_includes(xml_file) 196 | 197 | def reload(self, path): 198 | """ 199 | update include, color and font infos, depending on open file 200 | """ 201 | folder = path.split(os.sep)[-2] 202 | if folder in self.include_files: 203 | if path in self.include_files[folder]: 204 | self.update_include_list() 205 | if path.endswith("colors/defaults.xml"): 206 | self.get_colors() 207 | if path.endswith(("Font.xml", "font.xml")): 208 | self.get_fonts() 209 | 210 | def get_font_refs(self): 211 | """ 212 | get font references from all window files 213 | """ 214 | font_refs = {} 215 | for folder in self.xml_folders: 216 | font_refs[folder] = [] 217 | for xml_file in self.window_files[folder]: 218 | path = os.path.join(self.path, folder, xml_file) 219 | root = utils.get_root_from_file(path) 220 | if root is None: 221 | return None 222 | for node in root.xpath(".//font"): 223 | if node.getchildren(): 224 | continue 225 | item = {"file": path, 226 | "name": node.text if node.text else "", 227 | "line": node.sourceline} 228 | font_refs[folder].append(item) 229 | return font_refs 230 | 231 | def get_themes(self): 232 | """ 233 | returns a list of all theme names, taken from "themes" folder 234 | """ 235 | return [folder for folder in os.listdir(os.path.join(self.path, "themes"))] 236 | 237 | def resolve_include(self, ref, folder): 238 | """ 239 | resolves the xml node *ref with includes from *folder 240 | """ 241 | if not ref.text: 242 | return None 243 | include_names = [item["name"] for item in self.addon.includes[folder]] 244 | if ref.text not in include_names: 245 | return None 246 | index = include_names.index(ref.text) 247 | node = self.addon.includes[folder][index] 248 | root = ET.fromstring(node["content"]) 249 | return self.resolve_includes(root, folder) 250 | 251 | def resolve_includes(self, xml_source, folder): 252 | for node in xml_source.xpath(".//include"): 253 | if node.text: 254 | new_include = self.resolve_include(node, folder) 255 | if new_include is not None: 256 | node.getparent().replace(node, new_include) 257 | return xml_source 258 | 259 | def get_constants(self, folder): 260 | """ 261 | returns list with names of all constants defined. 262 | """ 263 | return [i["name"] for i in self.includes[folder] if i["type"] == "constant"] 264 | -------------------------------------------------------------------------------- /libs/addon.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | # Copyright (C) 2015 - Philipp Temminghoff 4 | # This program is Free Software see LICENSE file for details 5 | 6 | import os 7 | import logging 8 | from distutils.version import StrictVersion 9 | 10 | from . import utils 11 | from .polib import polib 12 | 13 | SETTINGS_FILE = 'kodidevkit.sublime-settings' 14 | 15 | 16 | class Addon(object): 17 | """ 18 | Represents a kodi addon with path *project_path and *settings 19 | """ 20 | 21 | RELEASES = [{"gui_version": '5.0.1', 22 | "python_version": "2.14.0", 23 | "name": "gotham"}, 24 | {"gui_version": '5.3.0', 25 | "python_version": "2.19.0", 26 | "name": "helix"}, 27 | {"gui_version": '5.9.0', 28 | "python_version": "2.20.0", 29 | "name": "isengard"}, 30 | {"gui_version": '5.10.0', 31 | "python_version": "2.24.0", 32 | "name": "jarvis"}, 33 | {"gui_version": '5.12.0', 34 | "python_version": "2.25.0", 35 | "name": "krypton"}, 36 | {"gui_version": '5.13.0', 37 | "python_version": "2.25.0", 38 | "name": "leia"}] 39 | 40 | LANG_START_ID = 32000 41 | LANG_OFFSET = 2 42 | 43 | def __init__(self, *args, **kwargs): 44 | self.type = "python" 45 | self.po_files = [] 46 | self.colors = [] 47 | self.color_labels = set() 48 | self.fonts = {} 49 | self.xml_folders = [] 50 | self.window_files = {} 51 | self.include_files = {} 52 | self.font_file = None 53 | self.includes = {} 54 | self.api_version = None 55 | self.settings = kwargs.get("settings") 56 | self.path = kwargs.get("project_path") 57 | self.xml_file = os.path.join(self.path, "addon.xml") 58 | self.root = utils.get_root_from_file(self.xml_file) 59 | api_import = self.root.find(".//import[@addon='xbmc.python']") 60 | if api_import is not None: 61 | api_version = api_import.attrib.get("version") 62 | for item in self.RELEASES: 63 | if StrictVersion(api_version) <= StrictVersion(item["python_version"]): 64 | self.api_version = item["name"] 65 | break 66 | self.version = self.root.attrib.get("version") 67 | for item in self.root.xpath("/addon[@id]"): 68 | self.name = item.attrib["id"] 69 | break 70 | self.load_xml_folders() 71 | self.update_xml_files() 72 | self.update_labels() 73 | 74 | @property 75 | def default_xml_folder(self): 76 | """ 77 | returns the fallback xml folder as a string 78 | """ 79 | return self.xml_folders[0] 80 | 81 | def load_xml_folders(self): 82 | """ 83 | find and load skin xml folder if existing 84 | """ 85 | paths = [os.path.join(self.path, "resources", "skins", "Default", "720p"), 86 | os.path.join(self.path, "resources", "skins", "Default", "1080i")] 87 | folder = utils.check_paths(paths) 88 | self.xml_folders.append(folder) 89 | 90 | @property 91 | def lang_path(self): 92 | """ 93 | returns the add-on language folder path 94 | """ 95 | return os.path.join(self.path, "resources", "language") 96 | 97 | @property 98 | def changelog_path(self): 99 | """ 100 | returns the add-on language folder path 101 | """ 102 | return os.path.join(self.path, "changelog.txt") 103 | 104 | @property 105 | def primary_lang_folder(self): 106 | """ 107 | returns default language folder (first one from settings file) 108 | """ 109 | lang_folder = self.settings.get("language_folders")[0] 110 | lang_path = os.path.join(self.path, "resources", "language", lang_folder) 111 | if not os.path.exists(lang_path): 112 | os.makedirs(lang_path) 113 | return lang_path 114 | 115 | @property 116 | def media_path(self): 117 | """ 118 | returns the add-on media folder path 119 | """ 120 | return os.path.join(self.path, "resources", "skins", "Default", "media") 121 | 122 | @staticmethod 123 | def by_project(project_path, settings): 124 | """ 125 | factory, return proper instance based on addon.xml 126 | """ 127 | xml_file = os.path.join(project_path, "addon.xml") 128 | root = utils.get_root_from_file(xml_file) 129 | if root.find(".//import[@addon='xbmc.python']") is None: 130 | from . import skin 131 | return skin.Skin(project_path=project_path, 132 | settings=settings) 133 | else: 134 | return Addon(project_path=project_path, 135 | settings=settings) 136 | # TODO: parse all python skin folders correctly 137 | 138 | def update_labels(self): 139 | """ 140 | get addon po files and update po files list 141 | """ 142 | self.po_files = self.get_po_files(self.lang_path) 143 | 144 | def get_po_files(self, lang_folder_root): 145 | """ 146 | get list with pofile objects 147 | """ 148 | po_files = [] 149 | folders = self.settings.get("language_folders", ["resource.language.en_gb", "English"]) 150 | for item in folders: 151 | path = utils.check_paths([os.path.join(lang_folder_root, item, "strings.po"), 152 | os.path.join(lang_folder_root, item, "resources", "strings.po")]) 153 | if path: 154 | po_file = utils.get_po_file(path) 155 | if po_file: 156 | po_file.language = item 157 | po_files.append(po_file) 158 | return po_files 159 | 160 | def update_xml_files(self): 161 | """ 162 | update list of all include and window xmls 163 | """ 164 | self.window_files = {} 165 | for path in self.xml_folders: 166 | xml_folder = os.path.join(self.path, path) 167 | self.window_files[path] = [] 168 | if not os.path.exists(xml_folder): 169 | return [] 170 | for xml_file in os.listdir(xml_folder): 171 | filename = os.path.basename(xml_file) 172 | if not filename.endswith(".xml"): 173 | continue 174 | if filename.lower() not in ["font.xml"]: 175 | self.window_files[path].append(xml_file) 176 | logging.info("found %i XMLs in %s" % (len(self.window_files[path]), xml_folder)) 177 | 178 | def create_new_label(self, word, filepath): 179 | """ 180 | adds a label to the first pofile from settings (or creates new one if non-existing) 181 | """ 182 | if not self.po_files: 183 | po_file = utils.create_new_po_file(os.path.join(self.primary_lang_folder, "strings.po")) 184 | po_file.save() 185 | self.po_files.append(po_file) 186 | logging.critical("New language file created") 187 | else: 188 | po_file = self.po_files[0] 189 | string_ids = [] 190 | for entry in po_file: 191 | try: 192 | string_ids.append(int(entry.msgctxt[1:])) 193 | except Exception: 194 | string_ids.append(entry.msgctxt) 195 | for label_id in range(self.LANG_START_ID, self.LANG_START_ID + 1000): 196 | if label_id not in string_ids: 197 | logging.info("first free: " + str(label_id)) 198 | break 199 | entry = polib.POEntry(msgid=word, 200 | msgstr="", 201 | msgctxt="#%s" % label_id, 202 | occurrences=[(filepath, None)]) 203 | po_file.insert(index=int(label_id) - self.LANG_START_ID + self.LANG_OFFSET, 204 | entry=entry) 205 | po_file.save() 206 | self.update_labels() 207 | return label_id 208 | 209 | def attach_occurrence_to_label(self, label_id, rel_path): 210 | """ 211 | add *rel_path to label with *label id as a file comment 212 | """ 213 | if 31000 <= int(label_id[1:]) < 33000: 214 | entry = self.po_files[0].find(label_id, by="msgctxt") 215 | entry.occurrences.append((rel_path, None)) 216 | self.po_files[0].save() 217 | 218 | def translate_path(self, path): 219 | """ 220 | return translated path for textures 221 | """ 222 | if path.startswith("special://skin/"): 223 | return os.path.join(self.path, path.replace("special://skin/", "")) 224 | else: 225 | return os.path.join(self.media_path, path) 226 | 227 | def return_node(self, keyword=None, folder=False): 228 | """ 229 | get value from include list 230 | """ 231 | if not keyword or not folder: 232 | return None 233 | if folder in self.fonts: 234 | for node in self.fonts[folder]: 235 | if node["name"] == keyword: 236 | return node 237 | if folder in self.includes: 238 | for node in self.includes[folder]: 239 | if node["name"] == keyword: 240 | return node 241 | return None 242 | 243 | def reload(self, path): 244 | """ 245 | update include, color and font infos (not needed yet for python) 246 | """ 247 | pass 248 | 249 | def get_xml_files(self): 250 | """ 251 | yields absolute paths of all window files 252 | """ 253 | if self.xml_folders: 254 | for folder in self.xml_folders: 255 | for xml_file in self.window_files[folder]: 256 | yield os.path.join(self.path, folder, xml_file) 257 | 258 | def bump_version(self, version): 259 | """ 260 | bump addon.xml version and create changelog entry 261 | """ 262 | self.root.attrib["version"] = version 263 | utils.save_xml(self.xml_file, self.root) 264 | with open(self.changelog_path, "r") as f: 265 | contents = f.readlines() 266 | contents = [version, "", "-", "-", "", ""] + contents 267 | with open(self.changelog_path, "w") as changelog_file: 268 | changelog_file.write("\n".join(contents)) 269 | 270 | def get_constants(self, folder): 271 | """ 272 | returns empty list because Kodi python add-ons do not support constants yet 273 | """ 274 | return [] 275 | -------------------------------------------------------------------------------- /libs/yattag/indentation.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | __all__ = ['indent'] 4 | 5 | class TokenMeta(type): 6 | 7 | _token_classes = {} 8 | 9 | def __new__(cls, name, bases, attrs): 10 | kls = type.__new__(cls, name, bases, attrs) 11 | cls._token_classes[name] = kls 12 | return kls 13 | 14 | @classmethod 15 | def getclass(cls, name): 16 | return cls._token_classes[name] 17 | 18 | # need to proceed that way for Python 2/3 compatility: 19 | TokenBase = TokenMeta('TokenBase', (object,), {}) 20 | 21 | class Token(TokenBase): 22 | regex = None 23 | 24 | def __init__(self, groupdict): 25 | self.content = groupdict[self.__class__.__name__] 26 | 27 | class Text(Token): 28 | regex = '[^<>]+' 29 | def __init__(self, *args, **kwargs): 30 | super(Text, self).__init__(*args, **kwargs) 31 | self._isblank = None 32 | 33 | @property 34 | def isblank(self): 35 | if self._isblank is None: 36 | self._isblank = not self.content.strip() 37 | return self._isblank 38 | 39 | class Comment(Token): 40 | regex = r').)*.?-->' 41 | 42 | class CData(Token): 43 | regex = r').*).?\]\]>' 44 | 45 | class Doctype(Token): 46 | regex = r'''"']+|"[^"]*"|'[^']*'))*>''' 47 | 48 | _open_tag_start = r''' 49 | <\s* 50 | (?P<{tag_name_key}>{tag_name_rgx}) 51 | (\s+[^/><"=\s]+ # attribute 52 | (\s*=\s* 53 | ( 54 | [^/><"=\s]+ | # unquoted attribute value 55 | ("[^"]*") | # " quoted attribute value 56 | ('[^']*') # ' quoted attribute value 57 | ) 58 | )? # the attribute value is optional (we're forgiving) 59 | )* 60 | \s*''' 61 | 62 | class Script(Token): 63 | _end_script = r'<\s*/\s*script\s*>' 64 | 65 | regex = _open_tag_start.format( 66 | tag_name_key = 'script_ignore', 67 | tag_name_rgx = 'script', 68 | ) + r'>((?!({end_script})).)*.?{end_script}'.format( 69 | end_script = _end_script 70 | ) 71 | 72 | class Style(Token): 73 | _end_style = r'<\s*/\s*style\s*>' 74 | 75 | regex = _open_tag_start.format( 76 | tag_name_key = 'style_ignore', 77 | tag_name_rgx = 'style', 78 | ) + r'>((?!({end_style})).)*.?{end_style}'.format( 79 | end_style = _end_style 80 | ) 81 | 82 | class XMLDeclaration(Token): 83 | regex = _open_tag_start.format( 84 | tag_name_key = 'xmldecl_ignore', 85 | tag_name_rgx = r'\?\s*xml' 86 | ) + r'\?\s*>' 87 | 88 | class NamedTagTokenMeta(TokenMeta): 89 | def __new__(cls, name, bases, attrs): 90 | kls = TokenMeta.__new__(cls, name, bases, attrs) 91 | if name not in('NamedTagTokenBase', 'NamedTagToken'): 92 | kls.tag_name_key = 'tag_name_%s' % name 93 | kls.regex = kls.regex_template.format( 94 | tag_name_key = kls.tag_name_key, 95 | tag_name_rgx = kls.tag_name_rgx 96 | ) 97 | return kls 98 | 99 | # need to proceed that way for Python 2/3 compatility 100 | NamedTagTokenBase = NamedTagTokenMeta( 101 | 'NamedTagTokenBase', 102 | (Token,), 103 | {'tag_name_rgx': r'[^/><"\s]+'} 104 | ) 105 | 106 | class NamedTagToken(NamedTagTokenBase): 107 | def __init__(self, groupdict): 108 | super(NamedTagToken, self).__init__(groupdict) 109 | self.tag_name = groupdict[self.__class__.tag_name_key] 110 | 111 | class OpenTag(NamedTagToken): 112 | regex_template = _open_tag_start + '>' 113 | 114 | class SelfTag(NamedTagToken): # a self closing tag 115 | regex_template = _open_tag_start + r'/\s*>' 116 | 117 | class CloseTag(NamedTagToken): 118 | regex_template = r'<\s*/(?P<{tag_name_key}>{tag_name_rgx})(\s[^/><"]*)?>' 119 | 120 | class XMLTokenError(Exception): 121 | pass 122 | 123 | class Tokenizer(object): 124 | 125 | def __init__(self, token_classes): 126 | self.token_classes = token_classes 127 | self.token_names = [kls.__name__ for kls in token_classes] 128 | self.get_token = None 129 | 130 | def _compile_regex(self): 131 | self.get_token = re.compile( 132 | '|'.join( 133 | '(?P<%s>%s)' % (klass.__name__, klass.regex) for klass in self.token_classes 134 | ), 135 | re.X | re.I | re.S 136 | ).match 137 | 138 | def tokenize(self, string): 139 | if not self.get_token: 140 | self._compile_regex() 141 | result = [] 142 | append = result.append 143 | while string: 144 | mobj = self.get_token(string) 145 | if mobj: 146 | groupdict = mobj.groupdict() 147 | class_name = next(name for name in self.token_names if groupdict[name]) 148 | token = TokenMeta.getclass(class_name)(groupdict) 149 | append(token) 150 | string = string[len(token.content):] 151 | else: 152 | raise XMLTokenError("Unrecognized XML token near %s" % repr(string[:100])) 153 | 154 | return result 155 | 156 | tokenize = Tokenizer( 157 | (Text, Comment, CData, Doctype, XMLDeclaration, Script, Style, OpenTag, SelfTag, CloseTag) 158 | ).tokenize 159 | 160 | class TagMatcher(object): 161 | 162 | class SameNameMatcher(object): 163 | def __init__(self): 164 | self.unmatched_open = [] 165 | self.matched = {} 166 | 167 | def sigclose(self, i): 168 | if self.unmatched_open: 169 | open_tag = self.unmatched_open.pop() 170 | self.matched[open_tag] = i 171 | self.matched[i] = open_tag 172 | return open_tag 173 | else: 174 | return None 175 | 176 | def sigopen(self, i): 177 | self.unmatched_open.append(i) 178 | 179 | def __init__(self, token_list, blank_is_text = False): 180 | self.token_list = token_list 181 | self.name_matchers = {} 182 | self.direct_text_parents = set() 183 | 184 | for i in range(len(token_list)): 185 | token = token_list[i] 186 | tpe = type(token) 187 | if tpe is OpenTag: 188 | self._get_name_matcher(token.tag_name).sigopen(i) 189 | elif tpe is CloseTag: 190 | self._get_name_matcher(token.tag_name).sigclose(i) 191 | 192 | # TODO move this somewhere else 193 | current_nodes = [] 194 | for i in range(len(token_list)): 195 | token = token_list[i] 196 | tpe = type(token) 197 | if tpe is OpenTag and self.ismatched(i): 198 | current_nodes.append(i) 199 | elif tpe is CloseTag and self.ismatched(i): 200 | current_nodes.pop() 201 | elif tpe is Text and (blank_is_text or not token.isblank): 202 | if current_nodes: 203 | self.direct_text_parents.add(current_nodes[-1]) 204 | 205 | def _get_name_matcher(self, tag_name): 206 | try: 207 | return self.name_matchers[tag_name] 208 | except KeyError: 209 | self.name_matchers[tag_name] = name_matcher = self.__class__.SameNameMatcher() 210 | return name_matcher 211 | 212 | def ismatched(self, i): 213 | return i in self.name_matchers[self.token_list[i].tag_name].matched 214 | 215 | def directly_contains_text(self, i): 216 | return i in self.direct_text_parents 217 | 218 | 219 | def indent(string, indentation = ' ', newline = '\n', indent_text = False, blank_is_text = False): 220 | """ 221 | takes a string representing a html or xml document and returns 222 | a well indented version of it 223 | 224 | arguments: 225 | - string: the string to process 226 | - indentation: the indentation unit (default to two spaces) 227 | - newline: the string to be use for new lines 228 | (default to '\\n', could be set to '\\r\\n' for example) 229 | - indent_text: 230 | 231 | if True, text nodes will be indented: 232 | 233 |

Hello

234 | 235 | would result in 236 | 237 |

238 | hello 239 |

240 | 241 | if False, text nodes won't be indented, and the content 242 | of any node directly containing text will be unchanged: 243 | 244 |

Hello

will be unchanged 245 | 246 |

Hello world!

will be unchanged 247 | since ' world!' is directly contained in the

node. 248 | 249 | This is the default since that's generally what you want for HTML. 250 | 251 | - blank_is_text: 252 | if False, completely blank texts are ignored. That is the default. 253 | """ 254 | tokens = tokenize(string) 255 | tag_matcher = TagMatcher(tokens, blank_is_text = blank_is_text) 256 | ismatched = tag_matcher.ismatched 257 | directly_contains_text = tag_matcher.directly_contains_text 258 | result = [] 259 | append = result.append 260 | level = 0 261 | sameline = 0 262 | was_just_opened = False 263 | tag_appeared = False 264 | def _indent(): 265 | if tag_appeared: 266 | append(newline) 267 | for i in range(level): 268 | append(indentation) 269 | for i,token in enumerate(tokens): 270 | tpe = type(token) 271 | if tpe is Text: 272 | if blank_is_text or not token.isblank: 273 | if not sameline: 274 | _indent() 275 | append(token.content) 276 | was_just_opened = False 277 | elif tpe is OpenTag and ismatched(i): 278 | was_just_opened = True 279 | if sameline: 280 | sameline += 1 281 | else: 282 | _indent() 283 | if not indent_text and directly_contains_text(i): 284 | sameline = sameline or 1 285 | append(token.content) 286 | level += 1 287 | tag_appeared = True 288 | elif tpe is CloseTag and ismatched(i): 289 | level -= 1 290 | tag_appeared = True 291 | if sameline: 292 | sameline -= 1 293 | elif not was_just_opened: 294 | _indent() 295 | append(token.content) 296 | was_just_opened = False 297 | else: 298 | if not sameline: 299 | _indent() 300 | append(token.content) 301 | was_just_opened = False 302 | tag_appeared = True 303 | return ''.join(result) 304 | 305 | if __name__ == '__main__': 306 | import sys 307 | print(indent(sys.stdin.read())) 308 | 309 | --------------------------------------------------------------------------------