Compare commits

..

No commits in common. "develop" and "dev/render-as-dom" have entirely different histories.

19 changed files with 327 additions and 1512 deletions

View File

@ -1,53 +0,0 @@
name: Testing
# run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on: [push]
jobs:
pytest:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v3
- run: apt-get update && apt-get install -y python3-pip
- run: pip install --break-system-package -e .
- run: pip install --break-system-package pytest
- run: pytest
mypy:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v3
- run: apt-get update && apt-get install -y python3-pip
- run: pip install --break-system-package -e .
- run: pip install --break-system-package mypy
- run: mypy org_rw --check-untyped-defs
style-formatting:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v3
- run: apt-get update && apt-get install -y python3-pip
- run: pip install --break-system-package -e .
- run: pip install --break-system-package black
- run: black --check .
style-sorted-imports:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v3
- run: apt-get update && apt-get install -y python3-pip
- run: pip install --break-system-package -e .
- run: pip install --break-system-package isort
- run: isort --profile black --check .
stability-extra-test:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v3
- run: apt-get update && apt-get install -y git-core python3-pip
- run: pip install --break-system-package -e .
- run: bash extra-tests/check_all.sh

3
.gitignore vendored
View File

@ -139,6 +139,3 @@ dmypy.json
# Cython debug symbols # Cython debug symbols
cython_debug/ cython_debug/
# Files for PyPI publishing
README.md

View File

