# -*- coding: utf-8 -*- # Gedit External Tools plugin # Copyright (C) 2006 Steve Frécinaux # # 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 class ToolLibrary(object): _instance = None def __new__(cls): if cls._instance is None: cls._instance = object.__new__(cls) return cls._instance def __init__(self): self.locations = [os.path.join(d, 'gedit-2/plugins/tools') for d in self.get_xdg_data_dirs()] # self.locations[0] is where we save the custom scripts self.locations.insert(0, os.path.expanduser('~/.gnome2/gedit/tools')) 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 ElementTree as et filename = os.path.expanduser('~/.gnome2/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._load() 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()[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: if m.group(0) not in self._properties: self._properties[m.group(1)] = value elif lang is not None and lang.startswith(m.group(3)): self._properties[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) # 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 [] filename = self.library.get_full_path(self.get_path()) if filename is None: return [] 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[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) try: script = iter(script) line = script.next() # Shebang (should be always present) if line.startswith('#!'): fp.write(line) line = script.next() # Emacs modeline (might or might not be there) if '-*-' in line: fp.write(line) line = script.next() # Vim modeline if 'ex:' in line or 'vi:' in line or 'vim:' in line: fp.write(line) line = script.next() # We put a white line before the info block if there is one available if line.strip() == '': fp.write(line) line = script.next() # Write the info block fp.write(self._dump_properties()) fp.write("\n") # And write the remaining part of the script while True: fp.write(line) line = script.next() except StopIteration: pass 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: