Compare commits

..

72 Commits

Author SHA1 Message Date
Sergio Martínez Portela
55fc87cfdc Add absence of dependencies as principle.
All checks were successful
Testing / pytest (push) Successful in 20s
Testing / mypy (push) Successful in 29s
Testing / style-formatting (push) Successful in 19s
Testing / style-sorted-imports (push) Successful in 16s
Testing / stability-extra-test (push) Successful in 22s
2025-04-16 01:00:09 +02:00
Sergio Martínez Portela
f936bccf7f doc: Add a small "Principles" section to README.
All checks were successful
Testing / pytest (push) Successful in 17s
Testing / mypy (push) Successful in 22s
Testing / style-formatting (push) Successful in 20s
Testing / style-sorted-imports (push) Successful in 19s
Testing / stability-extra-test (push) Successful in 21s
2025-04-16 00:46:52 +02:00
78bd091e61 Merge pull request 'Multiple fixes on loader due to extended tests.' (#15) from fixes/loading into develop
All checks were successful
Testing / pytest (push) Successful in 20s
Testing / mypy (push) Successful in 27s
Testing / style-formatting (push) Successful in 23s
Testing / style-sorted-imports (push) Successful in 19s
Testing / stability-extra-test (push) Successful in 23s
Reviewed-on: kenkeiras/org-rw#15
2025-04-15 21:56:51 +00:00
Sergio Martínez Portela
3b90723250 format: Automatic formatting fixes.
All checks were successful
Testing / pytest (push) Successful in 23s
Testing / mypy (push) Successful in 28s
Testing / style-formatting (push) Successful in 23s
Testing / style-sorted-imports (push) Successful in 19s
Testing / stability-extra-test (push) Successful in 26s
2025-02-09 16:50:52 +01:00
Sergio Martínez Portela
506a17dc5c fix(org_rw): Ensure closing delimiters are same subtype as openers. 2025-02-09 16:50:52 +01:00
Sergio Martínez Portela
0bdb29a278 Don't cut delimiter lines out of get_lines_between(). 2025-02-09 16:50:52 +01:00
Sergio Martínez Portela
8b4e12ea2e Add dom.TableRow.get_raw() support. 2025-02-09 16:50:52 +01:00
Sergio Martínez Portela
dbac8b2d6e feat(dom): Add support for generic drawer outputs. 2025-02-09 16:50:52 +01:00
Sergio Martínez Portela
c0fc78fe33 fix(gitea): Fix build with newer images. 2025-02-09 14:13:28 +01:00
Sergio Martínez Portela
9c04717a12 Fix support of code blocks outside headlines.
Some checks failed
Testing / pytest (push) Failing after 1m11s
Testing / mypy (push) Failing after 17s
Testing / style-formatting (push) Failing after 15s
Testing / style-sorted-imports (push) Failing after 16s
Testing / stability-extra-test (push) Failing after 20s
2025-02-09 13:49:09 +01:00
8280949f23 Merge pull request 'feat: Simplify headline state setting.' (#12) from feat/cleaner-state-setting into develop
All checks were successful
Testing / pytest (push) Successful in 32s
Testing / mypy (push) Successful in 33s
Testing / style-formatting (push) Successful in 36s
Testing / style-sorted-imports (push) Successful in 25s
Testing / stability-extra-test (push) Successful in 29s
Reviewed-on: kenkeiras/org-rw#12
2024-10-07 21:26:19 +00:00
Sergio Martínez Portela
691ce30a68 Simplify state setting, update .is_todo/.is_done props.
All checks were successful
Testing / pytest (push) Successful in 28s
Testing / mypy (push) Successful in 35s
Testing / style-formatting (push) Successful in 30s
Testing / style-sorted-imports (push) Successful in 24s
Testing / stability-extra-test (push) Successful in 32s
2024-10-07 23:23:15 +02:00
48de06abc7 Merge pull request 'feat: Name code snippets' (#11) from feat/named-code-snippets into develop
All checks were successful
Testing / pytest (push) Successful in 28s
Testing / mypy (push) Successful in 37s
Testing / style-formatting (push) Successful in 33s
Testing / style-sorted-imports (push) Successful in 24s
Testing / stability-extra-test (push) Successful in 26s
Reviewed-on: kenkeiras/org-rw#11
2024-10-06 22:26:00 +00:00
Sergio Martínez Portela
d4b40e404d Apply autoformatter.
All checks were successful
Testing / pytest (push) Successful in 28s
Testing / mypy (push) Successful in 37s
Testing / style-formatting (push) Successful in 33s
Testing / style-sorted-imports (push) Successful in 25s
Testing / stability-extra-test (push) Successful in 30s
2024-10-05 10:08:41 +02:00
Sergio Martínez Portela
5432c23202 Explicitly extract code block language.
Some checks failed
Testing / pytest (push) Successful in 26s
Testing / mypy (push) Successful in 35s
Testing / style-formatting (push) Failing after 30s
Testing / style-sorted-imports (push) Successful in 35s
Testing / stability-extra-test (push) Successful in 28s
2024-09-30 23:55:07 +02:00
Sergio Martínez Portela
8fe3c27595 Read names for code blocks. 2024-09-30 23:39:37 +02:00
Sergio Martínez Portela
1dc6eb0b43 fix: On OrgDoc.get_code_snippets, consider headlines of all levels. 2024-09-30 22:59:04 +02:00
5019b44dd5 Merge pull request 'Feat: Complete tags property' (#10) from feat/consider-file-tags-on-headlines into develop
All checks were successful
Testing / pytest (push) Successful in 28s
Testing / mypy (push) Successful in 36s
Testing / style-formatting (push) Successful in 28s
Testing / style-sorted-imports (push) Successful in 32s
Testing / stability-extra-test (push) Successful in 35s
Reviewed-on: kenkeiras/org-rw#10
2024-09-03 18:33:03 +00:00
Sergio Martínez Portela
78bc57e55d Fix formatting.
All checks were successful
Testing / pytest (push) Successful in 27s
Testing / mypy (push) Successful in 30s
Testing / style-formatting (push) Successful in 28s
Testing / style-sorted-imports (push) Successful in 22s
Testing / stability-extra-test (push) Successful in 36s
2024-09-01 23:51:38 +02:00
Sergio Martínez Portela
d4b0d0301f Test and implement org-use-tag-inheritance.
Some checks failed
Testing / pytest (push) Has been cancelled
Testing / mypy (push) Has been cancelled
Testing / style-formatting (push) Has been cancelled
Testing / style-sorted-imports (push) Has been cancelled
Testing / stability-extra-test (push) Has been cancelled
2024-09-01 23:51:10 +02:00
Sergio Martínez Portela
92078617fc Add tests and implement org-tags-exclude-from-inheritance.
Some checks failed
Testing / pytest (push) Successful in 25s
Testing / mypy (push) Successful in 31s
Testing / style-formatting (push) Failing after 31s
Testing / style-sorted-imports (push) Successful in 30s
Testing / stability-extra-test (push) Successful in 27s
2024-09-01 23:46:10 +02:00
Sergio Martínez Portela
852f472374 Implement OrgDoc .shallow_tags .
Some checks failed
Testing / pytest (push) Successful in 27s
Testing / mypy (push) Successful in 32s
Testing / style-formatting (push) Failing after 42s
Testing / style-sorted-imports (push) Successful in 28s
Testing / stability-extra-test (push) Successful in 28s
2024-09-01 23:37:26 +02:00
Sergio Martínez Portela
570e6bb764 Implement OrgDoc .tags. 2024-09-01 23:35:33 +02:00
Sergio Martínez Portela
e0306bf3a5 Add (failing) test for tags property read.
Some checks failed
Testing / pytest (push) Failing after 26s
Testing / mypy (push) Successful in 37s
Testing / style-formatting (push) Failing after 31s
Testing / style-sorted-imports (push) Successful in 25s
Testing / stability-extra-test (push) Has been cancelled
2024-09-01 23:35:23 +02:00
bfe60271eb Merge pull request 'Fix text parsing issues' (#9) from fix/require-whitespace-for-list-item-tag-separator into develop
All checks were successful
Testing / pytest (push) Successful in 33s
Testing / mypy (push) Successful in 32s
Testing / style-formatting (push) Successful in 26s
Testing / style-sorted-imports (push) Successful in 32s
Testing / stability-extra-test (push) Successful in 33s
Reviewed-on: kenkeiras/org-rw#9
2024-09-01 12:10:25 +00:00
Sergio Martínez Portela
4af4cda44b Fix formatting.
All checks were successful
Testing / pytest (push) Successful in 31s
Testing / mypy (push) Successful in 41s
Testing / style-formatting (push) Successful in 43s
Testing / style-sorted-imports (push) Successful in 28s
Testing / stability-extra-test (push) Successful in 41s
2024-08-22 00:26:11 +02:00
Sergio Martínez Portela
5552b3324b Handle ] which not close link descriptions or references.
Some checks failed
Testing / stability-extra-test (push) Waiting to run
Testing / pytest (push) Successful in 38s
Testing / style-sorted-imports (push) Waiting to run
Testing / mypy (push) Successful in 46s
Testing / style-formatting (push) Has been cancelled
2024-08-22 00:21:02 +02:00
Sergio Martínez Portela
f31c64c242 Properly track which tokens are used for closing formats. 2024-08-22 00:20:54 +02:00
Sergio Martínez Portela
490b36887a Require space before list item tag separator. 2024-08-22 00:20:15 +02:00
05718e1001 Merge pull request 'feat: Apply and check autoformatting' (#8) from apply-and-check-autoformatting into develop
All checks were successful
Testing / pytest (push) Successful in 43s
Testing / mypy (push) Successful in 54s
Testing / style-formatting (push) Successful in 55s
Testing / style-sorted-imports (push) Successful in 28s
Testing / stability-extra-test (push) Successful in 53s
Reviewed-on: kenkeiras/org-rw#8
2024-08-19 21:41:32 +00:00
Sergio Martínez Portela
e991074346 fix: Apply import sorting.
All checks were successful
Testing / pytest (push) Successful in 30s
Testing / mypy (push) Successful in 34s
Testing / style-formatting (push) Successful in 29s
Testing / style-sorted-imports (push) Successful in 26s
Testing / stability-extra-test (push) Successful in 32s
2024-08-18 22:49:33 +02:00
Sergio Martínez Portela
66b42e0b96 feat: Add script to apply formatting tools. 2024-08-18 22:49:06 +02:00
Sergio Martínez Portela
c6d8575ae5 test: Test sorted imports. 2024-08-18 22:47:42 +02:00
Sergio Martínez Portela
8ca480ad77 fix: Apply black formatter. 2024-08-18 22:47:24 +02:00
Sergio Martínez Portela
0a55c64551 test: Add formatting check to CI/CD. 2024-08-18 22:47:24 +02:00
991781a249 Merge pull request 'feat: enhance type annotations and formatting' (#7) from lyz/org-rw:feat/small-improvements into develop
All checks were successful
Testing / pytest (push) Successful in 37s
Testing / mypy (push) Successful in 34s
Testing / stability-extra-test (push) Successful in 51s
Reviewed-on: kenkeiras/org-rw#7
Reviewed-by: kenkeiras <kenkeiras@codigoparallevar.com>
2024-08-18 20:16:16 +00:00
Lyz
75055f5e08
feat: enhance type annotations and formatting
feat: Added `py.typed` file to indicate the presence of type information in the package.

Mypy needs this
2024-08-02 20:08:04 +02:00
40d58d5488 Merge pull request 'Add TODO keywords programmatically' (#2) from feat/add-todo-keywords-programmatically into develop
All checks were successful
Testing / pytest (push) Successful in 25s
Testing / mypy (push) Successful in 32s
Testing / stability-extra-test (push) Successful in 24s
Reviewed-on: kenkeiras/org-rw#2
Reviewed-by: lyz <lyz@riseup.net>
2024-07-29 14:34:18 +00:00
Sergio Martínez Portela
09f9030818 tests: fix typings to match mypy expectations.
All checks were successful
Testing / pytest (push) Successful in 25s
Testing / mypy (push) Successful in 31s
Testing / stability-extra-test (push) Successful in 21s
2024-07-29 15:31:39 +01:00
Sergio Martínez Portela
7e44bce145 Merge remote-tracking branch 'origin/develop' into feat/add-todo-keywords-programmatically
Some checks failed
Testing / pytest (push) Successful in 26s
Testing / mypy (push) Failing after 30s
Testing / stability-extra-test (push) Successful in 23s
2024-07-29 15:19:58 +01:00
ef893a72a0 Merge pull request 'feat/improvements' (#1) from lyz/org-rw:feat/improvements into develop
Some checks failed
Testing / pytest (push) Successful in 30s
Testing / mypy (push) Failing after 34s
Testing / stability-extra-test (push) Successful in 27s
Reviewed-on: kenkeiras/org-rw#1
Reviewed-by: kenkeiras <kenkeiras@codigoparallevar.com>
2024-07-29 14:17:16 +00:00
Lyz
9fea315115
merge upstream 2024-07-26 13:36:04 +02:00
Lyz
191bb753c4
tests: fix repetition string 2024-07-26 13:34:38 +02:00
Sergio Martínez Portela
b174405c90 Refactor headline state parsing.
Some checks failed
Testing / pytest (push) Successful in 25s
Testing / mypy (push) Failing after 33s
Testing / stability-extra-test (push) Successful in 22s
- Add separate function to parse states.
- Handle edge case when no `|` is used to split TODO and DONE states.
- Add typing to the states to future-proof for handling keyboard shortcuts and actions on state changes.
2024-07-20 18:10:05 +02:00
cca2a9ec1c Merge branch 'develop' into feat/improvements 2024-07-20 15:53:55 +00:00
Sergio Martínez Portela
da2d8c8c6d Add org-todo-keywords environment to programatically set states.
Some checks failed
Testing / pytest (push) Successful in 41s
Testing / mypy (push) Failing after 32s
Testing / stability-extra-test (push) Successful in 25s
2024-07-20 14:42:41 +02:00
Sergio Martínez Portela
4c169f5d47 Add (passing) test to read TODO/DONE states from file. 2024-07-20 14:42:20 +02:00
Sergio Martínez Portela
f4d63c2f93 Add (failing) test. 2024-07-20 14:41:09 +02:00
Lyz
ff841f82f0
feat: Set the default Timestamp active to True
That way you don't need to specify it if you don't want
2024-07-20 11:41:15 +02:00
Lyz
be68d10d7a
feat: initialise a Timestamp from a datetime object 2024-07-20 11:38:19 +02:00
Lyz
f640521b56
feat: add the scheduled, deadline and closed arguments to Headline init
style: Improve the type hints of Time

When reading them it's more natural to read Optional[Time] than to
assume that None is part of the Union in Time
2024-07-20 11:14:15 +02:00
Lyz
694f3f59e2
Merge branch 'develop' into feat/improvements 2024-07-20 10:49:06 +02:00
Lyz
921495fca8
Merge branch 'develop' of ssh://code.codigoparallevar.com:2022/lyz/org-rw into develop 2024-07-20 10:48:19 +02:00
Lyz
c5cc14f65c
feat(Timestamp): add the from_datetime method
To update the current Timestamp instance based on a datetime or date object.

I've also included a set_datetime method to OrgTime

feat: add activate and deactivate methods to TimeRange and OrgTime

I need it in a program I'm making
refactor: Create the Time type hint

I had to move the parse_time and parse_org_time_range below OrgTime
because it used the Time type hint and the Time type hint needed the
other two

style: reformat the code following black

style: Add some type hints and docstrings

style: remove unused imports

tests: Correct some mypy errors
2024-07-19 21:36:00 +02:00
Sergio Martínez Portela
a56ac018a8 Prepare for PyPI pushising, bumb version.
Some checks failed
Testing / pytest (push) Successful in 25s
Testing / mypy (push) Failing after 30s
Testing / stability-extra-test (push) Successful in 22s
2024-07-19 20:01:27 +02:00
Sergio Martínez Portela
423d6f9842 Fix multiline specifications of TODO properties.
Some checks failed
Testing / pytest (push) Successful in 41s
Testing / mypy (push) Failing after 37s
Testing / stability-extra-test (push) Successful in 28s
2024-03-22 01:54:46 +01:00
Sergio Martínez Portela
9e994ba323 Accept numbers as end of implicit link.
Some checks failed
Testing / pytest (push) Successful in 38s
Testing / mypy (push) Failing after 37s
Testing / stability-extra-test (push) Successful in 26s
2024-02-21 23:01:10 +01:00
Sergio Martínez Portela
4fd29819ea Fix implicit link parsing.
Some checks failed
Testing / pytest (push) Successful in 1m34s
Testing / mypy (push) Failing after 30s
Testing / stability-extra-test (push) Successful in 24s
2024-02-21 23:00:59 +01:00
Sergio Martínez Portela
985098e091 Find web links not marked as such when returning doc.get_links().
Some checks failed
Testing / pytest (push) Successful in 36s
Testing / mypy (push) Failing after 42s
Testing / stability-extra-test (push) Successful in 26s
2024-02-04 00:18:31 +01:00
Sergio Martínez Portela
feb836b2b6 Merge branch 'fix/handle-headlines-with-skip-levels' into develop
Some checks reported warnings
Testing / pytest (push) Has been cancelled
Testing / mypy (push) Has been cancelled
Testing / stability-extra-test (push) Has been cancelled
2023-10-16 23:39:30 +02:00
Sergio Martínez Portela
e26a2f04ac Fix typing of headline_hierarchy, remove incorrect assertion. 2023-10-16 23:38:54 +02:00
Sergio Martínez Portela
9d87d533f4 Add (failing) test 2023-10-16 23:32:18 +02:00
Sergio Martínez Portela
e4821f02cd Fix: run checks on the updated headline_hierarchy.
Some checks reported warnings
Testing / pytest (push) Has been cancelled
Testing / mypy (push) Has been cancelled
Testing / stability-extra-test (push) Has been cancelled
2023-10-16 23:16:41 +02:00
Sergio Martínez Portela
1f54307fdb Merge branch 'dev/add-types' into develop
All checks were successful
Testing / pytest (push) Successful in 29s
Testing / mypy (push) Successful in 33s
Testing / stability-extra-test (push) Successful in 29s
2023-10-16 00:24:51 +02:00
Sergio Martínez Portela
1d0b4cce14 Complete typing with mypy --check-untyped-defs.
All checks were successful
Testing / pytest (push) Successful in 29s
Testing / mypy (push) Successful in 35s
Testing / stability-extra-test (push) Successful in 27s
2023-10-16 00:21:30 +02:00
Sergio Martínez Portela
9fb4bce5ef Add extra-test validation.
All checks were successful
Testing / pytest (push) Successful in 32s
Testing / mypy (push) Successful in 36s
Testing / stability-extra-test (push) Successful in 28s
2023-10-15 23:45:43 +02:00
Sergio Martínez Portela
61246da521 Merge MyPy and Pytest Gitea actions.
All checks were successful
Testing / pytest (push) Successful in 32s
Testing / mypy (push) Successful in 42s
2023-10-15 23:37:45 +02:00
Sergio Martínez Portela
da1288a6ba Add MyPy Gitea action.
All checks were successful
Mypy / mypy (push) Successful in 42s
Pytest / pytest (push) Successful in 31s
2023-10-15 23:36:43 +02:00
Sergio Martínez Portela
f7ddddb8c9 Add PyTest Gitea action.
All checks were successful
Pytest / pytest (push) Successful in 32s
2023-10-15 23:32:32 +02:00
Sergio Martínez Portela
343d864559 Fix handling of headline title-tags separated by tabs. 2023-10-15 16:39:00 +02:00
Sergio Martínez Portela
5ed34df57a Make typed functions pass mypy check. 2023-10-15 16:34:33 +02:00
Sergio Martínez Portela
99e44fd8b2 Merge branch 'dev/render-as-dom' into develop 2023-10-07 13:14:51 +02:00
19 changed files with 1512 additions and 327 deletions

View File

@ -0,0 +1,53 @@
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,3 +139,6 @@ dmypy.json
# Cython debug symbols # Cython debug symbols
cython_debug/ cython_debug/
# Files for PyPI publishing
README.md

View File

@ -7,6 +7,12 @@ 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
@ -21,6 +27,9 @@ 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,3 +1,6 @@
from typing import List, Optional, Union
class DrawerNode: class DrawerNode:
def __init__(self): def __init__(self):
self.children = [] self.children = []
@ -21,6 +24,14 @@ 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
@ -38,11 +49,12 @@ 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 = []
@ -53,21 +65,30 @@ 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:
@ -92,7 +113,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 = None self.lines: Optional[List] = None
self.subtype = subtype self.subtype = subtype
self.arguments = arguments self.arguments = arguments
@ -100,6 +121,26 @@ class CodeBlock(BlockNode):
self.lines = lines self.lines = lines
def __repr__(self): def __repr__(self):
return "<Code: {}>".format(len(self.lines)) return "<Code: {}>".format(len(self.lines or []))
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

0
org_rw/py.typed Normal file
View File

18
org_rw/types.py Normal file
View File

@ -0,0 +1,18 @@
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,9 +1,20 @@
import uuid import uuid
from .org_rw import (Bold, Code, Headline, Italic, Line, RawLine, ListItem, Strike, Text, from .org_rw import (
Underlined, Verbatim) Bold,
Code,
from .org_rw import dump_contents Headline,
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:
@ -40,6 +51,8 @@ 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))

View File

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

11
scripts/apply-formatting.sh Executable file
View File

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

View File

@ -5,6 +5,8 @@ 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.1.dev1", version="0.0.2",
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,3 +21,10 @@
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,6 +9,7 @@
: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

@ -0,0 +1,22 @@
#+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

13
tests/13-tags.org Normal file
View File

@ -0,0 +1,13 @@
#+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,14 +1,23 @@
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 org_rw import MarkerToken, MarkerType, Timestamp, dumps, load, loads, dom from utils.assertions import (
import org_rw BOLD,
CODE,
HL,
ITALIC,
SPAN,
STRIKE,
UNDERLINED,
VERBATIM,
WEB_LINK,
Doc,
Tokens,
)
from utils.assertions import (BOLD, CODE, HL, ITALIC, SPAN, STRIKE, UNDERLINED, import org_rw
VERBATIM, WEB_LINK, Doc, Tokens) from org_rw import MarkerToken, MarkerType, Timestamp, dom, dumps, load, loads
DIR = os.path.dirname(os.path.abspath(__file__)) DIR = os.path.dirname(os.path.abspath(__file__))
@ -202,7 +211,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), 7) self.assertEqual(len(links), 8)
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")
@ -224,6 +233,9 @@ 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"),
@ -280,17 +292,35 @@ class TestSerde(unittest.TestCase):
SPAN("\n"), SPAN("\n"),
SPAN( SPAN(
" This is a ", " This is a ",
WEB_LINK("[tricky web link]\u200b", "https://codigoparallevar.com/4"), WEB_LINK(
"[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("another tricky web link", "https://codigoparallevar.com/5"), WEB_LINK(
"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",
),
],
),
), ),
) )
@ -301,7 +331,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), 7) self.assertEqual(len(links), 8)
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"
@ -337,6 +367,9 @@ 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"),
@ -416,7 +449,19 @@ 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",
),
],
),
), ),
) )
@ -435,18 +480,22 @@ 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(), ['shell', ':results', 'verbatim']) self.assertEqual(snippets[0].arguments.split(), [":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'
@ -457,12 +506,14 @@ 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):
@ -500,7 +551,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,
@ -508,7 +559,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):
@ -561,7 +612,8 @@ 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",
], ],
) )
@ -595,12 +647,24 @@ class TestSerde(unittest.TestCase):
print(lists4) print(lists4)
self.assertEqual(len(lists4), 2) self.assertEqual(len(lists4), 2)
self.assertEqual(lists4[0][0].content, ["This is a list item...", "\n that spans multiple lines", "\n"]) self.assertEqual(
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(lists4[0][1].content, ["This is another list item...", "\n that has content on multiple lines", "\n"]) self.assertEqual(
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(lists4[1][0].content, ["This is another", "\n multiline list", "\n"]) self.assertEqual(
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):
@ -644,20 +708,22 @@ 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(hl.title.contents, [ self.assertEqual(
'Headline ', hl.title.contents,
MarkerToken(closing=False, tok_type=MarkerType.UNDERLINED_MODE), [
'with', "Headline ",
MarkerToken(closing=True, tok_type=MarkerType.UNDERLINED_MODE), MarkerToken(closing=False, tok_type=MarkerType.UNDERLINED_MODE),
' markup', "with",
]) MarkerToken(closing=True, tok_type=MarkerType.UNDERLINED_MODE),
" 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:
@ -678,9 +744,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]
@ -690,9 +756,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:
@ -702,27 +768,26 @@ class TestSerde(unittest.TestCase):
tree = hl.as_dom() tree = hl.as_dom()
non_props = [ non_props = [
item item for item in tree if not isinstance(item, dom.PropertyDrawerNode)
for item in tree
if not isinstance(item, dom.PropertyDrawerNode)
] ]
self.assertTrue(isinstance(non_props[0], dom.Text) self.assertTrue(
and isinstance(non_props[1], dom.TableNode) isinstance(non_props[0], dom.Text)
and isinstance(non_props[2], dom.Text), and isinstance(non_props[1], dom.TableNode)
'Expected <Text><Table><Text>') and isinstance(non_props[2], dom.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 (isinstance(item, dom.PropertyDrawerNode) if not (
or isinstance(item, dom.Text)) isinstance(item, dom.PropertyDrawerNode) or isinstance(item, dom.Text)
)
] ]
print_tree(non_props) print_tree(non_props)
self.assertTrue(len(non_props) == 1, self.assertTrue(len(non_props) == 1, "Expected <List>, with only (1) element")
'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:
@ -732,30 +797,232 @@ class TestSerde(unittest.TestCase):
tree = hl.as_dom() tree = hl.as_dom()
non_props = [ non_props = [
item item for item in tree if not isinstance(item, dom.PropertyDrawerNode)
for item in tree
if not isinstance(item, dom.PropertyDrawerNode)
] ]
print_tree(non_props) print_tree(non_props)
self.assertTrue((len(non_props) == 1) and (isinstance(non_props[0], dom.ListGroupNode)), self.assertTrue(
'Expected only <List> as top level') (len(non_props) == 1) and (isinstance(non_props[0], dom.ListGroupNode)),
"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(len(children) == 5, 'Expected 5 items inside <List>, 3 texts and 2 sublists') self.assertTrue(
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(children[4].content[0], '3') # Might be ['3', '\n'] but shouldn't be a breaking change self.assertEqual(
children[4].content[0], "3"
) # Might be ['3', '\n'] but shouldn't be a breaking change
# Assert lists # Assert lists
self.assertTrue(isinstance(children[1], dom.ListGroupNode), 'Expected sublist inside "1"') self.assertTrue(
self.assertEqual(children[1].children[0].content, ['1.1']) isinstance(children[1], dom.ListGroupNode), 'Expected sublist inside "1"'
self.assertEqual(children[1].children[1].content, ['1.2']) )
self.assertTrue(isinstance(children[3], dom.ListGroupNode), 'Expected sublist inside "2"') self.assertEqual(children[1].children[0].content, ["1.1"])
self.assertEqual(children[3].children[0].content, ['2.1']) self.assertEqual(children[1].children[1].content, ["1.2"])
self.assertEqual(children[3].children[1].content, ['2.2']) self.assertTrue(
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):
@ -775,6 +1042,10 @@ 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(" " * indentation * 2, "Str[" + element.replace('\n', '<NL>') + "]", type(element)) print(
" " * indentation * 2,
"Str[" + element.replace("\n", "<NL>") + "]",
type(element),
)
else: else:
print_tree(element, indentation, headline) print_tree(element, indentation, headline)

86
tests/test_timestamp.py Normal file
View File

@ -0,0 +1,86 @@
"""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,8 +2,17 @@ import collections
import unittest import unittest
from datetime import datetime from datetime import datetime
from org_rw import (Bold, Code, Italic, Line, Strike, Text, Underlined, from org_rw import (
Verbatim, get_raw_contents) Bold,
Code,
Italic,
Line,
Strike,
Text,
Underlined,
Verbatim,
get_raw_contents,
)
def timestamp_to_datetime(ts): def timestamp_to_datetime(ts):