summaryrefslogtreecommitdiff
path: root/plugins/externaltools/tools/library.py
diff options
context:
space:
mode:
authorPerberos <[email protected]>2011-11-07 16:46:58 -0300
committerPerberos <[email protected]>2011-11-07 16:46:58 -0300
commit528c1e5ff51e213936e800fc5a9a25da99c0bdf2 (patch)
tree77f8aa456b09367ba81f04d4562fc935f898a951 /plugins/externaltools/tools/library.py
downloadpluma-528c1e5ff51e213936e800fc5a9a25da99c0bdf2.tar.bz2
pluma-528c1e5ff51e213936e800fc5a9a25da99c0bdf2.tar.xz
initial
Diffstat (limited to 'plugins/externaltools/tools/library.py')
-rwxr-xr-xplugins/externaltools/tools/library.py493
1 files changed, 493 insertions, 0 deletions
diff --git a/plugins/externaltools/tools/library.py b/plugins/externaltools/tools/library.py
new file mode 100755
index 00000000..6eb6ff1a
--- /dev/null
+++ b/plugins/externaltools/tools/library.py
@@ -0,0 +1,493 @@
+# -*- coding: utf-8 -*-
+# Gedit External Tools plugin
+# Copyright (C) 2006 Steve Frécinaux <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import re
+import locale
+import platform
+
+class Singleton(object):
+ _instance = None
+
+ def __new__(cls, *args, **kwargs):
+ if not cls._instance:
+ cls._instance = super(Singleton, cls).__new__(
+ cls, *args, **kwargs)
+ cls._instance.__init_once__()
+
+ return cls._instance
+
+class ToolLibrary(Singleton):
+ def __init_once__(self):
+ self.locations = []
+
+ def set_locations(self, datadir):
+ self.locations = []
+
+ if platform.platform() != 'Windows':
+ for d in self.get_xdg_data_dirs():
+ self.locations.append(os.path.join(d, 'gedit-2', 'plugins', 'externaltools', 'tools'))
+
+ self.locations.append(datadir)
+
+ # self.locations[0] is where we save the custom scripts
+ if platform.platform() == 'Windows':
+ toolsdir = os.path.expanduser('~/gedit/tools')
+ else:
+ userdir = os.getenv('MATE22_USER_DIR')
+ if userdir:
+ toolsdir = os.path.join(userdir, 'gedit/tools')
+ else:
+ toolsdir = os.path.expanduser('~/.mate2/gedit/tools')
+
+ self.locations.insert(0, toolsdir);
+
+ if not os.path.isdir(self.locations[0]):
+ os.makedirs(self.locations[0])
+ self.tree = ToolDirectory(self, '')
+ self.import_old_xml_store()
+ else:
+ self.tree = ToolDirectory(self, '')
+
+ # cf. http://standards.freedesktop.org/basedir-spec/latest/
+ def get_xdg_data_dirs(self):
+ dirs = os.getenv('XDG_DATA_DIRS')
+ if dirs:
+ dirs = dirs.split(os.pathsep)
+ else:
+ dirs = ('/usr/local/share', '/usr/share')
+ return dirs
+
+ # This function is meant to be ran only once, when the tools directory is
+ # created. It imports eventual tools that have been saved in the old XML
+ # storage file.
+ def import_old_xml_store(self):
+ import xml.etree.ElementTree as et
+ userdir = os.getenv('MATE22_USER_DIR')
+ if userdir:
+ filename = os.path.join(userdir, 'gedit/gedit-tools.xml')
+ else:
+ filename = os.path.expanduser('~/.mate2/gedit/gedit-tools.xml')
+
+ if not os.path.isfile(filename):
+ return
+
+ print "External tools: importing old tools into the new store..."
+
+ xtree = et.parse(filename)
+ xroot = xtree.getroot()
+
+ for xtool in xroot:
+ for i in self.tree.tools:
+ if i.name == xtool.get('label'):
+ tool = i
+ break
+ else:
+ tool = Tool(self.tree)
+ tool.name = xtool.get('label')
+ tool.autoset_filename()
+ self.tree.tools.append(tool)
+ tool.comment = xtool.get('description')
+ tool.shortcut = xtool.get('accelerator')
+ tool.applicability = xtool.get('applicability')
+ tool.output = xtool.get('output')
+ tool.input = xtool.get('input')
+
+ tool.save_with_script(xtool.text)
+
+ def get_full_path(self, path, mode='r', system = True, local = True):
+ assert (system or local)
+ if path is None:
+ return None
+ if mode == 'r':
+ if system and local:
+ locations = self.locations
+ elif local and not system:
+ locations = [self.locations[0]]
+ elif system and not local:
+ locations = self.locations[1:]
+ else:
+ raise ValueError("system and local can't be both set to False")
+
+ for i in locations:
+ p = os.path.join(i, path)
+ if os.path.lexists(p):
+ return p
+ return None
+ else:
+ path = os.path.join(self.locations[0], path)
+ dirname = os.path.dirname(path)
+ if not os.path.isdir(dirname):
+ os.mkdir(dirname)
+ return path
+
+class ToolDirectory(object):
+ def __init__(self, parent, dirname):
+ super(ToolDirectory, self).__init__()
+ self.subdirs = list()
+ self.tools = list()
+ if isinstance(parent, ToolDirectory):
+ self.parent = parent
+ self.library = parent.library
+ else:
+ self.parent = None
+ self.library = parent
+ self.dirname = dirname
+ self._load()
+
+ def listdir(self):
+ elements = dict()
+ for l in self.library.locations:
+ d = os.path.join(l, self.dirname)
+ if not os.path.isdir(d):
+ continue
+ for i in os.listdir(d):
+ elements[i] = None
+ keys = elements.keys()
+ keys.sort()
+ return keys
+
+ def _load(self):
+ for p in self.listdir():
+ path = os.path.join(self.dirname, p)
+ full_path = self.library.get_full_path(path)
+ if os.path.isdir(full_path):
+ self.subdirs.append(ToolDirectory(self, p))
+ elif os.path.isfile(full_path) and os.access(full_path, os.X_OK):
+ self.tools.append(Tool(self, p))
+
+ def get_path(self):
+ if self.parent is None:
+ return self.dirname
+ else:
+ return os.path.join(self.parent.get_path(), self.dirname)
+ path = property(get_path)
+
+ def get_name(self):
+ return os.path.basename(self.dirname)
+ name = property(get_name)
+
+ def delete_tool(self, tool):
+ # Only remove it if it lays in $HOME
+ if tool in self.tools:
+ path = tool.get_path()
+ if path is not None:
+ filename = os.path.join(self.library.locations[0], path)
+ if os.path.isfile(filename):
+ os.unlink(filename)
+ self.tools.remove(tool)
+ return True
+ else:
+ return False
+
+ def revert_tool(self, tool):
+ # Only remove it if it lays in $HOME
+ filename = os.path.join(self.library.locations[0], tool.get_path())
+ if tool in self.tools and os.path.isfile(filename):
+ os.unlink(filename)
+ tool._load()
+ return True
+ else:
+ return False
+
+
+class Tool(object):
+ RE_KEY = re.compile('^([a-zA-Z_][a-zA-Z0-9_.\-]*)(\[([a-zA-Z_@]+)\])?$')
+
+ def __init__(self, parent, filename = None):
+ super(Tool, self).__init__()
+ self.parent = parent
+ self.library = parent.library
+ self.filename = filename
+ self.changed = False
+ self._properties = dict()
+ self._transform = {
+ 'Languages': [self._to_list, self._from_list]
+ }
+ self._load()
+
+ def _to_list(self, value):
+ if value.strip() == '':
+ return []
+ else:
+ return map(lambda x: x.strip(), value.split(','))
+
+ def _from_list(self, value):
+ return ','.join(value)
+
+ def _parse_value(self, key, value):
+ if key in self._transform:
+ return self._transform[key][0](value)
+ else:
+ return value
+
+ def _load(self):
+ if self.filename is None:
+ return
+
+ filename = self.library.get_full_path(self.get_path())
+ if filename is None:
+ return
+
+ fp = file(filename, 'r', 1)
+ in_block = False
+ lang = locale.getlocale(locale.LC_MESSAGES)[0]
+
+ for line in fp:
+ if not in_block:
+ in_block = line.startswith('# [Gedit Tool]')
+ continue
+ if line.startswith('##') or line.startswith('# #'): continue
+ if not line.startswith('# '): break
+
+ try:
+ (key, value) = [i.strip() for i in line[2:].split('=', 1)]
+ m = self.RE_KEY.match(key)
+ if m.group(3) is None:
+ self._properties[m.group(1)] = self._parse_value(m.group(1), value)
+ elif lang is not None and lang.startswith(m.group(3)):
+ self._properties[m.group(1)] = self._parse_value(m.group(1), value)
+ except ValueError:
+ break
+ fp.close()
+ self.changed = False
+
+ def _set_property_if_changed(self, key, value):
+ if value != self._properties.get(key):
+ self._properties[key] = value
+
+ self.changed = True
+
+ def is_global(self):
+ return self.library.get_full_path(self.get_path(), local=False) is not None
+
+ def is_local(self):
+ return self.library.get_full_path(self.get_path(), system=False) is not None
+
+ def is_global(self):
+ return self.library.get_full_path(self.get_path(), local=False) is not None
+
+ def get_path(self):
+ if self.filename is not None:
+ return os.path.join(self.parent.get_path(), self.filename)
+ else:
+ return None
+ path = property(get_path)
+
+ # This command is the one that is meant to be ran
+ # (later, could have an Exec key or something)
+ def get_command(self):
+ return self.library.get_full_path(self.get_path())
+ command = property(get_command)
+
+ def get_applicability(self):
+ applicability = self._properties.get('Applicability')
+ if applicability: return applicability
+ return 'all'
+ def set_applicability(self, value):
+ self._set_property_if_changed('Applicability', value)
+ applicability = property(get_applicability, set_applicability)
+
+ def get_name(self):
+ name = self._properties.get('Name')
+ if name: return name
+ return os.path.basename(self.filename)
+ def set_name(self, value):
+ self._set_property_if_changed('Name', value)
+ name = property(get_name, set_name)
+
+ def get_shortcut(self):
+ shortcut = self._properties.get('Shortcut')
+ if shortcut: return shortcut
+ return None
+ def set_shortcut(self, value):
+ self._set_property_if_changed('Shortcut', value)
+ shortcut = property(get_shortcut, set_shortcut)
+
+ def get_comment(self):
+ comment = self._properties.get('Comment')
+ if comment: return comment
+ return self.filename
+ def set_comment(self, value):
+ self._set_property_if_changed('Comment', value)
+ comment = property(get_comment, set_comment)
+
+ def get_input(self):
+ input = self._properties.get('Input')
+ if input: return input
+ return 'nothing'
+ def set_input(self, value):
+ self._set_property_if_changed('Input', value)
+ input = property(get_input, set_input)
+
+ def get_output(self):
+ output = self._properties.get('Output')
+ if output: return output
+ return 'output-panel'
+ def set_output(self, value):
+ self._set_property_if_changed('Output', value)
+ output = property(get_output, set_output)
+
+ def get_save_files(self):
+ save_files = self._properties.get('Save-files')
+ if save_files: return save_files
+ return 'nothing'
+ def set_save_files(self, value):
+ self._set_property_if_changed('Save-files', value)
+ save_files = property(get_save_files, set_save_files)
+
+ def get_languages(self):
+ languages = self._properties.get('Languages')
+ if languages: return languages
+ return []
+ def set_languages(self, value):
+ self._set_property_if_changed('Languages', value)
+ languages = property(get_languages, set_languages)
+
+ def has_hash_bang(self):
+ if self.filename is None:
+ return True
+
+ filename = self.library.get_full_path(self.get_path())
+ if filename is None:
+ return True
+
+ fp = open(filename, 'r', 1)
+ for line in fp:
+ if line.strip() == '':
+ continue
+
+ return line.startswith('#!')
+
+ # There is no property for this one because this function is quite
+ # expensive to perform
+ def get_script(self):
+ if self.filename is None:
+ return ["#!/bin/sh\n"]
+
+ filename = self.library.get_full_path(self.get_path())
+ if filename is None:
+ return ["#!/bin/sh\n"]
+
+ fp = open(filename, 'r', 1)
+ lines = list()
+
+ # before entering the data block
+ for line in fp:
+ if line.startswith('# [Gedit Tool]'):
+ break
+ lines.append(line)
+ # in the block:
+ for line in fp:
+ if line.startswith('##'): continue
+ if not (line.startswith('# ') and '=' in line):
+ # after the block: strip one emtpy line (if present)
+ if line.strip() != '':
+ lines.append(line)
+ break
+ # after the block
+ for line in fp:
+ lines.append(line)
+ fp.close()
+ return lines
+
+ def _dump_properties(self):
+ lines = ['# [Gedit Tool]']
+ for item in self._properties.iteritems():
+ if item[0] in self._transform:
+ lines.append('# %s=%s' % (item[0], self._transform[item[0]][1](item[1])))
+ elif item[1] is not None:
+ lines.append('# %s=%s' % item)
+ return '\n'.join(lines) + '\n'
+
+ def save_with_script(self, script):
+ filename = self.library.get_full_path(self.filename, 'w')
+
+ fp = open(filename, 'w', 1)
+
+ # Make sure to first print header (shebang, modeline), then
+ # properties, and then actual content
+ header = []
+ content = []
+ inheader = True
+
+ # Parse
+ for line in script:
+ line = line.rstrip("\n")
+
+ if not inheader:
+ content.append(line)
+ elif line.startswith('#!'):
+ # Shebang (should be always present)
+ header.append(line)
+ elif line.strip().startswith('#') and ('-*-' in line or 'ex:' in line or 'vi:' in line or 'vim:' in line):
+ header.append(line)
+ else:
+ content.append(line)
+ inheader = False
+
+ # Write out header
+ for line in header:
+ fp.write(line + "\n")
+
+ fp.write(self._dump_properties())
+ fp.write("\n")
+
+ for line in content:
+ fp.write(line + "\n")
+
+ fp.close()
+ os.chmod(filename, 0750)
+ self.changed = False
+
+ def save(self):
+ if self.changed:
+ self.save_with_script(self.get_script())
+
+ def autoset_filename(self):
+ if self.filename is not None:
+ return
+ dirname = self.parent.path
+ if dirname != '':
+ dirname += os.path.sep
+
+ basename = self.name.lower().replace(' ', '-').replace('/', '-')
+
+ if self.library.get_full_path(dirname + basename):
+ i = 2
+ while self.library.get_full_path(dirname + "%s-%d" % (basename, i)):
+ i += 1
+ basename = "%s-%d" % (basename, i)
+ self.filename = basename
+
+if __name__ == '__main__':
+ library = ToolLibrary()
+
+ def print_tool(t, indent):
+ print indent * " " + "%s: %s" % (t.filename, t.name)
+
+ def print_dir(d, indent):
+ print indent * " " + d.dirname + '/'
+ for i in d.subdirs:
+ print_dir(i, indent+1)
+ for i in d.tools:
+ print_tool(i, indent+1)
+
+ print_dir(library.tree, 0)
+
+# ex:ts=4:et: