mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-02-24 19:22:11 +01:00
This makes it easier to track the current state, and, for example, put the line number in error messages.
876 lines
30 KiB
Python
Executable File
876 lines
30 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# vim: set ts=4 sw=4 et: coding=UTF-8
|
|
#
|
|
# Copyright (c) 2010, Novell, Inc.
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU Lesser 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 Lesser General Public
|
|
# License along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
|
|
# USA.
|
|
#
|
|
# Authors: Vincent Untz <vuntz@gnome.org>
|
|
|
|
# TODO: support alias for choice
|
|
|
|
import os
|
|
import sys
|
|
|
|
import optparse
|
|
|
|
try:
|
|
from lxml import etree as ET
|
|
except ImportError:
|
|
try:
|
|
from xml.etree import cElementTree as ET
|
|
except ImportError:
|
|
import cElementTree as ET
|
|
|
|
|
|
GSETTINGS_SIMPLE_SCHEMA_INDENT = ' '
|
|
TYPES_FOR_CHOICES = [ 's' ]
|
|
TYPES_FOR_RANGE = [ 'y', 'n', 'q', 'i', 'u', 'x', 't', 'h', 'd' ]
|
|
|
|
|
|
######################################
|
|
|
|
|
|
class GSettingsSchemaConvertException(Exception):
|
|
pass
|
|
|
|
|
|
######################################
|
|
|
|
|
|
class GSettingsSchemaRoot:
|
|
|
|
def __init__(self):
|
|
self.schemas = []
|
|
self.gettext_domain = None
|
|
|
|
def simplify(self):
|
|
for schema in self.schemas:
|
|
schema.simplify()
|
|
|
|
def get_simple_string(self):
|
|
need_empty_line = False
|
|
result = ''
|
|
|
|
for schema in self.schemas:
|
|
if need_empty_line:
|
|
result += '\n'
|
|
result += schema.get_simple_string()
|
|
if result:
|
|
need_empty_line = True
|
|
|
|
# Only put the gettext domain if we have some content
|
|
if result and self.gettext_domain:
|
|
result = 'gettext-domain: %s\n\n%s' % (self.gettext_domain, result)
|
|
|
|
return result
|
|
|
|
def get_xml_node(self):
|
|
schemalist_node = ET.Element('schemalist')
|
|
if self.gettext_domain:
|
|
schemalist_node.set('gettext-domain', self.gettext_domain)
|
|
for schema in self.schemas:
|
|
for schema_node in schema.get_xml_nodes():
|
|
schemalist_node.append(schema_node)
|
|
return schemalist_node
|
|
|
|
|
|
######################################
|
|
|
|
|
|
# Note: defined before GSettingsSchema because GSettingsSchema is a subclass.
|
|
# But from a schema point of view, GSettingsSchema is a parent of
|
|
# GSettingsSchemaDir.
|
|
class GSettingsSchemaDir:
|
|
|
|
def __init__(self):
|
|
self.name = None
|
|
self.gettext_domain = None
|
|
self.dirs = []
|
|
self.keys = []
|
|
|
|
def get_simple_string(self, current_indent):
|
|
content = self._get_simple_string_for_content(current_indent)
|
|
if not content:
|
|
return ''
|
|
|
|
result = ''
|
|
result += '%schild %s:\n' % (current_indent, self.name)
|
|
if self.gettext_domain:
|
|
result += '%sgettext-domain: %s\n' % (current_indent + GSETTINGS_SIMPLE_SCHEMA_INDENT, self.gettext_domain)
|
|
result += '\n'
|
|
result += content
|
|
return result
|
|
|
|
def _get_simple_string_for_content(self, current_indent):
|
|
need_empty_line = False
|
|
result = ''
|
|
|
|
for key in self.keys:
|
|
result += key.get_simple_string(current_indent + GSETTINGS_SIMPLE_SCHEMA_INDENT)
|
|
need_empty_line = True
|
|
|
|
for dir in self.dirs:
|
|
if need_empty_line:
|
|
result += '\n'
|
|
result += dir.get_simple_string(current_indent + GSETTINGS_SIMPLE_SCHEMA_INDENT)
|
|
if result:
|
|
need_empty_line = True
|
|
|
|
return result
|
|
|
|
def get_xml_nodes(self, parent_id, parent_path):
|
|
id = '%s.%s' % (parent_id, self.name)
|
|
path = '%s%s/' % (parent_path, self.name)
|
|
|
|
(node, children) = self._get_xml_nodes_for_content(id, path)
|
|
if node is None:
|
|
return []
|
|
|
|
node.set('id', id)
|
|
node.set('path', path)
|
|
nodes = [ node ]
|
|
nodes.extend(children)
|
|
|
|
return nodes
|
|
|
|
def _get_xml_nodes_for_content(self, id, path):
|
|
if not self.keys and not self.dirs:
|
|
return (None, None)
|
|
|
|
children = []
|
|
|
|
schema_node = ET.Element('schema')
|
|
if self.gettext_domain:
|
|
schema_node.set('gettext-domain', self.gettext_domain)
|
|
|
|
for key in self.keys:
|
|
key_node = key.get_xml_node()
|
|
schema_node.append(key_node)
|
|
for dir in self.dirs:
|
|
dir_nodes = dir.get_xml_nodes(id, path)
|
|
children.extend(dir_nodes)
|
|
child_node = ET.SubElement(schema_node, 'child')
|
|
child_node.set('name', dir.name)
|
|
child_node.set('schema', '%s.%s' % (id, dir.name))
|
|
|
|
return (schema_node, children)
|
|
|
|
|
|
######################################
|
|
|
|
|
|
class GSettingsSchema(GSettingsSchemaDir):
|
|
|
|
def __init__(self):
|
|
self.id = None
|
|
self.path = None
|
|
self.gettext_domain = None
|
|
self.dirs = []
|
|
self.keys = []
|
|
|
|
def simplify(self):
|
|
while len(self.dirs) == 1 and not self.keys:
|
|
dir = self.dirs[0]
|
|
self.dirs = dir.dirs
|
|
self.keys = dir.keys
|
|
if self.path:
|
|
self.path += dir.name + '/'
|
|
|
|
def get_simple_string(self):
|
|
if not self.dirs and not self.keys:
|
|
return ''
|
|
|
|
result = ''
|
|
result += 'schema %s:\n' % self.id
|
|
if self.gettext_domain:
|
|
result += '%sgettext-domain: %s\n' % (GSETTINGS_SIMPLE_SCHEMA_INDENT, self.gettext_domain)
|
|
if self.path:
|
|
result += '%spath: %s\n' % (GSETTINGS_SIMPLE_SCHEMA_INDENT, self.path)
|
|
result += '\n'
|
|
result += self._get_simple_string_for_content('')
|
|
|
|
return result
|
|
|
|
def get_xml_nodes(self):
|
|
if not self.dirs and not self.keys:
|
|
return []
|
|
|
|
(node, children) = self._get_xml_nodes_for_content(self.id, self.path or '')
|
|
if node is None:
|
|
return []
|
|
|
|
node.set('id', self.id)
|
|
if self.path:
|
|
node.set('path', self.path)
|
|
|
|
nodes = [ node ]
|
|
nodes.extend(children)
|
|
|
|
return nodes
|
|
|
|
|
|
######################################
|
|
|
|
|
|
class GSettingsSchemaKey:
|
|
|
|
def __init__(self):
|
|
self.name = None
|
|
self.type = None
|
|
self.default = None
|
|
self.typed_default = None
|
|
self.l10n = None
|
|
self.l10n_context = None
|
|
self.summary = None
|
|
self.description = None
|
|
self.choices = None
|
|
self.range = None
|
|
|
|
def fill(self, name, type, default, typed_default, l10n, l10n_context, summary, description, choices, range):
|
|
self.name = name
|
|
self.type = type
|
|
self.default = default
|
|
self.typed_default = typed_default
|
|
self.l10n = l10n
|
|
self.l10n_context = l10n_context
|
|
self.summary = summary
|
|
self.description = description
|
|
self.choices = choices
|
|
self.range = range
|
|
|
|
def _has_range_choices(self):
|
|
return self.choices is not None and self.type in TYPES_FOR_CHOICES
|
|
|
|
def _has_range_minmax(self):
|
|
return self.range is not None and len(self.range) == 2 and self.type in TYPES_FOR_RANGE
|
|
|
|
def get_simple_string(self, current_indent):
|
|
# FIXME: kill this when we'll have python bindings for GVariant. Right
|
|
# now, every simple format schema we'll generate has to have an
|
|
# explicit type since we can't guess the type later on when converting
|
|
# to XML.
|
|
self.typed_default = '@%s %s' % (self.type, self.default)
|
|
|
|
result = ''
|
|
result += '%skey %s = %s\n' % (current_indent, self.name, self.typed_default or self.default)
|
|
current_indent += GSETTINGS_SIMPLE_SCHEMA_INDENT
|
|
if self.l10n:
|
|
l10n = self.l10n
|
|
if self.l10n_context:
|
|
l10n += ' %s' % self.l10n_context
|
|
result += '%sl10n: %s\n' % (current_indent, l10n)
|
|
if self.summary:
|
|
result += '%ssummary: %s\n' % (current_indent, self.summary)
|
|
if self.description:
|
|
result += '%sdescription: %s\n' % (current_indent, self.description)
|
|
if self._has_range_choices():
|
|
result += '%schoices: %s\n' % (current_indent, ', '.join(self.choices))
|
|
elif self._has_range_minmax():
|
|
result += '%srange: %s\n' % (current_indent, '%s..%s' % self.range)
|
|
return result
|
|
|
|
def get_xml_node(self):
|
|
key_node = ET.Element('key')
|
|
key_node.set('name', self.name)
|
|
key_node.set('type', self.type)
|
|
default_node = ET.SubElement(key_node, 'default')
|
|
default_node.text = self.default
|
|
if self.l10n:
|
|
default_node.set('l10n', self.l10n)
|
|
if self.l10n_context:
|
|
default_node.set('context', self.l10n_context)
|
|
if self.summary:
|
|
summary_node = ET.SubElement(key_node, 'summary')
|
|
summary_node.text = self.summary
|
|
if self.description:
|
|
description_node = ET.SubElement(key_node, 'description')
|
|
description_node.text = self.description
|
|
if self._has_range_choices():
|
|
choices_node = ET.SubElement(key_node, 'choices')
|
|
for choice in self.choices:
|
|
choice_node = ET.SubElement(choices_node, 'choice')
|
|
choice_node.set('value', choice)
|
|
elif self._has_range_minmax():
|
|
(min, max) = self.range
|
|
range_node = ET.SubElement(key_node, 'range')
|
|
min_node = ET.SubElement(range_node, 'min')
|
|
if min:
|
|
min_node.text = min
|
|
max_node = ET.SubElement(range_node, 'max')
|
|
if max:
|
|
max_node.text = max
|
|
return key_node
|
|
|
|
|
|
######################################
|
|
|
|
|
|
def map_gconf_type_to_variant_type(gconftype, gconfsubtype):
|
|
typemap = { 'string': 's', 'int': 'i', 'float': 'f', 'bool': 'b', 'list': 'a' }
|
|
result = typemap[gconftype]
|
|
if gconftype == 'list':
|
|
result = result + typemap[gconfsubtype]
|
|
return result
|
|
|
|
|
|
class GConfSchema:
|
|
|
|
def __init__(self, node):
|
|
locale_node = node.find('locale')
|
|
|
|
self.key = node.find('key').text
|
|
self.type = node.find('type').text
|
|
if self.type == 'list':
|
|
self.list_type = node.find('list_type').text
|
|
else:
|
|
self.list_type = None
|
|
self.varianttype = map_gconf_type_to_variant_type(self.type, self.list_type)
|
|
|
|
applyto_node = node.find('applyto')
|
|
if applyto_node is not None:
|
|
self.applyto = node.find('applyto').text
|
|
self.applyto.strip()
|
|
self.keyname = self.applyto[self.applyto.rfind('/')+1:]
|
|
self.prefix = self.applyto[:self.applyto.rfind('/')+1]
|
|
else:
|
|
self.applyto = None
|
|
self.key.strip()
|
|
self.keyname = self.key[self.key.rfind('/')+1:]
|
|
self.prefix = self.key[:self.key.rfind('/')+1]
|
|
self.prefix = os.path.normpath(self.prefix)
|
|
|
|
try:
|
|
self.default = locale_node.find('default').text
|
|
self.localized = 'messages'
|
|
except:
|
|
try:
|
|
self.default = node.find('default').text
|
|
self.localized = None
|
|
except:
|
|
raise GSettingsSchemaConvertException('No default value for key \'%s\'. A default value is always required in GSettings schemas.' % self.applyto or self.key)
|
|
self.typed_default = None
|
|
|
|
self.short = self._get_value_with_locale(node, locale_node, 'short')
|
|
self.long = self._get_value_with_locale(node, locale_node, 'long')
|
|
|
|
self.short = self._oneline(self.short)
|
|
self.long = self._oneline(self.long)
|
|
|
|
# Fix the default to be parsable by GVariant
|
|
if self.type == 'string':
|
|
if not self.default:
|
|
self.default = '\'\''
|
|
else:
|
|
self.default.replace('\'', '\\\'')
|
|
self.default = '\'%s\'' % self.default
|
|
elif self.type == 'bool':
|
|
self.default = self.default.lower()
|
|
elif self.type == 'list':
|
|
l = self.default.strip()
|
|
if not (l[0] == '[' and l[-1] == ']'):
|
|
raise GSettingsSchemaConvertException('Cannot parse default list value \'%s\' for key \'%s\'.' % (self.default, self.applyto or self.key))
|
|
values = l[1:-1].strip()
|
|
if not values:
|
|
self.typed_default = '@%s []' % self.varianttype
|
|
elif self.list_type == 'string':
|
|
items = [ item.strip() for item in values.split(',') ]
|
|
items = [ item.replace('\'', '\\\'') for item in items ]
|
|
values = ', '.join([ '\'%s\'' % item for item in items ])
|
|
self.default = '[ %s ]' % values
|
|
|
|
def _get_value_with_locale(self, node, locale_node, element):
|
|
element_node = None
|
|
if locale_node is not None:
|
|
element_node = locale_node.find(element)
|
|
if element_node is None:
|
|
element_node = node.find(element)
|
|
if element_node is not None:
|
|
return element_node.text
|
|
else:
|
|
return None
|
|
|
|
def _oneline(self, s):
|
|
lines = s.splitlines()
|
|
result = ''
|
|
for line in lines:
|
|
result += ' ' + line.lstrip()
|
|
return result.strip()
|
|
|
|
def get_gsettings_schema_key(self):
|
|
key = GSettingsSchemaKey()
|
|
key.fill(self.keyname, self.varianttype, self.default, self.typed_default, self.localized, self.keyname, self.short, self.long, None, None)
|
|
return key
|
|
|
|
|
|
######################################
|
|
|
|
|
|
class SimpleSchemaParser:
|
|
|
|
allowed_tokens = {
|
|
'' : [ 'gettext-domain', 'schema' ],
|
|
'gettext-domain' : [ ],
|
|
'schema' : [ 'gettext-domain', 'path', 'child', 'key' ],
|
|
'path' : [ ],
|
|
'child' : [ 'gettext-domain', 'child', 'key' ],
|
|
'key' : [ 'l10n', 'summary', 'description', 'choices', 'range' ],
|
|
'l10n' : [ ],
|
|
'summary' : [ ],
|
|
'description' : [ ],
|
|
'choices' : [ ],
|
|
'range' : [ ]
|
|
}
|
|
|
|
allowed_separators = [ ':', '=' ]
|
|
|
|
def __init__(self, file):
|
|
self.file = file
|
|
|
|
self.root = GSettingsSchemaRoot()
|
|
|
|
# this is just a convenient helper to remove the leading indentation
|
|
# that should be common to all lines
|
|
self.leading_indent = None
|
|
|
|
self.indent_stack = []
|
|
self.token_stack = []
|
|
self.object_stack = [ self.root ]
|
|
|
|
self.previous_token = None
|
|
self.current_token = None
|
|
self.unparsed_line = ''
|
|
|
|
def _eat_indent(self):
|
|
line = self.unparsed_line
|
|
i = 0
|
|
buf = ''
|
|
previous_max_index = len(self.indent_stack) - 1
|
|
index = -1
|
|
|
|
while i < len(line) - 1 and line[i].isspace():
|
|
buf += line[i]
|
|
i += 1
|
|
if previous_max_index > index:
|
|
if buf == self.indent_stack[index + 1]:
|
|
buf = ''
|
|
index += 1
|
|
continue
|
|
elif self.indent_stack[index + 1].startswith(buf):
|
|
continue
|
|
else:
|
|
raise GSettingsSchemaConvertException('Inconsistent indentation.')
|
|
else:
|
|
continue
|
|
|
|
if buf and previous_max_index > index:
|
|
raise GSettingsSchemaConvertException('Inconsistent indentation.')
|
|
elif buf and previous_max_index <= index:
|
|
self.indent_stack.append(buf)
|
|
elif previous_max_index > index:
|
|
self.indent_stack = self.indent_stack[:index + 1]
|
|
|
|
self.unparsed_line = line[i:]
|
|
|
|
def _parse_word(self):
|
|
line = self.unparsed_line
|
|
i = 0
|
|
while i < len(line) and not line[i].isspace() and not line[i] in self.allowed_separators:
|
|
i += 1
|
|
self.unparsed_line = line[i:]
|
|
return line[:i]
|
|
|
|
def _word_to_token(self, word):
|
|
lower = word.lower()
|
|
if lower and lower in self.allowed_tokens.keys():
|
|
return lower
|
|
raise GSettingsSchemaConvertException('\'%s\' is not a valid token.' % lower)
|
|
|
|
def _token_allow_separator(self):
|
|
return self.current_token in [ 'gettext-domain', 'path', 'l10n', 'summary', 'description', 'choices', 'range' ]
|
|
|
|
def _parse_name_without_separator(self):
|
|
line = self.unparsed_line
|
|
if line[-1] in self.allowed_separators:
|
|
line = line[:-1].strip()
|
|
self.unparsed_line = ''
|
|
# FIXME: we could check there's no space
|
|
return line
|
|
|
|
def _parse_key(self):
|
|
line = self.unparsed_line
|
|
|
|
split = False
|
|
for separator in self.allowed_separators:
|
|
items = line.split(separator)
|
|
if len(items) == 2:
|
|
split = True
|
|
break
|
|
|
|
if not split:
|
|
raise GSettingsSchemaConvertException('Key \'%s\' cannot be parsed.' % line)
|
|
|
|
# FIXME: we could check there's no space
|
|
name = items[0].strip()
|
|
type = ''
|
|
value = items[1].strip()
|
|
if value[0] == '@':
|
|
i = 1
|
|
while not value[i].isspace():
|
|
i += 1
|
|
type = value[1:i]
|
|
value = value[i:].strip()
|
|
if not value:
|
|
raise GSettingsSchemaConvertException('No value specified for key \'%s\' (\'%s\').' % (name, line))
|
|
|
|
self.unparsed_line = ''
|
|
|
|
object = GSettingsSchemaKey()
|
|
object.name = name
|
|
object.type = type
|
|
object.default = value
|
|
|
|
return object
|
|
|
|
def _parse_l10n(self):
|
|
line = self.unparsed_line
|
|
|
|
items = [ item.strip() for item in line.split(' ') if item.strip() ]
|
|
if not items:
|
|
self.unparsed_line = ''
|
|
return (None, None)
|
|
if len(items) == 1:
|
|
self.unparsed_line = ''
|
|
return (items[0], None)
|
|
if len(items) == 2:
|
|
self.unparsed_line = ''
|
|
return (items[0], items[1])
|
|
|
|
raise GSettingsSchemaConvertException('Localization \'%s\' cannot be parsed.' % line)
|
|
|
|
def _parse_choices(self, object):
|
|
if object.type in TYPES_FOR_CHOICES:
|
|
line = self.unparsed_line
|
|
result = [ item.strip() for item in line.split(',') ]
|
|
self.unparsed_line = ''
|
|
return result
|
|
else:
|
|
raise GSettingsSchemaConvertException('Key \'%s\' of type \'%s\' cannot have choices.' % (object.name, object.type))
|
|
|
|
def _parse_range(self, object):
|
|
minmax = None
|
|
if object.type in TYPES_FOR_RANGE:
|
|
line = self.unparsed_line
|
|
minmax = [ item.strip() for item in line.split('..') ]
|
|
if len(minmax) != 2:
|
|
raise GSettingsSchemaConvertException('Range \'%s\' cannot be parsed.' % line)
|
|
# FIXME: we'll be able to check min < max once we can convert the
|
|
# values with GVariant
|
|
self.unparsed_line = ''
|
|
return tuple(minmax)
|
|
else:
|
|
raise GSettingsSchemaConvertException('Key \'%s\' of type \'%s\' cannot have a range.' % (object.name, object.type))
|
|
|
|
def parse_line(self, line):
|
|
# make sure that lines with only spaces are ignored and considered as
|
|
# empty lines
|
|
self.unparsed_line = line.rstrip()
|
|
|
|
# ignore empty line
|
|
if not self.unparsed_line:
|
|
return
|
|
|
|
# look at the indentation to know where we should be
|
|
self._eat_indent()
|
|
if self.leading_indent is None:
|
|
self.leading_indent = len(self.indent_stack)
|
|
|
|
# ignore comments
|
|
if self.unparsed_line[0] == '#':
|
|
return
|
|
|
|
word = self._parse_word()
|
|
if self.current_token:
|
|
self.previous_token = self.current_token
|
|
self.current_token = self._word_to_token(word)
|
|
self.unparsed_line = self.unparsed_line.lstrip()
|
|
|
|
allow_separator = self._token_allow_separator()
|
|
if len(self.unparsed_line) > 0 and self.unparsed_line[0] in self.allowed_separators:
|
|
if allow_separator:
|
|
self.unparsed_line = self.unparsed_line[1:].lstrip()
|
|
else:
|
|
raise GSettingsSchemaConvertException('Separator \'%s\' is not allowed after \'%s\'.' % (self.unparsed_line[0], self.current_token))
|
|
|
|
new_level = len(self.indent_stack) - self.leading_indent
|
|
old_level = len(self.token_stack)
|
|
|
|
if new_level > old_level + 1:
|
|
raise GSettingsSchemaConvertException('Internal error: stacks not in sync.')
|
|
elif new_level <= old_level:
|
|
self.token_stack = self.token_stack[:new_level]
|
|
# we always have the root
|
|
self.object_stack = self.object_stack[:new_level + 1]
|
|
|
|
if new_level == 0:
|
|
parent_token = ''
|
|
else:
|
|
parent_token = self.token_stack[-1]
|
|
|
|
# there's new indentation, but no token is allowed under the previous
|
|
# one
|
|
if new_level == old_level + 1 and self.previous_token != parent_token:
|
|
raise GSettingsSchemaConvertException('\'%s\' is not allowed under \'%s\'.' % (self.current_token, self.previous_token))
|
|
|
|
if not self.current_token in self.allowed_tokens[parent_token]:
|
|
if parent_token:
|
|
error = '\'%s\' is not allowed under \'%s\'.' % (self.current_token, parent_token)
|
|
else:
|
|
error = '\'%s\' is not allowed at the root level.' % self.current_token
|
|
raise GSettingsSchemaConvertException(error)
|
|
|
|
current_object = self.object_stack[-1]
|
|
|
|
new_object = None
|
|
if self.current_token == 'gettext-domain':
|
|
current_object.gettext_domain = self.unparsed_line
|
|
elif self.current_token == 'schema':
|
|
name = self._parse_name_without_separator()
|
|
new_object = GSettingsSchema()
|
|
new_object.id = name
|
|
current_object.schemas.append(new_object)
|
|
elif self.current_token == 'path':
|
|
current_object.path = self.unparsed_line
|
|
elif self.current_token == 'child':
|
|
name = self._parse_name_without_separator()
|
|
new_object = GSettingsSchemaDir()
|
|
new_object.name = name
|
|
current_object.dirs.append(new_object)
|
|
elif self.current_token == 'key':
|
|
new_object = self._parse_key()
|
|
current_object.keys.append(new_object)
|
|
elif self.current_token == 'l10n':
|
|
(current_object.l10n, current_object.l10n_context) = self._parse_l10n()
|
|
elif self.current_token == 'summary':
|
|
current_object.summary = self.unparsed_line
|
|
elif self.current_token == 'description':
|
|
current_object.description = self.unparsed_line
|
|
elif self.current_token == 'choices':
|
|
current_object.choices = self._parse_choices(current_object)
|
|
elif self.current_token == 'range':
|
|
current_object.range = self._parse_range(current_object)
|
|
|
|
if new_object:
|
|
self.token_stack.append(self.current_token)
|
|
self.object_stack.append(new_object)
|
|
|
|
def parse(self):
|
|
f = open(self.file, 'r')
|
|
lines = [ line[:-1] for line in f.readlines() ]
|
|
f.close()
|
|
|
|
try:
|
|
current_line_nb = 0
|
|
for line in lines:
|
|
current_line_nb += 1
|
|
self.parse_line(line)
|
|
except GSettingsSchemaConvertException, e:
|
|
raise GSettingsSchemaConvertException('%s:%s: %s' % (os.path.basename(self.file), current_line_nb, e))
|
|
|
|
return self.root
|
|
|
|
|
|
######################################
|
|
|
|
|
|
def read_gconf_schema(gconf_schema_file, default_schema_id):
|
|
gsettings_schema_root = GSettingsSchemaRoot()
|
|
|
|
default_schema_id_count = 0
|
|
|
|
gconfschemafile_node = ET.parse(gconf_schema_file).getroot()
|
|
for schemalist_node in gconfschemafile_node.findall('schemalist'):
|
|
for schema_node in schemalist_node.findall('schema'):
|
|
gconf_schema = GConfSchema(schema_node)
|
|
|
|
schemas_only = (gconf_schema.applyto is not None)
|
|
|
|
dirpath = gconf_schema.prefix
|
|
if dirpath[0] != '/':
|
|
raise GSettingsSchemaConvertException('Key \'%s\' has a relative path. There is no relative path in GSettings schemas.' % gconf_schema.applyto or gconf_schema.key)
|
|
|
|
# remove leading 'schemas/' for schemas-only keys
|
|
if schemas_only and dirpath.startswith('/schemas/'):
|
|
dirpath = dirpath[len('/schemas'):]
|
|
|
|
if len(dirpath) == 1:
|
|
raise GSettingsSchemaConvertException('Key \'%s\' is a toplevel key. Toplevel keys are not accepted in GSettings schemas.' % gconf_schema.applyto or gconf_schema.key)
|
|
|
|
# remove trailing slash because we'll split the string
|
|
if dirpath[-1] == '/':
|
|
dirpath = dirpath[:-1]
|
|
# and also remove leading slash when splitting
|
|
hierarchy = dirpath[1:].split('/')
|
|
|
|
# we don't want to put apps/ and desktop/ keys in the same schema,
|
|
# so we have a first step where we make sure to create a new schema
|
|
# to avoid this case if necessary
|
|
gsettings_schema = None
|
|
for schema in gsettings_schema_root.schemas:
|
|
if schemas_only:
|
|
schema_path = schema.path
|
|
else:
|
|
schema_path = schema._hacky_path
|
|
if dirpath.startswith(schema_path):
|
|
gsettings_schema = schema
|
|
break
|
|
if not gsettings_schema:
|
|
gsettings_schema = GSettingsSchema()
|
|
if default_schema_id:
|
|
gsettings_schema.id = default_schema_id
|
|
if default_schema_id_count > 0:
|
|
gsettings_schema.id += '.FIXME-%s' % default_schema_id_count
|
|
default_schema_id_count += 1
|
|
else:
|
|
gsettings_schema.id = 'FIXME'
|
|
if schemas_only:
|
|
gsettings_schema.path = '/' + hierarchy[0] + '/'
|
|
else:
|
|
gsettings_schema._hacky_path = '/' + hierarchy[0] + '/'
|
|
gsettings_schema_root.schemas.append(gsettings_schema)
|
|
|
|
# we create all the subdirs that lead to this key
|
|
gsettings_dir = gsettings_schema
|
|
for item in hierarchy[1:]:
|
|
subdir = None
|
|
for dir in gsettings_dir.dirs:
|
|
if dir.name == item:
|
|
subdir = dir
|
|
break
|
|
if not subdir:
|
|
subdir = GSettingsSchemaDir()
|
|
subdir.name = item
|
|
gsettings_dir.dirs.append(subdir)
|
|
gsettings_dir = subdir
|
|
|
|
# we have the final directory, so we can put the key there
|
|
gsettings_dir.keys.append(gconf_schema.get_gsettings_schema_key())
|
|
|
|
gsettings_schema_root.simplify()
|
|
|
|
return gsettings_schema_root
|
|
|
|
|
|
######################################
|
|
|
|
|
|
def main(args):
|
|
parser = optparse.OptionParser()
|
|
|
|
parser.add_option("-o", "--output", dest="output",
|
|
help="output file")
|
|
parser.add_option("-g", "--gconf", action="store_true", dest="gconf",
|
|
default=False, help="convert a gconf schema file")
|
|
parser.add_option("-i", "--schema-id", dest="schema_id",
|
|
help="default schema ID to use when converting gconf schema file")
|
|
parser.add_option("-s", "--simple", action="store_true", dest="simple",
|
|
default=False, help="use the simple schema format as output (only for gconf schema conversion)")
|
|
parser.add_option("-x", "--xml", action="store_true", dest="xml",
|
|
default=False, help="use the xml schema format as output")
|
|
parser.add_option("-f", "--force", action="store_true", dest="force",
|
|
default=False, help="overwrite output file if already existing")
|
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
if len(args) < 1:
|
|
print >> sys.stderr, 'Need a filename to work on.'
|
|
return 1
|
|
elif len(args) > 1:
|
|
print >> sys.stderr, 'Too many arguments.'
|
|
return 1
|
|
|
|
if options.simple and options.xml:
|
|
print >> sys.stderr, 'Too many output formats requested.'
|
|
return 1
|
|
if not options.simple and not options.xml:
|
|
if options.gconf:
|
|
options.simple = True
|
|
else:
|
|
options.xml = True
|
|
|
|
if not options.gconf and options.schema_id:
|
|
print >> sys.stderr, 'Default schema ID can only be specified when converting a gconf schema.'
|
|
return 1
|
|
|
|
argfile = os.path.expanduser(args[0])
|
|
if not os.path.exists(argfile):
|
|
print >> sys.stderr, '\'%s\' does not exist.' % argfile
|
|
return 1
|
|
|
|
if options.output:
|
|
options.output = os.path.expanduser(options.output)
|
|
|
|
try:
|
|
if options.output and not options.force and os.path.exists(options.output):
|
|
raise GSettingsSchemaConvertException('\'%s\' already exists. Use --force to overwrite it.' % options.output)
|
|
|
|
if options.gconf:
|
|
try:
|
|
schema_root = read_gconf_schema(argfile, options.schema_id)
|
|
except SyntaxError, e:
|
|
raise GSettingsSchemaConvertException('\'%s\' does not look like a valid gconf schema file: %s' % (argfile, e))
|
|
else:
|
|
parser = SimpleSchemaParser(argfile)
|
|
schema_root = parser.parse()
|
|
|
|
if options.xml:
|
|
node = schema_root.get_xml_node()
|
|
tree = ET.ElementTree(node)
|
|
try:
|
|
output = ET.tostring(tree, pretty_print = True)
|
|
except TypeError:
|
|
# pretty_print only works with lxml
|
|
output = ET.tostring(tree)
|
|
else:
|
|
output = schema_root.get_simple_string()
|
|
|
|
if not options.output:
|
|
sys.stdout.write(output)
|
|
else:
|
|
try:
|
|
fout = open(options.output, 'w')
|
|
fout.write(output)
|
|
fout.close()
|
|
except GSettingsSchemaConvertException, e:
|
|
fout.close()
|
|
if os.path.exists(options.output):
|
|
os.unlink(options.output)
|
|
raise e
|
|
|
|
except GSettingsSchemaConvertException, e:
|
|
print >> sys.stderr, '%s' % e
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
res = main(sys.argv)
|
|
sys.exit(res)
|
|
except KeyboardInterrupt:
|
|
pass
|