Compare commits
34 Commits
8450785186
...
v0.1.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de87b7df93 | ||
|
|
a267d13fd7 | ||
|
|
a29973a110 | ||
|
|
31c782499e | ||
|
|
b7c7057095 | ||
|
|
49e3c90a3a | ||
|
|
129228c5c5 | ||
|
|
f0a7493a89 | ||
|
|
dc5695ec9f | ||
|
|
4ff62fbfae | ||
|
|
c892d406c3 | ||
|
|
1a41cfc6c7 | ||
|
|
4f34ab9089 | ||
|
|
9b2348c0ef | ||
|
|
5716cbccea | ||
|
|
124cd50243 | ||
|
|
bac5d6e1d9 | ||
|
|
ba15999534 | ||
|
|
61c3e6c10e | ||
|
|
a7e130838d | ||
|
|
853adadf91 | ||
|
|
7b61329889 | ||
|
|
9bcfb2f1da | ||
|
|
4c8d9a3063 | ||
|
|
48cb3c4a02 | ||
|
|
9e60ff6683 | ||
|
|
c1de001786 | ||
|
|
716af5bb45 | ||
|
|
6137a46231 | ||
|
|
bdd04f4d5c | ||
|
|
36bdc54703 | ||
|
|
3031b6edd4 | ||
|
|
1a704dd312 | ||
|
|
a74ea730f4 |
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "organic"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
authors = ["Tom Alexander <tom@fizz.buzz>"]
|
||||
description = "An org-mode parser."
|
||||
edition = "2021"
|
||||
|
||||
4
Makefile
4
Makefile
@@ -33,6 +33,10 @@ release:
|
||||
clean:
|
||||
> cargo clean
|
||||
|
||||
.PHONY: format
|
||||
format:
|
||||
> $(MAKE) -C docker/cargo_fmt run
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
> cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
FROM alpine:3.17 AS build
|
||||
RUN apk add --no-cache build-base musl-dev git autoconf make texinfo gnutls-dev ncurses-dev gawk
|
||||
RUN apk add --no-cache build-base musl-dev git autoconf make texinfo gnutls-dev ncurses-dev gawk libgccjit-dev
|
||||
|
||||
|
||||
FROM build AS build-emacs
|
||||
@@ -8,13 +8,13 @@ RUN git clone --depth 1 --branch $EMACS_VERSION https://git.savannah.gnu.org/git
|
||||
WORKDIR /root/emacs
|
||||
RUN mkdir /root/dist
|
||||
RUN ./autogen.sh
|
||||
RUN ./configure --prefix /usr --without-x --without-sound
|
||||
RUN ./configure --prefix /usr --without-x --without-sound --with-native-compilation=aot
|
||||
RUN make
|
||||
RUN make DESTDIR="/root/dist" install
|
||||
|
||||
|
||||
FROM build AS build-org-mode
|
||||
ARG ORG_VERSION=163bafb43dcc2bc94a2c7ccaa77d3d1dd488f1af
|
||||
ARG ORG_VERSION=c703541ffcc14965e3567f928de1683a1c1e33f6
|
||||
COPY --from=build-emacs /root/dist/ /
|
||||
RUN mkdir /root/dist
|
||||
# Savannah does not allow fetching specific revisions, so we're going to have to put unnecessary load on their server by cloning main and then checking out the revision we want.
|
||||
@@ -27,7 +27,7 @@ RUN make DESTDIR="/root/dist" install
|
||||
|
||||
FROM rustlang/rust:nightly-alpine3.17 AS tester
|
||||
ENV LANG=en_US.UTF-8
|
||||
RUN apk add --no-cache musl-dev ncurses gnutls
|
||||
RUN apk add --no-cache musl-dev ncurses gnutls libgccjit
|
||||
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
|
||||
COPY --from=build-emacs /root/dist/ /
|
||||
COPY --from=build-org-mode /root/dist/ /
|
||||
@@ -88,7 +88,7 @@ ARG DOOMEMACS_PATH=/foreign_documents/doomemacs
|
||||
ARG DOOMEMACS_REPO=https://github.com/doomemacs/doomemacs.git
|
||||
RUN mkdir -p $DOOMEMACS_PATH && git -C $DOOMEMACS_PATH init --initial-branch=main && git -C $DOOMEMACS_PATH remote add origin $DOOMEMACS_REPO && git -C $DOOMEMACS_PATH fetch origin $DOOMEMACS_VERSION && git -C $DOOMEMACS_PATH checkout FETCH_HEAD
|
||||
|
||||
ARG WORG_VERSION=74e80b0f7600801b1d1594542602394c085cc2f9
|
||||
ARG WORG_VERSION=0c8d5679b536af450b61812246a3e02b8103f4b8
|
||||
ARG WORG_PATH=/foreign_documents/worg
|
||||
ARG WORG_REPO=https://git.sr.ht/~bzg/worg
|
||||
RUN mkdir -p $WORG_PATH && git -C $WORG_PATH init --initial-branch=main && git -C $WORG_PATH remote add origin $WORG_REPO && git -C $WORG_PATH fetch origin $WORG_VERSION && git -C $WORG_PATH checkout FETCH_HEAD
|
||||
|
||||
@@ -41,9 +41,9 @@ function main {
|
||||
|
||||
set -e
|
||||
if [ "$all_status" -ne 0 ]; then
|
||||
echo "$(red_text "Some tests failed.")"
|
||||
red_text "Some tests failed."
|
||||
else
|
||||
echo "$(green_text "All tests passed.")"
|
||||
green_text "All tests passed."
|
||||
fi
|
||||
return "$all_status"
|
||||
}
|
||||
@@ -64,8 +64,9 @@ function indent {
|
||||
local depth="$1"
|
||||
local scaled_depth=$((depth * 2))
|
||||
shift 1
|
||||
local prefix=$(printf -- "%${scaled_depth}s")
|
||||
while read l; do
|
||||
local prefix
|
||||
prefix=$(printf -- "%${scaled_depth}s")
|
||||
while read -r l; do
|
||||
(IFS=' '; printf -- '%s%s\n' "$prefix" "$l")
|
||||
done
|
||||
}
|
||||
@@ -93,12 +94,13 @@ function compare_all_org_document {
|
||||
local target_document
|
||||
local all_status=0
|
||||
while read target_document; do
|
||||
local relative_path=$($REALPATH --relative-to "$root_dir" "$target_document")
|
||||
local relative_path
|
||||
relative_path=$($REALPATH --relative-to "$root_dir" "$target_document")
|
||||
set +e
|
||||
(run_compare "$relative_path" "$target_document")
|
||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||
set -e
|
||||
done<<<$(find "$root_dir" -type f -iname '*.org')
|
||||
done<<<"$(find "$root_dir" -type f -iname '*.org' | sort)"
|
||||
return "$all_status"
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#+BEGIN: timestamp :format "%Y-%m-%d %H:%M"
|
||||
|
||||
#+END
|
||||
@@ -0,0 +1,6 @@
|
||||
- foo ::
|
||||
|
||||
- bar ::
|
||||
|
||||
|
||||
baz
|
||||
@@ -0,0 +1,3 @@
|
||||
1. foo
|
||||
- bar
|
||||
- lorem :: ipsum
|
||||
@@ -0,0 +1,2 @@
|
||||
# Since this is an ordered list, the text before the " :: " is NOT parsed as a tag.
|
||||
1. foo :: bar
|
||||
4
org_mode_samples/greater_element/table/empty_formula.org
Normal file
4
org_mode_samples/greater_element/table/empty_formula.org
Normal file
@@ -0,0 +1,4 @@
|
||||
| Name | Value |
|
||||
|------+-------|
|
||||
| foo | bar |
|
||||
#+tblfm:
|
||||
@@ -0,0 +1,2 @@
|
||||
# Fixed width areas must begin with colon followed by a space, not a tab, so this is not a fixed width area.
|
||||
: foo
|
||||
11
org_mode_samples/object/plain_link/with_parenthesis.org
Normal file
11
org_mode_samples/object/plain_link/with_parenthesis.org
Normal file
@@ -0,0 +1,11 @@
|
||||
# Should be a link:
|
||||
https://en.wikipedia.org/wiki/Shebang_(Unix)
|
||||
|
||||
# No closing parenthesis, so link ends at underscore.
|
||||
https://en.wikipedia.org/wiki/Shebang_(Unix
|
||||
|
||||
# Parenthesis only allowed to depth of 2 so link ends at underscore.
|
||||
https://en.wikipedia.org/wiki/Shebang_(((Unix)))
|
||||
|
||||
# Even though they eventually become balanced, we hit negative parenthesis depth so link ends at )
|
||||
https://en.wikipedia.org/wiki/Shebang)Unix(
|
||||
@@ -0,0 +1 @@
|
||||
[[https://en.wikipedia.org/wiki/Shebang_(Unix)]]
|
||||
@@ -0,0 +1 @@
|
||||
[[[http://foo.bar/baz][lorem]]]
|
||||
@@ -0,0 +1,7 @@
|
||||
# Even though *exporting* honors the setting to require braces for subscript/superscript, the official org-mode parser still parses subscripts and superscripts.
|
||||
|
||||
#+OPTIONS: ^:{}
|
||||
foo_this isn't a subscript when exported due to lack of braces (but its still a subscript during parsing)
|
||||
|
||||
|
||||
bar_{this is a subscript}
|
||||
@@ -0,0 +1,13 @@
|
||||
foo_(bar)
|
||||
|
||||
foo_(b(ar)
|
||||
|
||||
foo_(b{ar)
|
||||
|
||||
foo_{b(ar}
|
||||
|
||||
foo_(b(a)r)
|
||||
|
||||
foo_b(a)r
|
||||
|
||||
foo_(b+ar)
|
||||
1
org_mode_samples/object/text_markup/double_star.org
Normal file
1
org_mode_samples/object/text_markup/double_star.org
Normal file
@@ -0,0 +1 @@
|
||||
foo ** bar ** baz
|
||||
1
org_mode_samples/object/text_markup/double_tilde.org
Normal file
1
org_mode_samples/object/text_markup/double_tilde.org
Normal file
@@ -0,0 +1 @@
|
||||
foo ~~ bar ~~ baz
|
||||
4
org_mode_samples/object/text_markup/target_substring.org
Normal file
4
org_mode_samples/object/text_markup/target_substring.org
Normal file
@@ -0,0 +1,4 @@
|
||||
# Since "foos" has an extra "s", this does not match the target.
|
||||
the foos bar
|
||||
|
||||
The <<<foo>>> and stuff.
|
||||
@@ -4,3 +4,5 @@
|
||||
* Baz
|
||||
*** Lorem
|
||||
* Ipsum
|
||||
**** Dolar
|
||||
***** Cat
|
||||
|
||||
@@ -8,10 +8,26 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
: ${TRACE:="NO"} # or YES to send traces to jaeger
|
||||
: ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking
|
||||
: ${NO_COLOR:=""} # Set to anything to disable color output
|
||||
: ${PROFILE:="debug"}
|
||||
|
||||
REALPATH=$(command -v uu-realpath || command -v realpath)
|
||||
MAKE=$(command -v gmake || command -v make)
|
||||
|
||||
############## Setup #########################
|
||||
|
||||
function die {
|
||||
local status_code="$1"
|
||||
shift
|
||||
(>&2 echo "${@}")
|
||||
exit "$status_code"
|
||||
}
|
||||
|
||||
function log {
|
||||
(>&2 echo "${@}")
|
||||
}
|
||||
|
||||
############## Program #########################
|
||||
|
||||
function main {
|
||||
build_container
|
||||
launch_container "${@}"
|
||||
@@ -23,7 +39,6 @@ function build_container {
|
||||
|
||||
function launch_container {
|
||||
local additional_flags=()
|
||||
local additional_args=()
|
||||
local features=(compare)
|
||||
|
||||
if [ "$NO_COLOR" != "" ]; then
|
||||
@@ -37,11 +52,8 @@ function launch_container {
|
||||
fi
|
||||
|
||||
if [ "$SHELL" != "YES" ]; then
|
||||
local features_joined=$(IFS=","; echo "${features[*]}")
|
||||
additional_args+=(cargo run --bin compare --no-default-features --features "$features_joined")
|
||||
additional_flags+=(--read-only)
|
||||
else
|
||||
additional_args+=(/bin/sh)
|
||||
additional_flags+=(-t)
|
||||
fi
|
||||
|
||||
@@ -49,16 +61,50 @@ function launch_container {
|
||||
additional_flags+=(--env RUST_BACKTRACE=full)
|
||||
fi
|
||||
|
||||
if [ "$SHELL" = "YES" ]; then
|
||||
exec docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/input:ro" -v "$($REALPATH "$DIR/../"):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test /bin/sh
|
||||
fi
|
||||
|
||||
local features_joined
|
||||
features_joined=$(IFS=","; echo "${features[*]}")
|
||||
|
||||
local build_flags=()
|
||||
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
|
||||
PROFILE="debug"
|
||||
else
|
||||
build_flags+=(--profile "$PROFILE")
|
||||
fi
|
||||
|
||||
|
||||
if [ $# -gt 0 ]; then
|
||||
# If we passed in args, we need to forward them along
|
||||
for path in "${@}"; do
|
||||
local full_path=$($REALPATH "$path")
|
||||
local containing_folder=$(dirname "$full_path")
|
||||
local file_name=$(basename "$full_path")
|
||||
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "${containing_folder}:/input:ro" -v "$($REALPATH "$DIR/../"):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}" -- "/input/$file_name"
|
||||
local full_path
|
||||
full_path=$($REALPATH "$path")
|
||||
init_script=$(cat <<EOF
|
||||
set -euo pipefail
|
||||
IFS=\$'\n\t'
|
||||
|
||||
cargo build --bin compare --no-default-features --features "$features_joined" ${build_flags[@]}
|
||||
exec /target/${PROFILE}/compare "/input${full_path}"
|
||||
EOF
|
||||
)
|
||||
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/input:ro" -v "$($REALPATH "$DIR/../"):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test sh -c "$init_script"
|
||||
done
|
||||
else
|
||||
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "$($REALPATH "$DIR/../"):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}"
|
||||
local current_directory init_script
|
||||
current_directory=$(pwd)
|
||||
init_script=$(cat <<EOF
|
||||
set -euo pipefail
|
||||
IFS=\$'\n\t'
|
||||
|
||||
cargo build --bin compare --no-default-features --features "$features_joined" ${build_flags[@]}
|
||||
cd /input${current_directory}
|
||||
exec /target/${PROFILE}/compare
|
||||
EOF
|
||||
)
|
||||
|
||||
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/input:ro" -v "$($REALPATH "$DIR/../"):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test sh -c "$init_script"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -9,17 +9,6 @@ REALPATH=$(command -v uu-realpath || command -v realpath)
|
||||
|
||||
############## Setup #########################
|
||||
|
||||
function cleanup {
|
||||
for f in "${folders[@]}"; do
|
||||
log "Deleting $f"
|
||||
rm -rf "$f"
|
||||
done
|
||||
}
|
||||
folders=()
|
||||
for sig in EXIT INT QUIT HUP TERM; do
|
||||
trap "set +e; cleanup" "$sig"
|
||||
done
|
||||
|
||||
function die {
|
||||
local status_code="$1"
|
||||
shift
|
||||
@@ -34,18 +23,18 @@ function log {
|
||||
############## Program #########################
|
||||
|
||||
function main {
|
||||
log "Is is recommended that the output of \`mktemp -d -t 'compare_bisect.XXXXXXXX'\` is inside a tmpfs filesystem since this script will make many writes to these folders."
|
||||
|
||||
local target_full_path=$($REALPATH "$1")
|
||||
local target_full_path
|
||||
target_full_path=$($REALPATH "$1")
|
||||
SOURCE_FOLDER=$(dirname "$target_full_path")
|
||||
TARGET_DOCUMENT=$(basename "$target_full_path")
|
||||
|
||||
|
||||
local good=0
|
||||
local bad=$(wc -l "$SOURCE_FOLDER/$TARGET_DOCUMENT" | awk '{print $1}')
|
||||
local bad
|
||||
bad=$(wc -l "$SOURCE_FOLDER/$TARGET_DOCUMENT" | awk '{print $1}')
|
||||
|
||||
set +e
|
||||
run_parse "$bad" &> /dev/null
|
||||
(run_parse "$bad")
|
||||
local status=$?
|
||||
set -e
|
||||
if [ $status -eq 0 ]; then
|
||||
@@ -71,21 +60,12 @@ function main {
|
||||
echo "Bad line: $bad"
|
||||
}
|
||||
|
||||
function setup_temp_dir {
|
||||
local temp_dir=$(mktemp -d -t 'compare_bisect.XXXXXXXX')
|
||||
cp -r "$SOURCE_FOLDER/"* "$temp_dir/"
|
||||
echo "$temp_dir"
|
||||
}
|
||||
|
||||
function run_parse {
|
||||
local lines="$1"
|
||||
local temp_dir=$(setup_temp_dir)
|
||||
folders+=("$temp_dir")
|
||||
cat "$SOURCE_FOLDER/$TARGET_DOCUMENT" | head -n "$lines" > "$temp_dir/$TARGET_DOCUMENT"
|
||||
"${DIR}/run_docker_compare.bash" "$temp_dir/$TARGET_DOCUMENT"
|
||||
|
||||
cd "$SOURCE_FOLDER"
|
||||
head -n "$lines" "$SOURCE_FOLDER/$TARGET_DOCUMENT" | PROFILE=release-lto "${DIR}/run_docker_compare.bash"
|
||||
local status=$?
|
||||
rm -rf "$temp_dir"
|
||||
# TODO: Remove temp_dir from folders
|
||||
return "$status"
|
||||
}
|
||||
|
||||
|
||||
@@ -490,11 +490,11 @@ fn compare_heading<'s>(
|
||||
let level = get_property(emacs, ":level")?
|
||||
.ok_or("Level should not be nil")?
|
||||
.as_atom()?;
|
||||
if rust.stars.to_string() != level {
|
||||
if rust.level.to_string() != level {
|
||||
this_status = DiffStatus::Bad;
|
||||
message = Some(format!(
|
||||
"Headline level do not match (emacs != rust): {} != {}",
|
||||
level, rust.stars
|
||||
level, rust.level
|
||||
))
|
||||
}
|
||||
|
||||
@@ -732,6 +732,10 @@ fn compare_plain_list<'s>(
|
||||
Ok(_) => {}
|
||||
};
|
||||
|
||||
// TODO compare :type
|
||||
//
|
||||
// :type is an unquoted atom of either descriptive, ordered, or unordered
|
||||
|
||||
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
|
||||
child_status.push(compare_plain_list_item(source, emacs_child, rust_child)?);
|
||||
}
|
||||
@@ -894,6 +898,8 @@ fn compare_dynamic_block<'s>(
|
||||
Ok(_) => {}
|
||||
};
|
||||
|
||||
// TODO: Compare :block-name :arguments
|
||||
|
||||
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
|
||||
child_status.push(compare_element(source, emacs_child, rust_child)?);
|
||||
}
|
||||
@@ -2475,6 +2481,8 @@ fn compare_subscript<'s>(
|
||||
Ok(_) => {}
|
||||
};
|
||||
|
||||
// TODO compare :use-brackets-p
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
@@ -2506,6 +2514,8 @@ fn compare_superscript<'s>(
|
||||
Ok(_) => {}
|
||||
};
|
||||
|
||||
// TODO compare :use-brackets-p
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
|
||||
@@ -22,6 +22,11 @@ pub struct GlobalSettings<'g, 's> {
|
||||
///
|
||||
/// Corresponds to the tab-width elisp variable.
|
||||
pub tab_width: IndentationLevel,
|
||||
|
||||
/// Whether to only allow odd headline levels.
|
||||
///
|
||||
/// Corresponds to org-odd-levels-only elisp variable.
|
||||
pub odd_levels_only: HeadlineLevelFilter,
|
||||
}
|
||||
|
||||
impl<'g, 's> GlobalSettings<'g, 's> {
|
||||
@@ -35,6 +40,7 @@ impl<'g, 's> GlobalSettings<'g, 's> {
|
||||
complete_todo_keywords: BTreeSet::new(),
|
||||
org_list_allow_alphabetical: false,
|
||||
tab_width: 8,
|
||||
odd_levels_only: HeadlineLevelFilter::OddEven,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,3 +50,9 @@ impl<'g, 's> Default for GlobalSettings<'g, 's> {
|
||||
GlobalSettings::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HeadlineLevelFilter {
|
||||
Odd,
|
||||
OddEven,
|
||||
}
|
||||
|
||||
@@ -25,5 +25,6 @@ pub(crate) use exiting::ExitClass;
|
||||
pub use file_access_interface::FileAccessInterface;
|
||||
pub use file_access_interface::LocalFileAccessInterface;
|
||||
pub use global_settings::GlobalSettings;
|
||||
pub use global_settings::HeadlineLevelFilter;
|
||||
pub(crate) use list::List;
|
||||
pub(crate) use parser_with_context::parser_with_context;
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::is_not;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::bytes::complete::tag_no_case;
|
||||
use nom::character::complete::line_ending;
|
||||
use nom::character::complete::space0;
|
||||
use nom::character::complete::space1;
|
||||
use nom::combinator::consumed;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::not;
|
||||
use nom::combinator::opt;
|
||||
use nom::combinator::recognize;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many_till;
|
||||
use nom::sequence::preceded;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::org_source::OrgSource;
|
||||
@@ -67,24 +71,23 @@ pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
|
||||
};
|
||||
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||
let (remaining, children) = match tuple((
|
||||
not(exit_matcher),
|
||||
not(exit_matcher)(remaining)?;
|
||||
let (remaining, leading_blank_lines) = opt(consumed(tuple((
|
||||
blank_line,
|
||||
many_till(blank_line, exit_matcher),
|
||||
))(remaining)
|
||||
{
|
||||
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
|
||||
many0(preceded(not(exit_matcher), blank_line)),
|
||||
))))(remaining)?;
|
||||
let leading_blank_lines =
|
||||
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
|
||||
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
||||
let source = get_consumed(remaining, remain);
|
||||
element.set_source(source.into());
|
||||
(remain, vec![element])
|
||||
}
|
||||
Err(_) => {
|
||||
let (remaining, (children, _exit_contents)) =
|
||||
element
|
||||
});
|
||||
let (remaining, (mut children, _exit_contents)) =
|
||||
many_till(element_matcher, exit_matcher)(remaining)?;
|
||||
(remaining, children)
|
||||
if let Some(lines) = leading_blank_lines {
|
||||
children.insert(0, lines);
|
||||
}
|
||||
};
|
||||
|
||||
let (remaining, _end) = dynamic_block_end(&parser_context, remaining)?;
|
||||
|
||||
let source = get_consumed(input, remaining);
|
||||
@@ -117,7 +120,8 @@ fn dynamic_block_end<'b, 'g, 'r, 's>(
|
||||
start_of_line(input)?;
|
||||
let (remaining, source) = recognize(tuple((
|
||||
space0,
|
||||
tag_no_case("#+end:"),
|
||||
tag_no_case("#+end"),
|
||||
opt(tag(":")),
|
||||
alt((eof, line_ending)),
|
||||
)))(input)?;
|
||||
Ok((remaining, source))
|
||||
|
||||
@@ -3,7 +3,6 @@ use nom::bytes::complete::is_not;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::character::complete::line_ending;
|
||||
use nom::character::complete::space0;
|
||||
use nom::character::complete::space1;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::not;
|
||||
use nom::combinator::recognize;
|
||||
@@ -12,6 +11,7 @@ use nom::sequence::preceded;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::only_space1;
|
||||
use super::util::org_line_ending;
|
||||
use crate::context::parser_with_context;
|
||||
use crate::context::RefContext;
|
||||
@@ -50,7 +50,7 @@ fn fixed_width_area_line<'b, 'g, 'r, 's>(
|
||||
let (remaining, _indent) = space0(input)?;
|
||||
let (remaining, _) = tuple((
|
||||
tag(":"),
|
||||
alt((recognize(tuple((space1, is_not("\r\n")))), space0)),
|
||||
alt((recognize(tuple((only_space1, is_not("\r\n")))), space0)),
|
||||
org_line_ending,
|
||||
))(remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::is_a;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::space0;
|
||||
@@ -11,7 +12,6 @@ use nom::combinator::recognize;
|
||||
use nom::combinator::verify;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many1;
|
||||
use nom::multi::many1_count;
|
||||
use nom::multi::separated_list1;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
@@ -34,30 +34,39 @@ use crate::parser::object_parser::standard_set_object;
|
||||
use crate::parser::util::blank_line;
|
||||
use crate::types::DocumentElement;
|
||||
use crate::types::Heading;
|
||||
use crate::types::HeadlineLevel;
|
||||
use crate::types::Object;
|
||||
use crate::types::PriorityCookie;
|
||||
use crate::types::TodoKeywordType;
|
||||
|
||||
pub(crate) const fn heading(
|
||||
parent_stars: usize,
|
||||
parent_level: HeadlineLevel,
|
||||
) -> impl for<'b, 'g, 'r, 's> Fn(
|
||||
RefContext<'b, 'g, 'r, 's>,
|
||||
OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Heading<'s>> {
|
||||
move |context, input: OrgSource<'_>| _heading(context, input, parent_stars)
|
||||
move |context, input: OrgSource<'_>| _heading(context, input, parent_level)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn _heading<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
parent_stars: usize,
|
||||
parent_star_count: HeadlineLevel,
|
||||
) -> Res<OrgSource<'s>, Heading<'s>> {
|
||||
not(|i| context.check_exit_matcher(i))(input)?;
|
||||
let (
|
||||
remaining,
|
||||
(star_count, maybe_todo_keyword, maybe_priority, maybe_comment, title, heading_tags),
|
||||
) = headline(context, input, parent_stars)?;
|
||||
(
|
||||
headline_level,
|
||||
star_count,
|
||||
maybe_todo_keyword,
|
||||
maybe_priority,
|
||||
maybe_comment,
|
||||
title,
|
||||
heading_tags,
|
||||
),
|
||||
) = headline(context, input, parent_star_count)?;
|
||||
let section_matcher = parser_with_context!(section)(context);
|
||||
let heading_matcher = parser_with_context!(heading(star_count))(context);
|
||||
let (remaining, maybe_section) =
|
||||
@@ -82,7 +91,7 @@ fn _heading<'b, 'g, 'r, 's>(
|
||||
remaining,
|
||||
Heading {
|
||||
source: source.into(),
|
||||
stars: star_count,
|
||||
level: headline_level,
|
||||
todo_keyword: maybe_todo_keyword.map(|(todo_keyword_type, todo_keyword)| {
|
||||
(todo_keyword_type, Into::<&str>::into(todo_keyword))
|
||||
}),
|
||||
@@ -106,11 +115,12 @@ pub(crate) fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()
|
||||
fn headline<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
parent_stars: usize,
|
||||
parent_star_count: HeadlineLevel,
|
||||
) -> Res<
|
||||
OrgSource<'s>,
|
||||
(
|
||||
usize,
|
||||
HeadlineLevel,
|
||||
HeadlineLevel,
|
||||
Option<(TodoKeywordType, OrgSource<'s>)>,
|
||||
Option<(OrgSource<'s>, PriorityCookie)>,
|
||||
Option<OrgSource<'s>>,
|
||||
@@ -124,11 +134,12 @@ fn headline<'b, 'g, 'r, 's>(
|
||||
});
|
||||
let parser_context = context.with_additional_node(&parser_context);
|
||||
|
||||
let (remaining, (_, star_count, _)) = tuple((
|
||||
let (remaining, (_, (headline_level, star_count, _), _)) = tuple((
|
||||
start_of_line,
|
||||
verify(many1_count(tag("*")), |star_count| {
|
||||
*star_count > parent_stars
|
||||
}),
|
||||
verify(
|
||||
parser_with_context!(headline_level)(&parser_context),
|
||||
|(_, count, _)| *count > parent_star_count,
|
||||
),
|
||||
peek(org_space),
|
||||
))(input)?;
|
||||
|
||||
@@ -158,6 +169,7 @@ fn headline<'b, 'g, 'r, 's>(
|
||||
Ok((
|
||||
remaining,
|
||||
(
|
||||
headline_level,
|
||||
star_count,
|
||||
maybe_todo_keyword.map(|(_, todo, _)| todo),
|
||||
maybe_priority,
|
||||
@@ -255,3 +267,23 @@ fn priority_cookie<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PriorityCooki
|
||||
})?;
|
||||
Ok((remaining, cookie))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn headline_level<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, (HeadlineLevel, HeadlineLevel, OrgSource<'s>)> {
|
||||
let (remaining, stars) = is_a("*")(input)?;
|
||||
let count = stars.len().try_into().unwrap();
|
||||
let level = match context.get_global_settings().odd_levels_only {
|
||||
crate::context::HeadlineLevelFilter::Odd => {
|
||||
if count % 2 == 0 {
|
||||
(count + 2) / 2
|
||||
} else {
|
||||
(count + 1) / 2
|
||||
}
|
||||
}
|
||||
crate::context::HeadlineLevelFilter::OddEven => count,
|
||||
};
|
||||
Ok((remaining, (level, count, stars)))
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::is_not;
|
||||
use nom::bytes::complete::tag_no_case;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::space1;
|
||||
use nom::combinator::map;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many_till;
|
||||
use nom::multi::separated_list0;
|
||||
|
||||
use super::keyword::filtered_keyword;
|
||||
use super::keyword_todo::todo_keywords;
|
||||
use super::OrgSource;
|
||||
use crate::context::HeadlineLevelFilter;
|
||||
use crate::error::Res;
|
||||
use crate::types::Keyword;
|
||||
use crate::GlobalSettings;
|
||||
@@ -50,6 +54,7 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
||||
) -> Result<GlobalSettings<'g, 's>, String> {
|
||||
let mut new_settings = original_settings.clone();
|
||||
|
||||
// Todo Keywords
|
||||
for kw in keywords.iter().filter(|kw| {
|
||||
kw.key.eq_ignore_ascii_case("todo")
|
||||
|| kw.key.eq_ignore_ascii_case("seq_todo")
|
||||
@@ -65,5 +70,21 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
||||
.extend(complete_words.into_iter().map(str::to_string));
|
||||
}
|
||||
|
||||
// Startup settings
|
||||
for kw in keywords
|
||||
.iter()
|
||||
.filter(|kw| kw.key.eq_ignore_ascii_case("startup"))
|
||||
{
|
||||
let (_remaining, settings) =
|
||||
separated_list0(space1::<&str, nom::error::Error<_>>, is_not(" \t"))(kw.value)
|
||||
.map_err(|err: nom::Err<_>| err.to_string())?;
|
||||
if settings.contains(&"odd") {
|
||||
new_settings.odd_levels_only = HeadlineLevelFilter::Odd;
|
||||
}
|
||||
if settings.contains(&"oddeven") {
|
||||
new_settings.odd_levels_only = HeadlineLevelFilter::OddEven;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(new_settings)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use nom::combinator::eof;
|
||||
use nom::combinator::not;
|
||||
use nom::combinator::peek;
|
||||
use nom::combinator::recognize;
|
||||
use nom::combinator::verify;
|
||||
use nom::multi::many_till;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
@@ -116,7 +117,9 @@ pub(crate) fn table_formula_keyword<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Keyword<'s>> {
|
||||
filtered_keyword(table_formula_key)(input)
|
||||
verify(filtered_keyword(table_formula_key), |kw| {
|
||||
!kw.value.is_empty()
|
||||
})(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
|
||||
@@ -59,6 +59,10 @@ impl<'s> OrgSource<'s> {
|
||||
self.end - self.start
|
||||
}
|
||||
|
||||
pub(crate) fn get_byte_offset(&self) -> usize {
|
||||
self.start
|
||||
}
|
||||
|
||||
pub(crate) fn get_preceding_character(&self) -> Option<char> {
|
||||
self.preceding_character
|
||||
}
|
||||
|
||||
@@ -5,17 +5,24 @@ use nom::character::complete::anychar;
|
||||
use nom::character::complete::none_of;
|
||||
use nom::character::complete::one_of;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::not;
|
||||
use nom::combinator::peek;
|
||||
use nom::combinator::recognize;
|
||||
use nom::combinator::verify;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many1;
|
||||
use nom::multi::many_till;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::org_source::BracketDepth;
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
|
||||
use crate::context::parser_with_context;
|
||||
use crate::context::ContextElement;
|
||||
use crate::context::ContextMatcher;
|
||||
use crate::context::ExitClass;
|
||||
use crate::context::ExitMatcherNode;
|
||||
use crate::context::Matcher;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
@@ -130,17 +137,77 @@ fn path_plain<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
// TODO: "optionally containing parenthesis-wrapped non-whitespace non-bracket substrings up to a depth of two. The string must end with either a non-punctation non-whitespace character, a forwards slash, or a parenthesis-wrapped substring"
|
||||
let path_plain_end = path_plain_end(input.get_parenthesis_depth());
|
||||
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &path_plain_end,
|
||||
});
|
||||
let parser_context = context.with_additional_node(&parser_context);
|
||||
|
||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||
let (remaining, _components) = many1(alt((
|
||||
parser_with_context!(path_plain_no_parenthesis)(&parser_context),
|
||||
parser_with_context!(path_plain_parenthesis)(&parser_context),
|
||||
)))(input)?;
|
||||
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((remaining, source))
|
||||
}
|
||||
|
||||
fn path_plain_end(starting_parenthesis_depth: BracketDepth) -> impl ContextMatcher {
|
||||
move |context, input: OrgSource<'_>| _path_plain_end(context, input, starting_parenthesis_depth)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn _path_plain_end<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
starting_parenthesis_depth: BracketDepth,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let (remaining, _leading_punctuation) = many0(verify(anychar, |c| {
|
||||
!" \t\r\n[]<>()/".contains(*c) && c.is_ascii_punctuation()
|
||||
}))(input)?;
|
||||
|
||||
let disallowed_character = recognize(one_of(" \t\r\n[]<>"))(remaining);
|
||||
if disallowed_character.is_ok() {
|
||||
return disallowed_character;
|
||||
}
|
||||
|
||||
let current_depth = remaining.get_parenthesis_depth() - starting_parenthesis_depth;
|
||||
if current_depth == 0 {
|
||||
let close_parenthesis =
|
||||
tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(remaining);
|
||||
if close_parenthesis.is_ok() {
|
||||
return close_parenthesis;
|
||||
}
|
||||
|
||||
let open_parenthesis_without_match = recognize(tuple((
|
||||
peek(tag("(")),
|
||||
not(parser_with_context!(path_plain_parenthesis)(context)),
|
||||
)))(remaining);
|
||||
if open_parenthesis_without_match.is_ok() {
|
||||
return open_parenthesis_without_match;
|
||||
}
|
||||
}
|
||||
|
||||
// many0 punctuation
|
||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"No path plain end".into(),
|
||||
))))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn path_plain_no_parenthesis<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let (remaining, path) = recognize(verify(
|
||||
many_till(anychar, peek(exit_matcher)),
|
||||
many_till(
|
||||
anychar,
|
||||
alt((
|
||||
peek(path_plain_no_parenthesis_disallowed_character),
|
||||
parser_with_context!(exit_matcher_parser)(context),
|
||||
)),
|
||||
),
|
||||
|(children, _exit_contents)| !children.is_empty(),
|
||||
))(input)?;
|
||||
|
||||
@@ -148,14 +215,65 @@ fn path_plain<'b, 'g, 'r, 's>(
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn path_plain_end<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
fn path_plain_no_parenthesis_disallowed_character<'s>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
recognize(many_till(
|
||||
verify(anychar, |c| {
|
||||
*c != '/' && (c.is_ascii_punctuation() || c.is_whitespace())
|
||||
}),
|
||||
one_of(" \t\r\n()[]<>"),
|
||||
))(input)
|
||||
recognize(verify(anychar, |c| {
|
||||
c.is_whitespace() || "()[]<>".contains(*c)
|
||||
}))(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn path_plain_parenthesis<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let (remaining, _opening) = tag("(")(input)?;
|
||||
let starting_depth = remaining.get_parenthesis_depth();
|
||||
|
||||
let (remaining, _path) = recognize(verify(
|
||||
many_till(
|
||||
anychar,
|
||||
alt((
|
||||
peek(path_plain_parenthesis_end(starting_depth)),
|
||||
parser_with_context!(exit_matcher_parser)(context),
|
||||
)),
|
||||
),
|
||||
|(children, _exit_contents)| !children.is_empty(),
|
||||
))(remaining)?;
|
||||
let (remaining, _opening) = tag(")")(remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
|
||||
Ok((remaining, source))
|
||||
}
|
||||
|
||||
fn path_plain_parenthesis_end(starting_parenthesis_depth: BracketDepth) -> impl Matcher {
|
||||
move |input: OrgSource<'_>| _path_plain_parenthesis_end(input, starting_parenthesis_depth)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn _path_plain_parenthesis_end<'s>(
|
||||
input: OrgSource<'s>,
|
||||
starting_parenthesis_depth: BracketDepth,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth;
|
||||
if current_depth < 0 {
|
||||
// This shouldn't be possible because if depth is 0 then a closing parenthesis should end the link.
|
||||
unreachable!("Exceeded plain link parenthesis depth.")
|
||||
}
|
||||
if current_depth == 0 {
|
||||
let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(input);
|
||||
if close_parenthesis.is_ok() {
|
||||
return close_parenthesis;
|
||||
}
|
||||
}
|
||||
if current_depth == 1 {
|
||||
let open_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("(")(input);
|
||||
if open_parenthesis.is_ok() {
|
||||
return open_parenthesis;
|
||||
}
|
||||
}
|
||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"No closing parenthesis".into(),
|
||||
))))
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
|
||||
parser_with_context!(bullet)(context),
|
||||
alt((space1, line_ending, eof)),
|
||||
)),
|
||||
|(_start, indent, bull, _after_whitespace)| {
|
||||
|(_start, indent, (_bullet_type, bull), _after_whitespace)| {
|
||||
Into::<&str>::into(bull) != "*" || indent.len() > 0
|
||||
},
|
||||
)(input)
|
||||
@@ -151,9 +151,9 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
||||
) -> Res<OrgSource<'s>, PlainListItem<'s>> {
|
||||
start_of_line(input)?;
|
||||
let (remaining, (indent_level, _leading_whitespace)) = indentation_level(context, input)?;
|
||||
let (remaining, bull) = verify(
|
||||
let (remaining, (bullet_type, bull)) = verify(
|
||||
parser_with_context!(bullet)(context),
|
||||
|bull: &OrgSource<'_>| Into::<&str>::into(bull) != "*" || indent_level > 0,
|
||||
|(_bullet_type, bull)| Into::<&str>::into(bull) != "*" || indent_level > 0,
|
||||
)(remaining)?;
|
||||
|
||||
let (remaining, _maybe_counter_set) = opt(tuple((
|
||||
@@ -165,15 +165,33 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
||||
|
||||
let (remaining, maybe_checkbox) = opt(tuple((space1, item_checkbox)))(remaining)?;
|
||||
|
||||
let (remaining, maybe_tag) =
|
||||
opt(tuple((space1, parser_with_context!(item_tag)(context))))(remaining)?;
|
||||
let (remaining, maybe_tag) = if let BulletType::Unordered = bullet_type {
|
||||
opt(tuple((space1, parser_with_context!(item_tag)(context))))(remaining)?
|
||||
} else {
|
||||
(remaining, None)
|
||||
};
|
||||
|
||||
let exit_matcher = plain_list_item_end(indent_level);
|
||||
let contexts = [
|
||||
ContextElement::ConsumeTrailingWhitespace(true),
|
||||
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Beta,
|
||||
exit_matcher: &exit_matcher,
|
||||
}),
|
||||
];
|
||||
let parser_context = context.with_additional_node(&contexts[0]);
|
||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||
|
||||
let maybe_contentless_item: Res<OrgSource<'_>, ()> = peek(parser_with_context!(
|
||||
detect_contentless_item_contents
|
||||
)(context))(remaining);
|
||||
)(&parser_context))(remaining);
|
||||
match maybe_contentless_item {
|
||||
Ok((_rem, _ws)) => {
|
||||
let (remaining, _trailing_ws) = opt(blank_line)(remaining)?;
|
||||
let (remaining, _trailing_ws) = if context.should_consume_trailing_whitespace() {
|
||||
recognize(alt((recognize(many1(blank_line)), eof)))(remaining)?
|
||||
} else {
|
||||
recognize(alt((blank_line, eof)))(remaining)?
|
||||
};
|
||||
let source = get_consumed(input, remaining);
|
||||
return Ok((
|
||||
remaining,
|
||||
@@ -191,17 +209,7 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
||||
}
|
||||
Err(_) => {}
|
||||
};
|
||||
let (remaining, _ws) = item_tag_post_gap(context, remaining)?;
|
||||
let exit_matcher = plain_list_item_end(indent_level);
|
||||
let contexts = [
|
||||
ContextElement::ConsumeTrailingWhitespace(true),
|
||||
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Beta,
|
||||
exit_matcher: &exit_matcher,
|
||||
}),
|
||||
];
|
||||
let parser_context = context.with_additional_node(&contexts[0]);
|
||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||
let (remaining, _ws) = item_tag_post_gap(&parser_context, remaining)?;
|
||||
|
||||
let (mut remaining, (mut children, _exit_contents)) = many_till(
|
||||
include_input(parser_with_context!(element(true))(&parser_context)),
|
||||
@@ -241,19 +249,28 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
||||
));
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum BulletType {
|
||||
Ordered,
|
||||
Unordered,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn bullet<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
) -> Res<OrgSource<'s>, (BulletType, OrgSource<'s>)> {
|
||||
alt((
|
||||
tag("*"),
|
||||
tag("-"),
|
||||
tag("+"),
|
||||
map(tag("*"), |bull| (BulletType::Unordered, bull)),
|
||||
map(tag("-"), |bull| (BulletType::Unordered, bull)),
|
||||
map(tag("+"), |bull| (BulletType::Unordered, bull)),
|
||||
map(
|
||||
recognize(tuple((
|
||||
parser_with_context!(counter)(context),
|
||||
alt((tag("."), tag(")"))),
|
||||
))),
|
||||
|bull| (BulletType::Ordered, bull),
|
||||
),
|
||||
))(input)
|
||||
}
|
||||
|
||||
@@ -359,22 +376,22 @@ fn item_tag_end<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
alt((
|
||||
alt((item_tag_divider, line_ending))(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn item_tag_divider<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
recognize(tuple((
|
||||
item_tag_divider,
|
||||
one_of(" \t"),
|
||||
tag("::"),
|
||||
peek(tuple((
|
||||
opt(tuple((
|
||||
peek(one_of(" \t")),
|
||||
many_till(anychar, peek(alt((item_tag_divider, line_ending, eof)))),
|
||||
))),
|
||||
alt((line_ending, eof)),
|
||||
))),
|
||||
line_ending,
|
||||
))(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn item_tag_divider<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
recognize(tuple((one_of(" \t"), tag("::"))))(input)
|
||||
)))(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
|
||||
@@ -4,11 +4,13 @@ use nom::bytes::complete::tag_no_case;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::line_ending;
|
||||
use nom::character::complete::one_of;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::peek;
|
||||
use nom::combinator::recognize;
|
||||
use nom::combinator::verify;
|
||||
use nom::multi::many1;
|
||||
use nom::multi::many_till;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::org_source::OrgSource;
|
||||
use super::radio_link::RematchObject;
|
||||
@@ -87,11 +89,17 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
|
||||
break;
|
||||
}
|
||||
|
||||
// let is_whitespace = recognize(many1(org_space_or_line_ending))(input);
|
||||
let is_not_whitespace = is_not::<&str, &str, CustomError<_>>(" \t\r\n")(goal);
|
||||
match is_not_whitespace {
|
||||
Ok((new_goal, payload)) => {
|
||||
let (new_remaining, _) = tag_no_case(payload)(remaining)?;
|
||||
let (new_remaining, _) = tuple((
|
||||
tag_no_case(payload),
|
||||
// TODO: Test to see what the REAL condition is. Checking for not-alphabetic works fine for now, but the real criteria might be something like the plain text exit matcher.
|
||||
peek(alt((
|
||||
recognize(verify(anychar, |c| !c.is_alphanumeric())),
|
||||
eof,
|
||||
))),
|
||||
))(remaining)?;
|
||||
remaining = new_remaining;
|
||||
goal = new_goal;
|
||||
continue;
|
||||
|
||||
@@ -62,6 +62,22 @@ pub(crate) fn rematch_target<'x, 'b, 'g, 'r, 's>(
|
||||
remaining = new_remaining;
|
||||
new_matches.push(new_match);
|
||||
}
|
||||
Object::Italic(italic) => {
|
||||
let (new_remaining, new_match) = italic.rematch_object(context, remaining)?;
|
||||
remaining = new_remaining;
|
||||
new_matches.push(new_match);
|
||||
}
|
||||
Object::Underline(underline) => {
|
||||
let (new_remaining, new_match) = underline.rematch_object(context, remaining)?;
|
||||
remaining = new_remaining;
|
||||
new_matches.push(new_match);
|
||||
}
|
||||
Object::StrikeThrough(strikethrough) => {
|
||||
let (new_remaining, new_match) =
|
||||
strikethrough.rematch_object(context, remaining)?;
|
||||
remaining = new_remaining;
|
||||
new_matches.push(new_match);
|
||||
}
|
||||
Object::PlainText(plaintext) => {
|
||||
let (new_remaining, new_match) = plaintext.rematch_object(context, remaining)?;
|
||||
remaining = new_remaining;
|
||||
|
||||
@@ -78,7 +78,7 @@ fn pathreg<'b, 'g, 'r, 's>(
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let (remaining, path) = escaped(
|
||||
take_till1(|c| match c {
|
||||
'\\' | ']' => true,
|
||||
'\\' | '[' | ']' => true,
|
||||
_ => false,
|
||||
}),
|
||||
'\\',
|
||||
|
||||
@@ -23,6 +23,7 @@ use crate::context::ContextElement;
|
||||
use crate::context::ContextMatcher;
|
||||
use crate::context::ExitClass;
|
||||
use crate::context::ExitMatcherNode;
|
||||
use crate::context::Matcher;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
@@ -112,6 +113,10 @@ fn script_body<'b, 'g, 'r, 's>(
|
||||
map(parser_with_context!(script_with_braces)(context), |body| {
|
||||
ScriptBody::WithBraces(body.into())
|
||||
}),
|
||||
map(
|
||||
parser_with_context!(script_with_parenthesis)(context),
|
||||
|body| ScriptBody::Braceless(body.into()),
|
||||
),
|
||||
))(input)
|
||||
}
|
||||
|
||||
@@ -199,3 +204,49 @@ fn _script_with_braces_end<'b, 'g, 'r, 's>(
|
||||
}
|
||||
tag("}")(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn script_with_parenthesis<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let (remaining, _) = tag("(")(input)?;
|
||||
let exit_with_depth = script_with_parenthesis_end(remaining.get_parenthesis_depth());
|
||||
|
||||
let (remaining, _) = many_till(
|
||||
anychar,
|
||||
alt((
|
||||
peek(exit_with_depth),
|
||||
parser_with_context!(exit_matcher_parser)(context),
|
||||
)),
|
||||
)(remaining)?;
|
||||
|
||||
let (remaining, _) = tag(")")(remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((remaining, source))
|
||||
}
|
||||
|
||||
fn script_with_parenthesis_end(starting_parenthesis_depth: BracketDepth) -> impl Matcher {
|
||||
move |input: OrgSource<'_>| _script_with_parenthesis_end(input, starting_parenthesis_depth)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn _script_with_parenthesis_end<'s>(
|
||||
input: OrgSource<'s>,
|
||||
starting_parenthesis_depth: BracketDepth,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth;
|
||||
if current_depth < 0 {
|
||||
// This shouldn't be possible because if depth is 0 then a closing bracket should end the citation.
|
||||
unreachable!("Exceeded citation key suffix bracket depth.")
|
||||
}
|
||||
if current_depth == 0 {
|
||||
let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(input);
|
||||
if close_parenthesis.is_ok() {
|
||||
return close_parenthesis;
|
||||
}
|
||||
}
|
||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"No script parenthesis end.".into(),
|
||||
))))
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ use super::org_source::OrgSource;
|
||||
use super::radio_link::RematchObject;
|
||||
use super::util::in_object_section;
|
||||
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
|
||||
use super::util::start_of_line;
|
||||
use crate::context::parser_with_context;
|
||||
use crate::context::ContextElement;
|
||||
use crate::context::ContextMatcher;
|
||||
@@ -64,8 +65,7 @@ fn bold<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Bold<'s>> {
|
||||
let text_markup_object_specialized = text_markup_object("*");
|
||||
let (remaining, children) = text_markup_object_specialized(context, input)?;
|
||||
let (remaining, children) = text_markup_object("*")(context, input)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
remaining,
|
||||
@@ -81,8 +81,7 @@ fn italic<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Italic<'s>> {
|
||||
let text_markup_object_specialized = text_markup_object("/");
|
||||
let (remaining, children) = text_markup_object_specialized(context, input)?;
|
||||
let (remaining, children) = text_markup_object("/")(context, input)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
remaining,
|
||||
@@ -98,8 +97,7 @@ fn underline<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Underline<'s>> {
|
||||
let text_markup_object_specialized = text_markup_object("_");
|
||||
let (remaining, children) = text_markup_object_specialized(context, input)?;
|
||||
let (remaining, children) = text_markup_object("_")(context, input)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
remaining,
|
||||
@@ -115,8 +113,7 @@ fn strike_through<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, StrikeThrough<'s>> {
|
||||
let text_markup_object_specialized = text_markup_object("+");
|
||||
let (remaining, children) = text_markup_object_specialized(context, input)?;
|
||||
let (remaining, children) = text_markup_object("+")(context, input)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
remaining,
|
||||
@@ -132,8 +129,7 @@ fn verbatim<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Verbatim<'s>> {
|
||||
let text_markup_string_specialized = text_markup_string("=");
|
||||
let (remaining, contents) = text_markup_string_specialized(context, input)?;
|
||||
let (remaining, contents) = text_markup_string("=")(context, input)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
remaining,
|
||||
@@ -149,8 +145,7 @@ fn code<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Code<'s>> {
|
||||
let text_markup_string_specialized = text_markup_string("~");
|
||||
let (remaining, contents) = text_markup_string_specialized(context, input)?;
|
||||
let (remaining, contents) = text_markup_string("~")(context, input)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
remaining,
|
||||
@@ -168,8 +163,7 @@ fn text_markup_object<'c>(
|
||||
OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Vec<Object<'s>>>
|
||||
+ 'c {
|
||||
let marker_symbol = marker_symbol.to_owned();
|
||||
move |context, input: OrgSource<'_>| _text_markup_object(context, input, marker_symbol.as_str())
|
||||
move |context, input: OrgSource<'_>| _text_markup_object(context, input, marker_symbol)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
@@ -188,7 +182,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
|
||||
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
||||
let (remaining, _peek_not_whitespace) =
|
||||
peek(verify(anychar, |c| !c.is_whitespace() && *c != '\u{200B}'))(remaining)?;
|
||||
let text_markup_end_specialized = text_markup_end(open.into());
|
||||
let text_markup_end_specialized = text_markup_end(open.into(), remaining.get_byte_offset());
|
||||
let contexts = [
|
||||
ContextElement::ContextObject(marker_symbol),
|
||||
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
@@ -250,7 +244,7 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
|
||||
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
||||
let (remaining, _peek_not_whitespace) =
|
||||
peek(verify(anychar, |c| !c.is_whitespace() && *c != '\u{200B}'))(remaining)?;
|
||||
let text_markup_end_specialized = text_markup_end(open.into());
|
||||
let text_markup_end_specialized = text_markup_end(open.into(), remaining.get_byte_offset());
|
||||
let contexts = [
|
||||
ContextElement::ContextObject(marker_symbol),
|
||||
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
@@ -292,16 +286,22 @@ fn pre<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, ()> {
|
||||
if start_of_line(input).is_ok() {
|
||||
return Ok((input, ()));
|
||||
}
|
||||
if preceded_by_whitespace(true)(input).is_ok() {
|
||||
return Ok((input, ()));
|
||||
}
|
||||
let preceding_character = input.get_preceding_character();
|
||||
match preceding_character {
|
||||
// If None, we are at the start of the file which is technically the beginning of a line.
|
||||
None | Some('\r') | Some('\n') | Some(' ') | Some('\t') | Some('-') | Some('(')
|
||||
| Some('{') | Some('\'') | Some('"') | Some('<') => {}
|
||||
Some('-') | Some('(') | Some('{') | Some('\'') | Some('"') => {}
|
||||
Some(_) => {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Not a valid pre character for text markup.".into(),
|
||||
))));
|
||||
}
|
||||
None => unreachable!(), // None is for start of file, which should already be handled by the start_of_line matcher above.
|
||||
};
|
||||
Ok((input, ()))
|
||||
}
|
||||
@@ -311,12 +311,17 @@ fn post<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, ()> {
|
||||
let (remaining, _) = alt((recognize(one_of(" \r\n\t-.,;:!?')}[\"")), line_ending))(input)?;
|
||||
let (remaining, _) = alt((recognize(one_of(" \r\n\t-.,;:!?')}[\"\\")), line_ending))(input)?;
|
||||
Ok((remaining, ()))
|
||||
}
|
||||
|
||||
fn text_markup_end<'c>(marker_symbol: &'c str) -> impl ContextMatcher + 'c {
|
||||
move |context, input: OrgSource<'_>| _text_markup_end(context, input, marker_symbol)
|
||||
fn text_markup_end<'c>(
|
||||
marker_symbol: &'c str,
|
||||
contents_start_offset: usize,
|
||||
) -> impl ContextMatcher + 'c {
|
||||
move |context, input: OrgSource<'_>| {
|
||||
_text_markup_end(context, input, marker_symbol, contents_start_offset)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
@@ -324,7 +329,13 @@ fn _text_markup_end<'b, 'g, 'r, 's, 'c>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
marker_symbol: &'c str,
|
||||
contents_start_offset: usize,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
if input.get_byte_offset() == contents_start_offset {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Text markup cannot be empty".into(),
|
||||
))));
|
||||
}
|
||||
not(preceded_by_whitespace(false))(input)?;
|
||||
let (remaining, _marker) = terminated(
|
||||
tag(marker_symbol),
|
||||
@@ -354,6 +365,66 @@ impl<'x> RematchObject<'x> for Bold<'x> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> RematchObject<'x> for Italic<'x> {
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn rematch_object<'b, 'g, 'r, 's>(
|
||||
&'x self,
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||
let (remaining, children) =
|
||||
_rematch_text_markup_object(_context, input, "/", &self.children)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
remaining,
|
||||
Object::Italic(Italic {
|
||||
source: source.into(),
|
||||
children,
|
||||
}),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> RematchObject<'x> for Underline<'x> {
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn rematch_object<'b, 'g, 'r, 's>(
|
||||
&'x self,
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||
let (remaining, children) =
|
||||
_rematch_text_markup_object(_context, input, "_", &self.children)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
remaining,
|
||||
Object::Underline(Underline {
|
||||
source: source.into(),
|
||||
children,
|
||||
}),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> RematchObject<'x> for StrikeThrough<'x> {
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn rematch_object<'b, 'g, 'r, 's>(
|
||||
&'x self,
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||
let (remaining, children) =
|
||||
_rematch_text_markup_object(_context, input, "+", &self.children)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
remaining,
|
||||
Object::StrikeThrough(StrikeThrough {
|
||||
source: source.into(),
|
||||
children,
|
||||
}),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
@@ -364,7 +435,7 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
|
||||
let (remaining, _) = pre(context, input)?;
|
||||
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
||||
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
|
||||
let text_markup_end_specialized = text_markup_end(open.into());
|
||||
let text_markup_end_specialized = text_markup_end(open.into(), remaining.get_byte_offset());
|
||||
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &text_markup_end_specialized,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::is_a;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::line_ending;
|
||||
use nom::character::complete::none_of;
|
||||
@@ -228,9 +229,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Match at least one space character.
|
||||
///
|
||||
/// This is similar to nom's space1 parser except space1 matches both spaces and tabs whereas this only matches spaces.
|
||||
pub(crate) fn only_space1<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
is_a(" ")(input)
|
||||
}
|
||||
|
||||
/// Match single space or tab.
|
||||
///
|
||||
/// In org-mode syntax, spaces and tabs are interchangeable.
|
||||
/// In org-mode syntax, spaces and tabs are often (but not always!) interchangeable.
|
||||
pub(crate) fn org_space<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, char> {
|
||||
one_of(" \t")(input)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use super::Object;
|
||||
use super::Source;
|
||||
|
||||
pub type PriorityCookie = u8;
|
||||
pub type HeadlineLevel = u16;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Document<'s> {
|
||||
@@ -14,7 +15,7 @@ pub struct Document<'s> {
|
||||
#[derive(Debug)]
|
||||
pub struct Heading<'s> {
|
||||
pub source: &'s str,
|
||||
pub stars: usize,
|
||||
pub level: HeadlineLevel,
|
||||
pub todo_keyword: Option<(TodoKeywordType, &'s str)>,
|
||||
pub priority_cookie: Option<PriorityCookie>,
|
||||
pub title: Vec<Object<'s>>,
|
||||
|
||||
@@ -7,6 +7,7 @@ mod source;
|
||||
pub use document::Document;
|
||||
pub use document::DocumentElement;
|
||||
pub use document::Heading;
|
||||
pub use document::HeadlineLevel;
|
||||
pub use document::PriorityCookie;
|
||||
pub use document::Section;
|
||||
pub use document::TodoKeywordType;
|
||||
|
||||
Reference in New Issue
Block a user