@ -7,12 +7,6 @@ A python library to parse, modify and save Org-mode files.
- Modify these data and write it back to disk. - Modify these data and write it back to disk.
- Keep the original structure intact (indentation, spaces, format, ...). - Keep the original structure intact (indentation, spaces, format, ...).
** Principles
- Avoid any dependency outside of Python's standard library.
- Don't do anything outside of the scope of parsing/re-serializing Org-mode files.
- *Modification of the original text if there's no change is considered a bug (see [[id:7363ba38-1662-4d3c-9e83-0999824975b7][Known issues]]).*
- Data structures should be exposed as it's read on Emacs's org-mode or when in doubt as raw as possible.
- Data in the objects should be modificable as a way to update the document itself. *Consider this a Object-oriented design.*
** Safety mechanism ** Safety mechanism
As this library is still in early development. Running it over files might As this library is still in early development. Running it over files might
produce unexpected changes on them. For this reason it's heavily recommended to produce unexpected changes on them. For this reason it's heavily recommended to
@ -27,9 +21,6 @@ Also, see [[id:76e77f7f-c9e0-4c83-ad2f-39a5a8894a83][Known issues:Structure modi
not properly stored and can trigger this safety mechanism on a false-positive. not properly stored and can trigger this safety mechanism on a false-positive.
* Known issues * Known issues
:PROPERTIES:
:ID: 7363ba38-1662-4d3c-9e83-0999824975b7
:END:
** Structure modifications ** Structure modifications
:PROPERTIES: :PROPERTIES:
:ID: 76e77f7f-c9e0-4c83-ad2f-39a5a8894a83 :ID: 76e77f7f-c9e0-4c83-ad2f-39a5a8894a83

View File

@ -1,6 +1,3 @@
from typing import List, Optional, Union
class DrawerNode: class DrawerNode:
def __init__(self): def __init__(self):
self.children = [] self.children = []
@ -24,14 +21,6 @@ class ResultsDrawerNode(DrawerNode):
return "<Results: {}>".format(len(self.children)) return "<Results: {}>".format(len(self.children))
class GenericDrawerNode(DrawerNode):
def __init__(self, drawer_name):
self.drawer_name = drawer_name
def __repr__(self):
return "<Drawer{}: {}>".format(self.drawer_name, len(self.children))
class PropertyNode: class PropertyNode:
def __init__(self, key, value): def __init__(self, key, value):
self.key = key self.key = key
@ -49,12 +38,11 @@ class ListGroupNode:
self.children.append(child) self.children.append(child)
def get_raw(self): def get_raw(self):
return "\n".join([c.get_raw() for c in self.children]) return '\n'.join([c.get_raw() for c in self.children])
def __repr__(self): def __repr__(self):
return "<List: {}>".format(len(self.children)) return "<List: {}>".format(len(self.children))
class TableNode: class TableNode:
def __init__(self): def __init__(self):
self.children = [] self.children = []
@ -65,30 +53,21 @@ class TableNode:
def __repr__(self): def __repr__(self):
return "<Table: {}>".format(len(self.children)) return "<Table: {}>".format(len(self.children))
class TableSeparatorRow: class TableSeparatorRow:
def __init__(self, orig=None): def __init__(self, orig=None):
self.orig = orig self.orig = orig
def get_raw(self):
return get_raw_contents(self.orig)
class TableRow: class TableRow:
def __init__(self, cells, orig=None): def __init__(self, cells, orig=None):
self.cells = cells self.cells = cells
self.orig = orig self.orig = orig
def get_raw(self):
return get_raw_contents(self.orig)
class Text: class Text:
def __init__(self, content): def __init__(self, content):
self.content = content self.content = content
def get_raw(self): def get_raw(self):
return "".join(self.content.get_raw()) return ''.join(self.content.get_raw())
class ListItem: class ListItem:
@ -113,7 +92,7 @@ class CodeBlock(BlockNode):
def __init__(self, header, subtype, arguments): def __init__(self, header, subtype, arguments):
super().__init__() super().__init__()
self.header = header self.header = header
self.lines: Optional[List] = None self.lines = None
self.subtype = subtype self.subtype = subtype
self.arguments = arguments self.arguments = arguments
@ -121,26 +100,6 @@ class CodeBlock(BlockNode):
self.lines = lines self.lines = lines
def __repr__(self): def __repr__(self):
return "<Code: {}>".format(len(self.lines or [])) return "<Code: {}>".format(len(self.lines))
DomNode = Union[
DrawerNode,
PropertyNode,
ListGroupNode,
TableNode,
TableSeparatorRow,
TableRow,
Text,
ListItem,
BlockNode,
]
ContainerDomNode = Union[
DrawerNode,
ListGroupNode,
TableNode,
BlockNode,
]
from .utils import get_raw_contents from .utils import get_raw_contents

File diff suppressed because it is too large Load Diff

View File

View File

@ -1,18 +0,0 @@
import re
from typing import List, TypedDict
class HeadlineDict(TypedDict):
linenum: int
orig: re.Match
title: str
contents: List
children: List
keywords: List
properties: List
logbook: List
structural: List
delimiters: List
results: List # TODO: Move to each specific code block?
list_items: List
table_rows: List

View File

@ -1,20 +1,9 @@
import uuid import uuid
from .org_rw import ( from .org_rw import (Bold, Code, Headline, Italic, Line, RawLine, ListItem, Strike, Text,
Bold, Underlined, Verbatim)
Code,
Headline, from .org_rw import dump_contents
Italic,
Line,
ListItem,
RawLine,
Strike,
TableRow,
Text,
Underlined,
Verbatim,
dump_contents,
)
def get_hl_raw_contents(doc: Headline) -> str: def get_hl_raw_contents(doc: Headline) -> str:
@ -51,8 +40,6 @@ def get_raw_contents(doc) -> str:
return doc.get_raw() return doc.get_raw()
if isinstance(doc, ListItem): if isinstance(doc, ListItem):
return dump_contents(doc)[1] return dump_contents(doc)[1]
if isinstance(doc, TableRow):
return dump_contents(doc)[1]
print("Unhandled type: " + str(doc)) print("Unhandled type: " + str(doc))
raise NotImplementedError("Unhandled type: " + str(doc)) raise NotImplementedError("Unhandled type: " + str(doc))

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
# No external requirements at this point

View File

@ -1,11 +0,0 @@
#!/bin/sh
set -eu
cd "`dirname $0`"
cd ..
set -x
isort --profile black .
black .

View File

@ -5,8 +5,6 @@ set -eu
cd "`dirname $0`" cd "`dirname $0`"
cd .. cd ..
pandoc README.org -o README.md # PyPI doesn't accept Org files
python setup.py sdist python setup.py sdist
twine upload --verbose dist/* twine upload --verbose dist/*

View File

@ -2,7 +2,7 @@ from setuptools import setup
setup( setup(
name="org-rw", name="org-rw",
version="0.0.2", version="0.0.1.dev1",
description="Library to de/serialize org-files and manipulate them.", description="Library to de/serialize org-files and manipulate them.",
author="kenkeiras", author="kenkeiras",
author_email="kenkeiras@codigoparallevar.com", author_email="kenkeiras@codigoparallevar.com",

View File

@ -21,10 +21,3 @@
This is a [[https://codigoparallevar.com/4][[tricky web link]]] followed up with some text. This is a [[https://codigoparallevar.com/4][[tricky web link]]] followed up with some text.
This is [[[https://codigoparallevar.com/5][another tricky web link]]] followed up with some text. This is [[[https://codigoparallevar.com/5][another tricky web link]]] followed up with some text.
* Implicit links
:PROPERTIES:
:ID: 03-markup-implicit-links
:CREATED: [2020-01-01 Wed 01:01]
:END:
This is an implicit web link: https://codigoparallevar.com/implicit.

View File

@ -9,7 +9,6 @@
:CREATED: [2020-01-01 Wed 01:01] :CREATED: [2020-01-01 Wed 01:01]
:END: :END:
#+NAME: first-code-name
#+BEGIN_SRC shell :results verbatim #+BEGIN_SRC shell :results verbatim
echo "This is a test" echo "This is a test"
echo "with two lines" echo "with two lines"

View File

@ -1,22 +0,0 @@
#+TITLE: 12-Headlines with skip levels
#+DESCRIPTION: Simple org file to test Headlines with skip levels
#+TODO: TODO(t) PAUSED(p) | DONE(d)
* Level 1
:PROPERTIES:
:ID: 12-headlines-with-skip-levels
:CREATED: [2020-01-01 Wed 01:01]
:END:
*** Level 3
*** Level 3-2
* Level 1-2
** Level 2
**** Level 4
*** Level3

View File

@ -1,13 +0,0 @@
#+TITLE: 13-Tags
#+DESCRIPTION: Simple org file to test tags
#+FILETAGS: :filetag:
* Level 1 :h1tag:
:PROPERTIES:
:ID: 13-tags
:CREATED: [2020-01-01 Wed 01:01]
:END:
** Level2 :h2tag:
* Level 1-1 :otherh1tag:
** Level2 :otherh2tag:

View File

@ -1,23 +1,14 @@
import logging
import os import os
import unittest import unittest
from datetime import date
from datetime import datetime as DT from datetime import datetime as DT
from utils.assertions import ( from org_rw import MarkerToken, MarkerType, Timestamp, dumps, load, loads, dom
BOLD,
CODE,
HL,
ITALIC,
SPAN,
STRIKE,
UNDERLINED,
VERBATIM,
WEB_LINK,
Doc,
Tokens,
)
import org_rw import org_rw
from org_rw import MarkerToken, MarkerType, Timestamp, dom, dumps, load, loads
from utils.assertions import (BOLD, CODE, HL, ITALIC, SPAN, STRIKE, UNDERLINED,
VERBATIM, WEB_LINK, Doc, Tokens)
DIR = os.path.dirname(os.path.abspath(__file__)) DIR = os.path.dirname(os.path.abspath(__file__))
@ -211,7 +202,7 @@ class TestSerde(unittest.TestCase):
doc = load(f) doc = load(f)
links = list(doc.get_links()) links = list(doc.get_links())
self.assertEqual(len(links), 8) self.assertEqual(len(links), 7)
self.assertEqual(links[0].value, "https://codigoparallevar.com/1") self.assertEqual(links[0].value, "https://codigoparallevar.com/1")
self.assertEqual(links[0].description, "web link") self.assertEqual(links[0].description, "web link")
@ -233,9 +224,6 @@ class TestSerde(unittest.TestCase):
self.assertEqual(links[6].value, "https://codigoparallevar.com/5") self.assertEqual(links[6].value, "https://codigoparallevar.com/5")
self.assertEqual(links[6].description, "another tricky web link") self.assertEqual(links[6].description, "another tricky web link")
self.assertEqual(links[7].value, "https://codigoparallevar.com/implicit")
self.assertEqual(links[7].description, "https://codigoparallevar.com/implicit")
ex = Doc( ex = Doc(
props=[ props=[
("TITLE", "03-Links"), ("TITLE", "03-Links"),
@ -292,35 +280,17 @@ class TestSerde(unittest.TestCase):
SPAN("\n"), SPAN("\n"),
SPAN( SPAN(
" This is a ", " This is a ",
WEB_LINK( WEB_LINK("[tricky web link]\u200b", "https://codigoparallevar.com/4"),
"[tricky web link]\u200b",
"https://codigoparallevar.com/4",
),
" followed up with some text.\n", " followed up with some text.\n",
), ),
SPAN("\n"), SPAN("\n"),
SPAN( SPAN(
" This is [", " This is [",
WEB_LINK( WEB_LINK("another tricky web link", "https://codigoparallevar.com/5"),
"another tricky web link",
"https://codigoparallevar.com/5",
),
"] followed up with some text.\n", "] followed up with some text.\n",
), ),
], ],
), )
HL(
"Implicit links",
props=[
("ID", "03-markup-implicit-links"),
("CREATED", DT(2020, 1, 1, 1, 1)),
],
content=[
SPAN(
" This is an implicit web link: https://codigoparallevar.com/implicit.\n",
),
],
),
), ),
) )
@ -331,7 +301,7 @@ class TestSerde(unittest.TestCase):
doc = load(f) doc = load(f)
links = list(doc.get_links()) links = list(doc.get_links())
self.assertEqual(len(links), 8) self.assertEqual(len(links), 7)
self.assertEqual(links[0].value, "https://codigoparallevar.com/1") self.assertEqual(links[0].value, "https://codigoparallevar.com/1")
self.assertEqual(links[0].description, "web link") self.assertEqual(links[0].description, "web link")
links[0].value = "https://codigoparallevar.com/1-updated" links[0].value = "https://codigoparallevar.com/1-updated"
@ -367,9 +337,6 @@ class TestSerde(unittest.TestCase):
links[6].value = "https://codigoparallevar.com/5-updated" links[6].value = "https://codigoparallevar.com/5-updated"
links[6].description = "another tricky web link #5 with update" links[6].description = "another tricky web link #5 with update"
self.assertEqual(links[7].value, "https://codigoparallevar.com/implicit")
self.assertEqual(links[7].description, "https://codigoparallevar.com/implicit")
ex = Doc( ex = Doc(
props=[ props=[
("TITLE", "03-Links"), ("TITLE", "03-Links"),
@ -449,19 +416,7 @@ class TestSerde(unittest.TestCase):
"] followed up with some text.\n", "] followed up with some text.\n",
), ),
], ],
), )
HL(
"Implicit links",
props=[
("ID", "03-markup-implicit-links"),
("CREATED", DT(2020, 1, 1, 1, 1)),
],
content=[
SPAN(
" This is an implicit web link: https://codigoparallevar.com/implicit.\n",
),
],
),
), ),
) )
@ -480,22 +435,18 @@ class TestSerde(unittest.TestCase):
snippets = list(doc.get_code_snippets()) snippets = list(doc.get_code_snippets())
self.assertEqual(len(snippets), 3) self.assertEqual(len(snippets), 3)
self.assertEqual(snippets[0].name, "first-code-name")
self.assertEqual(snippets[0].language, "shell")
self.assertEqual( self.assertEqual(
snippets[0].content, snippets[0].content,
'echo "This is a test"\n' 'echo "This is a test"\n'
+ 'echo "with two lines"\n' + 'echo "with two lines"\n'
+ "exit 0 # Exit successfully", + "exit 0 # Exit successfully",
) )
self.assertEqual(snippets[0].arguments.split(), [":results", "verbatim"]) self.assertEqual(snippets[0].arguments.split(), ['shell', ':results', 'verbatim'])
self.assertEqual( self.assertEqual(
snippets[0].result, snippets[0].result,
"This is a test\n" + "with two lines", "This is a test\n" + "with two lines",
) )
self.assertEqual(snippets[1].name, None)
self.assertEqual(snippets[1].language, "shell")
self.assertEqual( self.assertEqual(
snippets[1].content, snippets[1].content,
'echo "This is another test"\n' 'echo "This is another test"\n'
@ -506,14 +457,12 @@ class TestSerde(unittest.TestCase):
snippets[1].result, "This is another test\n" + "with two lines too" snippets[1].result, "This is another test\n" + "with two lines too"
) )
self.assertEqual(snippets[2].name, None)
self.assertEqual(snippets[2].language, "c")
self.assertEqual( self.assertEqual(
snippets[2].content, snippets[2].content,
"/* This code has to be escaped to\n" '/* This code has to be escaped to\n'
+ " * avoid confusion with new headlines.\n" + ' * avoid confusion with new headlines.\n'
+ " */\n" + ' */\n'
+ "main(){}", + 'main(){}',
) )
def test_mimic_write_file_05(self): def test_mimic_write_file_05(self):
@ -551,7 +500,7 @@ class TestSerde(unittest.TestCase):
hl_schedule_range = hl.children[1] hl_schedule_range = hl.children[1]
self.assertEqual( self.assertEqual(
hl_schedule_range.scheduled.time, hl_schedule_range.scheduled.time,
Timestamp(True, 2020, 12, 15, "Mar", 0, 5, "++1w"), Timestamp(True, 2020, 12, 15, "Mar", 0, 5, '++1w')
) )
self.assertEqual( self.assertEqual(
hl_schedule_range.scheduled.end_time, hl_schedule_range.scheduled.end_time,
@ -559,7 +508,7 @@ class TestSerde(unittest.TestCase):
) )
self.assertEqual( self.assertEqual(
hl_schedule_range.scheduled.repetition, hl_schedule_range.scheduled.repetition,
"++1w", '++1w',
) )
def test_update_info_file_05(self): def test_update_info_file_05(self):
@ -612,8 +561,7 @@ class TestSerde(unittest.TestCase):
MarkerToken(closing=False, tok_type=MarkerType.UNDERLINED_MODE), MarkerToken(closing=False, tok_type=MarkerType.UNDERLINED_MODE),
"markup", "markup",
MarkerToken(closing=True, tok_type=MarkerType.UNDERLINED_MODE), MarkerToken(closing=True, tok_type=MarkerType.UNDERLINED_MODE),
".", ".", "\n"
"\n",
], ],
) )
@ -647,24 +595,12 @@ class TestSerde(unittest.TestCase):
print(lists4) print(lists4)
self.assertEqual(len(lists4), 2) self.assertEqual(len(lists4), 2)
self.assertEqual( self.assertEqual(lists4[0][0].content, ["This is a list item...", "\n that spans multiple lines", "\n"])
lists4[0][0].content,
["This is a list item...", "\n that spans multiple lines", "\n"],
)
self.assertEqual(lists4[0][0].bullet, "-") self.assertEqual(lists4[0][0].bullet, "-")
self.assertEqual( self.assertEqual(lists4[0][1].content, ["This is another list item...", "\n that has content on multiple lines", "\n"])
lists4[0][1].content,
[
"This is another list item...",
"\n that has content on multiple lines",
"\n",
],
)
self.assertEqual(lists4[0][1].bullet, "-") self.assertEqual(lists4[0][1].bullet, "-")
self.assertEqual( self.assertEqual(lists4[1][0].content, ["This is another", "\n multiline list", "\n"])
lists4[1][0].content, ["This is another", "\n multiline list", "\n"]
)
self.assertEqual(lists4[1][0].bullet, "-") self.assertEqual(lists4[1][0].bullet, "-")
def test_org_roam_07(self): def test_org_roam_07(self):
@ -708,22 +644,20 @@ class TestSerde(unittest.TestCase):
""".strip(), """.strip(),
) )
def test_markup_file_09(self): def test_markup_file_09(self):
with open(os.path.join(DIR, "09-markup-on-headline.org")) as f: with open(os.path.join(DIR, "09-markup-on-headline.org")) as f:
doc = load(f) doc = load(f)
hl = doc.getTopHeadlines()[0] hl = doc.getTopHeadlines()[0]
print(hl.title) print(hl.title)
self.assertEqual( self.assertEqual(hl.title.contents, [
hl.title.contents, 'Headline ',
[
"Headline ",
MarkerToken(closing=False, tok_type=MarkerType.UNDERLINED_MODE), MarkerToken(closing=False, tok_type=MarkerType.UNDERLINED_MODE),
"with", 'with',
MarkerToken(closing=True, tok_type=MarkerType.UNDERLINED_MODE), MarkerToken(closing=True, tok_type=MarkerType.UNDERLINED_MODE),
" markup", ' markup',
], ])
)
def test_mimic_write_file_10(self): def test_mimic_write_file_10(self):
with open(os.path.join(DIR, "10-tables.org")) as f: with open(os.path.join(DIR, "10-tables.org")) as f:
@ -744,9 +678,9 @@ class TestSerde(unittest.TestCase):
print(first_table[0]) print(first_table[0])
self.assertEqual(len(first_table[0].cells), 3) self.assertEqual(len(first_table[0].cells), 3)
self.assertEqual(first_table[0].cells[0].strip(), "Header1") self.assertEqual(first_table[0].cells[0].strip(), 'Header1')
self.assertEqual(first_table[0].cells[1].strip(), "Header2") self.assertEqual(first_table[0].cells[1].strip(), 'Header2')
self.assertEqual(first_table[0].cells[2].strip(), "Header3") self.assertEqual(first_table[0].cells[2].strip(), 'Header3')
hl = hl.children[0] hl = hl.children[0]
@ -756,9 +690,9 @@ class TestSerde(unittest.TestCase):
print(first_table[0]) print(first_table[0])
self.assertEqual(len(first_table[0].cells), 3) self.assertEqual(len(first_table[0].cells), 3)
self.assertEqual(first_table[0].cells[0].strip(), "Header1") self.assertEqual(first_table[0].cells[0].strip(), 'Header1')
self.assertEqual(first_table[0].cells[1].strip(), "Header2") self.assertEqual(first_table[0].cells[1].strip(), 'Header2')
self.assertEqual(first_table[0].cells[2].strip(), "Header3") self.assertEqual(first_table[0].cells[2].strip(), 'Header3')
def test_tables_html_file_10(self): def test_tables_html_file_10(self):
with open(os.path.join(DIR, "10-tables.org")) as f: with open(os.path.join(DIR, "10-tables.org")) as f:
@ -768,26 +702,27 @@ class TestSerde(unittest.TestCase):
tree = hl.as_dom() tree = hl.as_dom()
non_props = [ non_props = [
item for item in tree if not isinstance(item, dom.PropertyDrawerNode) item
for item in tree
if not isinstance(item, dom.PropertyDrawerNode)
] ]
self.assertTrue( self.assertTrue(isinstance(non_props[0], dom.Text)
isinstance(non_props[0], dom.Text)
and isinstance(non_props[1], dom.TableNode) and isinstance(non_props[1], dom.TableNode)
and isinstance(non_props[2], dom.Text), and isinstance(non_props[2], dom.Text),
"Expected <Text><Table><Text>", 'Expected <Text><Table><Text>')
)
hl = hl.children[0] hl = hl.children[0]
tree = hl.as_dom() tree = hl.as_dom()
non_props = [ non_props = [
item item
for item in tree for item in tree
if not ( if not (isinstance(item, dom.PropertyDrawerNode)
isinstance(item, dom.PropertyDrawerNode) or isinstance(item, dom.Text) or isinstance(item, dom.Text))
)
] ]
print_tree(non_props) print_tree(non_props)
self.assertTrue(len(non_props) == 1, "Expected <List>, with only (1) element") self.assertTrue(len(non_props) == 1,
'Expected <List>, with only (1) element')
def test_nested_lists_html_file_11(self): def test_nested_lists_html_file_11(self):
with open(os.path.join(DIR, "11-nested-lists.org")) as f: with open(os.path.join(DIR, "11-nested-lists.org")) as f:
@ -797,232 +732,30 @@ class TestSerde(unittest.TestCase):
tree = hl.as_dom() tree = hl.as_dom()
non_props = [ non_props = [
item for item in tree if not isinstance(item, dom.PropertyDrawerNode) item
for item in tree
if not isinstance(item, dom.PropertyDrawerNode)
] ]
print_tree(non_props) print_tree(non_props)
self.assertTrue( self.assertTrue((len(non_props) == 1) and (isinstance(non_props[0], dom.ListGroupNode)),
(len(non_props) == 1) and (isinstance(non_props[0], dom.ListGroupNode)), 'Expected only <List> as top level')
"Expected only <List> as top level",
)
dom_list = non_props[0] dom_list = non_props[0]
children = dom_list.children children = dom_list.children
self.assertTrue( self.assertTrue(len(children) == 5, 'Expected 5 items inside <List>, 3 texts and 2 sublists')
len(children) == 5, "Expected 5 items inside <List>, 3 texts and 2 sublists"
)
# Assert texts # Assert texts
self.assertEqual(children[0].content, ["1"]) self.assertEqual(children[0].content, ['1'])
self.assertEqual(children[2].content, ["2"]) self.assertEqual(children[2].content, ['2'])
self.assertEqual( self.assertEqual(children[4].content[0], '3') # Might be ['3', '\n'] but shouldn't be a breaking change
children[4].content[0], "3"
) # Might be ['3', '\n'] but shouldn't be a breaking change
# Assert lists # Assert lists
self.assertTrue( self.assertTrue(isinstance(children[1], dom.ListGroupNode), 'Expected sublist inside "1"')
isinstance(children[1], dom.ListGroupNode), 'Expected sublist inside "1"' self.assertEqual(children[1].children[0].content, ['1.1'])
) self.assertEqual(children[1].children[1].content, ['1.2'])
self.assertEqual(children[1].children[0].content, ["1.1"]) self.assertTrue(isinstance(children[3], dom.ListGroupNode), 'Expected sublist inside "2"')
self.assertEqual(children[1].children[1].content, ["1.2"]) self.assertEqual(children[3].children[0].content, ['2.1'])
self.assertTrue( self.assertEqual(children[3].children[1].content, ['2.2'])
isinstance(children[3], dom.ListGroupNode), 'Expected sublist inside "2"'
)
self.assertEqual(children[3].children[0].content, ["2.1"])
self.assertEqual(children[3].children[1].content, ["2.2"])
def test_mimic_write_file_12(self):
with open(os.path.join(DIR, "12-headlines-with-skip-levels.org")) as f:
orig = f.read()
doc = loads(orig)
self.assertEqual(dumps(doc), orig)
def test_add_todo_keywords_programatically(self):
orig = """* NEW_TODO_STATE First entry
* NEW_DONE_STATE Second entry"""
doc = loads(
orig, environment={"org-todo-keywords": "NEW_TODO_STATE | NEW_DONE_STATE"}
)
self.assertEqual(doc.headlines[0].is_todo, True)
self.assertEqual(doc.headlines[0].is_done, False)
self.assertEqual(doc.headlines[1].is_todo, False)
self.assertEqual(doc.headlines[1].is_done, True)
self.assertEqual(dumps(doc), orig)
def test_add_todo_keywords_in_file(self):
orig = """#+TODO: NEW_TODO_STATE | NEW_DONE_STATE
* NEW_TODO_STATE First entry
* NEW_DONE_STATE Second entry"""
doc = loads(
orig, environment={"org-todo-keywords": "NEW_TODO_STATE | NEW_DONE_STATE"}
)
self.assertEqual(doc.headlines[0].is_todo, True)
self.assertEqual(doc.headlines[0].is_done, False)
self.assertEqual(doc.headlines[1].is_todo, False)
self.assertEqual(doc.headlines[1].is_done, True)
self.assertEqual(dumps(doc), orig)
def test_mimic_write_file_13(self):
with open(os.path.join(DIR, "13-tags.org")) as f:
orig = f.read()
doc = loads(orig)
self.assertEqual(dumps(doc), orig)
def test_tag_property_read_13(self):
with open(os.path.join(DIR, "13-tags.org")) as f:
orig = f.read()
doc = loads(orig)
self.assertEqual(doc.tags, ["filetag"])
h1_1, h1_2 = doc.getTopHeadlines()
self.assertEqual(sorted(h1_1.tags), ["filetag", "h1tag"])
self.assertEqual(sorted(h1_2.tags), ["filetag", "otherh1tag"])
h1_1_h2 = h1_1.children[0]
self.assertEqual(sorted(h1_1_h2.tags), ["filetag", "h1tag", "h2tag"])
h1_2_h2 = h1_2.children[0]
self.assertEqual(sorted(h1_2_h2.tags), ["filetag", "otherh1tag", "otherh2tag"])
def test_shallow_tag_property_read_13(self):
with open(os.path.join(DIR, "13-tags.org")) as f:
orig = f.read()
doc = loads(orig)
self.assertEqual(doc.shallow_tags, ["filetag"])
h1_1, h1_2 = doc.getTopHeadlines()
self.assertEqual(sorted(h1_1.shallow_tags), ["h1tag"])
self.assertEqual(sorted(h1_2.shallow_tags), ["otherh1tag"])
h1_1_h2 = h1_1.children[0]
self.assertEqual(sorted(h1_1_h2.shallow_tags), ["h2tag"])
h1_2_h2 = h1_2.children[0]
self.assertEqual(sorted(h1_2_h2.shallow_tags), ["otherh2tag"])
def test_exclude_tags_from_inheritance_property_read_13(self):
with open(os.path.join(DIR, "13-tags.org")) as f:
orig = f.read()
doc = loads(
orig,
{
"org-tags-exclude-from-inheritance": ("h1tag", "otherh2tag"),
},
)
self.assertEqual(doc.tags, ["filetag"])
h1_1, h1_2 = doc.getTopHeadlines()
self.assertEqual(sorted(h1_1.tags), ["filetag", "h1tag"])
self.assertEqual(sorted(h1_2.tags), ["filetag", "otherh1tag"])
h1_1_h2 = h1_1.children[0]
self.assertEqual(sorted(h1_1_h2.tags), ["filetag", "h2tag"])
h1_2_h2 = h1_2.children[0]
self.assertEqual(sorted(h1_2_h2.tags), ["filetag", "otherh1tag", "otherh2tag"])
def test_select_tags_to_inheritance_property_read_13(self):
with open(os.path.join(DIR, "13-tags.org")) as f:
orig = f.read()
doc = loads(
orig,
{
"org-tags-exclude-from-inheritance": ("h1tag", "otherh2tag"),
"org-use-tag-inheritance": ("h1tag",),
},
)
self.assertEqual(doc.tags, ["filetag"])
h1_1, h1_2 = doc.getTopHeadlines()
self.assertEqual(sorted(h1_1.tags), ["h1tag"])
self.assertEqual(sorted(h1_2.tags), ["otherh1tag"])
h1_1_h2 = h1_1.children[0]
self.assertEqual(sorted(h1_1_h2.tags), ["h1tag", "h2tag"])
h1_2_h2 = h1_2.children[0]
self.assertEqual(sorted(h1_2_h2.tags), ["otherh2tag"])
def test_update_headline_from_none_to_todo(self):
orig = "* First entry"
doc = loads(orig)
self.assertEqual(doc.headlines[0].is_todo, False)
self.assertEqual(doc.headlines[0].is_done, False)
self.assertEqual(doc.headlines[0].state, None)
doc.headlines[0].state = "TODO"
self.assertEqual(doc.headlines[0].is_todo, True)
self.assertEqual(doc.headlines[0].is_done, False)
self.assertEqual(doc.headlines[0].state["name"], "TODO")
self.assertEqual(dumps(doc), "* TODO First entry")
def test_update_headline_from_none_to_done(self):
orig = "* First entry"
doc = loads(orig)
self.assertEqual(doc.headlines[0].is_todo, False)
self.assertEqual(doc.headlines[0].is_done, False)
self.assertEqual(doc.headlines[0].state, None)
doc.headlines[0].state = org_rw.HeadlineState(name="DONE")
self.assertEqual(doc.headlines[0].is_todo, False)
self.assertEqual(doc.headlines[0].is_done, True)
self.assertEqual(doc.headlines[0].state["name"], "DONE")
self.assertEqual(dumps(doc), "* DONE First entry")
def test_update_headline_from_todo_to_none(self):
orig = "* TODO First entry"
doc = loads(orig)
self.assertEqual(doc.headlines[0].is_todo, True)
self.assertEqual(doc.headlines[0].is_done, False)
self.assertEqual(doc.headlines[0].state["name"], "TODO")
doc.headlines[0].state = None
self.assertEqual(doc.headlines[0].is_todo, False)
self.assertEqual(doc.headlines[0].is_done, False)
self.assertEqual(doc.headlines[0].state, None)
self.assertEqual(dumps(doc), "* First entry")
def test_update_headline_from_todo_to_done(self):
orig = "* TODO First entry"
doc = loads(orig)
self.assertEqual(doc.headlines[0].is_todo, True)
self.assertEqual(doc.headlines[0].is_done, False)
self.assertEqual(doc.headlines[0].state["name"], "TODO")
doc.headlines[0].state = "DONE"
self.assertEqual(doc.headlines[0].is_todo, False)
self.assertEqual(doc.headlines[0].is_done, True)
self.assertEqual(doc.headlines[0].state["name"], "DONE")
self.assertEqual(dumps(doc), "* DONE First entry")
def test_update_headline_from_done_to_todo(self):
orig = "* DONE First entry"
doc = loads(orig)
self.assertEqual(doc.headlines[0].is_todo, False)
self.assertEqual(doc.headlines[0].is_done, True)
self.assertEqual(doc.headlines[0].state["name"], "DONE")
doc.headlines[0].state = org_rw.HeadlineState(name="TODO")
self.assertEqual(doc.headlines[0].is_todo, True)
self.assertEqual(doc.headlines[0].is_done, False)
self.assertEqual(doc.headlines[0].state["name"], "TODO")
self.assertEqual(dumps(doc), "* TODO First entry")
def print_tree(tree, indentation=0, headline=None): def print_tree(tree, indentation=0, headline=None):
@ -1042,10 +775,6 @@ def print_element(element, indentation, headline):
if isinstance(element, org_rw.Link): if isinstance(element, org_rw.Link):
print(" " * indentation * 2, "Link:", element.get_raw()) print(" " * indentation * 2, "Link:", element.get_raw())
elif isinstance(element, str): elif isinstance(element, str):
print( print(" " * indentation * 2, "Str[" + element.replace('\n', '<NL>') + "]", type(element))
" " * indentation * 2,
"Str[" + element.replace("\n", "<NL>") + "]",
type(element),
)
else: else:
print_tree(element, indentation, headline) print_tree(element, indentation, headline)

