initial
This commit is contained in:
493
plugins/externaltools/tools/library.py
Executable file
493
plugins/externaltools/tools/library.py
Executable file
@@ -0,0 +1,493 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Gedit External Tools plugin
|
||||
# Copyright (C) 2006 Steve Frécinaux <code@istique.net>
|
||||
#
|
||||
# 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:
|
Reference in New Issue
Block a user