Compare commits
27 Commits
v0.0.1
...
5228851c0e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5228851c0e | ||
|
|
4fc08f4375 | ||
|
|
8905c9356b | ||
|
|
424a970014 | ||
|
|
138d694b27 | ||
|
|
397d4ea0bc | ||
|
|
818fca87f2 | ||
|
|
df339f20fa | ||
|
|
d5572c93cd | ||
|
|
322dbb8f4f | ||
|
|
904f834c86 | ||
|
|
48af194da0 | ||
|
|
bcb6b2d75f | ||
|
|
134444b2c3 | ||
|
|
4447f1ed4a | ||
|
|
457ff9e759 | ||
|
|
6f244a0a5f | ||
|
|
cb5838345e | ||
|
|
1107a653cf | ||
|
|
95d4ee7080 | ||
|
|
fa2dd96f78 | ||
|
|
7741e192f5 | ||
|
|
5dfd46852f | ||
|
|
88e10010d8 | ||
|
|
52c564d4fd | ||
|
|
f7874c1843 | ||
|
|
40120667f7 |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
**/.git
|
||||||
|
target/
|
||||||
|
org_test_documents/
|
||||||
151
.lighthouse/pipeline-build-natter.yaml
Normal file
151
.lighthouse/pipeline-build-natter.yaml
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
apiVersion: tekton.dev/v1beta1
|
||||||
|
kind: PipelineRun
|
||||||
|
metadata:
|
||||||
|
name: build-natter
|
||||||
|
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-runner
|
||||||
|
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:
|
||||||
|
- "--destination=$(params.image-name)" # Also write the :latest image
|
||||||
|
- --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
|
||||||
|
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)"
|
||||||
|
workspaces:
|
||||||
|
- name: git-source
|
||||||
|
- name: docker-credentials
|
||||||
|
workspaces:
|
||||||
|
- name: git-source
|
||||||
|
volumeClaimTemplate:
|
||||||
|
spec:
|
||||||
|
storageClassName: "nfs-client"
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
subPath: rust-source
|
||||||
|
- name: docker-credentials
|
||||||
|
secret:
|
||||||
|
secretName: harbor-plain
|
||||||
|
serviceAccountName: build-bot
|
||||||
|
timeout: 240h0m0s
|
||||||
|
params:
|
||||||
|
- name: image-name
|
||||||
|
value: "harbor.fizz.buzz/private/natter"
|
||||||
|
- name: path-to-image-context
|
||||||
|
value: .
|
||||||
|
- name: path-to-dockerfile
|
||||||
|
value: docker/natter/Dockerfile
|
||||||
338
.lighthouse/pipeline-format.yaml
Normal file
338
.lighthouse/pipeline-format.yaml
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
apiVersion: tekton.dev/v1beta1
|
||||||
|
kind: PipelineRun
|
||||||
|
metadata:
|
||||||
|
name: rust-clippy
|
||||||
|
spec:
|
||||||
|
pipelineSpec:
|
||||||
|
timeouts:
|
||||||
|
pipeline: "2h0m0s"
|
||||||
|
tasks: "1h0m40s"
|
||||||
|
finally: "0h30m0s"
|
||||||
|
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:
|
||||||
|
resolver: git
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: https://github.com/tektoncd/catalog.git
|
||||||
|
- name: revision
|
||||||
|
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||||
|
- name: pathInRepo
|
||||||
|
value: task/gitea-set-status/0.1/gitea-set-status.yaml
|
||||||
|
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:
|
||||||
|
resolver: git
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: https://github.com/tektoncd/catalog.git
|
||||||
|
- name: revision
|
||||||
|
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||||
|
- name: pathInRepo
|
||||||
|
value: task/git-clone/0.9/git-clone.yaml
|
||||||
|
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:
|
||||||
|
resolver: git
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: https://github.com/tektoncd/catalog.git
|
||||||
|
- name: revision
|
||||||
|
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||||
|
- name: pathInRepo
|
||||||
|
value: task/kaniko/0.6//kaniko.yaml
|
||||||
|
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: run-cargo-fmt
|
||||||
|
taskSpec:
|
||||||
|
metadata: {}
|
||||||
|
params:
|
||||||
|
- name: docker-image
|
||||||
|
type: string
|
||||||
|
description: Docker image to run.
|
||||||
|
default: alpine:3.18
|
||||||
|
stepTemplate:
|
||||||
|
image: alpine:3.18
|
||||||
|
name: ""
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 10m
|
||||||
|
memory: 600Mi
|
||||||
|
workingDir: /workspace/source
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
mountPath: /source
|
||||||
|
- name: cargo-cache
|
||||||
|
mountPath: /usr/local/cargo/registry
|
||||||
|
optional: true
|
||||||
|
steps:
|
||||||
|
- name: run
|
||||||
|
image: $(params.docker-image)
|
||||||
|
workingDir: "$(workspaces.source.path)"
|
||||||
|
command: ["cargo", "fmt"]
|
||||||
|
args: []
|
||||||
|
env:
|
||||||
|
- name: CARGO_TARGET_DIR
|
||||||
|
value: /target
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
workspace: git-source
|
||||||
|
- name: cargo-cache
|
||||||
|
workspace: cargo-cache
|
||||||
|
runAfter:
|
||||||
|
- build-image
|
||||||
|
params:
|
||||||
|
- name: docker-image
|
||||||
|
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||||
|
- name: run-prettier
|
||||||
|
taskSpec:
|
||||||
|
metadata: {}
|
||||||
|
params:
|
||||||
|
- name: docker-image
|
||||||
|
type: string
|
||||||
|
description: Docker image to run.
|
||||||
|
default: alpine:3.18
|
||||||
|
stepTemplate:
|
||||||
|
image: alpine:3.18
|
||||||
|
name: ""
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 10m
|
||||||
|
memory: 600Mi
|
||||||
|
workingDir: /workspace/source
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
mountPath: /source
|
||||||
|
steps:
|
||||||
|
- name: run
|
||||||
|
image: $(params.docker-image)
|
||||||
|
workingDir: "$(workspaces.source.path)"
|
||||||
|
command: ["sh", "-c"]
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
prettier --write --no-error-on-unmatched-pattern "default_environment/**/*.js" "default_environment/**/*.css"
|
||||||
|
env:
|
||||||
|
- name: CARGO_TARGET_DIR
|
||||||
|
value: /target
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
workspace: git-source
|
||||||
|
runAfter:
|
||||||
|
- run-cargo-fmt
|
||||||
|
params:
|
||||||
|
- name: docker-image
|
||||||
|
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||||
|
- name: commit-changes
|
||||||
|
taskRef:
|
||||||
|
resolver: git
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: https://github.com/tektoncd/catalog.git
|
||||||
|
- name: revision
|
||||||
|
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||||
|
- name: pathInRepo
|
||||||
|
value: task/git-cli/0.4/git-cli.yaml
|
||||||
|
params:
|
||||||
|
- name: GIT_USER_NAME
|
||||||
|
value: fluxcdbot
|
||||||
|
- name: GIT_USER_EMAIL
|
||||||
|
value: "fluxcdbot@users.noreply.github.com"
|
||||||
|
- name: GIT_SCRIPT
|
||||||
|
value: |
|
||||||
|
pwd
|
||||||
|
git config --global --add safe.directory /workspace/source
|
||||||
|
git_status=$(git status --porcelain)
|
||||||
|
if [ -n "$git_status" ]; then
|
||||||
|
git commit -a -m "CI: autofix rust code."
|
||||||
|
git push origin HEAD:$(params.PULL_BASE_REF)
|
||||||
|
else
|
||||||
|
echo "No changes to commit."
|
||||||
|
fi
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
workspace: git-source
|
||||||
|
runAfter:
|
||||||
|
- run-prettier
|
||||||
|
finally:
|
||||||
|
- name: report-success
|
||||||
|
when:
|
||||||
|
- input: "$(tasks.status)"
|
||||||
|
operator: in
|
||||||
|
values: ["Succeeded", "Completed"]
|
||||||
|
taskRef:
|
||||||
|
resolver: git
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: https://github.com/tektoncd/catalog.git
|
||||||
|
- name: revision
|
||||||
|
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||||
|
- name: pathInRepo
|
||||||
|
value: task/gitea-set-status/0.1/gitea-set-status.yaml
|
||||||
|
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:
|
||||||
|
resolver: git
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: https://github.com/tektoncd/catalog.git
|
||||||
|
- name: revision
|
||||||
|
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||||
|
- name: pathInRepo
|
||||||
|
value: task/gitea-set-status/0.1/gitea-set-status.yaml
|
||||||
|
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
|
||||||
|
taskSpec:
|
||||||
|
metadata: {}
|
||||||
|
params:
|
||||||
|
- name: docker-image
|
||||||
|
type: string
|
||||||
|
description: Docker image to run.
|
||||||
|
default: alpine:3.18
|
||||||
|
stepTemplate:
|
||||||
|
image: alpine:3.18
|
||||||
|
name: ""
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 10m
|
||||||
|
memory: 600Mi
|
||||||
|
workingDir: /workspace/source
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
mountPath: /source
|
||||||
|
- name: cargo-cache
|
||||||
|
mountPath: /usr/local/cargo/registry
|
||||||
|
optional: true
|
||||||
|
steps:
|
||||||
|
- name: run
|
||||||
|
image: $(params.docker-image)
|
||||||
|
workingDir: "$(workspaces.source.path)"
|
||||||
|
command: [cargo, cache, --autoclean]
|
||||||
|
args: []
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
workspace: git-source
|
||||||
|
- name: cargo-cache
|
||||||
|
workspace: cargo-cache
|
||||||
|
params:
|
||||||
|
- 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: natter-cargo-cache-fmt
|
||||||
|
- name: docker-credentials
|
||||||
|
secret:
|
||||||
|
secretName: harbor-plain
|
||||||
|
serviceAccountName: build-bot
|
||||||
|
params:
|
||||||
|
- name: image-name
|
||||||
|
value: "harbor.fizz.buzz/private/natter-development"
|
||||||
|
- name: path-to-image-context
|
||||||
|
value: docker/natter_development/
|
||||||
|
- name: path-to-dockerfile
|
||||||
|
value: docker/natter_development/Dockerfile
|
||||||
279
.lighthouse/pipeline-rust-clippy.yaml
Normal file
279
.lighthouse/pipeline-rust-clippy.yaml
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
apiVersion: tekton.dev/v1beta1
|
||||||
|
kind: PipelineRun
|
||||||
|
metadata:
|
||||||
|
name: rust-clippy
|
||||||
|
spec:
|
||||||
|
pipelineSpec:
|
||||||
|
timeouts:
|
||||||
|
pipeline: "2h0m0s"
|
||||||
|
tasks: "1h0m40s"
|
||||||
|
finally: "0h30m0s"
|
||||||
|
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:
|
||||||
|
resolver: git
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: https://github.com/tektoncd/catalog.git
|
||||||
|
- name: revision
|
||||||
|
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||||
|
- name: pathInRepo
|
||||||
|
value: task/gitea-set-status/0.1/gitea-set-status.yaml
|
||||||
|
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:
|
||||||
|
resolver: git
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: https://github.com/tektoncd/catalog.git
|
||||||
|
- name: revision
|
||||||
|
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||||
|
- name: pathInRepo
|
||||||
|
value: task/git-clone/0.9/git-clone.yaml
|
||||||
|
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:
|
||||||
|
resolver: git
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: https://github.com/tektoncd/catalog.git
|
||||||
|
- name: revision
|
||||||
|
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||||
|
- name: pathInRepo
|
||||||
|
value: task/kaniko/0.6//kaniko.yaml
|
||||||
|
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: run-cargo-clippy
|
||||||
|
taskSpec:
|
||||||
|
metadata: {}
|
||||||
|
params:
|
||||||
|
- name: docker-image
|
||||||
|
type: string
|
||||||
|
description: Docker image to run.
|
||||||
|
default: alpine:3.18
|
||||||
|
stepTemplate:
|
||||||
|
image: alpine:3.18
|
||||||
|
name: ""
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 10m
|
||||||
|
memory: 600Mi
|
||||||
|
workingDir: /workspace/source
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
mountPath: /source
|
||||||
|
- name: cargo-cache
|
||||||
|
mountPath: /usr/local/cargo/registry
|
||||||
|
optional: true
|
||||||
|
steps:
|
||||||
|
- name: run
|
||||||
|
image: $(params.docker-image)
|
||||||
|
workingDir: "$(workspaces.source.path)"
|
||||||
|
command:
|
||||||
|
[
|
||||||
|
"cargo",
|
||||||
|
"clippy",
|
||||||
|
"--no-deps",
|
||||||
|
"--all-targets",
|
||||||
|
"--all-features",
|
||||||
|
"--",
|
||||||
|
"-D",
|
||||||
|
"warnings",
|
||||||
|
]
|
||||||
|
args: []
|
||||||
|
env:
|
||||||
|
- name: CARGO_TARGET_DIR
|
||||||
|
value: /target
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
workspace: git-source
|
||||||
|
- name: cargo-cache
|
||||||
|
workspace: cargo-cache
|
||||||
|
runAfter:
|
||||||
|
- build-image
|
||||||
|
params:
|
||||||
|
- 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:
|
||||||
|
resolver: git
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: https://github.com/tektoncd/catalog.git
|
||||||
|
- name: revision
|
||||||
|
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||||
|
- name: pathInRepo
|
||||||
|
value: task/gitea-set-status/0.1/gitea-set-status.yaml
|
||||||
|
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:
|
||||||
|
resolver: git
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: https://github.com/tektoncd/catalog.git
|
||||||
|
- name: revision
|
||||||
|
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||||
|
- name: pathInRepo
|
||||||
|
value: task/gitea-set-status/0.1/gitea-set-status.yaml
|
||||||
|
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
|
||||||
|
taskSpec:
|
||||||
|
metadata: {}
|
||||||
|
params:
|
||||||
|
- name: docker-image
|
||||||
|
type: string
|
||||||
|
description: Docker image to run.
|
||||||
|
default: alpine:3.18
|
||||||
|
stepTemplate:
|
||||||
|
image: alpine:3.18
|
||||||
|
name: ""
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 10m
|
||||||
|
memory: 600Mi
|
||||||
|
workingDir: /workspace/source
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
mountPath: /source
|
||||||
|
- name: cargo-cache
|
||||||
|
mountPath: /usr/local/cargo/registry
|
||||||
|
optional: true
|
||||||
|
steps:
|
||||||
|
- name: run
|
||||||
|
image: $(params.docker-image)
|
||||||
|
workingDir: "$(workspaces.source.path)"
|
||||||
|
command: [cargo, cache, --autoclean]
|
||||||
|
args: []
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
workspace: git-source
|
||||||
|
- name: cargo-cache
|
||||||
|
workspace: cargo-cache
|
||||||
|
params:
|
||||||
|
- 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: natter-cargo-cache-clippy
|
||||||
|
- name: docker-credentials
|
||||||
|
secret:
|
||||||
|
secretName: harbor-plain
|
||||||
|
serviceAccountName: build-bot
|
||||||
|
params:
|
||||||
|
- name: image-name
|
||||||
|
value: "harbor.fizz.buzz/private/natter-development"
|
||||||
|
- name: path-to-image-context
|
||||||
|
value: docker/natter_development/
|
||||||
|
- name: path-to-dockerfile
|
||||||
|
value: docker/natter_development/Dockerfile
|
||||||
269
.lighthouse/pipeline-rust-test.yaml
Normal file
269
.lighthouse/pipeline-rust-test.yaml
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
apiVersion: tekton.dev/v1beta1
|
||||||
|
kind: PipelineRun
|
||||||
|
metadata:
|
||||||
|
name: rust-test
|
||||||
|
spec:
|
||||||
|
pipelineSpec:
|
||||||
|
timeouts:
|
||||||
|
pipeline: "2h0m0s"
|
||||||
|
tasks: "1h0m40s"
|
||||||
|
finally: "0h30m0s"
|
||||||
|
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:
|
||||||
|
resolver: git
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: https://github.com/tektoncd/catalog.git
|
||||||
|
- name: revision
|
||||||
|
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||||
|
- name: pathInRepo
|
||||||
|
value: task/gitea-set-status/0.1/gitea-set-status.yaml
|
||||||
|
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:
|
||||||
|
resolver: git
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: https://github.com/tektoncd/catalog.git
|
||||||
|
- name: revision
|
||||||
|
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||||
|
- name: pathInRepo
|
||||||
|
value: task/git-clone/0.9/git-clone.yaml
|
||||||
|
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:
|
||||||
|
resolver: git
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: https://github.com/tektoncd/catalog.git
|
||||||
|
- name: revision
|
||||||
|
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||||
|
- name: pathInRepo
|
||||||
|
value: task/kaniko/0.6//kaniko.yaml
|
||||||
|
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: run-cargo-test
|
||||||
|
taskSpec:
|
||||||
|
metadata: {}
|
||||||
|
params:
|
||||||
|
- name: docker-image
|
||||||
|
type: string
|
||||||
|
description: Docker image to run.
|
||||||
|
default: alpine:3.18
|
||||||
|
stepTemplate:
|
||||||
|
image: alpine:3.18
|
||||||
|
name: ""
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 10m
|
||||||
|
memory: 600Mi
|
||||||
|
workingDir: /workspace/source
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
mountPath: /source
|
||||||
|
- name: cargo-cache
|
||||||
|
mountPath: /usr/local/cargo/registry
|
||||||
|
optional: true
|
||||||
|
steps:
|
||||||
|
- name: run
|
||||||
|
image: $(params.docker-image)
|
||||||
|
workingDir: "$(workspaces.source.path)"
|
||||||
|
command: [cargo, test, --no-fail-fast]
|
||||||
|
args: []
|
||||||
|
env:
|
||||||
|
- name: CARGO_TARGET_DIR
|
||||||
|
value: /target
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
workspace: git-source
|
||||||
|
- name: cargo-cache
|
||||||
|
workspace: cargo-cache
|
||||||
|
runAfter:
|
||||||
|
- build-image
|
||||||
|
params:
|
||||||
|
- 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:
|
||||||
|
resolver: git
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: https://github.com/tektoncd/catalog.git
|
||||||
|
- name: revision
|
||||||
|
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||||
|
- name: pathInRepo
|
||||||
|
value: task/gitea-set-status/0.1/gitea-set-status.yaml
|
||||||
|
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:
|
||||||
|
resolver: git
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: https://github.com/tektoncd/catalog.git
|
||||||
|
- name: revision
|
||||||
|
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||||
|
- name: pathInRepo
|
||||||
|
value: task/gitea-set-status/0.1/gitea-set-status.yaml
|
||||||
|
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
|
||||||
|
taskSpec:
|
||||||
|
metadata: {}
|
||||||
|
params:
|
||||||
|
- name: docker-image
|
||||||
|
type: string
|
||||||
|
description: Docker image to run.
|
||||||
|
default: alpine:3.18
|
||||||
|
stepTemplate:
|
||||||
|
image: alpine:3.18
|
||||||
|
name: ""
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 10m
|
||||||
|
memory: 600Mi
|
||||||
|
workingDir: /workspace/source
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
mountPath: /source
|
||||||
|
- name: cargo-cache
|
||||||
|
mountPath: /usr/local/cargo/registry
|
||||||
|
optional: true
|
||||||
|
steps:
|
||||||
|
- name: run
|
||||||
|
image: $(params.docker-image)
|
||||||
|
workingDir: "$(workspaces.source.path)"
|
||||||
|
command: [cargo, cache, --autoclean]
|
||||||
|
args: []
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
workspace: git-source
|
||||||
|
- name: cargo-cache
|
||||||
|
workspace: cargo-cache
|
||||||
|
params:
|
||||||
|
- 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: natter-cargo-cache-test
|
||||||
|
- name: docker-credentials
|
||||||
|
secret:
|
||||||
|
secretName: harbor-plain
|
||||||
|
serviceAccountName: build-bot
|
||||||
|
params:
|
||||||
|
- name: image-name
|
||||||
|
value: "harbor.fizz.buzz/private/natter-development"
|
||||||
|
- name: path-to-image-context
|
||||||
|
value: docker/natter_development/
|
||||||
|
- name: path-to-dockerfile
|
||||||
|
value: docker/natter_development/Dockerfile
|
||||||
32
.lighthouse/triggers.yaml
Normal file
32
.lighthouse/triggers.yaml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
apiVersion: config.lighthouse.jenkins-x.io/v1alpha1
|
||||||
|
kind: TriggerConfig
|
||||||
|
spec:
|
||||||
|
postsubmits:
|
||||||
|
- name: build-natter
|
||||||
|
source: "pipeline-build-natter.yaml"
|
||||||
|
# Override https-based url from lighthouse events.
|
||||||
|
clone_uri: "git@code.fizz.buzz:talexander/natter.git"
|
||||||
|
branches:
|
||||||
|
- ^main$
|
||||||
|
- ^master$
|
||||||
|
- name: rust-test
|
||||||
|
source: "pipeline-rust-test.yaml"
|
||||||
|
# Override https-based url from lighthouse events.
|
||||||
|
clone_uri: "git@code.fizz.buzz:talexander/natter.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]+$"
|
||||||
|
- name: rust-clippy
|
||||||
|
source: "pipeline-rust-clippy.yaml"
|
||||||
|
# Override https-based url from lighthouse events.
|
||||||
|
clone_uri: "git@code.fizz.buzz:talexander/natter.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]+$"
|
||||||
|
- name: format
|
||||||
|
source: "pipeline-format.yaml"
|
||||||
|
# Override https-based url from lighthouse events.
|
||||||
|
clone_uri: "git@code.fizz.buzz:talexander/natter.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]+$"
|
||||||
@@ -32,3 +32,9 @@ serde_json = "1.0.107"
|
|||||||
tokio = { version = "1.30.0", default-features = false, features = ["rt", "rt-multi-thread", "fs", "io-util"] }
|
tokio = { version = "1.30.0", default-features = false, features = ["rt", "rt-multi-thread", "fs", "io-util"] }
|
||||||
toml = "0.8.2"
|
toml = "0.8.2"
|
||||||
walkdir = "2.4.0"
|
walkdir = "2.4.0"
|
||||||
|
|
||||||
|
# Optimized build for any sort of release.
|
||||||
|
[profile.release-lto]
|
||||||
|
inherits = "release"
|
||||||
|
lto = true
|
||||||
|
strip = "symbols"
|
||||||
|
|||||||
39
Makefile
Normal file
39
Makefile
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
SHELL := bash
|
||||||
|
.ONESHELL:
|
||||||
|
.SHELLFLAGS := -eu -o pipefail -c
|
||||||
|
.DELETE_ON_ERROR:
|
||||||
|
MAKEFLAGS += --warn-undefined-variables
|
||||||
|
MAKEFLAGS += --no-builtin-rules
|
||||||
|
OS:=$(shell uname -s)
|
||||||
|
|
||||||
|
ifeq ($(origin .RECIPEPREFIX), undefined)
|
||||||
|
$(error This Make does not support .RECIPEPREFIX. Please use GNU Make 4.0 or later)
|
||||||
|
endif
|
||||||
|
.RECIPEPREFIX = >
|
||||||
|
|
||||||
|
IMAGE_NAME:=natter
|
||||||
|
TARGET :=
|
||||||
|
|
||||||
|
.PHONY: help
|
||||||
|
help:
|
||||||
|
> @grep -h "##" $(MAKEFILE_LIST) | grep -v grep | sed -E 's/^([^:]*): *## */\1: /'
|
||||||
|
|
||||||
|
.PHONY: docker_test
|
||||||
|
docker_test: ## Run the rust tests
|
||||||
|
> $(MAKE) -C docker/natter_development build
|
||||||
|
> docker run --rm -i -t --mount type=tmpfs,destination=/tmp -v "$(shell readlink -f .):/source" --workdir=/source --env CARGO_TARGET_DIR=/target -v "natter-cargo-registry:/usr/local/cargo/registry" natter-development cargo test
|
||||||
|
|
||||||
|
.PHONY: docker_clippy
|
||||||
|
docker_clippy: ## Run static analysis of the code.
|
||||||
|
> $(MAKE) -C docker/natter_development build
|
||||||
|
> docker run --rm -i -t --mount type=tmpfs,destination=/tmp -v "$(shell readlink -f .):/source" --workdir=/source --env CARGO_TARGET_DIR=/target -v "natter-cargo-registry:/usr/local/cargo/registry" natter-development cargo clippy --no-deps --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
|
.PHONY: docker_format
|
||||||
|
docker_format: ## Auto-format source files.
|
||||||
|
> $(MAKE) -C docker/natter_development build
|
||||||
|
> docker run --rm -i -t --mount type=tmpfs,destination=/tmp -v "$(shell readlink -f .):/source" --workdir=/source --env CARGO_TARGET_DIR=/target -v "natter-cargo-registry:/usr/local/cargo/registry" natter-development cargo fmt
|
||||||
|
> docker run --rm -i -t --mount type=tmpfs,destination=/tmp -v "$(shell readlink -f .):/source" --workdir=/source --env CARGO_TARGET_DIR=/target -v "natter-cargo-registry:/usr/local/cargo/registry" natter-development prettier --write --no-error-on-unmatched-pattern "default_environment/**/*.js" "default_environment/**/*.css"
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
> $(MAKE) -C docker/natter_development clean
|
||||||
@@ -6,7 +6,8 @@
|
|||||||
|
|
||||||
--stream-divider-color: #6ccff6;
|
--stream-divider-color: #6ccff6;
|
||||||
|
|
||||||
--src-font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
|
--src-font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo,
|
||||||
|
Consolas, "DejaVu Sans Mono", monospace;
|
||||||
|
|
||||||
--src-block-background-color: #141414;
|
--src-block-background-color: #141414;
|
||||||
--src-block-border-color: #84828f;
|
--src-block-border-color: #84828f;
|
||||||
@@ -14,14 +15,20 @@
|
|||||||
--src-block-language-background: #84828f;
|
--src-block-language-background: #84828f;
|
||||||
|
|
||||||
--quote-block-border-color: #84828f;
|
--quote-block-border-color: #84828f;
|
||||||
|
|
||||||
|
--table-border-color: #6a687a;
|
||||||
|
--table-odd-background-color: #0a0a0a;
|
||||||
|
--table-even-background-color: #141414;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
color: var(--site-text-color);
|
color: var(--site-text-color);
|
||||||
background-color: var(--site-background-color);
|
background-color: var(--site-background-color);
|
||||||
font-family: source-sans-pro, Seravek, 'Gill Sans Nova', Ubuntu, Calibri, 'DejaVu Sans', sans-serif;
|
font-family: source-sans-pro, Seravek, "Gill Sans Nova", Ubuntu, Calibri,
|
||||||
|
"DejaVu Sans", sans-serif;
|
||||||
|
|
||||||
a:link, a:visited {
|
a:link,
|
||||||
|
a:visited {
|
||||||
/* TODO: Should I use a different color for links? */
|
/* TODO: Should I use a different color for links? */
|
||||||
color: var(--site-text-color);
|
color: var(--site-text-color);
|
||||||
}
|
}
|
||||||
@@ -43,7 +50,8 @@ body {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&:link, &:visited {
|
&:link,
|
||||||
|
&:visited {
|
||||||
color: var(--site-text-color);
|
color: var(--site-text-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,7 +76,7 @@ body {
|
|||||||
|
|
||||||
/* A blog post in a blog stream (for example, the homepage). */
|
/* A blog post in a blog stream (for example, the homepage). */
|
||||||
.blog_stream_post {
|
.blog_stream_post {
|
||||||
background: #1F1F1F;
|
background: #1f1f1f;
|
||||||
padding: 1rem 0.2rem;
|
padding: 1rem 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +86,6 @@ body {
|
|||||||
padding-bottom: 1rem;
|
padding-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
}
|
}
|
||||||
@@ -115,7 +122,8 @@ body {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code, .verbatim {
|
.code,
|
||||||
|
.verbatim {
|
||||||
font-family: var(--src-font-family);
|
font-family: var(--src-font-family);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
@@ -126,7 +134,8 @@ body {
|
|||||||
margin: 1rem 0 1rem 2rem;
|
margin: 1rem 0 1rem 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2, h3 {
|
h2,
|
||||||
|
h3 {
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
@@ -184,4 +193,37 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.org_table {
|
||||||
|
table-layout: fixed;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 1px solid var(--table-border-color);
|
||||||
|
> tbody {
|
||||||
|
border-width: 1px 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: var(--table-border-color);
|
||||||
|
> tr {
|
||||||
|
> td {
|
||||||
|
padding: 0.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> tr:nth-child(odd) {
|
||||||
|
background-color: var(--table-odd-background-color);
|
||||||
|
}
|
||||||
|
> tr:nth-child(even) {
|
||||||
|
background-color: var(--table-even-background-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> thead {
|
||||||
|
border-width: 1px 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: var(--table-border-color);
|
||||||
|
> tr {
|
||||||
|
> th {
|
||||||
|
padding: 0.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,19 +3,87 @@
|
|||||||
License: none (public domain)
|
License: none (public domain)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
html, body, div, span, applet, object, iframe,
|
html,
|
||||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
body,
|
||||||
a, abbr, acronym, address, big, cite, code,
|
div,
|
||||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
span,
|
||||||
small, strike, strong, sub, sup, tt, var,
|
applet,
|
||||||
b, u, i, center,
|
object,
|
||||||
dl, dt, dd, ol, ul, li,
|
iframe,
|
||||||
fieldset, form, label, legend,
|
h1,
|
||||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
h2,
|
||||||
article, aside, canvas, details, embed,
|
h3,
|
||||||
figure, figcaption, footer, header, hgroup,
|
h4,
|
||||||
menu, nav, output, ruby, section, summary,
|
h5,
|
||||||
time, mark, audio, video {
|
h6,
|
||||||
|
p,
|
||||||
|
blockquote,
|
||||||
|
pre,
|
||||||
|
a,
|
||||||
|
abbr,
|
||||||
|
acronym,
|
||||||
|
address,
|
||||||
|
big,
|
||||||
|
cite,
|
||||||
|
code,
|
||||||
|
del,
|
||||||
|
dfn,
|
||||||
|
em,
|
||||||
|
img,
|
||||||
|
ins,
|
||||||
|
kbd,
|
||||||
|
q,
|
||||||
|
s,
|
||||||
|
samp,
|
||||||
|
small,
|
||||||
|
strike,
|
||||||
|
strong,
|
||||||
|
sub,
|
||||||
|
sup,
|
||||||
|
tt,
|
||||||
|
var,
|
||||||
|
b,
|
||||||
|
u,
|
||||||
|
i,
|
||||||
|
center,
|
||||||
|
dl,
|
||||||
|
dt,
|
||||||
|
dd,
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
li,
|
||||||
|
fieldset,
|
||||||
|
form,
|
||||||
|
label,
|
||||||
|
legend,
|
||||||
|
table,
|
||||||
|
caption,
|
||||||
|
tbody,
|
||||||
|
tfoot,
|
||||||
|
thead,
|
||||||
|
tr,
|
||||||
|
th,
|
||||||
|
td,
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
canvas,
|
||||||
|
details,
|
||||||
|
embed,
|
||||||
|
figure,
|
||||||
|
figcaption,
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
menu,
|
||||||
|
nav,
|
||||||
|
output,
|
||||||
|
ruby,
|
||||||
|
section,
|
||||||
|
summary,
|
||||||
|
time,
|
||||||
|
mark,
|
||||||
|
audio,
|
||||||
|
video {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
@@ -24,22 +92,35 @@ time, mark, audio, video {
|
|||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
}
|
}
|
||||||
/* HTML5 display-role reset for older browsers */
|
/* HTML5 display-role reset for older browsers */
|
||||||
article, aside, details, figcaption, figure,
|
article,
|
||||||
footer, header, hgroup, menu, nav, section {
|
aside,
|
||||||
|
details,
|
||||||
|
figcaption,
|
||||||
|
figure,
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
menu,
|
||||||
|
nav,
|
||||||
|
section {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
ol, ul {
|
ol,
|
||||||
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
blockquote, q {
|
blockquote,
|
||||||
|
q {
|
||||||
quotes: none;
|
quotes: none;
|
||||||
}
|
}
|
||||||
blockquote:before, blockquote:after,
|
blockquote:before,
|
||||||
q:before, q:after {
|
blockquote:after,
|
||||||
content: '';
|
q:before,
|
||||||
|
q:after {
|
||||||
|
content: "";
|
||||||
content: none;
|
content: none;
|
||||||
}
|
}
|
||||||
table {
|
table {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
{#.page_header}{>page_header/}{/.page_header}
|
{#.page_header}{>page_header/}{/.page_header}
|
||||||
<main class="main_content">
|
<main class="main_content">
|
||||||
{@select key=.type}
|
{@select key=.type}
|
||||||
|
{@eq value="page"}{>page/}{/eq}
|
||||||
{@eq value="blog_post_page"}{>blog_post_page/}{/eq}
|
{@eq value="blog_post_page"}{>blog_post_page/}{/eq}
|
||||||
{@eq value="blog_stream"}{>blog_stream/}{/eq}
|
{@eq value="blog_stream"}{>blog_stream/}{/eq}
|
||||||
{@none}{!TODO: make this panic!}ERROR: Unrecognized page content type{/none}
|
{@none}{!TODO: make this panic!}ERROR: Unrecognized page content type{/none}
|
||||||
|
|||||||
19
default_environment/templates/html/page.dust
Normal file
19
default_environment/templates/html/page.dust
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<article class="page">
|
||||||
|
{?.title}<h1 class="blog_post_title"><span>{.title}</span></h1>{/.title}
|
||||||
|
{! TODO: date? !}
|
||||||
|
|
||||||
|
{! TODO: Table of contents? !}
|
||||||
|
|
||||||
|
<div class="blog_post_body">
|
||||||
|
{#.children}
|
||||||
|
{>document_element/}
|
||||||
|
{/.children}
|
||||||
|
|
||||||
|
{?.footnotes}
|
||||||
|
<h2>Footnotes:</h2>
|
||||||
|
{#.footnotes}
|
||||||
|
{>real_footnote_definition/}
|
||||||
|
{/.footnotes}
|
||||||
|
{/.footnotes}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
@@ -1 +1,5 @@
|
|||||||
<table>{#.children}{>table_row/}{/.children}</table>
|
<table class="org_table">{#.children}{@select key=.type}
|
||||||
|
{@eq value="head"}{>table_head/}{/eq}
|
||||||
|
{@eq value="body"}{>table_body/}{/eq}
|
||||||
|
{@none}{!TODO: make this panic!}ERROR: Unrecognized type {.type}.{/none}
|
||||||
|
{/select}{/.children}</table>
|
||||||
|
|||||||
1
default_environment/templates/html/table_body.dust
Normal file
1
default_environment/templates/html/table_body.dust
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<tbody>{#.children}{>table_row/}{/.children}</tbody>
|
||||||
1
default_environment/templates/html/table_head.dust
Normal file
1
default_environment/templates/html/table_head.dust
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<thead>{#.children}{>table_head_row/}{/.children}</thead>
|
||||||
1
default_environment/templates/html/table_head_cell.dust
Normal file
1
default_environment/templates/html/table_head_cell.dust
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<th scope="col">{#.children}{>object/}{/.children}</th>
|
||||||
1
default_environment/templates/html/table_head_row.dust
Normal file
1
default_environment/templates/html/table_head_row.dust
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<tr>{#.children}{>table_head_cell/}{/.children}</tr>
|
||||||
13
docker/natter/Dockerfile
Normal file
13
docker/natter/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
FROM rustlang/rust:nightly-alpine3.17 AS builder
|
||||||
|
|
||||||
|
RUN apk add --no-cache musl-dev
|
||||||
|
|
||||||
|
RUN mkdir /root/natter
|
||||||
|
WORKDIR /root/natter
|
||||||
|
COPY . .
|
||||||
|
# TODO: Add static build, which currently errors due to proc_macro. RUSTFLAGS="-C target-feature=+crt-static"
|
||||||
|
RUN CARGO_TARGET_DIR=/target cargo build --profile release-lto
|
||||||
|
|
||||||
|
FROM alpine:3.17 AS runner
|
||||||
|
|
||||||
|
COPY --from=builder /target/release-lto/natter /usr/bin/
|
||||||
32
docker/natter/Makefile
Normal file
32
docker/natter/Makefile
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
SHELL := bash
|
||||||
|
.ONESHELL:
|
||||||
|
.SHELLFLAGS := -eu -o pipefail -c
|
||||||
|
.DELETE_ON_ERROR:
|
||||||
|
MAKEFLAGS += --warn-undefined-variables
|
||||||
|
MAKEFLAGS += --no-builtin-rules
|
||||||
|
OS:=$(shell uname -s)
|
||||||
|
|
||||||
|
ifeq ($(origin .RECIPEPREFIX), undefined)
|
||||||
|
$(error This Make does not support .RECIPEPREFIX. Please use GNU Make 4.0 or later)
|
||||||
|
endif
|
||||||
|
.RECIPEPREFIX = >
|
||||||
|
|
||||||
|
IMAGE_NAME:=natter
|
||||||
|
TARGET :=
|
||||||
|
|
||||||
|
.PHONY: help
|
||||||
|
help:
|
||||||
|
> @grep -h "##" $(MAKEFILE_LIST) | grep -v grep | sed -E 's/^([^:]*): *## */\1: /'
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build: ## Build the docker image.
|
||||||
|
> docker build --tag $(IMAGE_NAME) --target=$(TARGET) --file Dockerfile ../../
|
||||||
|
|
||||||
|
.PHONY: shell
|
||||||
|
shell: ## Launch an interactive shell inside the docker image.
|
||||||
|
shell: build
|
||||||
|
> docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp $(IMAGE_NAME)
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
> docker rmi $(IMAGE_NAME)
|
||||||
10
docker/natter_development/Dockerfile
Normal file
10
docker/natter_development/Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
FROM rustlang/rust:nightly-alpine3.17 AS builder
|
||||||
|
|
||||||
|
RUN apk add --no-cache musl-dev
|
||||||
|
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
|
||||||
|
RUN rustup component add rustfmt
|
||||||
|
|
||||||
|
FROM builder AS javascript
|
||||||
|
|
||||||
|
RUN apk add --no-cache npm
|
||||||
|
RUN npm install --global prettier@3.1.0
|
||||||
34
docker/natter_development/Makefile
Normal file
34
docker/natter_development/Makefile
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
SHELL := bash
|
||||||
|
.ONESHELL:
|
||||||
|
.SHELLFLAGS := -eu -o pipefail -c
|
||||||
|
.DELETE_ON_ERROR:
|
||||||
|
MAKEFLAGS += --warn-undefined-variables
|
||||||
|
MAKEFLAGS += --no-builtin-rules
|
||||||
|
OS:=$(shell uname -s)
|
||||||
|
|
||||||
|
ifeq ($(origin .RECIPEPREFIX), undefined)
|
||||||
|
$(error This Make does not support .RECIPEPREFIX. Please use GNU Make 4.0 or later)
|
||||||
|
endif
|
||||||
|
.RECIPEPREFIX = >
|
||||||
|
|
||||||
|
IMAGE_NAME:=natter-development
|
||||||
|
TARGET :=
|
||||||
|
|
||||||
|
.PHONY: help
|
||||||
|
help:
|
||||||
|
> @grep -h "##" $(MAKEFILE_LIST) | grep -v grep | sed -E 's/^([^:]*): *## */\1: /'
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build: ## Build the docker image.
|
||||||
|
> docker build --tag $(IMAGE_NAME) --target=$(TARGET) --file Dockerfile .
|
||||||
|
> docker volume create natter-cargo-registry
|
||||||
|
|
||||||
|
.PHONY: shell
|
||||||
|
shell: ## Launch an interactive shell inside the docker image with the source repository mounted at /source.
|
||||||
|
shell: build
|
||||||
|
> docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source" --workdir=/source --env CARGO_TARGET_DIR=/target -v "natter-cargo-registry:/usr/local/cargo/registry" $(IMAGE_NAME)
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
> docker rmi $(IMAGE_NAME)
|
||||||
|
> docker volume rm natter-cargo-registry
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use include_dir::include_dir;
|
use include_dir::include_dir;
|
||||||
use include_dir::Dir;
|
use include_dir::Dir;
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::context::RenderBlogPostPage;
|
use crate::context::RenderBlogPostPage;
|
||||||
@@ -10,9 +13,11 @@ use crate::context::RenderBlogPostPageInput;
|
|||||||
use crate::context::RenderBlogStream;
|
use crate::context::RenderBlogStream;
|
||||||
use crate::context::RenderBlogStreamInput;
|
use crate::context::RenderBlogStreamInput;
|
||||||
use crate::context::RenderContext;
|
use crate::context::RenderContext;
|
||||||
|
use crate::context::RenderPage;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::intermediate::get_web_path;
|
use crate::intermediate::get_web_path;
|
||||||
use crate::intermediate::BlogPost;
|
use crate::intermediate::BlogPost;
|
||||||
|
use crate::intermediate::IPage;
|
||||||
use crate::render::DusterRenderer;
|
use crate::render::DusterRenderer;
|
||||||
use crate::render::RendererIntegration;
|
use crate::render::RendererIntegration;
|
||||||
|
|
||||||
@@ -24,6 +29,7 @@ pub(crate) struct SiteRenderer {
|
|||||||
output_directory: PathBuf,
|
output_directory: PathBuf,
|
||||||
blog_posts: Vec<BlogPost>,
|
blog_posts: Vec<BlogPost>,
|
||||||
stylesheets: Vec<Stylesheet>,
|
stylesheets: Vec<Stylesheet>,
|
||||||
|
pages: Vec<IPage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SiteRenderer {
|
impl SiteRenderer {
|
||||||
@@ -31,11 +37,13 @@ impl SiteRenderer {
|
|||||||
output_directory: P,
|
output_directory: P,
|
||||||
blog_posts: Vec<BlogPost>,
|
blog_posts: Vec<BlogPost>,
|
||||||
stylesheets: Vec<Stylesheet>,
|
stylesheets: Vec<Stylesheet>,
|
||||||
|
pages: Vec<IPage>,
|
||||||
) -> SiteRenderer {
|
) -> SiteRenderer {
|
||||||
SiteRenderer {
|
SiteRenderer {
|
||||||
output_directory: output_directory.into(),
|
output_directory: output_directory.into(),
|
||||||
blog_posts,
|
blog_posts,
|
||||||
stylesheets,
|
stylesheets,
|
||||||
|
pages,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +78,29 @@ impl SiteRenderer {
|
|||||||
Ok(renderer_integration)
|
Ok(renderer_integration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn render_pages(&self, config: &Config) -> Result<(), CustomError> {
|
||||||
|
let renderer_integration = self.init_renderer_integration()?;
|
||||||
|
|
||||||
|
for page in &self.pages {
|
||||||
|
let output_path = self.output_directory.join(page.get_output_path());
|
||||||
|
let render_context = RenderContext::new(
|
||||||
|
config,
|
||||||
|
self.output_directory.as_path(),
|
||||||
|
output_path.as_path(),
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
let render_context = RenderPage::new(render_context, page)?;
|
||||||
|
let rendered_output = renderer_integration.render(render_context)?;
|
||||||
|
let parent_directory = output_path
|
||||||
|
.parent()
|
||||||
|
.ok_or("Output file should have a containing directory.")?;
|
||||||
|
tokio::fs::create_dir_all(parent_directory).await?;
|
||||||
|
tokio::fs::write(output_path, rendered_output).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn render_blog_posts(&self, config: &Config) -> Result<(), CustomError> {
|
pub(crate) async fn render_blog_posts(&self, config: &Config) -> Result<(), CustomError> {
|
||||||
let renderer_integration = self.init_renderer_integration()?;
|
let renderer_integration = self.init_renderer_integration()?;
|
||||||
|
|
||||||
@@ -196,6 +227,27 @@ impl SiteRenderer {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn copy_static_files(&self, config: &Config) -> Result<(), CustomError> {
|
||||||
|
let static_files_directory = config
|
||||||
|
.get_root_directory()
|
||||||
|
.join(config.get_relative_path_to_static_files());
|
||||||
|
if !static_files_directory.exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let static_files = get_all_files(&static_files_directory)?;
|
||||||
|
for entry in static_files {
|
||||||
|
let (path, contents) = entry.await??;
|
||||||
|
let relative_path = path.strip_prefix(&static_files_directory)?;
|
||||||
|
let output_path = self.output_directory.join(relative_path);
|
||||||
|
let parent_directory = output_path
|
||||||
|
.parent()
|
||||||
|
.ok_or("Output file should have a containing directory.")?;
|
||||||
|
tokio::fs::create_dir_all(parent_directory).await?;
|
||||||
|
tokio::fs::write(output_path, contents).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_name_contents_pairs<'a>(
|
fn build_name_contents_pairs<'a>(
|
||||||
@@ -210,3 +262,25 @@ fn build_name_contents_pairs<'a>(
|
|||||||
let contents = std::str::from_utf8(entry.contents())?;
|
let contents = std::str::from_utf8(entry.contents())?;
|
||||||
Ok((name, contents))
|
Ok((name, contents))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_all_files<P: AsRef<Path>>(
|
||||||
|
root_dir: P,
|
||||||
|
) -> Result<impl Iterator<Item = JoinHandle<std::io::Result<(PathBuf, Vec<u8>)>>>, walkdir::Error> {
|
||||||
|
let files = WalkDir::new(root_dir)
|
||||||
|
.into_iter()
|
||||||
|
.filter(|e| match e {
|
||||||
|
Ok(dir_entry) => dir_entry.file_type().is_file(),
|
||||||
|
Err(_) => true,
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
let org_files = files
|
||||||
|
.into_iter()
|
||||||
|
.map(walkdir::DirEntry::into_path)
|
||||||
|
.map(|path| tokio::spawn(read_file(path)));
|
||||||
|
Ok(org_files)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_file(path: PathBuf) -> std::io::Result<(PathBuf, Vec<u8>)> {
|
||||||
|
let contents = tokio::fs::read(&path).await?;
|
||||||
|
Ok((path, contents))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use super::stylesheet::Stylesheet;
|
use super::stylesheet::Stylesheet;
|
||||||
use crate::cli::parameters::BuildArgs;
|
use crate::cli::parameters::BuildArgs;
|
||||||
use crate::command::build::render::SiteRenderer;
|
use crate::command::build::render::SiteRenderer;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
|
use crate::intermediate::get_org_files;
|
||||||
use crate::intermediate::BlogPost;
|
use crate::intermediate::BlogPost;
|
||||||
|
use crate::intermediate::IPage;
|
||||||
|
use crate::intermediate::IntermediateContext;
|
||||||
|
use crate::intermediate::PageInput;
|
||||||
|
use crate::intermediate::Registry;
|
||||||
use include_dir::include_dir;
|
use include_dir::include_dir;
|
||||||
use include_dir::Dir;
|
use include_dir::Dir;
|
||||||
|
|
||||||
@@ -17,14 +24,18 @@ pub(crate) async fn build_site(args: BuildArgs) -> Result<(), CustomError> {
|
|||||||
let config = Config::load_from_file(args.config).await?;
|
let config = Config::load_from_file(args.config).await?;
|
||||||
let blog_posts = load_blog_posts(&config).await?;
|
let blog_posts = load_blog_posts(&config).await?;
|
||||||
let stylesheets = load_stylesheets().await?;
|
let stylesheets = load_stylesheets().await?;
|
||||||
|
let pages = load_pages(&config).await?;
|
||||||
let renderer = SiteRenderer::new(
|
let renderer = SiteRenderer::new(
|
||||||
get_output_directory(&config).await?,
|
get_output_directory(&config).await?,
|
||||||
blog_posts,
|
blog_posts,
|
||||||
stylesheets,
|
stylesheets,
|
||||||
|
pages,
|
||||||
);
|
);
|
||||||
renderer.render_blog_posts(&config).await?;
|
renderer.render_blog_posts(&config).await?;
|
||||||
renderer.render_blog_stream(&config).await?;
|
renderer.render_blog_stream(&config).await?;
|
||||||
|
renderer.render_pages(&config).await?;
|
||||||
renderer.render_stylesheets().await?;
|
renderer.render_stylesheets().await?;
|
||||||
|
renderer.copy_static_files(&config).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -62,7 +73,7 @@ async fn get_post_directories(config: &Config) -> Result<Vec<PathBuf>, CustomErr
|
|||||||
|
|
||||||
async fn load_blog_posts(config: &Config) -> Result<Vec<BlogPost>, CustomError> {
|
async fn load_blog_posts(config: &Config) -> Result<Vec<BlogPost>, CustomError> {
|
||||||
let root_directory = config.get_root_directory().to_owned();
|
let root_directory = config.get_root_directory().to_owned();
|
||||||
let post_directories = get_post_directories(&config).await?;
|
let post_directories = get_post_directories(config).await?;
|
||||||
let load_jobs = post_directories
|
let load_jobs = post_directories
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|path| tokio::spawn(BlogPost::load_blog_post(root_directory.clone(), path)));
|
.map(|path| tokio::spawn(BlogPost::load_blog_post(root_directory.clone(), path)));
|
||||||
@@ -73,6 +84,62 @@ async fn load_blog_posts(config: &Config) -> Result<Vec<BlogPost>, CustomError>
|
|||||||
Ok(blog_posts)
|
Ok(blog_posts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn load_pages(config: &Config) -> Result<Vec<IPage>, CustomError> {
|
||||||
|
let pages_source = config
|
||||||
|
.get_root_directory()
|
||||||
|
.join(config.get_relative_path_to_pages());
|
||||||
|
if !pages_source.exists() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
let page_files = get_org_files(&pages_source)?;
|
||||||
|
let org_files = {
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
for page in page_files {
|
||||||
|
ret.push(page.await??);
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
};
|
||||||
|
let parsed_org_files = {
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
for (path, contents) in org_files.iter() {
|
||||||
|
let parsed = organic::parser::parse_file(contents.as_str(), Some(path))
|
||||||
|
.map_err(|_| CustomError::Static("Failed to parse org-mode document."))?;
|
||||||
|
ret.push((path, contents, parsed));
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
};
|
||||||
|
|
||||||
|
let pages = {
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
for (real_path, _contents, parsed_document) in parsed_org_files.iter() {
|
||||||
|
let mut registry = Registry::new();
|
||||||
|
|
||||||
|
// Assign IDs to the targets
|
||||||
|
organic::types::AstNode::from(parsed_document)
|
||||||
|
.iter_all_ast_nodes()
|
||||||
|
.for_each(|node| {
|
||||||
|
if let organic::types::AstNode::Target(target) = node {
|
||||||
|
registry.get_target(target.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let registry = Arc::new(Mutex::new(registry));
|
||||||
|
let intermediate_context = IntermediateContext::new(registry)?;
|
||||||
|
let relative_to_pages_dir_path = real_path.strip_prefix(&pages_source)?;
|
||||||
|
ret.push(
|
||||||
|
IPage::new(
|
||||||
|
intermediate_context,
|
||||||
|
PageInput::new(relative_to_pages_dir_path, parsed_document),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(pages)
|
||||||
|
}
|
||||||
|
|
||||||
async fn load_stylesheets() -> Result<Vec<Stylesheet>, CustomError> {
|
async fn load_stylesheets() -> Result<Vec<Stylesheet>, CustomError> {
|
||||||
let sources: Vec<_> = DEFAULT_STYLESHEETS
|
let sources: Vec<_> = DEFAULT_STYLESHEETS
|
||||||
.files()
|
.files()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ pub(crate) async fn init_natter_folder(args: InitArgs) -> Result<(), CustomError
|
|||||||
|
|
||||||
let mut existing_entries = tokio::fs::read_dir(&args.path).await?;
|
let mut existing_entries = tokio::fs::read_dir(&args.path).await?;
|
||||||
let first_entry = existing_entries.next_entry().await?;
|
let first_entry = existing_entries.next_entry().await?;
|
||||||
if let Some(_) = first_entry {
|
if first_entry.is_some() {
|
||||||
return Err("The directory is not empty. Aborting.".into());
|
return Err("The directory is not empty. Aborting.".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,8 +47,7 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_root_directory(&self) -> &Path {
|
pub(crate) fn get_root_directory(&self) -> &Path {
|
||||||
&self
|
self.config_path
|
||||||
.config_path
|
|
||||||
.parent()
|
.parent()
|
||||||
.expect("Config file must exist inside a directory.")
|
.expect("Config file must exist inside a directory.")
|
||||||
}
|
}
|
||||||
@@ -86,8 +85,15 @@ impl Config {
|
|||||||
self.raw
|
self.raw
|
||||||
.stream
|
.stream
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|stream| stream.entries_per_page)
|
.and_then(|stream| stream.entries_per_page)
|
||||||
.flatten()
|
|
||||||
.unwrap_or(5)
|
.unwrap_or(5)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_relative_path_to_static_files(&self) -> PathBuf {
|
||||||
|
Path::new("static").into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_relative_path_to_pages(&self) -> PathBuf {
|
||||||
|
Path::new("pages").into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use serde::Deserialize;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
/// This is the struct for the natter.toml config file that ends up in each site's root directory.
|
/// This is the struct for the natter.toml config file that ends up in each site's root directory.
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize, Default)]
|
||||||
pub(crate) struct RawConfig {
|
pub(crate) struct RawConfig {
|
||||||
pub(super) site_title: Option<String>,
|
pub(super) site_title: Option<String>,
|
||||||
author: Option<String>,
|
author: Option<String>,
|
||||||
@@ -12,28 +12,7 @@ pub(crate) struct RawConfig {
|
|||||||
pub(super) stream: Option<RawConfigStream>,
|
pub(super) stream: Option<RawConfigStream>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RawConfig {
|
#[derive(Debug, Deserialize, Serialize, Default)]
|
||||||
fn default() -> Self {
|
|
||||||
RawConfig {
|
|
||||||
site_title: None,
|
|
||||||
author: None,
|
|
||||||
email: None,
|
|
||||||
use_relative_paths: None,
|
|
||||||
web_root: None,
|
|
||||||
stream: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub(crate) struct RawConfigStream {
|
pub(crate) struct RawConfigStream {
|
||||||
pub(super) entries_per_page: Option<usize>,
|
pub(super) entries_per_page: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RawConfigStream {
|
|
||||||
fn default() -> Self {
|
|
||||||
RawConfigStream {
|
|
||||||
entries_per_page: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -117,14 +117,14 @@ pub(crate) enum RenderAstNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait IntoRenderAstNode {
|
pub(crate) trait IntoRenderAstNode {
|
||||||
fn into_render_ast_node(
|
fn as_render_ast_node(
|
||||||
&self,
|
&self,
|
||||||
render_context: RenderContext<'_>,
|
render_context: RenderContext<'_>,
|
||||||
) -> Result<RenderAstNode, CustomError>;
|
) -> Result<RenderAstNode, CustomError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoRenderAstNode for IAstNode {
|
impl IntoRenderAstNode for IAstNode {
|
||||||
fn into_render_ast_node(
|
fn as_render_ast_node(
|
||||||
&self,
|
&self,
|
||||||
render_context: RenderContext<'_>,
|
render_context: RenderContext<'_>,
|
||||||
) -> Result<RenderAstNode, CustomError> {
|
) -> Result<RenderAstNode, CustomError> {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use super::RenderDocumentElement;
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct RenderBlogPostPageInput<'a> {
|
pub(crate) struct RenderBlogPostPageInput<'a> {
|
||||||
|
#[allow(dead_code)]
|
||||||
post: &'a BlogPost,
|
post: &'a BlogPost,
|
||||||
page: &'a BlogPostPage,
|
page: &'a BlogPostPage,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ render!(
|
|||||||
|
|
||||||
let children = original
|
let children = original
|
||||||
.original
|
.original
|
||||||
.into_iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, blog_post)| {
|
.map(|(i, blog_post)| {
|
||||||
RenderBlogStreamEntry::new(
|
RenderBlogStreamEntry::new(
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ render!(
|
|||||||
let contents = {
|
let contents = {
|
||||||
let mut ret = Vec::new();
|
let mut ret = Vec::new();
|
||||||
for obj in original.contents.iter() {
|
for obj in original.contents.iter() {
|
||||||
ret.push(obj.into_render_ast_node(render_context.clone())?);
|
ret.push(obj.as_render_ast_node(render_context.clone())?);
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ mod line_break;
|
|||||||
mod macros;
|
mod macros;
|
||||||
mod object;
|
mod object;
|
||||||
mod org_macro;
|
mod org_macro;
|
||||||
|
mod page;
|
||||||
mod page_header;
|
mod page_header;
|
||||||
mod paragraph;
|
mod paragraph;
|
||||||
mod plain_link;
|
mod plain_link;
|
||||||
@@ -59,6 +60,7 @@ mod subscript;
|
|||||||
mod superscript;
|
mod superscript;
|
||||||
mod table;
|
mod table;
|
||||||
mod table_cell;
|
mod table_cell;
|
||||||
|
mod table_group;
|
||||||
mod table_row;
|
mod table_row;
|
||||||
mod target;
|
mod target;
|
||||||
mod timestamp;
|
mod timestamp;
|
||||||
@@ -76,6 +78,7 @@ pub(crate) use footnote_definition::RenderRealFootnoteDefinition;
|
|||||||
pub(crate) use global_settings::GlobalSettings;
|
pub(crate) use global_settings::GlobalSettings;
|
||||||
pub(crate) use heading::RenderHeading;
|
pub(crate) use heading::RenderHeading;
|
||||||
pub(crate) use object::RenderObject;
|
pub(crate) use object::RenderObject;
|
||||||
|
pub(crate) use page::RenderPage;
|
||||||
pub(crate) use page_header::PageHeader;
|
pub(crate) use page_header::PageHeader;
|
||||||
pub(crate) use render_context::RenderContext;
|
pub(crate) use render_context::RenderContext;
|
||||||
pub(crate) use section::RenderSection;
|
pub(crate) use section::RenderSection;
|
||||||
|
|||||||
102
src/context/page.rs
Normal file
102
src/context/page.rs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
use super::footnote_definition::RenderRealFootnoteDefinition;
|
||||||
|
use super::macros::render;
|
||||||
|
use super::render_context::RenderContext;
|
||||||
|
use super::GlobalSettings;
|
||||||
|
use super::PageHeader;
|
||||||
|
use super::RenderDocumentElement;
|
||||||
|
use crate::error::CustomError;
|
||||||
|
use crate::intermediate::get_web_path;
|
||||||
|
use crate::intermediate::IPage;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
#[serde(rename = "page")]
|
||||||
|
pub(crate) struct RenderPage {
|
||||||
|
global_settings: GlobalSettings,
|
||||||
|
|
||||||
|
page_header: Option<PageHeader>,
|
||||||
|
|
||||||
|
/// The title that will be shown visibly on the page.
|
||||||
|
title: Option<String>,
|
||||||
|
|
||||||
|
self_link: Option<String>,
|
||||||
|
|
||||||
|
children: Vec<RenderDocumentElement>,
|
||||||
|
|
||||||
|
footnotes: Vec<RenderRealFootnoteDefinition>,
|
||||||
|
}
|
||||||
|
|
||||||
|
render!(RenderPage, IPage, original, render_context, {
|
||||||
|
let css_files = vec![
|
||||||
|
get_web_path(
|
||||||
|
render_context.config,
|
||||||
|
render_context.output_root_directory,
|
||||||
|
render_context.output_file,
|
||||||
|
"stylesheet/reset.css",
|
||||||
|
)?,
|
||||||
|
get_web_path(
|
||||||
|
render_context.config,
|
||||||
|
render_context.output_root_directory,
|
||||||
|
render_context.output_file,
|
||||||
|
"stylesheet/main.css",
|
||||||
|
)?,
|
||||||
|
];
|
||||||
|
let js_files = vec![get_web_path(
|
||||||
|
render_context.config,
|
||||||
|
render_context.output_root_directory,
|
||||||
|
render_context.output_file,
|
||||||
|
"blog_post.js",
|
||||||
|
)?];
|
||||||
|
let global_settings = GlobalSettings::new(original.title.clone(), css_files, js_files);
|
||||||
|
let page_header = PageHeader::new(
|
||||||
|
render_context.config.get_site_title().map(str::to_string),
|
||||||
|
Some(get_web_path(
|
||||||
|
render_context.config,
|
||||||
|
render_context.output_root_directory,
|
||||||
|
render_context.output_file,
|
||||||
|
"",
|
||||||
|
)?),
|
||||||
|
);
|
||||||
|
let link_to_blog_post = get_web_path(
|
||||||
|
render_context.config,
|
||||||
|
render_context.output_root_directory,
|
||||||
|
render_context.output_file,
|
||||||
|
render_context
|
||||||
|
.output_file
|
||||||
|
.strip_prefix(render_context.output_root_directory)?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let children = {
|
||||||
|
let mut children = Vec::new();
|
||||||
|
|
||||||
|
for child in original.children.iter() {
|
||||||
|
children.push(RenderDocumentElement::new(render_context.clone(), child)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
children
|
||||||
|
};
|
||||||
|
|
||||||
|
let footnotes = {
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
|
||||||
|
for footnote in original.footnotes.iter() {
|
||||||
|
ret.push(RenderRealFootnoteDefinition::new(
|
||||||
|
render_context.clone(),
|
||||||
|
footnote,
|
||||||
|
)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
};
|
||||||
|
|
||||||
|
let ret = RenderPage {
|
||||||
|
global_settings,
|
||||||
|
page_header: Some(page_header),
|
||||||
|
title: original.title.clone(),
|
||||||
|
self_link: Some(link_to_blog_post),
|
||||||
|
children,
|
||||||
|
footnotes,
|
||||||
|
};
|
||||||
|
Ok(ret)
|
||||||
|
});
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use super::render_context::RenderContext;
|
use super::render_context::RenderContext;
|
||||||
|
use super::table_group::RenderTableGroup;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::intermediate::ITable;
|
use crate::intermediate::ITable;
|
||||||
|
use crate::intermediate::ITableGroup;
|
||||||
|
|
||||||
use super::macros::render;
|
use super::macros::render;
|
||||||
use super::table_row::RenderTableRow;
|
use super::table_row::RenderTableRow;
|
||||||
@@ -11,15 +13,29 @@ use super::table_row::RenderTableRow;
|
|||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
#[serde(rename = "table")]
|
#[serde(rename = "table")]
|
||||||
pub(crate) struct RenderTable {
|
pub(crate) struct RenderTable {
|
||||||
children: Vec<RenderTableRow>,
|
children: Vec<RenderTableGroup>,
|
||||||
post_blank: organic::types::PostBlank,
|
post_blank: organic::types::PostBlank,
|
||||||
}
|
}
|
||||||
|
|
||||||
render!(RenderTable, ITable, original, render_context, {
|
render!(RenderTable, ITable, original, render_context, {
|
||||||
let children = {
|
let children = {
|
||||||
let mut ret = Vec::new();
|
let mut ret = Vec::new();
|
||||||
for obj in original.children.iter() {
|
for group in original.children.iter() {
|
||||||
ret.push(RenderTableRow::new(render_context.clone(), obj)?);
|
let mut rows = Vec::new();
|
||||||
|
match group {
|
||||||
|
ITableGroup::Head(irows) => {
|
||||||
|
for obj in irows {
|
||||||
|
rows.push(RenderTableRow::new(render_context.clone(), obj)?);
|
||||||
|
}
|
||||||
|
ret.push(RenderTableGroup::Head { children: rows });
|
||||||
|
}
|
||||||
|
ITableGroup::Body(irows) => {
|
||||||
|
for obj in irows {
|
||||||
|
rows.push(RenderTableRow::new(render_context.clone(), obj)?);
|
||||||
|
}
|
||||||
|
ret.push(RenderTableGroup::Body { children: rows });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
};
|
};
|
||||||
|
|||||||
12
src/context/table_group.rs
Normal file
12
src/context/table_group.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
use super::table_row::RenderTableRow;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub(crate) enum RenderTableGroup {
|
||||||
|
#[serde(rename = "head")]
|
||||||
|
Head { children: Vec<RenderTableRow> },
|
||||||
|
|
||||||
|
#[serde(rename = "body")]
|
||||||
|
Body { children: Vec<RenderTableRow> },
|
||||||
|
}
|
||||||
@@ -113,14 +113,14 @@ pub(crate) enum IAstNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait IntoIAstNode<'parse> {
|
pub(crate) trait IntoIAstNode<'parse> {
|
||||||
fn into_ast_node<'orig>(
|
fn as_ast_node<'orig>(
|
||||||
&'orig self,
|
&'orig self,
|
||||||
intermediate_context: IntermediateContext<'orig, 'parse>,
|
intermediate_context: IntermediateContext<'orig, 'parse>,
|
||||||
) -> BoxFuture<'orig, Result<IAstNode, CustomError>>;
|
) -> BoxFuture<'orig, Result<IAstNode, CustomError>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'parse> IntoIAstNode<'parse> for organic::types::DocumentElement<'parse> {
|
impl<'parse> IntoIAstNode<'parse> for organic::types::DocumentElement<'parse> {
|
||||||
fn into_ast_node<'orig>(
|
fn as_ast_node<'orig>(
|
||||||
&'orig self,
|
&'orig self,
|
||||||
intermediate_context: IntermediateContext<'orig, 'parse>,
|
intermediate_context: IntermediateContext<'orig, 'parse>,
|
||||||
) -> BoxFuture<'orig, Result<IAstNode, CustomError>> {
|
) -> BoxFuture<'orig, Result<IAstNode, CustomError>> {
|
||||||
@@ -139,7 +139,7 @@ impl<'parse> IntoIAstNode<'parse> for organic::types::DocumentElement<'parse> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'parse> IntoIAstNode<'parse> for organic::types::Element<'parse> {
|
impl<'parse> IntoIAstNode<'parse> for organic::types::Element<'parse> {
|
||||||
fn into_ast_node<'orig>(
|
fn as_ast_node<'orig>(
|
||||||
&'orig self,
|
&'orig self,
|
||||||
intermediate_context: IntermediateContext<'orig, 'parse>,
|
intermediate_context: IntermediateContext<'orig, 'parse>,
|
||||||
) -> BoxFuture<'orig, Result<IAstNode, CustomError>> {
|
) -> BoxFuture<'orig, Result<IAstNode, CustomError>> {
|
||||||
@@ -226,7 +226,7 @@ impl<'parse> IntoIAstNode<'parse> for organic::types::Element<'parse> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'parse> IntoIAstNode<'parse> for organic::types::Object<'parse> {
|
impl<'parse> IntoIAstNode<'parse> for organic::types::Object<'parse> {
|
||||||
fn into_ast_node<'orig>(
|
fn as_ast_node<'orig>(
|
||||||
&'orig self,
|
&'orig self,
|
||||||
intermediate_context: IntermediateContext<'orig, 'parse>,
|
intermediate_context: IntermediateContext<'orig, 'parse>,
|
||||||
) -> BoxFuture<'orig, Result<IAstNode, CustomError>> {
|
) -> BoxFuture<'orig, Result<IAstNode, CustomError>> {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use tokio::task::JoinHandle;
|
|||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::intermediate::page::BlogPostPageInput;
|
use crate::intermediate::blog_post_page::BlogPostPageInput;
|
||||||
use crate::intermediate::registry::Registry;
|
use crate::intermediate::registry::Registry;
|
||||||
use crate::intermediate::IntermediateContext;
|
use crate::intermediate::IntermediateContext;
|
||||||
|
|
||||||
@@ -55,11 +55,10 @@ impl BlogPost {
|
|||||||
// Assign IDs to the targets
|
// Assign IDs to the targets
|
||||||
organic::types::AstNode::from(parsed_document)
|
organic::types::AstNode::from(parsed_document)
|
||||||
.iter_all_ast_nodes()
|
.iter_all_ast_nodes()
|
||||||
.for_each(|node| match node {
|
.for_each(|node| {
|
||||||
organic::types::AstNode::Target(target) => {
|
if let organic::types::AstNode::Target(target) = node {
|
||||||
registry.get_target(target.value);
|
registry.get_target(target.value);
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let registry = Arc::new(Mutex::new(registry));
|
let registry = Arc::new(Mutex::new(registry));
|
||||||
@@ -96,15 +95,14 @@ impl BlogPost {
|
|||||||
pub(crate) fn get_date(&self) -> Option<&str> {
|
pub(crate) fn get_date(&self) -> Option<&str> {
|
||||||
let index_page_date = self
|
let index_page_date = self
|
||||||
.get_index_page()
|
.get_index_page()
|
||||||
.map(|index_page| index_page.date.as_ref().map(String::as_str))
|
.and_then(|index_page| index_page.date.as_deref());
|
||||||
.flatten();
|
|
||||||
if index_page_date.is_some() {
|
if index_page_date.is_some() {
|
||||||
return index_page_date;
|
return index_page_date;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pages
|
self.pages
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|page| page.date.as_ref().map(String::as_str))
|
.filter_map(|page| page.date.as_deref())
|
||||||
.next()
|
.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +119,7 @@ async fn read_file(path: PathBuf) -> std::io::Result<(PathBuf, String)> {
|
|||||||
Ok((path, contents))
|
Ok((path, contents))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_org_files<P: AsRef<Path>>(
|
pub(crate) fn get_org_files<P: AsRef<Path>>(
|
||||||
root_dir: P,
|
root_dir: P,
|
||||||
) -> Result<impl Iterator<Item = JoinHandle<std::io::Result<(PathBuf, String)>>>, walkdir::Error> {
|
) -> Result<impl Iterator<Item = JoinHandle<std::io::Result<(PathBuf, String)>>>, walkdir::Error> {
|
||||||
let org_files = WalkDir::new(root_dir)
|
let org_files = WalkDir::new(root_dir)
|
||||||
|
|||||||
121
src/intermediate/blog_post_page.rs
Normal file
121
src/intermediate/blog_post_page.rs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::error::CustomError;
|
||||||
|
|
||||||
|
use super::footnote_definition::IRealFootnoteDefinition;
|
||||||
|
|
||||||
|
use super::macros::intermediate;
|
||||||
|
use super::IDocumentElement;
|
||||||
|
use super::IHeading;
|
||||||
|
use super::ISection;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct BlogPostPageInput<'b, 'parse> {
|
||||||
|
path: PathBuf,
|
||||||
|
document: &'b organic::types::Document<'parse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b, 'parse> BlogPostPageInput<'b, 'parse> {
|
||||||
|
pub(crate) fn new<P: Into<PathBuf>>(
|
||||||
|
path: P,
|
||||||
|
document: &'b organic::types::Document<'parse>,
|
||||||
|
) -> BlogPostPageInput<'b, 'parse> {
|
||||||
|
BlogPostPageInput {
|
||||||
|
path: path.into(),
|
||||||
|
document,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct BlogPostPage {
|
||||||
|
/// Relative path from the root of the blog post.
|
||||||
|
pub(crate) path: PathBuf,
|
||||||
|
|
||||||
|
pub(crate) title: Option<String>,
|
||||||
|
|
||||||
|
pub(crate) date: Option<String>,
|
||||||
|
|
||||||
|
pub(crate) children: Vec<IDocumentElement>,
|
||||||
|
|
||||||
|
pub(crate) footnotes: Vec<IRealFootnoteDefinition>,
|
||||||
|
}
|
||||||
|
|
||||||
|
intermediate!(
|
||||||
|
BlogPostPage,
|
||||||
|
BlogPostPageInput<'orig, 'parse>,
|
||||||
|
original,
|
||||||
|
intermediate_context,
|
||||||
|
{
|
||||||
|
let mut children = Vec::new();
|
||||||
|
if let Some(section) = original.document.zeroth_section.as_ref() {
|
||||||
|
children.push(IDocumentElement::Section(
|
||||||
|
ISection::new(intermediate_context.clone(), section).await?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for heading in original.document.children.iter() {
|
||||||
|
children.push(IDocumentElement::Heading(
|
||||||
|
IHeading::new(intermediate_context.clone(), heading).await?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let footnotes = {
|
||||||
|
let footnote_definitions: Vec<_> = {
|
||||||
|
let registry = intermediate_context.registry.lock().unwrap();
|
||||||
|
let ret = registry
|
||||||
|
.get_footnote_ids()
|
||||||
|
.map(|(id, def)| (id, def.clone()))
|
||||||
|
.collect();
|
||||||
|
ret
|
||||||
|
};
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
for (id, def) in footnote_definitions.into_iter() {
|
||||||
|
ret.push(
|
||||||
|
IRealFootnoteDefinition::new(intermediate_context.clone(), id, def).await?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(BlogPostPage {
|
||||||
|
path: original.path,
|
||||||
|
title: get_title(original.document),
|
||||||
|
date: get_date(original.document),
|
||||||
|
children,
|
||||||
|
footnotes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
impl BlogPostPage {
|
||||||
|
/// Get the output path relative to the post directory.
|
||||||
|
pub(crate) fn get_output_path(&self) -> PathBuf {
|
||||||
|
let mut ret = self.path.clone();
|
||||||
|
ret.set_extension("html");
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_title(document: &organic::types::Document<'_>) -> Option<String> {
|
||||||
|
organic::types::AstNode::from(document)
|
||||||
|
.iter_all_ast_nodes()
|
||||||
|
.filter_map(|node| match node {
|
||||||
|
organic::types::AstNode::Keyword(kw) if kw.key.eq_ignore_ascii_case("title") => {
|
||||||
|
Some(kw)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.last()
|
||||||
|
.map(|kw| kw.value.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_date(document: &organic::types::Document<'_>) -> Option<String> {
|
||||||
|
organic::types::AstNode::from(document)
|
||||||
|
.iter_all_ast_nodes()
|
||||||
|
.filter_map(|node| match node {
|
||||||
|
organic::types::AstNode::Keyword(kw) if kw.key.eq_ignore_ascii_case("date") => Some(kw),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.last()
|
||||||
|
.map(|kw| kw.value.to_owned())
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ pub(crate) fn get_web_path<D: AsRef<Path>, F: AsRef<Path>, P: AsRef<Path>>(
|
|||||||
containing_file_relative_to_output_directory
|
containing_file_relative_to_output_directory
|
||||||
.parent()
|
.parent()
|
||||||
.ok_or("File should exist in a folder.")?,
|
.ok_or("File should exist in a folder.")?,
|
||||||
path_from_web_root.parent().unwrap_or(&Path::new("")),
|
path_from_web_root.parent().unwrap_or(Path::new("")),
|
||||||
)
|
)
|
||||||
.collect::<PathBuf>();
|
.collect::<PathBuf>();
|
||||||
// Subtracting 1 from the depth to "remove" the file name.
|
// Subtracting 1 from the depth to "remove" the file name.
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ impl IRealFootnoteDefinition {
|
|||||||
pub(crate) fn get_reference_id(&self, id_addition: Option<&str>) -> String {
|
pub(crate) fn get_reference_id(&self, id_addition: Option<&str>) -> String {
|
||||||
let id_addition = id_addition
|
let id_addition = id_addition
|
||||||
.map(|id_addition| format!("sec{}.", id_addition))
|
.map(|id_addition| format!("sec{}.", id_addition))
|
||||||
.unwrap_or(String::default());
|
.unwrap_or_default();
|
||||||
|
|
||||||
format!("{}fnr.{}", id_addition, self.get_display_label())
|
format!("{}fnr.{}", id_addition, self.get_display_label())
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ impl IRealFootnoteDefinition {
|
|||||||
pub(crate) fn get_definition_id(&self, id_addition: Option<&str>) -> String {
|
pub(crate) fn get_definition_id(&self, id_addition: Option<&str>) -> String {
|
||||||
let id_addition = id_addition
|
let id_addition = id_addition
|
||||||
.map(|id_addition| format!("sec{}.", id_addition))
|
.map(|id_addition| format!("sec{}.", id_addition))
|
||||||
.unwrap_or(String::default());
|
.unwrap_or_default();
|
||||||
|
|
||||||
format!("{}fn.{}", id_addition, self.get_display_label())
|
format!("{}fn.{}", id_addition, self.get_display_label())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ impl IFootnoteReference {
|
|||||||
pub(crate) fn get_reference_id(&self, id_addition: Option<&str>) -> String {
|
pub(crate) fn get_reference_id(&self, id_addition: Option<&str>) -> String {
|
||||||
let id_addition = id_addition
|
let id_addition = id_addition
|
||||||
.map(|id_addition| format!("sec{}.", id_addition))
|
.map(|id_addition| format!("sec{}.", id_addition))
|
||||||
.unwrap_or(String::default());
|
.unwrap_or_default();
|
||||||
|
|
||||||
if self.duplicate_offset == 0 {
|
if self.duplicate_offset == 0 {
|
||||||
format!("{}fnr.{}", id_addition, self.get_display_label())
|
format!("{}fnr.{}", id_addition, self.get_display_label())
|
||||||
@@ -55,7 +55,7 @@ impl IFootnoteReference {
|
|||||||
pub(crate) fn get_definition_id(&self, id_addition: Option<&str>) -> String {
|
pub(crate) fn get_definition_id(&self, id_addition: Option<&str>) -> String {
|
||||||
let id_addition = id_addition
|
let id_addition = id_addition
|
||||||
.map(|id_addition| format!("sec{}.", id_addition))
|
.map(|id_addition| format!("sec{}.", id_addition))
|
||||||
.unwrap_or(String::default());
|
.unwrap_or_default();
|
||||||
|
|
||||||
format!("{}fn.{}", id_addition, self.get_display_label())
|
format!("{}fn.{}", id_addition, self.get_display_label())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ intermediate!(
|
|||||||
{
|
{
|
||||||
let value: String = if original.value.starts_with("$$") && original.value.ends_with("$$") {
|
let value: String = if original.value.starts_with("$$") && original.value.ends_with("$$") {
|
||||||
format!("\\[{}\\]", &original.value[2..(original.value.len() - 2)])
|
format!("\\[{}\\]", &original.value[2..(original.value.len() - 2)])
|
||||||
} else if original.value.starts_with("$") && original.value.ends_with("$") {
|
} else if original.value.starts_with('$') && original.value.ends_with('$') {
|
||||||
format!("\\({}\\)", &original.value[1..(original.value.len() - 1)])
|
format!("\\({}\\)", &original.value[1..(original.value.len() - 1)])
|
||||||
} else {
|
} else {
|
||||||
original.value.to_owned()
|
original.value.to_owned()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ mod angle_link;
|
|||||||
mod ast_node;
|
mod ast_node;
|
||||||
mod babel_call;
|
mod babel_call;
|
||||||
mod blog_post;
|
mod blog_post;
|
||||||
|
mod blog_post_page;
|
||||||
mod bold;
|
mod bold;
|
||||||
mod center_block;
|
mod center_block;
|
||||||
mod citation;
|
mod citation;
|
||||||
@@ -59,6 +60,7 @@ mod subscript;
|
|||||||
mod superscript;
|
mod superscript;
|
||||||
mod table;
|
mod table;
|
||||||
mod table_cell;
|
mod table_cell;
|
||||||
|
mod table_group;
|
||||||
mod table_row;
|
mod table_row;
|
||||||
mod target;
|
mod target;
|
||||||
mod timestamp;
|
mod timestamp;
|
||||||
@@ -69,7 +71,9 @@ mod verse_block;
|
|||||||
pub(crate) use angle_link::IAngleLink;
|
pub(crate) use angle_link::IAngleLink;
|
||||||
pub(crate) use ast_node::IAstNode;
|
pub(crate) use ast_node::IAstNode;
|
||||||
pub(crate) use babel_call::IBabelCall;
|
pub(crate) use babel_call::IBabelCall;
|
||||||
|
pub(crate) use blog_post::get_org_files;
|
||||||
pub(crate) use blog_post::BlogPost;
|
pub(crate) use blog_post::BlogPost;
|
||||||
|
pub(crate) use blog_post_page::BlogPostPage;
|
||||||
pub(crate) use bold::IBold;
|
pub(crate) use bold::IBold;
|
||||||
pub(crate) use center_block::ICenterBlock;
|
pub(crate) use center_block::ICenterBlock;
|
||||||
pub(crate) use citation::ICitation;
|
pub(crate) use citation::ICitation;
|
||||||
@@ -104,7 +108,8 @@ pub(crate) use latex_fragment::ILatexFragment;
|
|||||||
pub(crate) use line_break::ILineBreak;
|
pub(crate) use line_break::ILineBreak;
|
||||||
pub(crate) use object::IObject;
|
pub(crate) use object::IObject;
|
||||||
pub(crate) use org_macro::IOrgMacro;
|
pub(crate) use org_macro::IOrgMacro;
|
||||||
pub(crate) use page::BlogPostPage;
|
pub(crate) use page::IPage;
|
||||||
|
pub(crate) use page::PageInput;
|
||||||
pub(crate) use paragraph::IParagraph;
|
pub(crate) use paragraph::IParagraph;
|
||||||
pub(crate) use plain_link::IPlainLink;
|
pub(crate) use plain_link::IPlainLink;
|
||||||
pub(crate) use plain_list::IPlainList;
|
pub(crate) use plain_list::IPlainList;
|
||||||
@@ -116,6 +121,7 @@ pub(crate) use property_drawer::IPropertyDrawer;
|
|||||||
pub(crate) use quote_block::IQuoteBlock;
|
pub(crate) use quote_block::IQuoteBlock;
|
||||||
pub(crate) use radio_link::IRadioLink;
|
pub(crate) use radio_link::IRadioLink;
|
||||||
pub(crate) use radio_target::IRadioTarget;
|
pub(crate) use radio_target::IRadioTarget;
|
||||||
|
pub(crate) use registry::Registry;
|
||||||
pub(crate) use regular_link::IRegularLink;
|
pub(crate) use regular_link::IRegularLink;
|
||||||
pub(crate) use section::ISection;
|
pub(crate) use section::ISection;
|
||||||
pub(crate) use special_block::ISpecialBlock;
|
pub(crate) use special_block::ISpecialBlock;
|
||||||
@@ -126,6 +132,7 @@ pub(crate) use subscript::ISubscript;
|
|||||||
pub(crate) use superscript::ISuperscript;
|
pub(crate) use superscript::ISuperscript;
|
||||||
pub(crate) use table::ITable;
|
pub(crate) use table::ITable;
|
||||||
pub(crate) use table_cell::ITableCell;
|
pub(crate) use table_cell::ITableCell;
|
||||||
|
pub(crate) use table_group::ITableGroup;
|
||||||
pub(crate) use table_row::ITableRow;
|
pub(crate) use table_row::ITableRow;
|
||||||
pub(crate) use target::ITarget;
|
pub(crate) use target::ITarget;
|
||||||
pub(crate) use timestamp::ITimestamp;
|
pub(crate) use timestamp::ITimestamp;
|
||||||
|
|||||||
@@ -1,39 +1,21 @@
|
|||||||
use std::path::PathBuf;
|
use super::blog_post_page::get_date;
|
||||||
|
use super::blog_post_page::get_title;
|
||||||
use crate::error::CustomError;
|
|
||||||
|
|
||||||
use super::footnote_definition::IRealFootnoteDefinition;
|
use super::footnote_definition::IRealFootnoteDefinition;
|
||||||
|
|
||||||
use super::macros::intermediate;
|
use super::macros::intermediate;
|
||||||
use super::IDocumentElement;
|
use super::IDocumentElement;
|
||||||
use super::IHeading;
|
use super::IHeading;
|
||||||
use super::ISection;
|
use super::ISection;
|
||||||
|
use crate::error::CustomError;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct BlogPostPageInput<'b, 'parse> {
|
pub(crate) struct IPage {
|
||||||
path: PathBuf,
|
/// Relative path from the root of the pages directory.
|
||||||
document: &'b organic::types::Document<'parse>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'b, 'parse> BlogPostPageInput<'b, 'parse> {
|
|
||||||
pub(crate) fn new<P: Into<PathBuf>>(
|
|
||||||
path: P,
|
|
||||||
document: &'b organic::types::Document<'parse>,
|
|
||||||
) -> BlogPostPageInput<'b, 'parse> {
|
|
||||||
BlogPostPageInput {
|
|
||||||
path: path.into(),
|
|
||||||
document,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct BlogPostPage {
|
|
||||||
/// Relative path from the root of the blog post.
|
|
||||||
pub(crate) path: PathBuf,
|
pub(crate) path: PathBuf,
|
||||||
|
|
||||||
pub(crate) title: Option<String>,
|
pub(crate) title: Option<String>,
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) date: Option<String>,
|
pub(crate) date: Option<String>,
|
||||||
|
|
||||||
pub(crate) children: Vec<IDocumentElement>,
|
pub(crate) children: Vec<IDocumentElement>,
|
||||||
@@ -42,8 +24,8 @@ pub(crate) struct BlogPostPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
intermediate!(
|
intermediate!(
|
||||||
BlogPostPage,
|
IPage,
|
||||||
BlogPostPageInput<'orig, 'parse>,
|
PageInput<'orig, 'parse>,
|
||||||
original,
|
original,
|
||||||
intermediate_context,
|
intermediate_context,
|
||||||
{
|
{
|
||||||
@@ -77,7 +59,7 @@ intermediate!(
|
|||||||
ret
|
ret
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(BlogPostPage {
|
Ok(IPage {
|
||||||
path: original.path,
|
path: original.path,
|
||||||
title: get_title(original.document),
|
title: get_title(original.document),
|
||||||
date: get_date(original.document),
|
date: get_date(original.document),
|
||||||
@@ -87,8 +69,8 @@ intermediate!(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
impl BlogPostPage {
|
impl IPage {
|
||||||
/// Get the output path relative to the post directory.
|
/// Get the output path relative to the pages directory.
|
||||||
pub(crate) fn get_output_path(&self) -> PathBuf {
|
pub(crate) fn get_output_path(&self) -> PathBuf {
|
||||||
let mut ret = self.path.clone();
|
let mut ret = self.path.clone();
|
||||||
ret.set_extension("html");
|
ret.set_extension("html");
|
||||||
@@ -96,26 +78,20 @@ impl BlogPostPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_title(document: &organic::types::Document<'_>) -> Option<String> {
|
#[derive(Debug)]
|
||||||
organic::types::AstNode::from(document)
|
pub(crate) struct PageInput<'b, 'parse> {
|
||||||
.iter_all_ast_nodes()
|
path: PathBuf,
|
||||||
.filter_map(|node| match node {
|
document: &'b organic::types::Document<'parse>,
|
||||||
organic::types::AstNode::Keyword(kw) if kw.key.eq_ignore_ascii_case("title") => {
|
|
||||||
Some(kw)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.last()
|
|
||||||
.map(|kw| kw.value.to_owned())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_date(document: &organic::types::Document<'_>) -> Option<String> {
|
impl<'b, 'parse> PageInput<'b, 'parse> {
|
||||||
organic::types::AstNode::from(document)
|
pub(crate) fn new<P: Into<PathBuf>>(
|
||||||
.iter_all_ast_nodes()
|
path: P,
|
||||||
.filter_map(|node| match node {
|
document: &'b organic::types::Document<'parse>,
|
||||||
organic::types::AstNode::Keyword(kw) if kw.key.eq_ignore_ascii_case("date") => Some(kw),
|
) -> PageInput<'b, 'parse> {
|
||||||
_ => None,
|
PageInput {
|
||||||
})
|
path: path.into(),
|
||||||
.last()
|
document,
|
||||||
.map(|kw| kw.value.to_owned())
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ intermediate!(
|
|||||||
let mut ret = Vec::new();
|
let mut ret = Vec::new();
|
||||||
|
|
||||||
// Special case for list items with only paragraphs and sublists as their children. In those cases, the paragraph tags are omitted.
|
// Special case for list items with only paragraphs and sublists as their children. In those cases, the paragraph tags are omitted.
|
||||||
let is_simple_list_item = original.children.iter().all(|child| match child {
|
let is_simple_list_item = original.children.iter().all(|child| {
|
||||||
organic::types::Element::Paragraph(_) | organic::types::Element::PlainList(_) => {
|
matches!(
|
||||||
true
|
child,
|
||||||
}
|
organic::types::Element::Paragraph(_) | organic::types::Element::PlainList(_)
|
||||||
_ => false,
|
)
|
||||||
});
|
});
|
||||||
if is_simple_list_item {
|
if is_simple_list_item {
|
||||||
for elem in original.children.iter() {
|
for elem in original.children.iter() {
|
||||||
|
|||||||
@@ -52,9 +52,9 @@ impl<'orig, 'parse> Registry<'orig, 'parse> {
|
|||||||
pub(crate) async fn get_footnote_reference_id<'orig, 'parse>(
|
pub(crate) async fn get_footnote_reference_id<'orig, 'parse>(
|
||||||
intermediate_context: IntermediateContext<'orig, 'parse>,
|
intermediate_context: IntermediateContext<'orig, 'parse>,
|
||||||
label: Option<&'parse str>,
|
label: Option<&'parse str>,
|
||||||
definition: &'orig Vec<Object<'parse>>,
|
definition: &'orig [Object<'parse>],
|
||||||
) -> Result<(usize, usize), CustomError> {
|
) -> Result<(usize, usize), CustomError> {
|
||||||
if let None = label {
|
if label.is_none() {
|
||||||
// If it has no label then it must always get a new ID.
|
// If it has no label then it must always get a new ID.
|
||||||
let contents = convert_reference_contents(intermediate_context.clone(), definition).await?;
|
let contents = convert_reference_contents(intermediate_context.clone(), definition).await?;
|
||||||
let pos = {
|
let pos = {
|
||||||
@@ -148,7 +148,7 @@ pub(crate) async fn register_footnote_definition<'orig, 'parse>(
|
|||||||
|
|
||||||
async fn convert_reference_contents<'orig, 'parse>(
|
async fn convert_reference_contents<'orig, 'parse>(
|
||||||
intermediate_context: IntermediateContext<'orig, 'parse>,
|
intermediate_context: IntermediateContext<'orig, 'parse>,
|
||||||
contents: &'orig Vec<Object<'parse>>,
|
contents: &'orig [Object<'parse>],
|
||||||
) -> Result<Vec<IAstNode>, CustomError> {
|
) -> Result<Vec<IAstNode>, CustomError> {
|
||||||
let children = {
|
let children = {
|
||||||
let mut ret = Vec::new();
|
let mut ret = Vec::new();
|
||||||
@@ -159,23 +159,19 @@ async fn convert_reference_contents<'orig, 'parse>(
|
|||||||
};
|
};
|
||||||
let containing_paragraph =
|
let containing_paragraph =
|
||||||
IParagraph::artificial(intermediate_context.clone(), children, 0).await?;
|
IParagraph::artificial(intermediate_context.clone(), children, 0).await?;
|
||||||
let contents = {
|
let contents = vec![IAstNode::Paragraph(containing_paragraph)];
|
||||||
let mut ret = Vec::new();
|
|
||||||
ret.push(IAstNode::Paragraph(containing_paragraph));
|
|
||||||
ret
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(contents)
|
Ok(contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn convert_definition_contents<'orig, 'parse>(
|
async fn convert_definition_contents<'orig, 'parse>(
|
||||||
intermediate_context: IntermediateContext<'orig, 'parse>,
|
intermediate_context: IntermediateContext<'orig, 'parse>,
|
||||||
contents: &'orig Vec<Element<'parse>>,
|
contents: &'orig [Element<'parse>],
|
||||||
) -> Result<Vec<IAstNode>, CustomError> {
|
) -> Result<Vec<IAstNode>, CustomError> {
|
||||||
let contents = {
|
let contents = {
|
||||||
let mut ret = Vec::new();
|
let mut ret = Vec::new();
|
||||||
for obj in contents.iter() {
|
for obj in contents.iter() {
|
||||||
ret.push(obj.into_ast_node(intermediate_context.clone()).await?);
|
ret.push(obj.as_ast_node(intermediate_context.clone()).await?);
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
};
|
};
|
||||||
@@ -190,8 +186,7 @@ pub(crate) async fn promote_footnote_definition<'orig, 'parse>(
|
|||||||
) -> Result<(), CustomError> {
|
) -> Result<(), CustomError> {
|
||||||
let definition = {
|
let definition = {
|
||||||
let mut registry = intermediate_context.registry.lock().unwrap();
|
let mut registry = intermediate_context.registry.lock().unwrap();
|
||||||
let definition = registry.on_deck_footnote_ids.remove(label);
|
registry.on_deck_footnote_ids.remove(label)
|
||||||
definition
|
|
||||||
};
|
};
|
||||||
if let Some(elements) = definition {
|
if let Some(elements) = definition {
|
||||||
let existing_id = {
|
let existing_id = {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use super::macros::intermediate;
|
use super::macros::intermediate;
|
||||||
use super::table_row::ITableRow;
|
use super::table_row::ITableRow;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
|
use crate::intermediate::table_group::ITableGroup;
|
||||||
use organic::types::StandardProperties;
|
use organic::types::StandardProperties;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct ITable {
|
pub(crate) struct ITable {
|
||||||
pub(crate) children: Vec<ITableRow>,
|
pub(crate) children: Vec<ITableGroup>,
|
||||||
pub(crate) post_blank: organic::types::PostBlank,
|
pub(crate) post_blank: organic::types::PostBlank,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,10 +16,40 @@ intermediate!(
|
|||||||
original,
|
original,
|
||||||
intermediate_context,
|
intermediate_context,
|
||||||
{
|
{
|
||||||
let children = {
|
// Separate groups by lines, multiple contiguous lines are the same as one.
|
||||||
|
// If there is only one group, it is a tbody.
|
||||||
|
// If there are more than one group, the first is thead and the rest are tbody.
|
||||||
|
|
||||||
|
let sections = group_into_sections(&original.children);
|
||||||
|
|
||||||
|
let children = if sections.len() == 1 {
|
||||||
|
// If there is only one section, then it is a body.
|
||||||
let mut ret = Vec::new();
|
let mut ret = Vec::new();
|
||||||
for obj in original.children.iter() {
|
for group in sections.into_iter() {
|
||||||
ret.push(ITableRow::new(intermediate_context.clone(), obj).await?);
|
let mut rows = Vec::new();
|
||||||
|
for obj in group.into_iter() {
|
||||||
|
rows.push(ITableRow::new(intermediate_context.clone(), obj).await?)
|
||||||
|
}
|
||||||
|
ret.push(ITableGroup::Body(rows));
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
} else {
|
||||||
|
// If there are more than one section, the first is a head and the rest are body.
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
let mut sections = sections.into_iter();
|
||||||
|
if let Some(group) = sections.next() {
|
||||||
|
let mut rows = Vec::new();
|
||||||
|
for obj in group.into_iter() {
|
||||||
|
rows.push(ITableRow::new(intermediate_context.clone(), obj).await?)
|
||||||
|
}
|
||||||
|
ret.push(ITableGroup::Head(rows));
|
||||||
|
}
|
||||||
|
for group in sections {
|
||||||
|
let mut rows = Vec::new();
|
||||||
|
for obj in group.into_iter() {
|
||||||
|
rows.push(ITableRow::new(intermediate_context.clone(), obj).await?)
|
||||||
|
}
|
||||||
|
ret.push(ITableGroup::Body(rows));
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
};
|
};
|
||||||
@@ -29,3 +60,41 @@ intermediate!(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
enum GroupIntoSectionsState<'orig, 'parse> {
|
||||||
|
NonSection,
|
||||||
|
Section(Vec<&'orig organic::types::TableRow<'parse>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn group_into_sections<'orig, 'parse>(
|
||||||
|
rows: &'orig [organic::types::TableRow<'parse>],
|
||||||
|
) -> Vec<Vec<&'orig organic::types::TableRow<'parse>>> {
|
||||||
|
let mut sections = Vec::new();
|
||||||
|
let mut rows = rows.iter();
|
||||||
|
let mut state = GroupIntoSectionsState::NonSection;
|
||||||
|
loop {
|
||||||
|
state = match (state, rows.next()) {
|
||||||
|
(GroupIntoSectionsState::NonSection, None) => break,
|
||||||
|
(GroupIntoSectionsState::NonSection, Some(row)) if row.children.is_empty() => {
|
||||||
|
GroupIntoSectionsState::NonSection
|
||||||
|
}
|
||||||
|
(GroupIntoSectionsState::NonSection, Some(row)) => {
|
||||||
|
GroupIntoSectionsState::Section(vec![row])
|
||||||
|
}
|
||||||
|
(GroupIntoSectionsState::Section(section), None) => {
|
||||||
|
sections.push(section);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
(GroupIntoSectionsState::Section(section), Some(row)) if row.children.is_empty() => {
|
||||||
|
sections.push(section);
|
||||||
|
GroupIntoSectionsState::NonSection
|
||||||
|
}
|
||||||
|
(GroupIntoSectionsState::Section(mut section), Some(row)) => {
|
||||||
|
section.push(row);
|
||||||
|
GroupIntoSectionsState::Section(section)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sections
|
||||||
|
}
|
||||||
|
|||||||
7
src/intermediate/table_group.rs
Normal file
7
src/intermediate/table_group.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
use super::ITableRow;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) enum ITableGroup {
|
||||||
|
Head(Vec<ITableRow>),
|
||||||
|
Body(Vec<ITableRow>),
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ use organic::types::StandardProperties;
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct ITarget {
|
pub(crate) struct ITarget {
|
||||||
pub(crate) id: String,
|
pub(crate) id: String,
|
||||||
|
#[allow(dead_code)]
|
||||||
value: String,
|
value: String,
|
||||||
pub(crate) post_blank: organic::types::PostBlank,
|
pub(crate) post_blank: organic::types::PostBlank,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,7 @@ mod render;
|
|||||||
|
|
||||||
fn main() -> Result<ExitCode, CustomError> {
|
fn main() -> Result<ExitCode, CustomError> {
|
||||||
let rt = tokio::runtime::Runtime::new()?;
|
let rt = tokio::runtime::Runtime::new()?;
|
||||||
rt.block_on(async {
|
rt.block_on(async { main_body().await })
|
||||||
let main_body_result = main_body().await;
|
|
||||||
main_body_result
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn main_body() -> Result<ExitCode, CustomError> {
|
async fn main_body() -> Result<ExitCode, CustomError> {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ impl<'a> DusterRenderer<'a> {
|
|||||||
|
|
||||||
impl<'a> RendererIntegration<'a> for DusterRenderer<'a> {
|
impl<'a> RendererIntegration<'a> for DusterRenderer<'a> {
|
||||||
fn load_template(&mut self, name: &'a str, contents: &'a str) -> Result<(), CustomError> {
|
fn load_template(&mut self, name: &'a str, contents: &'a str) -> Result<(), CustomError> {
|
||||||
let compiled_template = duster::renderer::compile_template(contents.as_ref())?;
|
let compiled_template = duster::renderer::compile_template(contents)?;
|
||||||
self.templates.insert(name, compiled_template);
|
self.templates.insert(name, compiled_template);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user