Since writers are normally only used synchronously (i.e. to write one document
at a time), we don't guard modifications to their internal
state (e.g. temporarily replacing the string.Builder in WriteNodesAsString)
against race conditions.
The package global `orgWriter` and corresponding use cases of it (`org.String`,
`$node.String`) break that pattern - the writer is potentially used from
multiple go routines at the same time. This results in race conditions that
manifest as error messages like e.g.
could not write output: runtime error: invalid memory address or nil pointer dereference. Using unrendered content.
Additionally, since we catch panics in `Document.Write`, the corresponding
stack trace is lost and dependents of go-org never know what hit them.
As using a writer across simultaneously across go routines is not a standard
pattern, we'll sync the use of the global `orgWriter` instead of trying to make
the actual writer threadsafe; less code noise for the common use case.
As headlines are always lvl (indent) 0 I thought it would be clever to abuse
the lvl field to store the headline lvl. Well, here we are - it wasn't clever.
List items only end when their parent ends or they run into something that's
not indented enough - everything else becomes part of the list item. Abusing
the token.lvl field (indent) for the headline lvl means headlines look indented
to the list item parsing logic - i.e. they become part of the list item if the
headline has a high enough lvl. That should never happen - so let's get rid of
the hack and (re-)calculate the headline lvl when we need it.
WriteNodesAsString is simple enough to implement but exposing it is helpful in
the implementation of extending writers and we don't aim to keep writer a small
interface so let's expose it.
Headlines are nested if their level is higher (more stars) than the current
headline. We're abusing the token.lvl field for this - as headlines can never
be indented we know the indentation must be 0 so we can cache the lvl (count of
stars) of the headline in that field.
This doesn't change anything right now so I'll postpone adding tests and stuff
until there are actual use cases for the AST and stuff.
Being able to very easily get the original [1] Org mode content seems like
something that will come up quite often and is very little code.
[1] it's not really the original content, but rather the pretty printed version
of that - as the semantics don't change it shouldn't matter.
this introduces the PropertyDrawer node to make it easier to access the
properties associated to a headline - normal drawers don't parse their content
into kv pairs
- we can't just look at the len of the string (~ #bytes) - that breaks down for
tables containing characters consisting of multiple bytes. This handles
more (still not all) cases and is good enough for now
- add _ to allowed tag chars - also require space between headline and tags
- links (link itself, not the description) spanning multiple lines are not
supported - otherwise we would have to take care of splitting link and adding
indentation for org pretty printing - and that sounds like such an edge case
that it seems cleaner to forbid them
Until now the footnotes section was parsed but not included in the resulting
AST - his required rebuilding it in the OrgWriter. It feels cleaner to include
it in the AST and only exclude it in the export