import os
from pathlib import Path
import json
from .emacs import Emacs, E
from .ast import assign_outline_ids
from .io import org_node_from_json, agenda_item_from_json
[docs]class OrgDirectory:
"""The directory where the user's org files are kept.
path : pathlib.Path
Absolute path to org directory.
"""
def __init__(self, path):
self.path = Path(path).expanduser().absolute()
[docs] def get_abs_path(self, path):
"""Get absolute path from path relative to org directory.
Path will be normalized with ".." components removed.
Returns
-------
pathlib.Path
Raises
------
ValueError
If the path is not relative or is outside of the org directory
(can happen if it contains ".." components).
"""
if os.path.isabs(path):
raise ValueError('Paths should be relative to org directory.')
path = self.path / os.path.normpath(path)
if '..' in path.parts:
raise ValueError('Path must be contained within org directory')
return path
[docs] def list_files(self, path=None, recursive=False, hidden=False):
"""List org files within the org directory.
Paths are relative to the org directory.
Parameters
----------
path : str or pathlib.Path
Optional subdirectory to search through.
recursive : bool
Recurse through subdirectories.
hidden : bool
Include hidden files.
Returns
-------
Iterator over :class:`pathlib.Path` instances.
"""
abspath = self.path if path is None else self.get_abs_path(path)
pattern = '**/*.org' if recursive else '*.org'
for file in abspath.glob(pattern):
if hidden or not file.name.startswith('.'):
yield file.relative_to(self.path)
def _get_org_file(self, path):
"""Convert path to absolute, ensuring it is an org file within the directory.
Parameters
----------
path : str or pathlib.Path
Returns
-------
pathlib.Path
Raises
------
ValueError
If path is not within org directory or does not have .org extension.
OSError
If path is not a file.
"""
path = self.get_abs_path(path)
if not path.is_file():
raise OSError('%s is not a file' % path)
if path.suffix != '.org':
raise ValueError('Must be an org file')
return path
[docs]class Org:
"""Interface to org mode.
Attributes
----------
emacs : pyorg.emacs.Emacs
orgdir : .OrgDirectory
Directory org files are read from.
"""
def __init__(self, emacs=None, orgdir=None):
"""
Parameters
----------
emacs : pyorg.emacs.Emacs
orgdir : pathlib.Path
Absolute path to org directory.
"""
self.emacs = emacs or Emacs()
self._setup_emacs()
if orgdir is None:
orgdir = self.emacs.getresult('org-directory')
self.orgdir = OrgDirectory(orgdir)
def _setup_emacs(self):
"""Perform initial setup with Emacs."""
self.emacs.eval(E.require(E.Q('org-json')))
def _read_file_direct(self, file):
"""Read in JSON data for org file directly from Emacs."""
el = E.with_current_buffer(
E.find_file_noselect(str(file)),
E.org_json_encode_node(E.org_element_parse_buffer())
)
result = self.emacs.getresult(el, encode=False)
return json.loads(result)
[docs] def read_org_file_direct(self, path, raw=False):
"""Read and parse an org file directly from Emacs.
Always reads the current file and does not use cached data, or perform
any additional processing other than parsing.
Parameters
----------
path : str or pathlib.Path
File path relative to org directory.
raw : bool
Don't parse and just return raw JSON exported from Emacs.
Returns
-------
pyorg.ast.OrgNode or dict
Raises
------
FileNotFoundError
"""
path = self.orgdir._get_org_file(path)
data = self._read_file_direct(path)
return data if raw else org_node_from_json(data)
[docs] def read_org_file(self, path, assign_ids=True):
"""Read and parse an org file.
Parameters
----------
path : str or pathlib.Path
File path relative to org directory.
assign_ids : bool
Assign IDs to outline nodes. See :func:`pyorg.ast.assign_outline_ids`.
Returns
-------
pyorg.ast.OrgNode
Raises
------
FileNotFoundError
"""
node = self.read_org_file_direct(path)
if assign_ids:
assign_outline_ids(node)
return node
[docs] def open_org_file(self, path, focus=False):
"""Open an org file in the org directory for editing in Emacs.
Parameters
----------
path : str or pathlib.Path
File path relative to org directory.
focus : bool
Switch window/input focus to opened buffer.
"""
path = self.orgdir._get_org_file(path)
el = E.find_file(str(path))
if focus:
el = [el, E.x_focus_frame(None)]
self.emacs.eval(el)
[docs] def agenda(self, key='t', raw=False):
"""TODO Read agenda information.
Parameters
----------
key : str
TODO
Returns
-------
list[dict]
"""
el = E.org_json_with_agenda_buffer(
key,
E.org_json_encode_agenda_buffer()
)
result = self.emacs.getresult(el, encode=False)
data = json.loads(result)
if raw:
return data
return list(map(agenda_item_from_json, data))