View File

@ -1,86 +0,0 @@
"""Test the Timestamp object."""
from datetime import date, datetime
import pytest
from org_rw import Timestamp
def test_init_with_datetime() -> None:
datetime_obj: datetime = datetime(2024, 7, 20, 15, 45)
ts: Timestamp = Timestamp(active=True, datetime_=datetime_obj)
assert ts.active is True
assert ts._year == 2024
assert ts._month == 7
assert ts._day == 20
assert ts.hour == 15
assert ts.minute == 45
assert ts.dow is None
assert ts.repetition is None
def test_init_with_date() -> None:
date_obj: date = date(2024, 7, 20)
ts: Timestamp = Timestamp(active=True, datetime_=date_obj)
assert ts.active is True
assert ts._year == 2024
assert ts._month == 7
assert ts._day == 20
assert ts.hour is None
assert ts.minute is None
assert ts.dow is None
assert ts.repetition is None
def test_init_with_year_month_day() -> None:
ts: Timestamp = Timestamp(
active=True,
year=2024,
month=7,
day=20,
hour=15,
minute=45,
dow="Saturday",
repetition=".+1d",
)
assert ts.active is True
assert ts._year == 2024
assert ts._month == 7
assert ts._day == 20
assert ts.hour == 15
assert ts.minute == 45
assert ts.dow == "Saturday"
assert ts.repetition == ".+1d"
def test_init_without_required_arguments() -> None:
with pytest.raises(ValueError):
Timestamp(active=True)
def test_init_with_partial_date_info() -> None:
with pytest.raises(ValueError):
Timestamp(active=True, year=2024, month=7)
def test_init_with_datetime_overrides_date_info() -> None:
datetime_obj: datetime = datetime(2024, 7, 20, 15, 45)
ts: Timestamp = Timestamp(
active=True, year=2020, month=1, day=1, datetime_=datetime_obj
)
assert ts.active is True
assert ts._year == 2024
assert ts._month == 7
assert ts._day == 20
assert ts.hour == 15
assert ts.minute == 45
assert ts.dow is None
assert ts.repetition is None

View File

@ -2,17 +2,8 @@ import collections
import unittest import unittest
from datetime import datetime from datetime import datetime
from org_rw import ( from org_rw import (Bold, Code, Italic, Line, Strike, Text, Underlined,
Bold, Verbatim, get_raw_contents)
Code,
Italic,
Line,
Strike,
Text,
Underlined,
Verbatim,
get_raw_contents,
)
def timestamp_to_datetime(ts): def timestamp_to_datetime(ts):