"""Convert org mode AST nodes to JSON."""
from collections.abc import Sequence, Mapping
from .base import OrgConverterBase
from ..ast import dispatch_node_type, OrgNode
from ..agenda import OrgAgendaItem
[docs]class OrgJsonConverter(OrgConverterBase):
DEFAULT_CONFIG = {
'object_type_key': '$$data_type',
'include_agenda_headline': True,
'include_agenda_extra': True,
**OrgConverterBase.DEFAULT_CONFIG
}
[docs] def make_object(self, type_, data):
key = self.config.get('object_type_key')
if key is not None:
assert key not in data
return {key: type_, **data}
return data
@dispatch_node_type()
def _convert_node(self, node, ctx):
properties = self._convert_properties(node, ctx)
contents = self._convert_contents(node, ctx)
return self.make_object('node', {
'type': node.type.name,
'properties': self.make_object('mapping', properties),
'contents': contents,
})
@_convert_node.register(['org-data', 'headline'])
def _convert_outline_node(self, node, ctx):
obj = self._convert_node.default(node, ctx)
obj['title_plain'] = self._convert(node.title, ctx)
obj['title'] = self._convert(node.title, ctx)
return obj
@dispatch_node_type()
def _convert_properties(self, node, ctx):
return {
key: self._convert(value, ctx)
for key, value in node.props.items()
}
@dispatch_node_type()
def _convert_contents(self, node, ctx):
converted = []
for item in node.contents:
c = self._convert_contents_item(node, item, ctx)
if c is not None:
converted.append(c)
return converted
@dispatch_node_type()
def _convert_contents_item(self, node, item, ctx):
return self._convert(item, ctx)
def _convert(self, value, ctx):
if isinstance(value, OrgNode):
return self._convert_node(value, ctx)
if isinstance(value, (str, int, float, bool, type(None))):
return value
if isinstance(value, Sequence):
return [self._convert(item, ctx) for item in value]
if isinstance(value, Mapping):
return self._convert_mapping(value, ctx)
if isinstance(value, OrgAgendaItem):
return self._convert_agenda_item(value, ctx)
raise TypeError("Can't convert object of type %r" % type(value))
def _convert_mapping(self, value, ctx):
converted = {}
for k, v in value.items():
assert isinstance(k, str)
converted[k] = self._convert(v, ctx)
return self.make_object('mapping', converted)
def _convert_agenda_item(self, item, ctx):
noconvert = {
'text_plain', 'type', 'keyword', 'headline_path', 'file', 'priority',
'view_priority', 'tags', 'category',
}
obj = {
'text': self._convert(item.text, ctx),
'deadline': self._convert_agenda_deadline(item.deadline, ctx),
}
obj.update({a: getattr(item, a) for a in noconvert})
if self.config['include_agenda_headline']:
obj['headline'] = self._convert_node(item.headline, ctx)
if self.config['include_agenda_extra']:
obj['extra'] = self._convert_mapping(item.extra, ctx)
return self.make_object('agenda-item', obj)
def _convert_agenda_deadline(self, value, ctx):
if value is None:
return None
return self.make_object('deadline', {
'begin': value.begin,
'end': value.end,
})
[docs]def to_json(node, **kwargs):
converter = OrgJsonConverter(**kwargs)
return converter.convert(node)