diff --git a/.gitignore b/.gitignore index d78e1b2..7ddd909 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ *.o *.so *.pyc +*.egg +*.egg-info # Packages # ############ @@ -42,6 +44,9 @@ Thumbs.db # Vim swap files *.swp +# png files +*.png + # Virtualenv ignore bin/ lib/ diff --git a/distribute_setup.py b/distribute_setup.py deleted file mode 100644 index 4f7bd08..0000000 --- a/distribute_setup.py +++ /dev/null @@ -1,481 +0,0 @@ -#!python -"""Bootstrap distribute installation - -If you want to use setuptools in your package's setup.py, just include this -file in the same directory with it, and add this to the top of your setup.py:: - - from distribute_setup import use_setuptools - use_setuptools() - -If you want to require a specific version of setuptools, set a download -mirror, or use an alternate download directory, you can do so by supplying -the appropriate options to ``use_setuptools()``. - -This file can also be run as a script to install or upgrade setuptools. -""" -import os -import sys -import time -import fnmatch -import tempfile -import tarfile -from distutils import log - -try: - from site import USER_SITE -except ImportError: - USER_SITE = None - -try: - import subprocess - - def _python_cmd(*args): - args = (sys.executable,) + args - return subprocess.call(args) == 0 - -except ImportError: - # will be used for python 2.3 - def _python_cmd(*args): - args = (sys.executable,) + args - # quoting arguments if windows - if sys.platform == 'win32': - def quote(arg): - if ' ' in arg: - return '"%s"' % arg - return arg - args = [quote(arg) for arg in args] - return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 - -DEFAULT_VERSION = "0.6.12" -DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" -SETUPTOOLS_FAKED_VERSION = "0.6c11" - -SETUPTOOLS_PKG_INFO = """\ -Metadata-Version: 1.0 -Name: setuptools -Version: %s -Summary: xxxx -Home-page: xxx -Author: xxx -Author-email: xxx -License: xxx -Description: xxx -""" % SETUPTOOLS_FAKED_VERSION - - -def _install(tarball): - # extracting the tarball - tmpdir = tempfile.mkdtemp() - log.warn('Extracting in %s', tmpdir) - old_wd = os.getcwd() - try: - os.chdir(tmpdir) - tar = tarfile.open(tarball) - _extractall(tar) - tar.close() - - # going in the directory - subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) - os.chdir(subdir) - log.warn('Now working in %s', subdir) - - # installing - log.warn('Installing Distribute') - if not _python_cmd('setup.py', 'install'): - log.warn('Something went wrong during the installation.') - log.warn('See the error message above.') - finally: - os.chdir(old_wd) - - -def _build_egg(egg, tarball, to_dir): - # extracting the tarball - tmpdir = tempfile.mkdtemp() - log.warn('Extracting in %s', tmpdir) - old_wd = os.getcwd() - try: - os.chdir(tmpdir) - tar = tarfile.open(tarball) - _extractall(tar) - tar.close() - - # going in the directory - subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) - os.chdir(subdir) - log.warn('Now working in %s', subdir) - - # building an egg - log.warn('Building a Distribute egg in %s', to_dir) - _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) - - finally: - os.chdir(old_wd) - # returning the result - log.warn(egg) - if not os.path.exists(egg): - raise IOError('Could not build the egg.') - - -def _do_download(version, download_base, to_dir, download_delay): - egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' - % (version, sys.version_info[0], sys.version_info[1])) - if not os.path.exists(egg): - tarball = download_setuptools(version, download_base, - to_dir, download_delay) - _build_egg(egg, tarball, to_dir) - sys.path.insert(0, egg) - import setuptools - setuptools.bootstrap_install_from = egg - - -def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, download_delay=15, no_fake=True): - # making sure we use the absolute path - to_dir = os.path.abspath(to_dir) - was_imported = 'pkg_resources' in sys.modules or \ - 'setuptools' in sys.modules - try: - try: - import pkg_resources - if not hasattr(pkg_resources, '_distribute'): - if not no_fake: - _fake_setuptools() - raise ImportError - except ImportError: - return _do_download(version, download_base, to_dir, download_delay) - try: - pkg_resources.require("distribute>="+version) - return - except pkg_resources.VersionConflict: - e = sys.exc_info()[1] - if was_imported: - sys.stderr.write( - "The required version of distribute (>=%s) is not available,\n" - "and can't be installed while this script is running. Please\n" - "install a more recent version first, using\n" - "'easy_install -U distribute'." - "\n\n(Currently using %r)\n" % (version, e.args[0])) - sys.exit(2) - else: - del pkg_resources, sys.modules['pkg_resources'] # reload ok - return _do_download(version, download_base, to_dir, - download_delay) - except pkg_resources.DistributionNotFound: - return _do_download(version, download_base, to_dir, - download_delay) - finally: - if not no_fake: - _create_fake_setuptools_pkg_info(to_dir) - -def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, delay=15): - """Download distribute from a specified location and return its filename - - `version` should be a valid distribute version number that is available - as an egg for download under the `download_base` URL (which should end - with a '/'). `to_dir` is the directory where the egg will be downloaded. - `delay` is the number of seconds to pause before an actual download - attempt. - """ - # making sure we use the absolute path - to_dir = os.path.abspath(to_dir) - try: - from urllib.request import urlopen - except ImportError: - from urllib2 import urlopen - tgz_name = "distribute-%s.tar.gz" % version - url = download_base + tgz_name - saveto = os.path.join(to_dir, tgz_name) - src = dst = None - if not os.path.exists(saveto): # Avoid repeated downloads - try: - log.warn("Downloading %s", url) - src = urlopen(url) - # Read/write all in one block, so we don't create a corrupt file - # if the download is interrupted. - data = src.read() - dst = open(saveto, "wb") - dst.write(data) - finally: - if src: - src.close() - if dst: - dst.close() - return os.path.realpath(saveto) - -def _no_sandbox(function): - def __no_sandbox(*args, **kw): - try: - from setuptools.sandbox import DirectorySandbox - if not hasattr(DirectorySandbox, '_old'): - def violation(*args): - pass - DirectorySandbox._old = DirectorySandbox._violation - DirectorySandbox._violation = violation - patched = True - else: - patched = False - except ImportError: - patched = False - - try: - return function(*args, **kw) - finally: - if patched: - DirectorySandbox._violation = DirectorySandbox._old - del DirectorySandbox._old - - return __no_sandbox - -@_no_sandbox -def _patch_file(path, content): - """Will backup the file then patch it""" - existing_content = open(path).read() - if existing_content == content: - # already patched - log.warn('Already patched.') - return False - log.warn('Patching...') - _rename_path(path) - f = open(path, 'w') - try: - f.write(content) - finally: - f.close() - return True - - -def _same_content(path, content): - return open(path).read() == content - -def _rename_path(path): - new_name = path + '.OLD.%s' % time.time() - log.warn('Renaming %s into %s', path, new_name) - os.rename(path, new_name) - return new_name - -@_no_sandbox -def _remove_flat_installation(placeholder): - if not os.path.isdir(placeholder): - log.warn('Unkown installation at %s', placeholder) - return False - found = False - for file in os.listdir(placeholder): - if fnmatch.fnmatch(file, 'setuptools*.egg-info'): - found = True - break - if not found: - log.warn('Could not locate setuptools*.egg-info') - return - - log.warn('Removing elements out of the way...') - pkg_info = os.path.join(placeholder, file) - if os.path.isdir(pkg_info): - patched = _patch_egg_dir(pkg_info) - else: - patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) - - if not patched: - log.warn('%s already patched.', pkg_info) - return False - # now let's move the files out of the way - for element in ('setuptools', 'pkg_resources.py', 'site.py'): - element = os.path.join(placeholder, element) - if os.path.exists(element): - _rename_path(element) - else: - log.warn('Could not find the %s element of the ' - 'Setuptools distribution', element) - return True - - -def _after_install(dist): - log.warn('After install bootstrap.') - placeholder = dist.get_command_obj('install').install_purelib - _create_fake_setuptools_pkg_info(placeholder) - -@_no_sandbox -def _create_fake_setuptools_pkg_info(placeholder): - if not placeholder or not os.path.exists(placeholder): - log.warn('Could not find the install location') - return - pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) - setuptools_file = 'setuptools-%s-py%s.egg-info' % \ - (SETUPTOOLS_FAKED_VERSION, pyver) - pkg_info = os.path.join(placeholder, setuptools_file) - if os.path.exists(pkg_info): - log.warn('%s already exists', pkg_info) - return - - log.warn('Creating %s', pkg_info) - f = open(pkg_info, 'w') - try: - f.write(SETUPTOOLS_PKG_INFO) - finally: - f.close() - - pth_file = os.path.join(placeholder, 'setuptools.pth') - log.warn('Creating %s', pth_file) - f = open(pth_file, 'w') - try: - f.write(os.path.join(os.curdir, setuptools_file)) - finally: - f.close() - -@_no_sandbox -def _patch_egg_dir(path): - # let's check if it's already patched - pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') - if os.path.exists(pkg_info): - if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): - log.warn('%s already patched.', pkg_info) - return False - _rename_path(path) - os.mkdir(path) - os.mkdir(os.path.join(path, 'EGG-INFO')) - pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') - f = open(pkg_info, 'w') - try: - f.write(SETUPTOOLS_PKG_INFO) - finally: - f.close() - return True - - -def _before_install(): - log.warn('Before install bootstrap.') - _fake_setuptools() - - -def _under_prefix(location): - if 'install' not in sys.argv: - return True - args = sys.argv[sys.argv.index('install')+1:] - for index, arg in enumerate(args): - for option in ('--root', '--prefix'): - if arg.startswith('%s=' % option): - top_dir = arg.split('root=')[-1] - return location.startswith(top_dir) - elif arg == option: - if len(args) > index: - top_dir = args[index+1] - return location.startswith(top_dir) - elif option == '--user' and USER_SITE is not None: - return location.startswith(USER_SITE) - return True - - -def _fake_setuptools(): - log.warn('Scanning installed packages') - try: - import pkg_resources - except ImportError: - # we're cool - log.warn('Setuptools or Distribute does not seem to be installed.') - return - ws = pkg_resources.working_set - try: - setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools', - replacement=False)) - except TypeError: - # old distribute API - setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools')) - - if setuptools_dist is None: - log.warn('No setuptools distribution found') - return - # detecting if it was already faked - setuptools_location = setuptools_dist.location - log.warn('Setuptools installation detected at %s', setuptools_location) - - # if --root or --preix was provided, and if - # setuptools is not located in them, we don't patch it - if not _under_prefix(setuptools_location): - log.warn('Not patching, --root or --prefix is installing Distribute' - ' in another location') - return - - # let's see if its an egg - if not setuptools_location.endswith('.egg'): - log.warn('Non-egg installation') - res = _remove_flat_installation(setuptools_location) - if not res: - return - else: - log.warn('Egg installation') - pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') - if (os.path.exists(pkg_info) and - _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): - log.warn('Already patched.') - return - log.warn('Patching...') - # let's create a fake egg replacing setuptools one - res = _patch_egg_dir(setuptools_location) - if not res: - return - log.warn('Patched done.') - _relaunch() - - -def _relaunch(): - log.warn('Relaunching...') - # we have to relaunch the process - args = [sys.executable] + sys.argv - sys.exit(subprocess.call(args)) - - -def _extractall(self, path=".", members=None): - """Extract all members from the archive to the current working - directory and set owner, modification time and permissions on - directories afterwards. `path' specifies a different directory - to extract to. `members' is optional and must be a subset of the - list returned by getmembers(). - """ - import copy - import operator - from tarfile import ExtractError - directories = [] - - if members is None: - members = self - - for tarinfo in members: - if tarinfo.isdir(): - # Extract directories with a safe mode. - directories.append(tarinfo) - tarinfo = copy.copy(tarinfo) - tarinfo.mode = 448 # decimal for oct 0700 - self.extract(tarinfo, path) - - # Reverse sort directories. - if sys.version_info < (2, 4): - def sorter(dir1, dir2): - return cmp(dir1.name, dir2.name) - directories.sort(sorter) - directories.reverse() - else: - directories.sort(key=operator.attrgetter('name'), reverse=True) - - # Set correct owner, mtime and filemode on directories. - for tarinfo in directories: - dirpath = os.path.join(path, tarinfo.name) - try: - self.chown(tarinfo, dirpath) - self.utime(tarinfo, dirpath) - self.chmod(tarinfo, dirpath) - except ExtractError: - e = sys.exc_info()[1] - if self.errorlevel > 1: - raise - else: - self._dbg(1, "tarfile: %s" % e) - - -def main(argv, version=DEFAULT_VERSION): - """Install or upgrade setuptools and EasyInstall""" - tarball = download_setuptools() - _install(tarball) - - -if __name__ == '__main__': - main(sys.argv[1:]) diff --git a/ez_setup.py b/ez_setup.py new file mode 100644 index 0000000..f9be432 --- /dev/null +++ b/ez_setup.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python +"""Bootstrap setuptools installation + +To use setuptools in your package's setup.py, include this +file in the same directory and add this to the top of your setup.py:: + + from ez_setup import use_setuptools + use_setuptools() + +To require a specific version of setuptools, set a download +mirror, or use an alternate download directory, simply supply +the appropriate options to ``use_setuptools()``. + +This file can also be run as a script to install or upgrade setuptools. +""" +import os +import shutil +import sys +import tempfile +import zipfile +import optparse +import subprocess +import platform +import textwrap +import contextlib + +from distutils import log + +try: + from urllib.request import urlopen +except ImportError: + from urllib2 import urlopen + +try: + from site import USER_SITE +except ImportError: + USER_SITE = None + +DEFAULT_VERSION = "5.4.1" +DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" + +def _python_cmd(*args): + """ + Return True if the command succeeded. + """ + args = (sys.executable,) + args + return subprocess.call(args) == 0 + + +def _install(archive_filename, install_args=()): + with archive_context(archive_filename): + # installing + log.warn('Installing Setuptools') + if not _python_cmd('setup.py', 'install', *install_args): + log.warn('Something went wrong during the installation.') + log.warn('See the error message above.') + # exitcode will be 2 + return 2 + + +def _build_egg(egg, archive_filename, to_dir): + with archive_context(archive_filename): + # building an egg + log.warn('Building a Setuptools egg in %s', to_dir) + _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + # returning the result + log.warn(egg) + if not os.path.exists(egg): + raise IOError('Could not build the egg.') + + +class ContextualZipFile(zipfile.ZipFile): + """ + Supplement ZipFile class to support context manager for Python 2.6 + """ + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.close() + + def __new__(cls, *args, **kwargs): + """ + Construct a ZipFile or ContextualZipFile as appropriate + """ + if hasattr(zipfile.ZipFile, '__exit__'): + return zipfile.ZipFile(*args, **kwargs) + return super(ContextualZipFile, cls).__new__(cls) + + +@contextlib.contextmanager +def archive_context(filename): + # extracting the archive + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + with ContextualZipFile(filename) as archive: + archive.extractall() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + yield + + finally: + os.chdir(old_wd) + shutil.rmtree(tmpdir) + + +def _do_download(version, download_base, to_dir, download_delay): + egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg' + % (version, sys.version_info[0], sys.version_info[1])) + if not os.path.exists(egg): + archive = download_setuptools(version, download_base, + to_dir, download_delay) + _build_egg(egg, archive, to_dir) + sys.path.insert(0, egg) + + # Remove previously-imported pkg_resources if present (see + # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details). + if 'pkg_resources' in sys.modules: + del sys.modules['pkg_resources'] + + import setuptools + setuptools.bootstrap_install_from = egg + + +def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, download_delay=15): + to_dir = os.path.abspath(to_dir) + rep_modules = 'pkg_resources', 'setuptools' + imported = set(sys.modules).intersection(rep_modules) + try: + import pkg_resources + except ImportError: + return _do_download(version, download_base, to_dir, download_delay) + try: + pkg_resources.require("setuptools>=" + version) + return + except pkg_resources.DistributionNotFound: + return _do_download(version, download_base, to_dir, download_delay) + except pkg_resources.VersionConflict as VC_err: + if imported: + msg = textwrap.dedent(""" + The required version of setuptools (>={version}) is not available, + and can't be installed while this script is running. Please + install a more recent version first, using + 'easy_install -U setuptools'. + + (Currently using {VC_err.args[0]!r}) + """).format(VC_err=VC_err, version=version) + sys.stderr.write(msg) + sys.exit(2) + + # otherwise, reload ok + del pkg_resources, sys.modules['pkg_resources'] + return _do_download(version, download_base, to_dir, download_delay) + +def _clean_check(cmd, target): + """ + Run the command to download target. If the command fails, clean up before + re-raising the error. + """ + try: + subprocess.check_call(cmd) + except subprocess.CalledProcessError: + if os.access(target, os.F_OK): + os.unlink(target) + raise + +def download_file_powershell(url, target): + """ + Download the file at url to target using Powershell (which will validate + trust). Raise an exception if the command cannot complete. + """ + target = os.path.abspath(target) + ps_cmd = ( + "[System.Net.WebRequest]::DefaultWebProxy.Credentials = " + "[System.Net.CredentialCache]::DefaultCredentials; " + "(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)" + % vars() + ) + cmd = [ + 'powershell', + '-Command', + ps_cmd, + ] + _clean_check(cmd, target) + +def has_powershell(): + if platform.system() != 'Windows': + return False + cmd = ['powershell', '-Command', 'echo test'] + with open(os.path.devnull, 'wb') as devnull: + try: + subprocess.check_call(cmd, stdout=devnull, stderr=devnull) + except Exception: + return False + return True + +download_file_powershell.viable = has_powershell + +def download_file_curl(url, target): + cmd = ['curl', url, '--silent', '--output', target] + _clean_check(cmd, target) + +def has_curl(): + cmd = ['curl', '--version'] + with open(os.path.devnull, 'wb') as devnull: + try: + subprocess.check_call(cmd, stdout=devnull, stderr=devnull) + except Exception: + return False + return True + +download_file_curl.viable = has_curl + +def download_file_wget(url, target): + cmd = ['wget', url, '--quiet', '--output-document', target] + _clean_check(cmd, target) + +def has_wget(): + cmd = ['wget', '--version'] + with open(os.path.devnull, 'wb') as devnull: + try: + subprocess.check_call(cmd, stdout=devnull, stderr=devnull) + except Exception: + return False + return True + +download_file_wget.viable = has_wget + +def download_file_insecure(url, target): + """ + Use Python to download the file, even though it cannot authenticate the + connection. + """ + src = urlopen(url) + try: + # Read all the data in one block. + data = src.read() + finally: + src.close() + + # Write all the data in one block to avoid creating a partial file. + with open(target, "wb") as dst: + dst.write(data) + +download_file_insecure.viable = lambda: True + +def get_best_downloader(): + downloaders = ( + download_file_powershell, + download_file_curl, + download_file_wget, + download_file_insecure, + ) + viable_downloaders = (dl for dl in downloaders if dl.viable()) + return next(viable_downloaders, None) + +def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, delay=15, downloader_factory=get_best_downloader): + """ + Download setuptools from a specified location and return its filename + + `version` should be a valid setuptools version number that is available + as an egg for download under the `download_base` URL (which should end + with a '/'). `to_dir` is the directory where the egg will be downloaded. + `delay` is the number of seconds to pause before an actual download + attempt. + + ``downloader_factory`` should be a function taking no arguments and + returning a function for downloading a URL to a target. + """ + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + zip_name = "setuptools-%s.zip" % version + url = download_base + zip_name + saveto = os.path.join(to_dir, zip_name) + if not os.path.exists(saveto): # Avoid repeated downloads + log.warn("Downloading %s", url) + downloader = downloader_factory() + downloader(url, saveto) + return os.path.realpath(saveto) + +def _build_install_args(options): + """ + Build the arguments to 'python setup.py install' on the setuptools package + """ + return ['--user'] if options.user_install else [] + +def _parse_args(): + """ + Parse the command line for options + """ + parser = optparse.OptionParser() + parser.add_option( + '--user', dest='user_install', action='store_true', default=False, + help='install in user site package (requires Python 2.6 or later)') + parser.add_option( + '--download-base', dest='download_base', metavar="URL", + default=DEFAULT_URL, + help='alternative URL from where to download the setuptools package') + parser.add_option( + '--insecure', dest='downloader_factory', action='store_const', + const=lambda: download_file_insecure, default=get_best_downloader, + help='Use internal, non-validating downloader' + ) + parser.add_option( + '--version', help="Specify which version to download", + default=DEFAULT_VERSION, + ) + options, args = parser.parse_args() + # positional arguments are ignored + return options + +def main(): + """Install or upgrade setuptools and EasyInstall""" + options = _parse_args() + archive = download_setuptools( + version=options.version, + download_base=options.download_base, + downloader_factory=options.downloader_factory, + ) + return _install(archive, _build_install_args(options)) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/locale/es.mo b/locale/es.mo new file mode 100644 index 0000000..cda6986 Binary files /dev/null and b/locale/es.mo differ diff --git a/locale/es.po b/locale/es.po new file mode 100644 index 0000000..e5c7fb0 --- /dev/null +++ b/locale/es.po @@ -0,0 +1,80 @@ +# Spanish translations for PACKAGE package +# Traducciones al español para el paquete PACKAGE. +# Copyright (C) 2014 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# José Carlos Cuevas , 2014. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-07-14 12:52+0200\n" +"PO-Revision-Date: 2014-07-14 12:54+0100\n" +"Last-Translator: José Carlos Cuevas \n" +"Language-Team: Spanish\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 1.5.4\n" + +#: jsoninspector.py:81 +msgid "Open JSON text file" +msgstr "Abrir archivo de texto JSON" + +#: jsoninspector.py:104 +msgid "No JSON loaded." +msgstr "No se ha cargado JSON." + +#: jsoninspector.py:152 +msgid "Loaded from the clipboard." +msgstr "Cargado desde el portapapeles." + +#: jsoninspector.py:194 +msgid "Not valid JSON!\n" +msgstr "JSON no válido!\n" + +#: jsoninspector.py:210 +msgid "Not valid JSON" +msgstr "JSON no válido" + +#: jsoninspector.glade.h:1 +msgid "About" +msgstr "Acerca de" + +#: jsoninspector.glade.h:2 +msgid "Enter JSON Text" +msgstr "Introduzca texto JSON" + +#: jsoninspector.glade.h:3 +msgid "JSON Inspector" +msgstr "JSON Inspector" + +#: jsoninspector.glade.h:4 +msgid "_File" +msgstr "_Archivo" + +#: jsoninspector.glade.h:5 +msgid "Cop_y JSON" +msgstr "_Copiar JSON" + +#: jsoninspector.glade.h:6 +msgid "_Help" +msgstr "A_yuda" + +#: jsoninspector.glade.h:7 +msgid "Node" +msgstr "Nodo" + +#: jsoninspector.glade.h:8 +msgid "Value" +msgstr "Valor" + +#: jsoninspector.glade.h:9 +msgid "Type" +msgstr "Tipo" + +#: jsoninspector.glade.h:10 +msgid "No JSON loaded" +msgstr "No se ha cargado JSON" diff --git a/locale/messages.pot b/locale/messages.pot new file mode 100644 index 0000000..6b8f0a1 --- /dev/null +++ b/locale/messages.pot @@ -0,0 +1,78 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-07-14 12:52+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: jsoninspector.py:81 +msgid "Open JSON text file" +msgstr "" + +#: jsoninspector.py:104 +msgid "No JSON loaded." +msgstr "" + +#: jsoninspector.py:152 +msgid "Loaded from the clipboard." +msgstr "" + +#: jsoninspector.py:194 +msgid "Not valid JSON!\n" +msgstr "" + +#: jsoninspector.py:210 +msgid "Not valid JSON" +msgstr "" + +#: jsoninspector.glade.h:1 +msgid "About" +msgstr "" + +#: jsoninspector.glade.h:2 +msgid "Enter JSON Text" +msgstr "" + +#: jsoninspector.glade.h:3 +msgid "JSON Inspector" +msgstr "" + +#: jsoninspector.glade.h:4 +msgid "_File" +msgstr "" + +#: jsoninspector.glade.h:5 +msgid "Cop_y JSON" +msgstr "" + +#: jsoninspector.glade.h:6 +msgid "_Help" +msgstr "" + +#: jsoninspector.glade.h:7 +msgid "Node" +msgstr "" + +#: jsoninspector.glade.h:8 +msgid "Value" +msgstr "" + +#: jsoninspector.glade.h:9 +msgid "Type" +msgstr "" + +#: jsoninspector.glade.h:10 +msgid "No JSON loaded" +msgstr "" diff --git a/locale/po/es/LC_MESSAGES/jsoninspector.mo b/locale/po/es/LC_MESSAGES/jsoninspector.mo new file mode 100644 index 0000000..cda6986 Binary files /dev/null and b/locale/po/es/LC_MESSAGES/jsoninspector.mo differ diff --git a/res/get_sizes.sh b/res/get_sizes.sh new file mode 100755 index 0000000..2aa4c47 --- /dev/null +++ b/res/get_sizes.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +SIZES=( 16x16 22x22 24x24 32x32 36x36 48x48 64x64 72x72 96x96 128x128 192x192 256x256 ) + +# If we can't find the svg, cd into res, we're +# probably being called from the root of +# the source tree +if [ ! -e jsoninspector.svg ]; then + cd res +fi + +for s in "${SIZES[@]}"; do + echo "Creating $s image from svg" ; + convert -background none jsoninspector.svg -resize $s jsoninspector$s.png +done diff --git a/res/jsoninspector.desktop b/res/jsoninspector.desktop new file mode 100644 index 0000000..60eff11 --- /dev/null +++ b/res/jsoninspector.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Comment=Lightweight JSON utility to inspect JSON documents +Terminal=false +Name=JSON Inspector +Exec=jsoninspector +Type=Application +Icon=jsoninspector diff --git a/src/jsoninspector.glade b/res/jsoninspector.glade similarity index 60% rename from src/jsoninspector.glade rename to res/jsoninspector.glade index 949469e..c4faca0 100644 --- a/src/jsoninspector.glade +++ b/res/jsoninspector.glade @@ -1,7 +1,165 @@ - - - - + + + + + + False + About + False + True + center-on-parent + True + jsoninspector + normal + False + center + False + JSON Inspector + v2.0 + https://github.com/resetreboot/jsoninspector + https://github.com/resetreboot/jsoninspector + José Carlos Cuevas Albadalejo + José Carlos Cuevas Albadalejo + jsoninspector + gpl-3-0 + + + + + False + vertical + 2 + + + False + end + + + False + True + end + 0 + + + + + + + + + + False + Enter JSON Text + True + center + 200 + 180 + jsoninspector + + + + + True + False + vertical + + + True + True + in + + + 200 + 140 + True + True + char + + + + + True + True + 0 + + + + + True + False + + + gtk-cancel + True + True + True + True + True + + + + False + True + 5 + end + 0 + + + + + gtk-ok + True + True + True + True + True + + + + False + True + 5 + end + 1 + + + + + + + + + + + + + + False + True + 1 + + + + + + + 100 + 1 + 10 + 10 + + + 100 + 1 + 10 + 10 + + + True + False + gtk-copy + + @@ -15,53 +173,65 @@ 400 400 + False JSON Inspector center - + jsoninspector + - + True + False + vertical True + False True - _Archivo + False + _File True True + False gtk-open True + False True True - + - Copiar JSON + Cop_y JSON True + False + True image1 False - + True + False gtk-quit True + False True True - + @@ -71,17 +241,21 @@ True - Ay_uda + False + _Help True True + False gtk-about True + False True True + @@ -91,6 +265,7 @@ False + True 0 @@ -100,26 +275,26 @@ True adjustment1 adjustment2 - automatic - automatic 300 300 True True - treestore1 adjustment1 adjustment2 + treestore1 False treeviewcolumn1 0 1 - both + + + True - Nodo + Node True True 0 @@ -128,14 +303,14 @@ True - Valor + Value True True - Tipo + Type True @@ -143,125 +318,39 @@ + True + True 1 True + False 2 True - No hay JSON cargado + False + No JSON loaded + False + True 0 False + True 2 - - 100 - 1 - 10 - 10 - - - - 100 - 1 - 10 - 10 - - - Introducir texto JSON - True - center - 200 - 180 - - - - - True - - - 200 - 140 - True - True - char - - - 5 - 0 - - - - - True - - - - - - gtk-ok - True - True - True - True - - - - False - 5 - 1 - - - - - - - - gtk-cancel - True - True - True - True - - - - False - 5 - 3 - - - - - - - - False - 5 - 1 - - - - - - - True - gtk-copy - diff --git a/res/jsoninspector.svg b/res/jsoninspector.svg new file mode 100644 index 0000000..3f66a09 --- /dev/null +++ b/res/jsoninspector.svg @@ -0,0 +1,1004 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup.py b/setup.py index 11c3a13..12ca842 100644 --- a/setup.py +++ b/setup.py @@ -1,27 +1,103 @@ -import ez_setup +import ez_setup, sys, shutil, os, os.path, subprocess ez_setup.use_setuptools() from setuptools import setup, find_packages -setup( - name = "jsoninspector", - version = "1.5", - packages = find_packages('src', exclude=['distribute_setup']), - scripts = ['src/jsoninspector.py','src/jsoninspector.glade'], - entry_points = { - 'gui_scripts': [ - 'jsoninspector = jsoninspector:MainApp.start', - ] - }, - package_data = { - # If any package contains *.glade files, include them: - '': ['*.glade'], - }, +from setuptools.command.install import install +from distutils.dir_util import copy_tree +class CustomInstall(install): + """ + We subclass the install command to add some more mojo + """ + def run(self): + install.run(self) # Do the usual setuptools magic + # Now we do our own magic + if sys.platform != 'win32' and sys.platform != 'darwin': + try: + print "Creating shared directory..." + os.mkdir("/usr/local/share/jsoninspector", 0755) + + except: + if not os.path.exists("/usr/local/share/jsoninspector"): + print "Warning: Couldn't create /usr/local/share/jsoninspector" + + # Copy the translations + try: + print "Installing translations..." + copy_tree('locale/po/', '/usr/share/locale/') + + except: + print "Warning: error copying translation files." + + # Generate the icons + try: + result = subprocess.call(['./res/get_sizes.sh']) + + except: + result = 1 + + if result != 0: + print "Warning: Error generating icons" + + # Copy the icons + print "Installing application icons..." + for icon_size in ['{sz}x{sz}'.format(sz = x) for x in ['16', '22','24', '32', '36', '48', '64', '72', '96', '128', '192']]: + try: + shutil.copyfile('res/jsoninspector' + icon_size + ".png", + '/usr/share/icons/hicolor/' + icon_size + "/apps/jsoninspector.png") + + except: + print "Warning: error copying icon {size}.".format(size = icon_size) + + try: + shutil.copyfile('res/jsoninspector48x48.png', '/usr/share/pixmaps/jsoninspector.png') + + except: + print "Warning: error copying icon to pixmaps directory." + + try: + shutil.copyfile('res/jsoninspector.svg' ,'/usr/share/icons/hicolor/scalable/apps/jsoninspector.svg') + + except: + print "Warning: error copying svg to scalable." + + print "Updating icon cache..." + + try: + result = subprocess.call(['/usr/bin/gtk-update-icon-cache /usr/share/icons/hicolor/']) + + except: + result = 1 + + if result != 0: + print "Warning: Error updating hicolor icon cache." + + try: + print "Installing glade file..." + shutil.copyfile('res/jsoninspector.glade', '/usr/local/share/jsoninspector/jsoninspector.glade') + + except: + print "Warning: error copying .glade file." + + try: + print "Installing desktop entry..." + shutil.copyfile('res/jsoninspector.desktop', '/usr/share/applications/jsoninspector.desktop') + + except: + print "Warning: error copying .desktop entry." + +setup( + name = "Jsoninspector", + version = "2.0", + packages = find_packages('src', exclude = ['ez_setup']), + entry_points = { 'gui_scripts' : [ 'jsoninspector = jsoninspector:main_start' ] }, + package_dir = { '' : 'src' }, # metadata for upload to PyPI author = "Jose Carlos Cuevas", author_email = "reset.reboot@gmail.com", description = "JSON Inspector is a simple application to study JSON code", license = "GPLv3", keywords = "json inspect gtk gnome", - url = "", # project home page, if any + url = "https://github.com/resetreboot/jsoninspector", # project home page, if any + cmdclass = { 'install' : CustomInstall } ) diff --git a/setup_windows.py b/setup_windows.py new file mode 100644 index 0000000..ef47cfd --- /dev/null +++ b/setup_windows.py @@ -0,0 +1,74 @@ +# This was borrowed from +# https://wiki.gnome.org/Projects/PyGObject#Building_on_Win32_with_cx_freeze +# Thanks to Gian Mario Tagliaretti + +import os, site, sys +from cx_Freeze import setup, Executable + +## Get the site-package folder, not everybody will install +## Python into C:\PythonXX +site_dir = site.getsitepackages()[1] +include_dll_path = os.path.join(site_dir, "gtk") + +## Collect the list of missing dll when cx_freeze builds the app +missing_dll = ['libgtk-3-0.dll', + 'libgdk-3-0.dll', + 'libatk-1.0-0.dll', + 'libcairo-gobject-2.dll', + 'libgdk_pixbuf-2.0-0.dll', + 'libjpeg-8.dll', + 'libpango-1.0-0.dll', + 'libpangocairo-1.0-0.dll', + 'libpangoft2-1.0-0.dll', + 'libpangowin32-1.0-0.dll', + 'libgnutls-26.dll', + 'libgcrypt-11.dll', + 'libp11-kit-0.dll' +] + +## We also need to add the glade folder, cx_freeze will walk +## into it and copy all the necessary files +glade_folder = 'res' + +## We need to add all the libraries too (for themes, etc..) +gtk_libs = ['etc', 'lib', 'share'] + +## Create the list of includes as cx_freeze likes +include_files = [] +for dll in missing_dll: + include_files.append((os.path.join(include_dll_path, dll), dll)) + +## Let's add glade folder and files +include_files.append((glade_folder, glade_folder)) + +## Let's add gtk libraries folders and files +for lib in gtk_libs: + include_files.append((os.path.join(include_dll_path, lib), lib)) + +base = None + +## Lets not open the console while running the app +if sys.platform == "win32": + base = "Win32GUI" + +executables = [ + Executable("jsoninspector.py", + base = base + ) +] + +buildOptions = dict( + compressed = False, + includes = ["gi"], + packages = ["gi"], + include_files = include_files + ) + +setup( + name = "JSONInspector", + author = "Jose Carlos Cuevas Albadalejo", + version = "2.0", + description = "JSON Inspection tool", + options = dict(build_exe = buildOptions), + executables = executables +) diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/jsoninspector/__init__.py b/src/jsoninspector/__init__.py new file mode 100644 index 0000000..6954003 --- /dev/null +++ b/src/jsoninspector/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from jsoninspector import LogicObject, MainWindowMethods + +def main_start(): + """ + So setuptools makes a nice startup script + """ + logicObject = LogicObject() + + mainWindow = MainWindowMethods(logicObject) + mainWindow.run(None) diff --git a/src/jsoninspector.py b/src/jsoninspector/jsoninspector.py similarity index 62% rename from src/jsoninspector.py rename to src/jsoninspector/jsoninspector.py index 86dd899..0bc65fe 100644 --- a/src/jsoninspector.py +++ b/src/jsoninspector/jsoninspector.py @@ -1,86 +1,104 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from pkg_resources import resource_filename +from gi.repository import Gtk, Gio -import json -import sys -import pygtk +import json, sys, os.path -try: - pygtk.require("2.0") -except: - print "Versión de PyGTK incorrecta!\n" - sys.exit(1) +# Internationalization support +import gettext +import locale -import gtk +APP = "jsoninspector" -class MainWindowMethods(object): +if os.path.exists('../locale/po') and os.path.exists('../../res'): + # We're in the development tree + DIR = "../../locale/po/" + RESOURCES = "../../res/" + +elif sys.platform != 'win32' and sys.platform != 'darwin': + DIR = "/usr/share/locale/" + RESOURCES = "/usr/local/share/jsoninspector" + +else: + DIR = "po" + RESOURCES = "res/" + +locale.setlocale(locale.LC_ALL, '') + +gettext.bindtextdomain(APP, DIR) +locale.bindtextdomain(APP, DIR) + + +gettext.textdomain(APP) +_ = gettext.gettext + + +class MainWindowMethods(Gtk.Application): """ - Clase que contiene los métodos de la ventana principal + Main Application object with the main window signals """ def __init__(self, logic): - """ - Carga y conecta el XML de gtkBuilder, además de mostrar la ventana - principal - """ - self.builder = gtk.Builder() - try: - self.builder.add_from_file(resource_filename(__name__,'../../src/jsoninspector.glade')) + Gtk.Application.__init__(self, application_id = "apps.gnome.jsoninspector", + flags = Gio.ApplicationFlags.FLAGS_NONE) - except: - self.builder.add_from_file(resource_filename(__name__,'jsoninspector.glade')) + # We store the reference to the app logic + self.logicObj = logic + self.connect("activate", self.on_app_start) + + def on_app_start(self, data = None): + """ + Loads the MainWindow widgets, shows it up and starts the main loop + """ + self.builder = Gtk.Builder() + self.builder.set_translation_domain(APP) + self.builder.add_from_file(os.path.join(RESOURCES, 'jsoninspector.glade')) self.builder.connect_signals(self) - # Prepara los renderizados de columna y las asigna a los valores - cell = gtk.CellRendererText() + # Prepares the renders of columns and assigns the values + cell = Gtk.CellRendererText() columns = self.builder.get_object("treeviewcolumn1") - columns.pack_start(cell) + columns.pack_start(cell, True) columns.add_attribute(cell, 'text', 0) treeview = self.builder.get_object("treeview1") - # Esta columna es la que tiene los nodos de apertura y cierre + # This column has the open and collapse nodes treeview.set_expander_column(columns) - cell = gtk.CellRendererText() + cell = Gtk.CellRendererText() columns = self.builder.get_object("treeviewcolumn2") - columns.pack_start(cell) + columns.pack_start(cell, True) columns.add_attribute(cell, 'text', 1) - cell = gtk.CellRendererText() + cell = Gtk.CellRendererText() columns = self.builder.get_object("treeviewcolumn3") - columns.pack_start(cell) + columns.pack_start(cell, True) columns.add_attribute(cell, 'text', 2) - # Obtenemos la ventana y la mostramos completamente + # We get the window and show it self.window = self.builder.get_object("MainWindow") + self.add_window(self.window) self.window.show_all() - # Obtenemos un enlace al objeto de lógica de aplicación - self.logicObj = logic - def onOpenMenuClicked(self, event): """ - El usuario ha presionado Abrir en el menu + User has pressed Open in the menu """ - # Creamos el dialogo de abrir fichero - chooser = gtk.FileChooserDialog(title=None, - action=gtk.FILE_CHOOSER_ACTION_OPEN, - buttons=(gtk.STOCK_CANCEL, - gtk.RESPONSE_CANCEL, - gtk.STOCK_OPEN, - gtk.RESPONSE_OK)) - chooser.set_default_response(gtk.RESPONSE_OK) + # Create the FileChooser Dialog + chooser = Gtk.FileChooserDialog(_("Open JSON text file"), self.window, + Gtk.FileChooserAction.OPEN, + (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, + Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) - # Lo lanzamos + # Launch it response = chooser.run() - # Si hemos seleccionado un fichero, lo cargamos - if response == gtk.RESPONSE_OK: + # If we choose a file, we load it + if response == Gtk.ResponseType.OK: filename = chooser.get_filename() label = self.builder.get_object("StatusLabel") - # Según todo haya ido, actualizamos la barra de estado + # We update the statusbar accordingly to what has happened if self.logicObj.loadjson(filename): label.set_text(filename) @@ -90,22 +108,22 @@ class MainWindowMethods(object): else: - label.set_text("No hay JSON cargado") + label.set_text(_("No JSON loaded.")) - # Nos deshacemos del dialogo + # We finish the dialog chooser.destroy() def onCopyJSONClicked(self, widget): """ - Se ha pulsado Copiar JSON + User asked to paste a JSON code """ - # Mostrar la ventana de texto + # Show up the TextWindow textWindow = self.builder.get_object("TextWindow") textWindow.show_all() def onCopyJSONDelete(self, widget, event): """ - Se ha dado a cerrar la ventana de copiar texto + We've been told to close the CopyJSON Window """ textWindow = self.builder.get_object("TextWindow") textWindow.hide() @@ -114,7 +132,7 @@ class MainWindowMethods(object): def onCopyJSONDestroy(self, widget): """ - Se ha cerrado la ventana + We've been tasked with removing the CopyJSON window """ textWindow = self.builder.get_object("TextWindow") textWindow.hide() @@ -123,12 +141,12 @@ class MainWindowMethods(object): def onCopyJSONAcceptClicked(self, widget): """ - Se ha aceptado el código + The input is finished and accepted. """ textView = self.builder.get_object("textview1") jsonBuffer = textView.get_buffer() jsonText = jsonBuffer.get_text(jsonBuffer.get_start_iter(), - jsonBuffer.get_end_iter()) + jsonBuffer.get_end_iter(), True) textWindow = self.builder.get_object("TextWindow") textWindow.hide() @@ -138,29 +156,41 @@ class MainWindowMethods(object): if self.logicObj.loadJSONText(jsonText): status_label = self.builder.get_object("StatusLabel") - status_label.set_text("Cargado desde portapapeles.") + status_label.set_text(_("Loaded from the clipboard.")) self.logicObj.loadTree(treestore) def onCopyJSONCancelClicked(self, widget): """ - Se ha cancelado + The user changed its mind and pressed cancel """ textWindow = self.builder.get_object("TextWindow") textWindow.hide() def onExitMenuClicked(self, widget): """ - Se ha pulsado salir + Menu option Exit has been clicked """ - gtk.main_quit() - sys.exit(0) + self.quit() + + def onAboutMenuActivate(self, widget): + """ + About option clicked + """ + about_dialog = self.builder.get_object("AboutDialog") + about_dialog.run() + about_dialog.hide() def onMainWindowDelete(self, widget, event): """ - Se ha destruido o mandado cerrar la ventana principal + Our MainWindow has been deleted or closed """ - gtk.main_quit() - sys.exit(0) + pass + + def onAboutDialogClose(self, widget, event = None): + pass + + def onAboutDialogDeleteEvent(self, widget, event = None): + pass class LogicObject(object): @@ -179,7 +209,7 @@ class LogicObject(object): try: self.json = json.loads(f.read()) except ValueError: - print "JSON no válido!\n" + print _("Not valid JSON!\n") self.json = None f.close() @@ -195,7 +225,7 @@ class LogicObject(object): try: self.json = json.loads(text) except ValueError: - print "JSON no válido" + print _("Not valid JSON") self.json = None return False @@ -256,21 +286,12 @@ class LogicObject(object): unicode(type(node))]) -class MainApp(object): - """ - Clase principal - """ - @staticmethod - def start(): - logicObject = LogicObject() - mainWindow = MainWindowMethods(logicObject) - gtk.main() - - -# Ejecucion del programa principal +# Main procedure if __name__ == "__main__": - app = MainApp() - app.start() + logicObject = LogicObject() + + mainWindow = MainWindowMethods(logicObject) + mainWindow.run(None) diff --git a/util/gen_pot.sh b/util/gen_pot.sh new file mode 100755 index 0000000..6bcfabf --- /dev/null +++ b/util/gen_pot.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +intltool-extract --type="gettext/glade" *.glade +xgettext -k_ -kN_ -o messages.pot *.py *.h +