Compare commits
211 Commits
ef2c351696
...
feature_ma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
678106bb65 | ||
|
|
19432d91ab | ||
|
|
16a107eebb | ||
|
|
77348b560c | ||
|
|
fc79507ef3 | ||
|
|
9c1e6ccc97 | ||
|
|
0dbc8f0925 | ||
|
|
02fe10fba3 | ||
|
|
33d7ae03d1 | ||
|
|
03faa7257f | ||
|
|
ae3510abd5 | ||
|
|
ad3f47864a | ||
|
|
533ef2a9a8 | ||
|
|
cf37bc4111 | ||
|
|
e5224cda63 | ||
|
|
64e3481660 | ||
|
|
32071ce74d | ||
|
|
e84e2b5147 | ||
|
|
3348807a05 | ||
|
|
720afa5d32 | ||
|
|
dab598e5e7 | ||
|
|
b7a5dd48ea | ||
|
|
c475dce6da | ||
|
|
6d1675fa00 | ||
|
|
cda49c628c | ||
|
|
65b87bd65d | ||
|
|
5a7f34b63e | ||
|
|
edff1e089d | ||
|
|
bc29f1dfc0 | ||
|
|
e4656cddf6 | ||
|
|
1e3dadd458 | ||
|
|
2ec055af5a | ||
|
|
6823db5c60 | ||
|
|
21e1ceb8e0 | ||
|
|
655af88cdf | ||
|
|
8561fdc1bd | ||
|
|
f2089257b0 | ||
|
|
09821c8898 | ||
|
|
69ecfd2646 | ||
|
|
8162f03051 | ||
|
|
d8c3285e3c | ||
|
|
5db6cd617e | ||
|
|
4cd3697fb0 | ||
|
|
2cd6f736c2 | ||
|
|
5686256039 | ||
|
|
7cf1b2d2b8 | ||
|
|
b848d7be73 | ||
|
|
74f4aa8d33 | ||
|
|
4776898894 | ||
|
|
8e95ce6368 | ||
|
|
6c9c304f37 | ||
|
|
7fafbfb6bb | ||
|
|
56281633f3 | ||
|
|
823c33ef8e | ||
|
|
e5e5120a10 | ||
|
|
7df393f31d | ||
|
|
72d5f8f35c | ||
|
|
dae46adc12 | ||
|
|
d0dc737c79 | ||
|
|
1c9877015d | ||
|
|
2938d5809a | ||
|
|
f7ec89858d | ||
|
|
67b4dfdce6 | ||
|
|
63d092c83d | ||
|
|
a7b298eeec | ||
|
|
1bbfbc3164 | ||
|
|
2bcc3f0599 | ||
|
|
b93a12c32c | ||
|
|
df3045e424 | ||
|
|
72b8fec1be | ||
|
|
ab17904b1c | ||
|
|
306878c95d | ||
|
|
5768c8acda | ||
|
|
e28290ed79 | ||
|
|
fbabf60559 | ||
|
|
92abac37e2 | ||
|
|
899073e54f | ||
|
|
eb379af78d | ||
|
|
422804d846 | ||
|
|
cc83431d62 | ||
|
|
00354ccc20 | ||
|
|
b75eed6b1e | ||
|
|
e33ec4a02c | ||
|
|
f7afcec824 | ||
|
|
cf0991fdff | ||
|
|
d1e0ee831c | ||
|
|
34985c9045 | ||
|
|
7da09fea74 | ||
|
|
fc28e3b514 | ||
|
|
df5ee5af16 | ||
|
|
012c192aed | ||
|
|
67ca0fe8dd | ||
|
|
290a700a22 | ||
|
|
729be9302b | ||
|
|
44ad6753ca | ||
|
|
cd1b4ba785 | ||
|
|
1f10d3d064 | ||
|
|
f6e539a40b | ||
|
|
3ee18072c2 | ||
|
|
77de97703f | ||
|
|
023dd05267 | ||
|
|
66c71e7e40 | ||
|
|
6941825e75 | ||
|
|
bda291f771 | ||
|
|
06b83f9156 | ||
|
|
ef31900b51 | ||
|
|
8a221e0e0d | ||
|
|
f359676e28 | ||
|
|
7d7446d843 | ||
|
|
17e523b74c | ||
|
|
ece8fcd0c4 | ||
|
|
1a5b7ca30c | ||
|
|
b5a029e2bf | ||
|
|
c5f81298ba | ||
|
|
529418a9d1 | ||
|
|
d4a3628481 | ||
|
|
70f2747291 | ||
|
|
49d5a4e4b5 | ||
|
|
fa5fc41121 | ||
|
|
73e15286dc | ||
|
|
efa56a6bef | ||
|
|
3b11d8fb61 | ||
|
|
63fcad2ac6 | ||
|
|
23d587c699 | ||
|
|
f717d5e7df | ||
|
|
6d4379d029 | ||
|
|
8c00ee24ba | ||
|
|
4a565601c1 | ||
|
|
993c73dc9f | ||
|
|
7d73ac4bf6 | ||
|
|
b8b2e33137 | ||
|
|
c73e26e2d6 | ||
|
|
abb0aeacaf | ||
|
|
cb8775f2f4 | ||
|
|
caa6c41798 | ||
|
|
e54218c0d7 | ||
|
|
d60cad07e0 | ||
|
|
537fc00fd3 | ||
|
|
5c1d913c99 | ||
|
|
a1f3e9ea47 | ||
|
|
4d114206ef | ||
|
|
6b82214ec3 | ||
|
|
fb83e8d453 | ||
|
|
80039aa605 | ||
|
|
0b41e12424 | ||
|
|
e8979513aa | ||
|
|
e0d2bb8213 | ||
|
|
b323a407c4 | ||
|
|
45b01012b3 | ||
|
|
2773b35438 | ||
|
|
eef2944307 | ||
|
|
1e2ea17a9c | ||
|
|
7ce9dafd96 | ||
|
|
d678391789 | ||
|
|
640a9375bc | ||
|
|
1a8bf01fba | ||
|
|
4ad297f58a | ||
|
|
6b47a6c6c3 | ||
|
|
e24b413cd0 | ||
|
|
339ff5cd26 | ||
|
|
d5c611674e | ||
|
|
4e791b175e | ||
|
|
7611deb1ff | ||
|
|
b850f59640 | ||
|
|
a36a820e84 | ||
|
|
6822069c2f | ||
|
|
5fb66a586d | ||
|
|
9c2eb3b122 | ||
|
|
c1a99a03f8 | ||
|
|
8cdca061f8 | ||
|
|
d3c265415c | ||
|
|
95e033a99b | ||
|
|
1fb8ce9af6 | ||
|
|
eb03342506 | ||
|
|
8be47c551d | ||
|
|
3328c94a21 | ||
|
|
5e73ca74d5 | ||
|
|
0d6f6288c9 | ||
|
|
a817eefce7 | ||
|
|
4f5c40cd4b | ||
|
|
faf81d0143 | ||
|
|
c79b8c7833 | ||
|
|
5ad8fdf4b2 | ||
|
|
3ab0dd4531 | ||
|
|
11e76814f4 | ||
|
|
d46b3b9a58 | ||
|
|
754e9ff7d7 | ||
|
|
21f46d09e6 | ||
|
|
2966b91a09 | ||
|
|
a596dcff39 | ||
|
|
322a10dc38 | ||
|
|
87c378bc4e | ||
|
|
b6e268bdbb | ||
|
|
bab9232ebc | ||
|
|
c9ee61eae9 | ||
|
|
0cd398c30c | ||
|
|
bf8bd5bcbd | ||
|
|
b3c638428b | ||
|
|
db0ea7394e | ||
|
|
97f956d0bf | ||
|
|
167ffa650c | ||
|
|
27d863b875 | ||
|
|
e608b73d1a | ||
|
|
b27f911ff3 | ||
|
|
08e6efe5f5 | ||
|
|
0e73b83bf3 | ||
|
|
793e560bd5 | ||
|
|
0073af19e2 | ||
|
|
76187a0cb9 | ||
|
|
688779ba40 | ||
|
|
bd04451d58 |
@@ -1,3 +1,4 @@
|
||||
**/.git
|
||||
target
|
||||
Cargo.lock
|
||||
notes/
|
||||
|
||||
208
.lighthouse/pipeline-rust-build.yaml
Normal file
208
.lighthouse/pipeline-rust-build.yaml
Normal file
@@ -0,0 +1,208 @@
|
||||
apiVersion: tekton.dev/v1beta1
|
||||
kind: PipelineRun
|
||||
metadata:
|
||||
name: rust-build
|
||||
spec:
|
||||
pipelineSpec:
|
||||
params:
|
||||
- name: image-name
|
||||
description: The name for the built image
|
||||
type: string
|
||||
- name: path-to-image-context
|
||||
description: The path to the build context
|
||||
type: string
|
||||
- name: path-to-dockerfile
|
||||
description: The path to the Dockerfile
|
||||
type: string
|
||||
tasks:
|
||||
- name: report-pending
|
||||
taskRef:
|
||||
name: gitea-set-status
|
||||
runAfter:
|
||||
- fetch-repository
|
||||
params:
|
||||
- name: CONTEXT
|
||||
value: "$(params.JOB_NAME)"
|
||||
- name: REPO_FULL_NAME
|
||||
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
|
||||
- name: GITEA_HOST_URL
|
||||
value: code.fizz.buzz
|
||||
- name: SHA
|
||||
value: "$(tasks.fetch-repository.results.commit)"
|
||||
- name: DESCRIPTION
|
||||
value: "Build $(params.JOB_NAME) has started"
|
||||
- name: STATE
|
||||
value: pending
|
||||
- name: TARGET_URL
|
||||
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
|
||||
- name: fetch-repository
|
||||
taskRef:
|
||||
name: git-clone
|
||||
workspaces:
|
||||
- name: output
|
||||
workspace: git-source
|
||||
params:
|
||||
- name: url
|
||||
value: $(params.REPO_URL)
|
||||
- name: revision
|
||||
value: $(params.PULL_BASE_SHA)
|
||||
- name: deleteExisting
|
||||
value: "true"
|
||||
- name: build-image
|
||||
taskRef:
|
||||
name: kaniko
|
||||
params:
|
||||
- name: IMAGE
|
||||
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||
- name: CONTEXT
|
||||
value: $(params.path-to-image-context)
|
||||
- name: DOCKERFILE
|
||||
value: $(params.path-to-dockerfile)
|
||||
- name: BUILDER_IMAGE
|
||||
value: "gcr.io/kaniko-project/executor:v1.12.1"
|
||||
- name: EXTRA_ARGS
|
||||
value:
|
||||
- --cache=true
|
||||
- --cache-copy-layers
|
||||
- --cache-repo=harbor.fizz.buzz/kanikocache/cache
|
||||
- --use-new-run # Should result in a speed-up
|
||||
- --reproducible # To remove timestamps so layer caching works.
|
||||
- --snapshot-mode=redo
|
||||
- --skip-unused-stages=true
|
||||
- --registry-mirror=dockerhub.dockerhub.svc.cluster.local
|
||||
workspaces:
|
||||
- name: source
|
||||
workspace: git-source
|
||||
- name: dockerconfig
|
||||
workspace: docker-credentials
|
||||
runAfter:
|
||||
- fetch-repository
|
||||
- name: build-organic
|
||||
taskRef:
|
||||
name: run-docker-image
|
||||
matrix:
|
||||
params:
|
||||
- name: feature-compare
|
||||
value:
|
||||
- "true"
|
||||
- "false"
|
||||
- name: feature-tracing
|
||||
value:
|
||||
- "true"
|
||||
- "false"
|
||||
workspaces:
|
||||
- name: source
|
||||
workspace: git-source
|
||||
- name: cargo-cache
|
||||
workspace: cargo-cache
|
||||
runAfter:
|
||||
- build-image
|
||||
params:
|
||||
- name: command
|
||||
value: ["/bin/sh", "-c"]
|
||||
- name: args
|
||||
value:
|
||||
- |
|
||||
set -euo pipefail
|
||||
IFS=$$'\n\t'
|
||||
features=()
|
||||
if [ $(params.feature-compare) = "true" ]; then features+=(compare); fi
|
||||
if [ $(params.feature-tracing) = "true" ]; then features+=(tracing); fi
|
||||
if [ $${#features[@]} -eq 0 ]; then
|
||||
exec cargo build --no-default-features
|
||||
else
|
||||
featurelist=$$(IFS="," ; echo "$${features[*]}")
|
||||
exec cargo build --no-default-features --features "$$featurelist"
|
||||
fi
|
||||
- name: docker-image
|
||||
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||
finally:
|
||||
- name: report-success
|
||||
when:
|
||||
- input: "$(tasks.status)"
|
||||
operator: in
|
||||
values: ["Succeeded", "Completed"]
|
||||
taskRef:
|
||||
name: gitea-set-status
|
||||
params:
|
||||
- name: CONTEXT
|
||||
value: "$(params.JOB_NAME)"
|
||||
- name: REPO_FULL_NAME
|
||||
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
|
||||
- name: GITEA_HOST_URL
|
||||
value: code.fizz.buzz
|
||||
- name: SHA
|
||||
value: "$(tasks.fetch-repository.results.commit)"
|
||||
- name: DESCRIPTION
|
||||
value: "Build $(params.JOB_NAME) has succeeded"
|
||||
- name: STATE
|
||||
value: success
|
||||
- name: TARGET_URL
|
||||
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
|
||||
- name: report-failure
|
||||
when:
|
||||
- input: "$(tasks.status)"
|
||||
operator: in
|
||||
values: ["Failed"]
|
||||
taskRef:
|
||||
name: gitea-set-status
|
||||
params:
|
||||
- name: CONTEXT
|
||||
value: "$(params.JOB_NAME)"
|
||||
- name: REPO_FULL_NAME
|
||||
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
|
||||
- name: GITEA_HOST_URL
|
||||
value: code.fizz.buzz
|
||||
- name: SHA
|
||||
value: "$(tasks.fetch-repository.results.commit)"
|
||||
- name: DESCRIPTION
|
||||
value: "Build $(params.JOB_NAME) has failed"
|
||||
- name: STATE
|
||||
value: failure
|
||||
- name: TARGET_URL
|
||||
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
|
||||
- name: cargo-cache-autoclean
|
||||
taskRef:
|
||||
name: run-docker-image
|
||||
workspaces:
|
||||
- name: source
|
||||
workspace: git-source
|
||||
- name: cargo-cache
|
||||
workspace: cargo-cache
|
||||
params:
|
||||
- name: command
|
||||
value: [cargo, cache, --autoclean]
|
||||
- name: args
|
||||
value: []
|
||||
- name: docker-image
|
||||
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||
workspaces:
|
||||
- name: git-source
|
||||
- name: docker-credentials
|
||||
- name: cargo-cache
|
||||
workspaces:
|
||||
- name: git-source
|
||||
volumeClaimTemplate:
|
||||
spec:
|
||||
storageClassName: "nfs-client"
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
subPath: rust-source
|
||||
- name: cargo-cache
|
||||
persistentVolumeClaim:
|
||||
claimName: organic-cargo-cache-build
|
||||
- name: docker-credentials
|
||||
secret:
|
||||
secretName: harbor-plain
|
||||
serviceAccountName: build-bot
|
||||
timeout: 240h0m0s
|
||||
params:
|
||||
- name: image-name
|
||||
value: "harbor.fizz.buzz/private/organic-build"
|
||||
- name: path-to-image-context
|
||||
value: docker/organic_build/
|
||||
- name: path-to-dockerfile
|
||||
value: docker/organic_build/Dockerfile
|
||||
@@ -4,6 +4,10 @@ metadata:
|
||||
name: rust-test
|
||||
spec:
|
||||
pipelineSpec:
|
||||
timeouts:
|
||||
pipeline: "2h0m0s"
|
||||
tasks: "1h0m40s"
|
||||
finally: "0h30m0s"
|
||||
params:
|
||||
- name: image-name
|
||||
description: The name for the built image
|
||||
@@ -196,12 +200,11 @@ spec:
|
||||
subPath: rust-source
|
||||
- name: cargo-cache
|
||||
persistentVolumeClaim:
|
||||
claimName: cargo-cache
|
||||
claimName: organic-cargo-cache-test
|
||||
- name: docker-credentials
|
||||
secret:
|
||||
secretName: harbor-plain
|
||||
serviceAccountName: build-bot
|
||||
timeout: 240h0m0s
|
||||
params:
|
||||
- name: image-name
|
||||
value: "harbor.fizz.buzz/private/organic-test"
|
||||
|
||||
@@ -14,13 +14,13 @@ spec:
|
||||
- name: path-to-dockerfile
|
||||
description: The path to the Dockerfile
|
||||
type: string
|
||||
- name: command
|
||||
- name: rustfmt-command
|
||||
type: array
|
||||
description: Command to run.
|
||||
description: Command to run rustfmt.
|
||||
default: []
|
||||
- name: args
|
||||
- name: rustfmt-args
|
||||
type: array
|
||||
description: Arguments passed to command.
|
||||
description: Arguments passed to rustfmt.
|
||||
default: []
|
||||
- name: GIT_USER_NAME
|
||||
description: The username for git
|
||||
@@ -81,14 +81,6 @@ spec:
|
||||
value: $(params.PULL_BASE_SHA)
|
||||
- name: deleteExisting
|
||||
value: "true"
|
||||
- name: detect-tag
|
||||
taskRef:
|
||||
name: detect-tag
|
||||
workspaces:
|
||||
- name: repo
|
||||
workspace: git-source
|
||||
runAfter:
|
||||
- fetch-repository
|
||||
- name: build-image
|
||||
taskRef:
|
||||
name: kaniko
|
||||
@@ -118,8 +110,7 @@ spec:
|
||||
workspace: docker-credentials
|
||||
runAfter:
|
||||
- fetch-repository
|
||||
- detect-tag
|
||||
- name: run-image
|
||||
- name: rustfmt
|
||||
taskRef:
|
||||
name: run-docker-image
|
||||
workspaces:
|
||||
@@ -129,9 +120,26 @@ spec:
|
||||
- build-image
|
||||
params:
|
||||
- name: command
|
||||
value: ["$(params.command[*])"]
|
||||
value: ["$(params.rustfmt-command[*])"]
|
||||
- name: args
|
||||
value: ["$(params.args[*])"]
|
||||
value: ["$(params.rustfmt-args[*])"]
|
||||
- name: docker-image
|
||||
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||
- name: cargo-fix
|
||||
taskRef:
|
||||
name: run-docker-image
|
||||
workspaces:
|
||||
- name: source
|
||||
workspace: git-source
|
||||
- name: cargo-cache
|
||||
workspace: cargo-cache
|
||||
runAfter:
|
||||
- rustfmt
|
||||
params:
|
||||
- name: command
|
||||
value: ["cargo", "fix"]
|
||||
- name: args
|
||||
value: ["--allow-dirty"]
|
||||
- name: docker-image
|
||||
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||
- name: commit-changes
|
||||
@@ -148,7 +156,7 @@ spec:
|
||||
git config --global --add safe.directory /workspace/source
|
||||
git_status=$(git status --porcelain)
|
||||
if [ -n "$git_status" ]; then
|
||||
git commit -a -m "CI: format rust code."
|
||||
git commit -a -m "CI: autofix rust code."
|
||||
git push origin HEAD:$(params.PULL_BASE_REF)
|
||||
else
|
||||
echo "No changes to commit."
|
||||
@@ -157,7 +165,7 @@ spec:
|
||||
- name: source
|
||||
workspace: git-source
|
||||
runAfter:
|
||||
- run-image
|
||||
- cargo-fix
|
||||
finally:
|
||||
- name: report-success
|
||||
when:
|
||||
@@ -217,6 +225,9 @@ spec:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
subPath: rust-source
|
||||
- name: cargo-cache
|
||||
persistentVolumeClaim:
|
||||
claimName: organic-cargo-cache-fmt
|
||||
- name: docker-credentials
|
||||
secret:
|
||||
secretName: harbor-plain
|
||||
|
||||
@@ -2,31 +2,6 @@ apiVersion: config.lighthouse.jenkins-x.io/v1alpha1
|
||||
kind: TriggerConfig
|
||||
spec:
|
||||
postsubmits:
|
||||
- name: semver
|
||||
agent: tekton-pipeline
|
||||
branches:
|
||||
- ^main$
|
||||
- ^master$
|
||||
max_concurrency: 1
|
||||
# Override https-based url from lighthouse events.
|
||||
clone_uri: "git@code.fizz.buzz:talexander/organic.git"
|
||||
pipeline_run_spec:
|
||||
serviceAccountName: build-bot
|
||||
pipelineRef:
|
||||
name: semver
|
||||
namespace: lighthouse
|
||||
workspaces:
|
||||
- name: git-source
|
||||
volumeClaimTemplate:
|
||||
spec:
|
||||
storageClassName: "nfs-client"
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
subPath: organic-source
|
||||
params: []
|
||||
- name: rustfmt
|
||||
source: "pipeline-rustfmt.yaml"
|
||||
# Override https-based url from lighthouse events.
|
||||
@@ -41,3 +16,10 @@ spec:
|
||||
skip_branches:
|
||||
# We already run on every commit, so running when the semver tags get pushed is causing needless double-processing.
|
||||
- "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
|
||||
- name: rust-build
|
||||
source: "pipeline-rust-build.yaml"
|
||||
# Override https-based url from lighthouse events.
|
||||
clone_uri: "git@code.fizz.buzz:talexander/organic.git"
|
||||
skip_branches:
|
||||
# We already run on every commit, so running when the semver tags get pushed is causing needless double-processing.
|
||||
- "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
|
||||
|
||||
39
Cargo.toml
39
Cargo.toml
@@ -1,24 +1,40 @@
|
||||
[package]
|
||||
name = "organic"
|
||||
version = "0.1.0"
|
||||
version = "0.1.2"
|
||||
authors = ["Tom Alexander <tom@fizz.buzz>"]
|
||||
description = "An org-mode parser."
|
||||
edition = "2021"
|
||||
license = "0BSD"
|
||||
repository = "https://code.fizz.buzz/talexander/organic"
|
||||
readme = "README.md"
|
||||
keywords = ["emacs", "org-mode"]
|
||||
categories = ["parsing"]
|
||||
resolver = "2"
|
||||
include = [
|
||||
"LICENSE",
|
||||
"**/*.rs",
|
||||
"Cargo.toml",
|
||||
"tests/*"
|
||||
]
|
||||
|
||||
[lib]
|
||||
name = "organic"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "toy"
|
||||
# This bin exists for development purposes only. The real target of this crate is the library.
|
||||
name = "compare"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
nom = "7.1.1"
|
||||
opentelemetry = "0.17.0"
|
||||
opentelemetry-jaeger = "0.16.0"
|
||||
tracing = "0.1.37"
|
||||
tracing-opentelemetry = "0.17.2"
|
||||
tracing-subscriber = {version="0.3.16", features=["env-filter"]}
|
||||
opentelemetry = { version = "0.20.0", optional = true, default-features = false, features = ["trace", "rt-tokio"] }
|
||||
opentelemetry-otlp = { version = "0.13.0", optional = true }
|
||||
opentelemetry-semantic-conventions = { version = "0.12.0", optional = true }
|
||||
tokio = { version = "1.30.0", optional = true, default-features = false, features = ["rt", "rt-multi-thread"] }
|
||||
tracing = { version = "0.1.37", optional = true }
|
||||
tracing-opentelemetry = { version = "0.20.0", optional = true }
|
||||
tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] }
|
||||
|
||||
[build-dependencies]
|
||||
walkdir = "2.3.3"
|
||||
@@ -26,7 +42,14 @@ walkdir = "2.3.3"
|
||||
[features]
|
||||
default = ["compare"]
|
||||
compare = []
|
||||
tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"]
|
||||
|
||||
[profile.release]
|
||||
[profile.release-lto]
|
||||
inherits = "release"
|
||||
lto = true
|
||||
strip = "symbols"
|
||||
|
||||
[profile.perf]
|
||||
inherits = "release"
|
||||
lto = true
|
||||
debug = true
|
||||
|
||||
27
Makefile
27
Makefile
@@ -35,7 +35,17 @@ clean:
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
> cargo test --lib --test test_loader -- --test-threads $(TESTJOBS)
|
||||
> cargo test --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
||||
|
||||
.PHONY: dockertest
|
||||
dockertest:
|
||||
> $(MAKE) -C docker/organic_test
|
||||
> docker run --init --rm -i -t -v "$$(readlink -f ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test cargo test --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
||||
|
||||
.PHONY: dockerclean
|
||||
dockerclean:
|
||||
# Delete volumes created for running the tests in docker. This does not touch anything related to the jaeger docker container.
|
||||
> docker volume rm cargo-cache rust-cache
|
||||
|
||||
.PHONY: integrationtest
|
||||
integrationtest:
|
||||
@@ -45,17 +55,12 @@ integrationtest:
|
||||
unittest:
|
||||
> cargo test --lib -- --test-threads $(TESTJOBS)
|
||||
|
||||
.PHONY: run
|
||||
run:
|
||||
> cargo run
|
||||
|
||||
.PHONY: debug
|
||||
debug:
|
||||
> RUST_LOG=debug cargo run
|
||||
|
||||
.PHONY: jaeger
|
||||
jaeger:
|
||||
> docker run -d --rm --name toylanguagedocker -p 6831:6831/udp -p 6832:6832/udp -p 16686:16686 -p 14268:14268 jaegertracing/all-in-one:latest
|
||||
# 4317 for OTLP gRPC, 4318 for OTLP HTTP. We currently use gRPC but I forward both ports regardless.
|
||||
#
|
||||
# These flags didn't help even though they seem like they would: --collector.queue-size=20000 --collector.num-workers=100
|
||||
> docker run -d --rm --name organicdocker -p 4317:4317 -p 4318:4318 -p 16686:16686 -e COLLECTOR_OTLP_ENABLED=true jaegertracing/all-in-one:1.47 --collector.grpc-server.max-message-size=20000000 --collector.otlp.grpc.max-message-size=20000000
|
||||
|
||||
.PHONY: jaegerweb
|
||||
jaegerweb:
|
||||
@@ -63,4 +68,4 @@ jaegerweb:
|
||||
|
||||
.PHONY: jaegerstop
|
||||
jaegerstop:
|
||||
> docker stop toylanguagedocker
|
||||
> docker stop organicdocker
|
||||
|
||||
13
README.md
Normal file
13
README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Organic - Free Range Org-Mode
|
||||
|
||||
Organic is an emacs-less implementation of an [org-mode](https://orgmode.org/) parser.
|
||||
|
||||
|
||||
## Project Status
|
||||
|
||||
This project is a personal learning project to grow my experience in [rust](https://www.rust-lang.org/). It is under development and at this time I would not recommend anyone use this code. The goal is to turn this into a project others can use, at which point more information will appear in this README.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This project is released under the public-domain-equivalent [0BSD license](https://www.tldrlegal.com/license/bsd-0-clause-license). This license puts no restrictions on the use of this code (you do not even have to include the copyright notice or license text when using it). HOWEVER, this project has a couple permissively licensed dependencies which do require their copyright notices and/or license texts to be included. I am not a lawyer and this is not legal advice but it is my layperson's understanding that if you distribute a binary with this library linked in, you will need to abide by their terms since their code will also be linked in your binary. I try to keep the dependencies to a minimum and the most restrictive dependency I will ever include is a permissively licensed one.
|
||||
15
build.rs
15
build.rs
@@ -41,6 +41,7 @@ fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
|
||||
.strip_suffix(".org")
|
||||
.expect("Should have .org extension")
|
||||
.replace("/", "_");
|
||||
let test_name = format!("autogen_{}", test_name);
|
||||
|
||||
if let Some(_reason) = is_expect_fail(test_name.as_str()) {
|
||||
write!(test_file, "#[ignore]\n").unwrap();
|
||||
@@ -71,17 +72,9 @@ use organic::parser::sexp::sexp_with_padding;
|
||||
|
||||
fn is_expect_fail(name: &str) -> Option<&str> {
|
||||
match name {
|
||||
"drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
|
||||
"element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
|
||||
"element_container_priority_drawer_greater_block" => Some("Need to implement subscript."),
|
||||
"element_container_priority_dynamic_block_greater_block" => Some("Need to implement subscript."),
|
||||
"element_container_priority_footnote_definition_greater_block" => Some("Need to implement subscript."),
|
||||
"element_container_priority_greater_block_greater_block" => Some("Need to implement subscript."),
|
||||
"element_container_priority_section_greater_block" => Some("Need to implement subscript."),
|
||||
"paragraphs_paragraph_with_backslash_line_breaks" => Some("The text we're getting out of the parse tree is already processed to remove line breaks, so our comparison needs to take that into account."),
|
||||
"radio_link_before_and_after" => Some("Matching the contents of radio targets not yet implemented."),
|
||||
"radio_link_simple" => Some("Matching the contents of radio targets not yet implemented."),
|
||||
"radio_link_identical_or_semantically_identical" => Some("Would require having the 2-pass parsing implemented."),
|
||||
"autogen_greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
|
||||
"autogen_element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
|
||||
"autogen_lesser_element_paragraphs_paragraph_with_backslash_line_breaks" => Some("The text we're getting out of the parse tree is already processed to remove line breaks, so our comparison needs to take that into account."),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
FROM rustlang/rust:nightly-alpine3.17
|
||||
|
||||
RUN apk add --no-cache musl-dev
|
||||
RUN rustup component add rustfmt
|
||||
|
||||
ENTRYPOINT ["cargo", "fmt"]
|
||||
|
||||
@@ -6,7 +6,7 @@ all: build push
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
docker build -t $(IMAGE_NAME) -f Dockerfile ../
|
||||
docker build -t $(IMAGE_NAME) -f Dockerfile ../../
|
||||
|
||||
.PHONY: push
|
||||
push:
|
||||
|
||||
4
docker/organic_build/Dockerfile
Normal file
4
docker/organic_build/Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
||||
FROM rustlang/rust:nightly-alpine3.17
|
||||
|
||||
RUN apk add --no-cache musl-dev
|
||||
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
|
||||
35
docker/organic_build/Makefile
Normal file
35
docker/organic_build/Makefile
Normal file
@@ -0,0 +1,35 @@
|
||||
IMAGE_NAME:=organic-build
|
||||
# REMOTE_REPO:=harbor.fizz.buzz/private
|
||||
|
||||
.PHONY: all
|
||||
all: build push
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
docker build -t $(IMAGE_NAME) -f Dockerfile ../../
|
||||
|
||||
.PHONY: push
|
||||
push:
|
||||
ifdef REMOTE_REPO
|
||||
docker tag $(IMAGE_NAME) $(REMOTE_REPO)/$(IMAGE_NAME)
|
||||
docker push $(REMOTE_REPO)/$(IMAGE_NAME)
|
||||
else
|
||||
@echo "REMOTE_REPO not defined, not pushing to a remote repo."
|
||||
endif
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
docker rmi $(IMAGE_NAME)
|
||||
ifdef REMOTE_REPO
|
||||
docker rmi $(REMOTE_REPO)/$(IMAGE_NAME)
|
||||
else
|
||||
@echo "REMOTE_REPO not defined, not removing from remote repo."
|
||||
endif
|
||||
|
||||
.PHONY: run
|
||||
run:
|
||||
docker run --rm -i -t $(IMAGE_NAME)
|
||||
|
||||
.PHONY: shell
|
||||
shell:
|
||||
docker run --rm -i -t --entrypoint /bin/bash $(IMAGE_NAME)
|
||||
@@ -1,4 +1,32 @@
|
||||
FROM rustlang/rust:nightly-alpine3.17
|
||||
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 musl-dev emacs
|
||||
|
||||
FROM build AS build-emacs
|
||||
ARG EMACS_VERSION=emacs-29.1
|
||||
RUN git clone --depth 1 --branch $EMACS_VERSION https://git.savannah.gnu.org/git/emacs.git /root/emacs
|
||||
WORKDIR /root/emacs
|
||||
RUN mkdir /root/dist
|
||||
RUN ./autogen.sh
|
||||
RUN ./configure --prefix /usr --without-x --without-sound
|
||||
RUN make
|
||||
RUN make DESTDIR="/root/dist" install
|
||||
|
||||
|
||||
FROM build AS build-org-mode
|
||||
ARG ORG_VERSION=7bdec435ff5d86220d13c431e799c5ed44a57da1
|
||||
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.
|
||||
RUN git clone https://git.savannah.gnu.org/git/emacs/org-mode.git /root/org-mode && git -C /root/org-mode checkout $ORG_VERSION
|
||||
# RUN mkdir /root/org-mode && git -C /root/org-mode init --initial-branch=main && git -C /root/org-mode remote add origin https://git.savannah.gnu.org/git/emacs/org-mode.git && git -C /root/org-mode fetch origin $ORG_VERSION && git -C /root/org-mode checkout FETCH_HEAD
|
||||
WORKDIR /root/org-mode
|
||||
RUN make compile
|
||||
RUN make DESTDIR="/root/dist" install
|
||||
|
||||
|
||||
FROM rustlang/rust:nightly-alpine3.17
|
||||
RUN apk add --no-cache musl-dev ncurses gnutls
|
||||
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/ /
|
||||
|
||||
@@ -6,7 +6,7 @@ all: build push
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
docker build -t $(IMAGE_NAME) -f Dockerfile ../
|
||||
docker build -t $(IMAGE_NAME) -f Dockerfile ../../
|
||||
|
||||
.PHONY: push
|
||||
push:
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
Headings add exit matcher for heading
|
||||
|
||||
Paragraphs add exit matcher for elements (but it should be sans paragraph)
|
||||
|
||||
|
||||
|
||||
|
||||
* foo
|
||||
* bar
|
||||
* baz
|
||||
|
||||
context tree -> ()
|
||||
|
||||
match * foo
|
||||
|
||||
context tree -> exit(heading matcher)
|
||||
|
||||
check exit
|
||||
invoke heading matcher
|
||||
check exit
|
||||
invoke heading matcher
|
||||
check exit
|
||||
invoke heading matcher
|
||||
adds second heading matcher exit
|
||||
|
||||
|
||||
Ways around this:
|
||||
- Always parse SOMETHING before checking for exit
|
||||
- Doesn't always seem possible
|
||||
- Disable exit matchers during exit check
|
||||
- Seems like it would break syntax
|
||||
- Have separate parsers for the beginning of the exit condition (for example, checking for just the headline instead of the full heading parser)
|
||||
- Won't be possible with paragraphs ending at any other element
|
||||
- Check exit matchers in parent parser
|
||||
- Will this work? seems like it would just create larger loops
|
||||
27
notes/optimization_ideas.org
Normal file
27
notes/optimization_ideas.org
Normal file
@@ -0,0 +1,27 @@
|
||||
* Analysis
|
||||
** Parse start per character
|
||||
It might help analysis to record how often we start a specific type of parse for each character. For example, at the start of a plain list, if we had a count of how often each character was the start of a parse of a list we could use that to see how often that list is getting re-parsed.
|
||||
* Optimizations
|
||||
** Edit whitespace for list items
|
||||
Whether or not a list item owns the trailing whitespace depends on if it is the last list item in that list. Since we do not know ahead of time if an item is the last item in the list, we have to either re-parse the list item or modify it after parsing.
|
||||
|
||||
*** For
|
||||
We already are modifying the source of some elements after-the-fact with src_rust{set_source()} so this would be more of the same.
|
||||
*** Against
|
||||
I'd like to phase out such modifications because they seem hacky and fragile.
|
||||
** Make detect element function
|
||||
Some exit matchers are based on when the next element is found. Some elements do not need to be fully parsed to identify that they are a valid element. For example, src_org{1. foo} can already be identified as the start of a plain list (in the right context) without needing to parse the entire element.
|
||||
*** For
|
||||
Avoiding parsing the entire element for an exit matcher would reduce redundant parses.
|
||||
*** Against
|
||||
This adds code complexity and introduces the potential for bugs.
|
||||
|
||||
How many elements can be reasonably early-detected? For example, src_org{#+begin_src foo} is not enough to detect the start of a source block because without the src_org{#+end_src} it is just plain text.
|
||||
** Grab multiple characters in plaintext parser before checking exit matcher
|
||||
Currently we check the exit matcher after each character inside the plain text parser (and many others). Are there character sequences we can assume no exit matcher will trigger between? For example, a contiguous string of latin-alphabet letters?
|
||||
*** For
|
||||
This could significantly reduce our calls to exit matchers.
|
||||
*** Against
|
||||
I think targets would break this.
|
||||
|
||||
The exit matchers are already implicitly building this behavior since they should all exit very early when the starting character is wrong. Putting this logic in a centralized place, far away from where those characters are actually going to be used, is unfortunate for readability.
|
||||
@@ -0,0 +1,9 @@
|
||||
1. foo
|
||||
|
||||
1. bar
|
||||
|
||||
2. baz
|
||||
|
||||
2. lorem
|
||||
|
||||
ipsum
|
||||
@@ -0,0 +1,11 @@
|
||||
1. foo
|
||||
|
||||
1. bar
|
||||
|
||||
2. baz
|
||||
|
||||
cat
|
||||
|
||||
2. lorem
|
||||
|
||||
ipsum
|
||||
@@ -0,0 +1,10 @@
|
||||
1. cat
|
||||
1. foo
|
||||
|
||||
1. bar
|
||||
|
||||
2. baz
|
||||
|
||||
2. lorem
|
||||
|
||||
ipsum
|
||||
@@ -0,0 +1,12 @@
|
||||
1. cat
|
||||
1. foo
|
||||
|
||||
1. bar
|
||||
|
||||
2. baz
|
||||
|
||||
2. lorem
|
||||
|
||||
2. dog
|
||||
|
||||
ipsum
|
||||
5
org_mode_samples/object/citation/simple.org
Normal file
5
org_mode_samples/object/citation/simple.org
Normal file
@@ -0,0 +1,5 @@
|
||||
[cite:@foo]
|
||||
|
||||
[cite/a/b-_/foo:globalprefix;keyprefix @foo keysuffix;globalsuffix]
|
||||
|
||||
text before [cite:@bar] text after
|
||||
3
org_mode_samples/object/entity/simple.org
Normal file
3
org_mode_samples/object/entity/simple.org
Normal file
@@ -0,0 +1,3 @@
|
||||
foo \Delta bar
|
||||
foo \pibar
|
||||
foo \pi{}bar
|
||||
@@ -0,0 +1,9 @@
|
||||
@@latex:
|
||||
|
||||
|
||||
|
||||
\documentclass[margin,11pt]{res} % default is 10 pt
|
||||
|
||||
|
||||
|
||||
@@
|
||||
4
org_mode_samples/object/export_snippet/simple.org
Normal file
4
org_mode_samples/object/export_snippet/simple.org
Normal file
@@ -0,0 +1,4 @@
|
||||
@@html:<b>@@This text is only bold for the html exporter@@html:</b>@@
|
||||
@@latex:
|
||||
\documentclass[margin,11pt]{res} % default is 10 pt
|
||||
@@
|
||||
8
org_mode_samples/object/footnote_reference/simple.org
Normal file
8
org_mode_samples/object/footnote_reference/simple.org
Normal file
@@ -0,0 +1,8 @@
|
||||
[fn:1] This is a regular footnote definition because it is on an unindented line while lacking a definition inside the brackets.
|
||||
|
||||
|
||||
[fn:1] This is a footnote reference because the line is indented
|
||||
|
||||
[fn:2:This is a footnote reference since it has the definition inside the brackets. This style is referred to as an "inline footnote".]
|
||||
|
||||
[fn::This is a footnote reference since it has the definition inside the brackets. This style is referred to as an "anonymous footnote".]
|
||||
4
org_mode_samples/object/inline_babel_call/simple.org
Normal file
4
org_mode_samples/object/inline_babel_call/simple.org
Normal file
@@ -0,0 +1,4 @@
|
||||
call_foo(arguments)
|
||||
call_bar[header](arguments)
|
||||
call_baz(arguments)[header]
|
||||
call_lorem[header](arguments)[another header]
|
||||
2
org_mode_samples/object/inline_source_block/simple.org
Normal file
2
org_mode_samples/object/inline_source_block/simple.org
Normal file
@@ -0,0 +1,2 @@
|
||||
before src_foo{ipsum} after
|
||||
src_bar[lorem]{ipsum}
|
||||
@@ -0,0 +1,7 @@
|
||||
\begin{itemize}
|
||||
\item foo \sqrt{x}
|
||||
\end{itemize}
|
||||
|
||||
\begin{itemize}
|
||||
\item bar \sqrt{y}
|
||||
\end{itemize} % Need text on this line to prevent it from becoming a LaTeX environment org-mode element
|
||||
1
org_mode_samples/object/latex_fragment/math_mode.org
Normal file
1
org_mode_samples/object/latex_fragment/math_mode.org
Normal file
@@ -0,0 +1 @@
|
||||
tex can have math between dollar signs like $x^2=y$ and $$ x=+\sqrt{y} $$ but also braces and brackets like \( x=2 \) and \[ x=-\sqrt{2} \]
|
||||
4
org_mode_samples/object/latex_fragment/simple.org
Normal file
4
org_mode_samples/object/latex_fragment/simple.org
Normal file
@@ -0,0 +1,4 @@
|
||||
\begin{itemize}
|
||||
% this would be a LaTeX comment if this was a LaTeX document
|
||||
\item Heres some math \sqrt{y}
|
||||
\end{itemize} % Need text on this line to prevent it from becoming a LaTeX environment org-mode element
|
||||
6
org_mode_samples/object/line_break/simple.org
Normal file
6
org_mode_samples/object/line_break/simple.org
Normal file
@@ -0,0 +1,6 @@
|
||||
foo\\
|
||||
bar
|
||||
|
||||
|
||||
lorem\\\
|
||||
ipsum
|
||||
3
org_mode_samples/object/statistics_cookie/simple.org
Normal file
3
org_mode_samples/object/statistics_cookie/simple.org
Normal file
@@ -0,0 +1,3 @@
|
||||
[70%]
|
||||
[3/5]
|
||||
[-3/5]
|
||||
@@ -0,0 +1,7 @@
|
||||
foo^*
|
||||
bar_*
|
||||
baz^{hello *world*}
|
||||
lorem_{}
|
||||
ipsum^+,\.a5
|
||||
dolar_,\.a5
|
||||
text before foo_7 text afterwards
|
||||
@@ -0,0 +1 @@
|
||||
_{foo}
|
||||
3
org_mode_samples/object/target/simple.org
Normal file
3
org_mode_samples/object/target/simple.org
Normal file
@@ -0,0 +1,3 @@
|
||||
foo <<bar>> baz
|
||||
|
||||
lorem << ipsum >> dolar
|
||||
14
org_mode_samples/object/timestamp/simple.org
Normal file
14
org_mode_samples/object/timestamp/simple.org
Normal file
@@ -0,0 +1,14 @@
|
||||
# diary
|
||||
<%%(foo bar baz)>
|
||||
# active
|
||||
<1970-01-01 Thu 8:15rest +1w -1d>
|
||||
# inactive
|
||||
[1970-01-01 Thu 8:15rest +1w -1d]
|
||||
# active date range
|
||||
<1970-01-01 Thu 8:15rest +1w -1d>--<1970-01-01 Thu 8:15rest +1w -1d>
|
||||
# active time range
|
||||
<1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d>
|
||||
# inactive date range
|
||||
[1970-01-01 Thu 8:15rest +1w -1d]--[1970-01-01 Thu 8:15rest +1w -1d]
|
||||
# inactive time range
|
||||
[1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d]
|
||||
@@ -0,0 +1,18 @@
|
||||
foo bar.
|
||||
|
||||
|
||||
|
||||
|
||||
* Lorem
|
||||
baz
|
||||
|
||||
|
||||
|
||||
|
||||
* Ipsum
|
||||
alpha
|
||||
|
||||
|
||||
|
||||
|
||||
beta
|
||||
13
scripts/callgrind.bash
Executable file
13
scripts/callgrind.bash
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
cd "$DIR/../"
|
||||
|
||||
RUSTFLAGS="-C opt-level=0" cargo build --no-default-features
|
||||
valgrind --tool=callgrind --callgrind-out-file=callgrind.out target/debug/compare
|
||||
|
||||
echo "You probably want to run:"
|
||||
echo "callgrind_annotate --auto=yes callgrind.out"
|
||||
18
scripts/perf.bash
Executable file
18
scripts/perf.bash
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
: ${PROFILE:="perf"}
|
||||
|
||||
cd "$DIR/../"
|
||||
|
||||
cargo build --profile "$PROFILE" --no-default-features
|
||||
perf record --freq=2000 --call-graph dwarf --output=perf.data target/${PROFILE}/compare
|
||||
# Convert to a format firefox will read
|
||||
# flags to consider --show-info
|
||||
perf script -F +pid --input perf.data > perf.firefox
|
||||
|
||||
echo "You probably want to go to https://profiler.firefox.com/"
|
||||
echo "Either that or run hotspot"
|
||||
46
scripts/run_docker_compare.bash
Executable file
46
scripts/run_docker_compare.bash
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
: ${SHELL:="NO"} # or YES to launch a shell instead of running the test
|
||||
: ${TRACE:="NO"} # or YES to send traces to jaeger
|
||||
: ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking
|
||||
|
||||
cd "$DIR/../"
|
||||
REALPATH=$(command -v uu-realpath || command -v realpath)
|
||||
MAKE=$(command -v gmake || command -v make)
|
||||
|
||||
function main {
|
||||
build_container
|
||||
launch_container
|
||||
}
|
||||
|
||||
function build_container {
|
||||
$MAKE -C "$DIR/../docker/organic_test"
|
||||
}
|
||||
|
||||
function launch_container {
|
||||
local additional_flags=()
|
||||
local additional_args=()
|
||||
|
||||
if [ "$SHELL" != "YES" ]; then
|
||||
additional_args+=(cargo run)
|
||||
else
|
||||
additional_flags+=(-t)
|
||||
fi
|
||||
|
||||
if [ "$TRACE" = "YES" ]; then
|
||||
# We use the host network so it can talk to jaeger hosted at 127.0.0.1
|
||||
additional_flags+=(--network=host --env RUST_LOG=debug)
|
||||
fi
|
||||
|
||||
if [ "$BACKTRACE" = "YES" ]; then
|
||||
additional_flags+=(--env RUST_BACKTRACE=full)
|
||||
fi
|
||||
|
||||
docker run "${additional_flags[@]}" --init --rm -i -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test "${additional_args[@]}"
|
||||
}
|
||||
|
||||
main "${@}"
|
||||
57
scripts/run_docker_integration_test.bash
Executable file
57
scripts/run_docker_integration_test.bash
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
cd "$DIR/../"
|
||||
REALPATH=$(command -v uu-realpath || command -v realpath)
|
||||
MAKE=$(command -v gmake || command -v make)
|
||||
|
||||
function main {
|
||||
local test_names=$(get_test_names "${@}")
|
||||
build_container
|
||||
|
||||
local test
|
||||
while read test; do
|
||||
launch_container "$test"
|
||||
done<<<"$test_names"
|
||||
}
|
||||
|
||||
function build_container {
|
||||
$MAKE -C "$DIR/../docker/organic_test"
|
||||
}
|
||||
|
||||
function get_test_names {
|
||||
local test_file
|
||||
local samples_dir=$($REALPATH "$DIR/../org_mode_samples")
|
||||
for test_file in "$@"
|
||||
do
|
||||
if [ -e "$test_file" ]; then
|
||||
local test_file_full_path=$($REALPATH "$test_file")
|
||||
local relative_to_samples=$($REALPATH --relative-to "$samples_dir" "$test_file_full_path")
|
||||
local without_extension="${relative_to_samples%.org}"
|
||||
echo "${without_extension/\//_}" | tr '[:upper:]' '[:lower:]'
|
||||
else
|
||||
echo "$test_file" | tr '[:upper:]' '[:lower:]'
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function launch_container {
|
||||
local test="$1"
|
||||
local additional_args=()
|
||||
|
||||
local init_script=$(cat <<EOF
|
||||
set -euo pipefail
|
||||
IFS=\$'\n\t'
|
||||
|
||||
cargo test --no-fail-fast --lib --test test_loader "$test" -- --show-output
|
||||
EOF
|
||||
)
|
||||
|
||||
docker run --init --rm -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test sh -c "$init_script"
|
||||
}
|
||||
|
||||
|
||||
main "${@}"
|
||||
@@ -4,17 +4,27 @@ set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
cd "$DIR/../"
|
||||
REALPATH=$(command -v uu-realpath || command -v realpath)
|
||||
|
||||
samples_dir=$(readlink -f "$DIR/../org_mode_samples")
|
||||
function main {
|
||||
local test_names=$(get_test_names "${@}")
|
||||
|
||||
local test
|
||||
while read test; do
|
||||
cargo test --no-fail-fast --test test_loader "$test" -- --show-output
|
||||
done<<<"$test_names"
|
||||
}
|
||||
|
||||
function get_test_names {
|
||||
local test_file
|
||||
local samples_dir=$($REALPATH "$DIR/../org_mode_samples")
|
||||
for test_file in "$@"
|
||||
do
|
||||
if [ -e "$test_file" ]; then
|
||||
test_file_full_path=$(readlink -f "$test_file")
|
||||
relative_to_samples=$($REALPATH --relative-to "$samples_dir" "$test_file_full_path")
|
||||
without_extension="${relative_to_samples%.org}"
|
||||
local test_file_full_path=$($REALPATH "$test_file")
|
||||
local relative_to_samples=$($REALPATH --relative-to "$samples_dir" "$test_file_full_path")
|
||||
local without_extension="${relative_to_samples%.org}"
|
||||
echo "${without_extension/\//_}" | tr '[:upper:]' '[:lower:]'
|
||||
else
|
||||
echo "$test_file" | tr '[:upper:]' '[:lower:]'
|
||||
@@ -22,6 +32,4 @@ function get_test_names {
|
||||
done
|
||||
}
|
||||
|
||||
get_test_names "$@" | while read test; do
|
||||
(cd "$DIR/../" && cargo test --no-fail-fast --test test_loader "$test" -- --show-output)
|
||||
done
|
||||
main "${@}"
|
||||
|
||||
@@ -3,6 +3,8 @@ use super::util::assert_name;
|
||||
use crate::parser::sexp::Token;
|
||||
use crate::parser::AngleLink;
|
||||
use crate::parser::Bold;
|
||||
use crate::parser::Citation;
|
||||
use crate::parser::CitationReference;
|
||||
use crate::parser::Clock;
|
||||
use crate::parser::Code;
|
||||
use crate::parser::Comment;
|
||||
@@ -13,16 +15,23 @@ use crate::parser::DocumentElement;
|
||||
use crate::parser::Drawer;
|
||||
use crate::parser::DynamicBlock;
|
||||
use crate::parser::Element;
|
||||
use crate::parser::Entity;
|
||||
use crate::parser::ExampleBlock;
|
||||
use crate::parser::ExportBlock;
|
||||
use crate::parser::ExportSnippet;
|
||||
use crate::parser::FixedWidthArea;
|
||||
use crate::parser::FootnoteDefinition;
|
||||
use crate::parser::FootnoteReference;
|
||||
use crate::parser::GreaterBlock;
|
||||
use crate::parser::Heading;
|
||||
use crate::parser::HorizontalRule;
|
||||
use crate::parser::InlineBabelCall;
|
||||
use crate::parser::InlineSourceBlock;
|
||||
use crate::parser::Italic;
|
||||
use crate::parser::Keyword;
|
||||
use crate::parser::LatexEnvironment;
|
||||
use crate::parser::LatexFragment;
|
||||
use crate::parser::LineBreak;
|
||||
use crate::parser::Object;
|
||||
use crate::parser::OrgMacro;
|
||||
use crate::parser::Paragraph;
|
||||
@@ -37,10 +46,15 @@ use crate::parser::RadioTarget;
|
||||
use crate::parser::RegularLink;
|
||||
use crate::parser::Section;
|
||||
use crate::parser::SrcBlock;
|
||||
use crate::parser::StatisticsCookie;
|
||||
use crate::parser::StrikeThrough;
|
||||
use crate::parser::Subscript;
|
||||
use crate::parser::Superscript;
|
||||
use crate::parser::Table;
|
||||
use crate::parser::TableCell;
|
||||
use crate::parser::TableRow;
|
||||
use crate::parser::Target;
|
||||
use crate::parser::Timestamp;
|
||||
use crate::parser::Underline;
|
||||
use crate::parser::Verbatim;
|
||||
use crate::parser::VerseBlock;
|
||||
@@ -154,6 +168,20 @@ fn compare_object<'s>(
|
||||
Object::PlainLink(obj) => compare_plain_link(source, emacs, obj),
|
||||
Object::AngleLink(obj) => compare_angle_link(source, emacs, obj),
|
||||
Object::OrgMacro(obj) => compare_org_macro(source, emacs, obj),
|
||||
Object::Entity(obj) => compare_entity(source, emacs, obj),
|
||||
Object::LatexFragment(obj) => compare_latex_fragment(source, emacs, obj),
|
||||
Object::ExportSnippet(obj) => compare_export_snippet(source, emacs, obj),
|
||||
Object::FootnoteReference(obj) => compare_footnote_reference(source, emacs, obj),
|
||||
Object::Citation(obj) => compare_citation(source, emacs, obj),
|
||||
Object::CitationReference(obj) => compare_citation_reference(source, emacs, obj),
|
||||
Object::InlineBabelCall(obj) => compare_inline_babel_call(source, emacs, obj),
|
||||
Object::InlineSourceBlock(obj) => compare_inline_source_block(source, emacs, obj),
|
||||
Object::LineBreak(obj) => compare_line_break(source, emacs, obj),
|
||||
Object::Target(obj) => compare_target(source, emacs, obj),
|
||||
Object::StatisticsCookie(obj) => compare_statistics_cookie(source, emacs, obj),
|
||||
Object::Subscript(obj) => compare_subscript(source, emacs, obj),
|
||||
Object::Superscript(obj) => compare_superscript(source, emacs, obj),
|
||||
Object::Timestamp(obj) => compare_timestamp(source, emacs, obj),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1238,3 +1266,325 @@ fn compare_org_macro<'s>(
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_entity<'s>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s Entity<'s>,
|
||||
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let emacs_name = "entity";
|
||||
if assert_name(emacs, emacs_name).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
if assert_bounds(source, emacs, rust).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
message: None,
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_latex_fragment<'s>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s LatexFragment<'s>,
|
||||
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let emacs_name = "latex-fragment";
|
||||
if assert_name(emacs, emacs_name).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
if assert_bounds(source, emacs, rust).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
message: None,
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_export_snippet<'s>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s ExportSnippet<'s>,
|
||||
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let emacs_name = "export-snippet";
|
||||
if assert_name(emacs, emacs_name).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
if assert_bounds(source, emacs, rust).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
message: None,
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_footnote_reference<'s>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s FootnoteReference<'s>,
|
||||
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let emacs_name = "footnote-reference";
|
||||
if assert_name(emacs, emacs_name).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
if assert_bounds(source, emacs, rust).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
message: None,
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_citation<'s>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s Citation<'s>,
|
||||
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let emacs_name = "citation";
|
||||
if assert_name(emacs, emacs_name).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
if assert_bounds(source, emacs, rust).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
message: None,
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_citation_reference<'s>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s CitationReference<'s>,
|
||||
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let emacs_name = "citation-reference";
|
||||
if assert_name(emacs, emacs_name).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
if assert_bounds(source, emacs, rust).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
message: None,
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_inline_babel_call<'s>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s InlineBabelCall<'s>,
|
||||
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let emacs_name = "inline-babel-call";
|
||||
if assert_name(emacs, emacs_name).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
if assert_bounds(source, emacs, rust).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
message: None,
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_inline_source_block<'s>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s InlineSourceBlock<'s>,
|
||||
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let emacs_name = "inline-src-block";
|
||||
if assert_name(emacs, emacs_name).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
if assert_bounds(source, emacs, rust).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
message: None,
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_line_break<'s>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s LineBreak<'s>,
|
||||
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let emacs_name = "line-break";
|
||||
if assert_name(emacs, emacs_name).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
if assert_bounds(source, emacs, rust).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
message: None,
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_target<'s>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s Target<'s>,
|
||||
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let emacs_name = "target";
|
||||
if assert_name(emacs, emacs_name).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
if assert_bounds(source, emacs, rust).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
message: None,
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_statistics_cookie<'s>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s StatisticsCookie<'s>,
|
||||
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let emacs_name = "statistics-cookie";
|
||||
if assert_name(emacs, emacs_name).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
if assert_bounds(source, emacs, rust).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
message: None,
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_subscript<'s>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s Subscript<'s>,
|
||||
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let emacs_name = "subscript";
|
||||
if assert_name(emacs, emacs_name).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
if assert_bounds(source, emacs, rust).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
message: None,
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_superscript<'s>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s Superscript<'s>,
|
||||
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let emacs_name = "superscript";
|
||||
if assert_name(emacs, emacs_name).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
if assert_bounds(source, emacs, rust).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
message: None,
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_timestamp<'s>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s Timestamp<'s>,
|
||||
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let emacs_name = "timestamp";
|
||||
if assert_name(emacs, emacs_name).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
if assert_bounds(source, emacs, rust).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
message: None,
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,3 +3,5 @@ mod parse;
|
||||
mod util;
|
||||
pub use diff::compare_document;
|
||||
pub use parse::emacs_parse_org_document;
|
||||
pub use parse::get_emacs_version;
|
||||
pub use parse::get_org_mode_version;
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
pub fn emacs_parse_org_document<'a, C>(file_path: C) -> Result<String, Box<dyn std::error::Error>>
|
||||
pub fn emacs_parse_org_document<C>(file_contents: C) -> Result<String, Box<dyn std::error::Error>>
|
||||
where
|
||||
C: AsRef<Path>,
|
||||
C: AsRef<str>,
|
||||
{
|
||||
let elisp_script = r#"(progn
|
||||
let escaped_file_contents = escape_elisp_string(file_contents);
|
||||
let elisp_script = format!(
|
||||
r#"(progn
|
||||
(erase-buffer)
|
||||
(insert "{escaped_file_contents}")
|
||||
(org-mode)
|
||||
(message "%s" (pp-to-string (org-element-parse-buffer)))
|
||||
)"#;
|
||||
)"#,
|
||||
escaped_file_contents = escaped_file_contents
|
||||
);
|
||||
let mut cmd = Command::new("emacs");
|
||||
let proc = cmd
|
||||
.arg("-q")
|
||||
.arg("--no-site-file")
|
||||
.arg("--no-splash")
|
||||
.arg("--batch")
|
||||
.arg("--insert")
|
||||
.arg(file_path.as_ref().as_os_str())
|
||||
.arg("--eval")
|
||||
.arg(elisp_script);
|
||||
let out = proc.output()?;
|
||||
@@ -24,3 +27,62 @@ where
|
||||
let org_sexp = out.stderr;
|
||||
Ok(String::from_utf8(org_sexp)?)
|
||||
}
|
||||
|
||||
fn escape_elisp_string<C>(file_contents: C) -> String
|
||||
where
|
||||
C: AsRef<str>,
|
||||
{
|
||||
let source = file_contents.as_ref();
|
||||
let source_len = source.len();
|
||||
// We allocate a string 10% larger than the source to account for escape characters. Without this, we would have more allocations during processing.
|
||||
let mut output = String::with_capacity(source_len + (source_len / 10));
|
||||
for c in source.chars() {
|
||||
match c {
|
||||
'"' | '\\' => {
|
||||
output.push('\\');
|
||||
output.push(c);
|
||||
}
|
||||
_ => {
|
||||
output.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
|
||||
let elisp_script = r#"(progn
|
||||
(message "%s" (version))
|
||||
)"#;
|
||||
let mut cmd = Command::new("emacs");
|
||||
let proc = cmd
|
||||
.arg("-q")
|
||||
.arg("--no-site-file")
|
||||
.arg("--no-splash")
|
||||
.arg("--batch")
|
||||
.arg("--eval")
|
||||
.arg(elisp_script);
|
||||
|
||||
let out = proc.output()?;
|
||||
out.status.exit_ok()?;
|
||||
Ok(String::from_utf8(out.stderr)?)
|
||||
}
|
||||
|
||||
pub fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> {
|
||||
let elisp_script = r#"(progn
|
||||
(org-mode)
|
||||
(message "%s" (org-version nil t nil))
|
||||
)"#;
|
||||
let mut cmd = Command::new("emacs");
|
||||
let proc = cmd
|
||||
.arg("-q")
|
||||
.arg("--no-site-file")
|
||||
.arg("--no-splash")
|
||||
.arg("--batch")
|
||||
.arg("--eval")
|
||||
.arg(elisp_script);
|
||||
|
||||
let out = proc.output()?;
|
||||
out.status.exit_ok()?;
|
||||
Ok(String::from_utf8(out.stderr)?)
|
||||
}
|
||||
|
||||
@@ -42,24 +42,100 @@ pub fn assert_bounds<'s, S: Source<'s>>(
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s S,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let standard_properties = get_standard_properties(emacs)?;
|
||||
let (begin, end) = (
|
||||
standard_properties
|
||||
.begin
|
||||
.ok_or("Token should have a begin.")?,
|
||||
standard_properties
|
||||
.end
|
||||
.ok_or("Token should have a begin.")?,
|
||||
);
|
||||
let (rust_begin, rust_end) = get_offsets(source, rust);
|
||||
if (rust_begin + 1) != begin || (rust_end + 1) != end {
|
||||
Err(format!("Rust bounds ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin + 1, rust_end = rust_end + 1, emacs_begin=begin, emacs_end=end))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct StandardProperties {
|
||||
begin: Option<usize>,
|
||||
#[allow(dead_code)]
|
||||
post_affiliated: Option<usize>,
|
||||
#[allow(dead_code)]
|
||||
contents_begin: Option<usize>,
|
||||
#[allow(dead_code)]
|
||||
contents_end: Option<usize>,
|
||||
end: Option<usize>,
|
||||
#[allow(dead_code)]
|
||||
post_blank: Option<usize>,
|
||||
}
|
||||
|
||||
fn get_standard_properties<'s>(
|
||||
emacs: &'s Token<'s>,
|
||||
) -> Result<StandardProperties, Box<dyn std::error::Error>> {
|
||||
let children = emacs.as_list()?;
|
||||
let attributes_child = children
|
||||
.iter()
|
||||
.nth(1)
|
||||
.ok_or("Should have an attributes child.")?;
|
||||
let attributes_map = attributes_child.as_map()?;
|
||||
let begin = attributes_map
|
||||
.get(":begin")
|
||||
.ok_or("Missing :begin attribute.")?
|
||||
.as_atom()?;
|
||||
let end = attributes_map
|
||||
.get(":end")
|
||||
.ok_or("Missing :end attribute.")?
|
||||
.as_atom()?;
|
||||
let (rust_begin, rust_end) = get_offsets(source, rust);
|
||||
if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end {
|
||||
Err(format!("Rust bounds ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin + 1, rust_end = rust_end + 1, emacs_begin=begin, emacs_end=end))?;
|
||||
let standard_properties = attributes_map.get(":standard-properties");
|
||||
Ok(if standard_properties.is_some() {
|
||||
let mut std_props = standard_properties
|
||||
.expect("if statement proves its Some")
|
||||
.as_vector()?
|
||||
.into_iter();
|
||||
let begin = maybe_token_to_usize(std_props.next())?;
|
||||
let post_affiliated = maybe_token_to_usize(std_props.next())?;
|
||||
let contents_begin = maybe_token_to_usize(std_props.next())?;
|
||||
let contents_end = maybe_token_to_usize(std_props.next())?;
|
||||
let end = maybe_token_to_usize(std_props.next())?;
|
||||
let post_blank = maybe_token_to_usize(std_props.next())?;
|
||||
StandardProperties {
|
||||
begin,
|
||||
post_affiliated,
|
||||
contents_begin,
|
||||
contents_end,
|
||||
end,
|
||||
post_blank,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
let begin = maybe_token_to_usize(attributes_map.get(":begin").map(|token| *token))?;
|
||||
let end = maybe_token_to_usize(attributes_map.get(":end").map(|token| *token))?;
|
||||
let contents_begin =
|
||||
maybe_token_to_usize(attributes_map.get(":contents-begin").map(|token| *token))?;
|
||||
let contents_end =
|
||||
maybe_token_to_usize(attributes_map.get(":contents-end").map(|token| *token))?;
|
||||
let post_blank =
|
||||
maybe_token_to_usize(attributes_map.get(":post-blank").map(|token| *token))?;
|
||||
let post_affiliated =
|
||||
maybe_token_to_usize(attributes_map.get(":post-affiliated").map(|token| *token))?;
|
||||
StandardProperties {
|
||||
begin,
|
||||
post_affiliated,
|
||||
contents_begin,
|
||||
contents_end,
|
||||
end,
|
||||
post_blank,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn maybe_token_to_usize(
|
||||
token: Option<&Token<'_>>,
|
||||
) -> Result<Option<usize>, Box<dyn std::error::Error>> {
|
||||
Ok(token
|
||||
.map(|token| token.as_atom())
|
||||
.map_or(Ok(None), |r| r.map(Some))?
|
||||
.map(|val| {
|
||||
if val == "nil" {
|
||||
None
|
||||
} else {
|
||||
Some(val.parse::<usize>())
|
||||
}
|
||||
})
|
||||
.flatten() // Outer option is whether or not the param exists, inner option is whether or not it is nil
|
||||
.map_or(Ok(None), |r| r.map(Some))?)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use nom::IResult;
|
||||
|
||||
pub type Res<T, U> = IResult<T, U, CustomError<T>>;
|
||||
|
||||
// TODO: MyError probably shouldn't be based on the same type as the input type since it's used exclusively with static strings right now.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum CustomError<I> {
|
||||
MyError(MyError<I>),
|
||||
|
||||
@@ -1,34 +1,71 @@
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
#[cfg(feature = "tracing")]
|
||||
use opentelemetry_otlp::WithExportConfig;
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
// use tracing_subscriber::EnvFilter;
|
||||
|
||||
const SERVICE_NAME: &'static str = "organic";
|
||||
|
||||
// Despite the obvious verbosity that fully-qualifying everything causes, in these functions I am fully-qualifying everything relating to tracing. This is because the tracing feature involves multiple libraries working together and so I think it is beneficial to see which libraries contribute which bits.
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
pub fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// let env_filter = EnvFilter::try_from_default_env().unwrap_or(EnvFilter::new("warn"));
|
||||
// by default it will hit http://localhost:4317 with a gRPC payload
|
||||
// TODO: I think the endpoint can be controlled by the OTEL_EXPORTER_OTLP_TRACES_ENDPOINT env variable instead of hard-coded into this code base. Regardless, I am the only developer right now so I am not too concerned.
|
||||
let exporter = opentelemetry_otlp::new_exporter()
|
||||
.tonic()
|
||||
// Using "localhost" is broken inside the docker container when tracing
|
||||
.with_endpoint("http://127.0.0.1:4317/v1/traces");
|
||||
|
||||
// let stdout = tracing_subscriber::fmt::Layer::new()
|
||||
// .pretty()
|
||||
// .with_file(true)
|
||||
// .with_line_number(true)
|
||||
// .with_thread_ids(false)
|
||||
// .with_target(false);
|
||||
let tracer = opentelemetry_otlp::new_pipeline()
|
||||
.tracing()
|
||||
.with_exporter(exporter)
|
||||
.with_trace_config(opentelemetry::sdk::trace::config().with_resource(
|
||||
opentelemetry::sdk::Resource::new(vec![opentelemetry::KeyValue::new(
|
||||
opentelemetry_semantic_conventions::resource::SERVICE_NAME,
|
||||
SERVICE_NAME.to_string(),
|
||||
)]),
|
||||
))
|
||||
// If I do install_batch then 1K+ spans will get orphaned off into their own trace and I get the error message "OpenTelemetry trace error occurred. cannot send message to batch processor as the channel is closed"
|
||||
//
|
||||
// If I do install_simple then it only creates 1 trace (which is good!) but my console gets spammed with this concerning log message that makes me think it might be dropping the extra spans on the floor: "OpenTelemetry trace error occurred. Exporter otlp encountered the following error(s): the grpc server returns error (Unknown error): , detailed error message: Service was not ready: transport error"
|
||||
//
|
||||
// I suspect it is related to this bug: https://github.com/open-telemetry/opentelemetry-rust/issues/888
|
||||
//
|
||||
// .install_simple()
|
||||
.install_batch(opentelemetry::runtime::Tokio)
|
||||
.expect("Error: Failed to initialize the tracer.");
|
||||
|
||||
opentelemetry::global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new());
|
||||
let tracer = opentelemetry_jaeger::new_pipeline()
|
||||
.with_service_name("toy_language")
|
||||
.install_simple()?;
|
||||
let subscriber = tracing_subscriber::Registry::default();
|
||||
let level_filter_layer = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or(tracing_subscriber::EnvFilter::new("WARN"));
|
||||
let tracing_layer = tracing_opentelemetry::layer().with_tracer(tracer);
|
||||
|
||||
let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer);
|
||||
opentelemetry::global::set_text_map_propagator(
|
||||
opentelemetry::sdk::propagation::TraceContextPropagator::new(),
|
||||
);
|
||||
|
||||
tracing_subscriber::registry()
|
||||
// .with(env_filter)
|
||||
.with(opentelemetry)
|
||||
// .with(stdout)
|
||||
subscriber
|
||||
.with(level_filter_layer)
|
||||
.with(tracing_layer)
|
||||
.try_init()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
pub fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
opentelemetry::global::shutdown_tracer_provider();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
pub fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
pub fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ mod compare;
|
||||
pub use compare::compare_document;
|
||||
#[cfg(feature = "compare")]
|
||||
pub use compare::emacs_parse_org_document;
|
||||
#[cfg(feature = "compare")]
|
||||
pub use compare::get_emacs_version;
|
||||
#[cfg(feature = "compare")]
|
||||
pub use compare::get_org_mode_version;
|
||||
|
||||
mod error;
|
||||
pub mod parser;
|
||||
|
||||
86
src/main.rs
86
src/main.rs
@@ -1,17 +1,91 @@
|
||||
#![feature(round_char_boundary)]
|
||||
use ::organic::parser::document;
|
||||
use std::io::Read;
|
||||
|
||||
use ::organic::parser::document;
|
||||
#[cfg(feature = "compare")]
|
||||
use organic::compare_document;
|
||||
#[cfg(feature = "compare")]
|
||||
use organic::emacs_parse_org_document;
|
||||
#[cfg(feature = "compare")]
|
||||
use organic::get_emacs_version;
|
||||
#[cfg(feature = "compare")]
|
||||
use organic::get_org_mode_version;
|
||||
#[cfg(feature = "compare")]
|
||||
use organic::parser::sexp::sexp_with_padding;
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
use crate::init_tracing::init_telemetry;
|
||||
#[cfg(feature = "tracing")]
|
||||
use crate::init_tracing::shutdown_telemetry;
|
||||
#[cfg(feature = "tracing")]
|
||||
mod init_tracing;
|
||||
|
||||
const TEST_DOC: &'static str = include_str!("../toy_language.txt");
|
||||
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
main_body()
|
||||
}
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
let result = rt.block_on(async {
|
||||
init_telemetry()?;
|
||||
let parsed = document(TEST_DOC);
|
||||
println!("{}\n\n\n", TEST_DOC);
|
||||
println!("{:#?}", parsed);
|
||||
let main_body_result = main_body();
|
||||
shutdown_telemetry()?;
|
||||
main_body_result
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn main_body() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let org_contents = read_stdin_to_string()?;
|
||||
run_compare(org_contents)
|
||||
}
|
||||
|
||||
fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
|
||||
let mut stdin_contents = String::new();
|
||||
std::io::stdin()
|
||||
.lock()
|
||||
.read_to_string(&mut stdin_contents)?;
|
||||
Ok(stdin_contents)
|
||||
}
|
||||
|
||||
#[cfg(feature = "compare")]
|
||||
fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let emacs_version = get_emacs_version()?;
|
||||
let org_mode_version = get_org_mode_version()?;
|
||||
eprintln!("Using emacs version: {}", emacs_version.trim());
|
||||
eprintln!("Using org-mode version: {}", org_mode_version.trim());
|
||||
let (remaining, rust_parsed) = document(org_contents.as_ref()).map_err(|e| e.to_string())?;
|
||||
let org_sexp = emacs_parse_org_document(org_contents.as_ref())?;
|
||||
let (_remaining, parsed_sexp) =
|
||||
sexp_with_padding(org_sexp.as_str()).map_err(|e| e.to_string())?;
|
||||
|
||||
println!("{}\n\n\n", org_contents.as_ref());
|
||||
println!("{}", org_sexp);
|
||||
println!("{:#?}", rust_parsed);
|
||||
|
||||
// We do the diffing after printing out both parsed forms in case the diffing panics
|
||||
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
|
||||
diff_result.print()?;
|
||||
|
||||
if diff_result.is_bad() {
|
||||
Err("Diff results do not match.")?;
|
||||
}
|
||||
if remaining != "" {
|
||||
Err(format!("There was unparsed text remaining: {}", remaining))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "compare"))]
|
||||
fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
|
||||
eprintln!(
|
||||
"This program was built with compare disabled. Only parsing with organic, not comparing."
|
||||
);
|
||||
let (remaining, rust_parsed) = document(org_contents.as_ref()).map_err(|e| e.to_string())?;
|
||||
println!("{:#?}", rust_parsed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use nom::combinator::peek;
|
||||
use nom::combinator::recognize;
|
||||
use nom::multi::many_till;
|
||||
|
||||
use super::org_source::OrgSource;
|
||||
use super::Context;
|
||||
use crate::error::Res;
|
||||
use crate::parser::exiting::ExitClass;
|
||||
@@ -15,8 +16,11 @@ use crate::parser::util::exit_matcher_parser;
|
||||
use crate::parser::util::get_consumed;
|
||||
use crate::parser::AngleLink;
|
||||
|
||||
#[tracing::instrument(ret, level = "debug")]
|
||||
pub fn angle_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, AngleLink<'s>> {
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn angle_link<'r, 's>(
|
||||
context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, AngleLink<'s>> {
|
||||
let (remaining, _) = tag("<")(input)?;
|
||||
let (remaining, proto) = protocol(context, remaining)?;
|
||||
let (remaining, _separator) = tag(":")(remaining)?;
|
||||
@@ -26,18 +30,21 @@ pub fn angle_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
|
||||
Ok((
|
||||
remaining,
|
||||
AngleLink {
|
||||
source,
|
||||
link_type: proto,
|
||||
path,
|
||||
source: source.into(),
|
||||
link_type: proto.into(),
|
||||
path: path.into(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[tracing::instrument(ret, level = "debug")]
|
||||
fn path_angle<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn path_angle<'r, 's>(
|
||||
context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let parser_context =
|
||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Beta,
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &path_angle_end,
|
||||
}));
|
||||
|
||||
@@ -47,7 +54,10 @@ fn path_angle<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
|
||||
Ok((remaining, path))
|
||||
}
|
||||
|
||||
#[tracing::instrument(ret, level = "debug")]
|
||||
fn path_angle_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn path_angle_end<'r, 's>(
|
||||
_context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
tag(">")(input)
|
||||
}
|
||||
|
||||
233
src/parser/citation.rs
Normal file
233
src/parser/citation.rs
Normal file
@@ -0,0 +1,233 @@
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::bytes::complete::tag_no_case;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::space0;
|
||||
use nom::combinator::opt;
|
||||
use nom::combinator::recognize;
|
||||
use nom::combinator::verify;
|
||||
use nom::multi::many1;
|
||||
use nom::multi::many_till;
|
||||
use nom::multi::separated_list1;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::org_source::OrgSource;
|
||||
use super::Context;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::citation_reference::citation_reference;
|
||||
use crate::parser::citation_reference::citation_reference_key;
|
||||
use crate::parser::citation_reference::get_bracket_depth;
|
||||
use crate::parser::exiting::ExitClass;
|
||||
use crate::parser::object::Citation;
|
||||
use crate::parser::object_parser::standard_set_object;
|
||||
use crate::parser::parser_context::CitationBracket;
|
||||
use crate::parser::parser_context::ContextElement;
|
||||
use crate::parser::parser_context::ExitMatcherNode;
|
||||
use crate::parser::parser_with_context::parser_with_context;
|
||||
use crate::parser::util::exit_matcher_parser;
|
||||
use crate::parser::util::get_consumed;
|
||||
use crate::parser::Object;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn citation<'r, 's>(
|
||||
context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Citation<'s>> {
|
||||
// TODO: Despite being a standard object, citations cannot exist inside the global prefix/suffix for other citations because citations must contain something that matches @key which is forbidden inside the global prefix/suffix. This TODO is to evaluate if its worth putting in an explicit check for this (which can be easily accomplished by checking the output of `get_bracket_depth()`). I suspect its not worth it because I expect, outside of intentionally crafted inputs, this parser will exit immediately inside a citation since it is unlikely to find the "[cite" substring inside a citation global prefix/suffix.
|
||||
let (remaining, _) = tag_no_case("[cite")(input)?;
|
||||
let (remaining, _) = opt(citestyle)(remaining)?;
|
||||
let (remaining, _) = tag(":")(remaining)?;
|
||||
let (remaining, _prefix) = opt(parser_with_context!(global_prefix)(context))(remaining)?;
|
||||
let (remaining, _references) =
|
||||
separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?;
|
||||
let (remaining, _suffix) = opt(tuple((
|
||||
tag(";"),
|
||||
parser_with_context!(global_suffix)(context),
|
||||
)))(remaining)?;
|
||||
let (remaining, _) = tag("]")(remaining)?;
|
||||
let (remaining, _) = space0(remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
remaining,
|
||||
Citation {
|
||||
source: source.into(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn citestyle<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let (remaining, _) = tuple((tag("/"), style))(input)?;
|
||||
let (remaining, _) = opt(tuple((tag("/"), variant)))(remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((remaining, source))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn style<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
recognize(many1(verify(anychar, |c| {
|
||||
c.is_alphanumeric() || "_-".contains(*c)
|
||||
})))(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn variant<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
recognize(many1(verify(anychar, |c| {
|
||||
c.is_alphanumeric() || "_-/".contains(*c)
|
||||
})))(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn global_prefix<'r, 's>(
|
||||
context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
||||
let parser_context = context
|
||||
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
|
||||
position: input,
|
||||
depth: 0,
|
||||
}))
|
||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &global_prefix_end,
|
||||
}));
|
||||
let (remaining, (children, _exit_contents)) = verify(
|
||||
many_till(
|
||||
parser_with_context!(standard_set_object)(&parser_context),
|
||||
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||
),
|
||||
|(children, _exit_contents)| !children.is_empty(),
|
||||
)(input)?;
|
||||
let (remaining, _) = tag(";")(remaining)?;
|
||||
Ok((remaining, children))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn global_prefix_end<'r, 's>(
|
||||
context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let context_depth = get_bracket_depth(context)
|
||||
.expect("This function should only be called from inside a citation.");
|
||||
let text_since_context_entry = get_consumed(context_depth.position, input);
|
||||
let mut current_depth = context_depth.depth;
|
||||
for c in Into::<&str>::into(text_since_context_entry).chars() {
|
||||
match c {
|
||||
'[' => {
|
||||
current_depth += 1;
|
||||
}
|
||||
']' if current_depth == 0 => {
|
||||
panic!("Exceeded citation global prefix bracket depth.")
|
||||
}
|
||||
']' if current_depth > 0 => {
|
||||
current_depth -= 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if current_depth == 0 {
|
||||
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
||||
if close_bracket.is_ok() {
|
||||
return close_bracket;
|
||||
}
|
||||
}
|
||||
alt((
|
||||
tag(";"),
|
||||
recognize(parser_with_context!(citation_reference_key)(context)),
|
||||
))(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn global_suffix<'r, 's>(
|
||||
context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
||||
let parser_context = context
|
||||
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
|
||||
position: input,
|
||||
depth: 0,
|
||||
}))
|
||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &global_suffix_end,
|
||||
}));
|
||||
let (remaining, (children, _exit_contents)) = verify(
|
||||
many_till(
|
||||
parser_with_context!(standard_set_object)(&parser_context),
|
||||
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||
),
|
||||
|(children, _exit_contents)| !children.is_empty(),
|
||||
)(input)?;
|
||||
Ok((remaining, children))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn global_suffix_end<'r, 's>(
|
||||
context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let context_depth = get_bracket_depth(context)
|
||||
.expect("This function should only be called from inside a citation.");
|
||||
let text_since_context_entry = get_consumed(context_depth.position, input);
|
||||
let mut current_depth = context_depth.depth;
|
||||
for c in Into::<&str>::into(text_since_context_entry).chars() {
|
||||
match c {
|
||||
'[' => {
|
||||
current_depth += 1;
|
||||
}
|
||||
']' if current_depth == 0 => {
|
||||
panic!("Exceeded citation global suffix bracket depth.")
|
||||
}
|
||||
']' if current_depth > 0 => {
|
||||
current_depth -= 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if current_depth == 0 {
|
||||
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
||||
if close_bracket.is_ok() {
|
||||
return close_bracket;
|
||||
}
|
||||
}
|
||||
alt((
|
||||
tag(";"),
|
||||
recognize(parser_with_context!(citation_reference_key)(context)),
|
||||
))(input)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::parser::element_parser::element;
|
||||
use crate::parser::parser_context::ContextTree;
|
||||
use crate::parser::parser_with_context::parser_with_context;
|
||||
use crate::parser::source::Source;
|
||||
|
||||
#[test]
|
||||
fn citation_simple() {
|
||||
let input = OrgSource::new("[cite:@foo]");
|
||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||
let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
|
||||
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
|
||||
let first_paragraph = match first_paragraph {
|
||||
crate::parser::Element::Paragraph(paragraph) => paragraph,
|
||||
_ => panic!("Should be a paragraph!"),
|
||||
};
|
||||
assert_eq!(Into::<&str>::into(remaining), "");
|
||||
assert_eq!(first_paragraph.get_source(), "[cite:@foo]");
|
||||
assert_eq!(first_paragraph.children.len(), 1);
|
||||
assert_eq!(
|
||||
first_paragraph
|
||||
.children
|
||||
.get(0)
|
||||
.expect("Len already asserted to be 1"),
|
||||
&Object::Citation(Citation {
|
||||
source: "[cite:@foo]"
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user