├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── Setup.hs
├── app
├── LibOA.hs
└── Main.hs
├── cabal-bounds-lower.project
├── cabal-bounds-upper.project
├── cabal.project
├── doc
├── literatex.1
└── literatex.1.md
├── examples
├── highlevel.lhs
└── lowlevel.hs
├── literatex.cabal
├── pkg
├── deb
│ ├── Makefile
│ ├── control
│ └── copyright
└── rpm
│ └── literatex-haskell.spec
├── project
├── TODO.md
├── animation
│ ├── .gitignore
│ ├── LICENSE
│ ├── Main.hs
│ ├── Makefile
│ ├── README.md
│ ├── default.nix
│ ├── literatex-animation.cabal
│ ├── literatex.gif
│ └── shell.nix
├── log
│ └── 20210523-design.md
└── release
│ ├── literatex-haskell-0.1.0.0.md
│ ├── literatex-haskell-0.1.0.1.md
│ ├── literatex-haskell-0.1.0.2.md
│ ├── literatex-haskell-0.2.0.0.md
│ ├── literatex-haskell-0.2.0.1.md
│ ├── literatex-haskell-0.2.0.2.md
│ ├── literatex-haskell-0.2.1.0.md
│ ├── literatex-haskell-0.3.0.0.md
│ └── literatex-haskell-0.4.0.0.md
├── src
├── LiterateX.hs
└── LiterateX
│ ├── Parser.hs
│ ├── Renderer.hs
│ ├── SourceDefaults.hs
│ ├── Types.hs
│ └── Types
│ ├── CodeLanguage.hs
│ ├── SourceFormat.hs
│ ├── SourceLine.hs
│ └── TargetFormat.hs
├── stack-8.10.7.yaml
├── stack-8.8.4.yaml
├── stack-9.0.2.yaml
├── stack-9.10.1.yaml
├── stack-9.12.1.yaml
├── stack-9.2.8.yaml
├── stack-9.4.8.yaml
├── stack-9.6.6.yaml
├── stack-9.8.4.yaml
├── stack.yaml
├── test-all.sh
└── test
├── LiterateX
└── Test
│ ├── API.hs
│ ├── SourceFormat.hs
│ ├── SourceFormat
│ ├── DoubleDash.hs
│ ├── DoubleSlash.hs
│ ├── Hash.hs
│ ├── LispSemicolons.hs
│ ├── LiterateHaskell.hs
│ └── Percent.hs
│ └── TargetFormat.hs
└── Spec.hs
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - develop
8 | - main
9 |
10 | permissions:
11 | contents: read
12 |
13 | jobs:
14 | config:
15 | name: "Load configuration"
16 | runs-on: ubuntu-latest
17 | outputs:
18 | ghcvers: ${{ steps.set-ghcvers.outputs.ghcvers }}
19 | ghcvers_lower: ${{ steps.set-ghcvers.outputs.ghcvers_lower }}
20 | ghcvers_upper: ${{ steps.set-ghcvers.outputs.ghcvers_upper }}
21 | steps:
22 | - name: Checkout
23 | uses: actions/checkout@v4
24 | - name: Set ghcvers
25 | id: set-ghcvers
26 | run: ./test-all.sh github >> $GITHUB_OUTPUT
27 |
28 | cabal:
29 | name: "Cabal: GHC ${{ matrix.ghc }}"
30 | needs: config
31 | runs-on: ubuntu-latest
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | ghc: ${{fromJSON(needs.config.outputs.ghcvers)}}
36 | steps:
37 | - name: Checkout
38 | uses: actions/checkout@v4
39 | - name: Setup Haskell
40 | uses: haskell-actions/setup@v2
41 | id: setup
42 | with:
43 | ghc-version: ${{ matrix.ghc }}
44 | cabal-version: latest
45 | cabal-update: true
46 | - name: Setup Environment
47 | run: |
48 | GHC_VERSION=$(ghc --numeric-version)
49 | echo "GHC_VERSION=${GHC_VERSION}" | tee -a "${GITHUB_ENV}"
50 | CABAL_VERSION=$(cabal --numeric-version)
51 | echo "CABAL_VERSION=${CABAL_VERSION}" | tee -a "${GITHUB_ENV}"
52 | CABAL_OPTS="--enable-tests --enable-benchmarks"
53 | if [ -f "cabal-${GHC_VERSION}.project" ] ; then
54 | CABAL_OPTS="--project-file=cabal-${GHC_VERSION}.project ${CABAL_OPTS}"
55 | fi
56 | echo "CABAL_OPTS=${CABAL_OPTS}" | tee -a "${GITHUB_ENV}"
57 | CACHE_RESTORE_KEY="${RUNNER_OS}-$(date +%Y%m)-ghc-${GHC_VERSION}-cabal-${CABAL_VERSION}-"
58 | echo "CACHE_RESTORE_KEY=${CACHE_RESTORE_KEY}" | tee -a "${GITHUB_ENV}"
59 | - name: Configure Build
60 | run: |
61 | cabal v2-configure $CABAL_OPTS --disable-documentation
62 | cabal v2-build --dry-run $CABAL_OPTS
63 | - name: Restore Cached Dependencies
64 | uses: actions/cache/restore@v4
65 | id: cache
66 | with:
67 | path: ${{ steps.setup.outputs.cabal-store }}
68 | key: ${{ env.CACHE_RESTORE_KEY }}plan-${{ hashFiles('**/plan.json') }}
69 | restore-keys: ${{ env.CACHE_RESTORE_KEY }}
70 | - name: Install Dependencies
71 | run: cabal v2-build all $CABAL_OPTS --only-dependencies
72 | - name: Save Cached Dependencies
73 | uses: actions/cache/save@v4
74 | if: ${{ !steps.cache.outputs.cache-hit
75 | || steps.cache.outputs.cache-primary-key != steps.cache.outputs.cache-matched-key }}
76 | with:
77 | path: ${{ steps.setup.outputs.cabal-store }}
78 | key: ${{ steps.cache.outputs.cache-primary-key }}
79 | - name: Build
80 | run: cabal v2-build all $CABAL_OPTS
81 | - name: Test
82 | run: cabal v2-test all $CABAL_OPTS
83 | - name: Haddock
84 | run: cabal v2-haddock all $CABAL_OPTS
85 | - name: Examples
86 | run: cabal v2-build exe:literatex -f examples $CABAL_OPTS
87 |
88 | stack:
89 | name: "Stack: GHC ${{ matrix.ghc }}"
90 | needs: config
91 | runs-on: ubuntu-latest
92 | strategy:
93 | fail-fast: false
94 | matrix:
95 | ghc: ${{fromJSON(needs.config.outputs.ghcvers)}}
96 | steps:
97 | - name: Checkout
98 | uses: actions/checkout@v4
99 | - name: Setup Haskell
100 | uses: haskell-actions/setup@v2
101 | id: setup
102 | with:
103 | ghc-version: ${{ matrix.ghc }}
104 | cabal-version: latest
105 | cabal-update: true
106 | enable-stack: true
107 | stack-version: latest
108 | - name: Setup Environment
109 | run: |
110 | GHC_VERSION=$(ghc --numeric-version)
111 | echo "GHC_VERSION=${GHC_VERSION}" | tee -a "${GITHUB_ENV}"
112 | CABAL_VERSION=$(cabal --numeric-version)
113 | echo "CABAL_VERSION=${CABAL_VERSION}" | tee -a "${GITHUB_ENV}"
114 | STACK_YAML="stack-${GHC_VERSION}.yaml"
115 | echo "STACK_YAML=${STACK_YAML}" | tee -a "${GITHUB_ENV}"
116 | CACHE_RESTORE_KEY="${RUNNER_OS}-$(date +%Y%m)-ghc-${GHC_VERSION}-stack-"
117 | echo "CACHE_RESTORE_KEY=${CACHE_RESTORE_KEY}" | tee -a "${GITHUB_ENV}"
118 | - name: Cache
119 | uses: actions/cache@v4
120 | with:
121 | path: |
122 | ~/.stack
123 | .stack-work
124 | key: ${{ env.CACHE_RESTORE_KEY }}${{ hashFiles('ttc.cabal', env.STACK_YAML) }}
125 | restore-keys: ${{ env.CACHE_RESTORE_KEY }}
126 | - name: Build
127 | run: stack build --system-ghc --test --bench --no-run-tests --no-run-benchmarks
128 | - name: Test
129 | run: stack test --system-ghc
130 | - name: Haddock
131 | run: stack haddock --system-ghc
132 | ##############################################################################
133 | # NOTE Examples are currently disabled for Stack.
134 | #
135 | # The optparse-applicative_ge_0_18 flag needs to be set manually when using
136 | # Stack, and this is done in the stack.yaml configuration files. Examples are
137 | # built using a flag as well, though, and specifying a flag on the command
138 | # line makes stack ignore the flag setting within the configuration file,
139 | # resulting in build failures.
140 | #
141 | # - name: Examples
142 | # run: stack build --system-ghc --flag literatex:examples
143 |
144 | bounds-lower:
145 | name: "Lower Bounds (GHC ${{ matrix.ghc }})"
146 | needs: config
147 | runs-on: ubuntu-latest
148 | strategy:
149 | fail-fast: false
150 | matrix:
151 | ghc: ${{fromJSON(needs.config.outputs.ghcvers_lower)}}
152 | steps:
153 | - name: Checkout
154 | uses: actions/checkout@v4
155 | - name: Setup Haskell
156 | uses: haskell-actions/setup@v2
157 | id: setup
158 | with:
159 | ghc-version: ${{ matrix.ghc }}
160 | cabal-version: latest
161 | cabal-update: true
162 | - name: Setup Environment
163 | run: |
164 | GHC_VERSION=$(ghc --numeric-version)
165 | echo "GHC_VERSION=${GHC_VERSION}" | tee -a "${GITHUB_ENV}"
166 | CABAL_VERSION=$(cabal --numeric-version)
167 | echo "CABAL_VERSION=${CABAL_VERSION}" | tee -a "${GITHUB_ENV}"
168 | CABAL_OPTS="--enable-tests --enable-benchmarks --project-file=cabal-bounds-lower.project"
169 | echo "CABAL_OPTS=${CABAL_OPTS}" | tee -a "${GITHUB_ENV}"
170 | CACHE_RESTORE_KEY="${RUNNER_OS}-$(date +%Y%m)-ghc-${GHC_VERSION}-cabal-${CABAL_VERSION}-"
171 | echo "CACHE_RESTORE_KEY=${CACHE_RESTORE_KEY}" | tee -a "${GITHUB_ENV}"
172 | - name: Configure Build
173 | run: |
174 | cabal v2-configure $CABAL_OPTS --disable-documentation
175 | cabal v2-build --dry-run $CABAL_OPTS
176 | - name: Restore Cached Dependencies
177 | uses: actions/cache/restore@v4
178 | id: cache
179 | with:
180 | path: ${{ steps.setup.outputs.cabal-store }}
181 | key: ${{ env.CACHE_RESTORE_KEY }}plan-${{ hashFiles('**/plan.json') }}
182 | restore-keys: ${{ env.CACHE_RESTORE_KEY }}
183 | - name: Install Dependencies
184 | run: cabal v2-build all $CABAL_OPTS --only-dependencies
185 | - name: Save Cached Dependencies
186 | uses: actions/cache/save@v4
187 | if: ${{ !steps.cache.outputs.cache-hit
188 | || steps.cache.outputs.cache-primary-key != steps.cache.outputs.cache-matched-key }}
189 | with:
190 | path: ${{ steps.setup.outputs.cabal-store }}
191 | key: ${{ steps.cache.outputs.cache-primary-key }}
192 | - name: Build
193 | run: cabal v2-build all $CABAL_OPTS
194 | - name: Test
195 | run: cabal v2-test all $CABAL_OPTS
196 | - name: Haddock
197 | run: cabal v2-haddock all $CABAL_OPTS
198 | - name: Examples
199 | run: cabal v2-build exe:literatex -f examples $CABAL_OPTS
200 |
201 | bounds-upper:
202 | name: "Upper Bounds (GHC ${{ matrix.ghc }})"
203 | needs: config
204 | runs-on: ubuntu-latest
205 | strategy:
206 | fail-fast: false
207 | matrix:
208 | ghc: ${{fromJSON(needs.config.outputs.ghcvers_upper)}}
209 | steps:
210 | - name: Checkout
211 | uses: actions/checkout@v4
212 | - name: Setup Haskell
213 | uses: haskell-actions/setup@v2
214 | id: setup
215 | with:
216 | ghc-version: ${{ matrix.ghc }}
217 | cabal-version: latest
218 | cabal-update: true
219 | - name: Setup Environment
220 | run: |
221 | GHC_VERSION=$(ghc --numeric-version)
222 | echo "GHC_VERSION=${GHC_VERSION}" | tee -a "${GITHUB_ENV}"
223 | CABAL_VERSION=$(cabal --numeric-version)
224 | echo "CABAL_VERSION=${CABAL_VERSION}" | tee -a "${GITHUB_ENV}"
225 | CABAL_OPTS="--enable-tests --enable-benchmarks --project-file=cabal-bounds-upper.project"
226 | echo "CABAL_OPTS=${CABAL_OPTS}" | tee -a "${GITHUB_ENV}"
227 | CACHE_RESTORE_KEY="${RUNNER_OS}-$(date +%Y%m)-ghc-${GHC_VERSION}-cabal-${CABAL_VERSION}-"
228 | echo "CACHE_RESTORE_KEY=${CACHE_RESTORE_KEY}" | tee -a "${GITHUB_ENV}"
229 | - name: Configure Build
230 | run: |
231 | cabal v2-configure $CABAL_OPTS --disable-documentation
232 | cabal v2-build --dry-run $CABAL_OPTS
233 | - name: Restore Cached Dependencies
234 | uses: actions/cache/restore@v4
235 | id: cache
236 | with:
237 | path: ${{ steps.setup.outputs.cabal-store }}
238 | key: ${{ env.CACHE_RESTORE_KEY }}plan-${{ hashFiles('**/plan.json') }}
239 | restore-keys: ${{ env.CACHE_RESTORE_KEY }}
240 | - name: Install Dependencies
241 | run: cabal v2-build all $CABAL_OPTS --only-dependencies
242 | - name: Save Cached Dependencies
243 | uses: actions/cache/save@v4
244 | if: ${{ !steps.cache.outputs.cache-hit
245 | || steps.cache.outputs.cache-primary-key != steps.cache.outputs.cache-matched-key }}
246 | with:
247 | path: ${{ steps.setup.outputs.cabal-store }}
248 | key: ${{ steps.cache.outputs.cache-primary-key }}
249 | - name: Build
250 | run: cabal v2-build all $CABAL_OPTS
251 | - name: Test
252 | run: cabal v2-test all $CABAL_OPTS
253 | - name: Haddock
254 | run: cabal v2-haddock all $CABAL_OPTS
255 | - name: Examples
256 | run: cabal v2-build exe:literatex -f examples $CABAL_OPTS
257 |
258 | cabal-version:
259 | name: "Cabal ${{ matrix.cabal }} (GHC ${{ matrix.ghc }})"
260 | runs-on: ubuntu-latest
261 | strategy:
262 | fail-fast: false
263 | matrix:
264 | cabal: ['3.0.0.0']
265 | ghc: ['8.8.4']
266 | steps:
267 | - name: Checkout
268 | uses: actions/checkout@v4
269 | - name: Setup Haskell
270 | uses: haskell-actions/setup@v2
271 | id: setup
272 | with:
273 | ghc-version: ${{ matrix.ghc }}
274 | cabal-version: ${{ matrix.cabal }}
275 | cabal-update: true
276 | - name: Setup Environment
277 | run: |
278 | GHC_VERSION=$(ghc --numeric-version)
279 | echo "GHC_VERSION=${GHC_VERSION}" | tee -a "${GITHUB_ENV}"
280 | CABAL_VERSION=$(cabal --numeric-version)
281 | echo "CABAL_VERSION=${CABAL_VERSION}" | tee -a "${GITHUB_ENV}"
282 | CABAL_OPTS="--enable-tests --enable-benchmarks"
283 | if [ -f "cabal-${GHC_VERSION}.project" ] ; then
284 | CABAL_OPTS="--project-file=cabal-${GHC_VERSION}.project ${CABAL_OPTS}"
285 | fi
286 | echo "CABAL_OPTS=${CABAL_OPTS}" | tee -a "${GITHUB_ENV}"
287 | CACHE_RESTORE_KEY="${RUNNER_OS}-$(date +%Y%m)-ghc-${GHC_VERSION}-cabal-${CABAL_VERSION}-"
288 | echo "CACHE_RESTORE_KEY=${CACHE_RESTORE_KEY}" | tee -a "${GITHUB_ENV}"
289 | - name: Configure Build
290 | run: |
291 | cabal v2-configure $CABAL_OPTS --disable-documentation
292 | cabal v2-build --dry-run $CABAL_OPTS
293 | - name: Restore Cached Dependencies
294 | uses: actions/cache/restore@v4
295 | id: cache
296 | with:
297 | path: ${{ steps.setup.outputs.cabal-store }}
298 | key: ${{ env.CACHE_RESTORE_KEY }}plan-${{ hashFiles('**/plan.json') }}
299 | restore-keys: ${{ env.CACHE_RESTORE_KEY }}
300 | - name: Install Dependencies
301 | run: cabal v2-build all $CABAL_OPTS --only-dependencies
302 | - name: Save Cached Dependencies
303 | uses: actions/cache/save@v4
304 | if: ${{ !steps.cache.outputs.cache-hit
305 | || steps.cache.outputs.cache-primary-key != steps.cache.outputs.cache-matched-key }}
306 | with:
307 | path: ${{ steps.setup.outputs.cabal-store }}
308 | key: ${{ steps.cache.outputs.cache-primary-key }}
309 | - name: Build
310 | run: cabal v2-build all $CABAL_OPTS
311 | - name: Test
312 | run: cabal v2-test all $CABAL_OPTS
313 | - name: Haddock
314 | run: cabal v2-haddock all $CABAL_OPTS
315 | - name: Examples
316 | run: cabal v2-build exe:literatex -f examples $CABAL_OPTS
317 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # project
2 | /build/
3 |
4 | # cabal
5 | cabal.project.local
6 | cabal.project.local~
7 | /dist-newstyle/
8 |
9 | # nix
10 | result*
11 |
12 | # stack
13 | .stack-work
14 | *.yaml.lock
15 | stack-nix*
16 |
17 | # stan
18 | /.hie/
19 |
20 | # vi
21 | .*.swp
22 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # `literatex-haskell` Changelog
2 |
3 | This project follows the [Haskell package versioning policy][PVP], with
4 | versions in `A.B.C.D` format. `A` may be incremented arbitrarily for
5 | non-technical reasons, but [semantic versioning][SemVer] is otherwise
6 | followed, where `A.B` is the major version, `C` is the minor version, and `D`
7 | is the patch version. Initial development uses versions `0.0.C.D`, for which
8 | every version is considered breaking.
9 |
10 | [PVP]:
11 | [SemVer]:
12 |
13 | The format of this changelog is based on [Keep a Changelog][KaC], with the
14 | following conventions:
15 |
16 | * Level-two heading `Unreleased` is used to track changes that have not been
17 | released.
18 | * Other level-two headings specify the release in `A.B.C.D (YYYY-MM-DD)`
19 | format, with newer versions above older versions.
20 | * Level-three headings are used to categorize changes as follows:
21 | 1. Breaking
22 | 2. Non-Breaking
23 | * Changes are listed in arbitrary order and present tense.
24 |
25 | [KaC]:
26 |
27 | ## 0.4.0.0 (2025-01-03)
28 |
29 | ### Breaking
30 |
31 | * Remove support for GHC 8.6, constraining lower bounds
32 | * Remove support for GHC 8.4, constraining lower bounds
33 | * Remove support for GHC 8.2, constraining lower bounds
34 | * Change minimal Cabal from 1.24 to 3.0
35 |
36 | ### Non-Breaking
37 |
38 | * Bump `base` dependency version upper bound
39 | * Bump `bytestring` dependency version upper bound
40 | * Bump `filepath` dependency version upper bound
41 | * Bump `tasty` dependency version upper bound
42 | * Bump `text` dependency version upper bound
43 | * Bump `ttc` dependency version upper bound
44 |
45 | ## 0.3.0.0 (2023-05-28)
46 |
47 | ## Breaking
48 |
49 | * Add support for `optparse-applicative` `0.18`
50 |
51 | ### Non-Breaking
52 |
53 | * Bump `ansi-wl-pprint` dependency version upper bound
54 |
55 | ## 0.2.1.0 (2023-03-21)
56 |
57 | ### Breaking
58 |
59 | * Add `MdBook` target format
60 |
61 | ### Non-Breaking
62 |
63 | * Bump TTC dependency version upper bound
64 | * Adjust dependency constraints to match tested versions
65 |
66 | ## 0.2.0.2 (2022-02-05)
67 |
68 | ### Non-Breaking
69 |
70 | * Bump `optparse-applicative` dependency version upper bound
71 |
72 | ## 0.2.0.1 (2021-12-25)
73 |
74 | ### Non-Breaking
75 |
76 | * Bump `text` dependency version upper bound
77 |
78 | ## 0.2.0.0 (2021-06-25)
79 |
80 | ### Breaking
81 |
82 | * Fix `--help` when using `optparse-applicative` `0.16`
83 |
84 | ### Non-Breaking
85 |
86 | * Refactor Nix configuration
87 | * Use TTC 1.1.0.1
88 |
89 | ## 0.1.0.2 (2021-06-10)
90 |
91 | ### Non-Breaking
92 |
93 | * Bump TTC dependency version upper bound
94 |
95 | ## 0.1.0.1 (2021-06-03)
96 |
97 | ### Non-Breaking
98 |
99 | * Use `docker-pkg` scripts to build packages
100 | * Bump TTC dependency version upper bound
101 |
102 | ## 0.1.0.0 (2021-05-26)
103 |
104 | ### Breaking
105 |
106 | * Initial public release
107 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2021-2025 Travis Cardwell
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | ##############################################################################
2 | # Project configuration
3 |
4 | PACKAGE := literatex
5 | CABAL_FILE := $(PACKAGE).cabal
6 | PROJECT := $(PACKAGE)-haskell
7 | EXECUTABLES := literatex
8 |
9 | MODE ?= stack
10 |
11 | DESTDIR ?=
12 | PREFIX ?= /usr/local
13 |
14 | DEB_CONTAINER ?= extremais/pkg-debian-stack:bookworm
15 | RPM_CONTAINER ?= extremais/pkg-fedora-stack:41
16 | MAINTAINER_NAME ?= Travis Cardwell
17 | MAINTAINER_EMAIL ?= travis.cardwell@extrema.is
18 |
19 | TEST_DEB_CONTAINER ?= debian:bookworm
20 | TEST_DEB_ARCH ?= amd64
21 | TEST_RPM_CONTAINER ?= fedora:41
22 | TEST_RPM_OS ?= fc41
23 | TEST_RPM_ARCH ?= x86_64
24 |
25 | ##############################################################################
26 | # Make configuration
27 |
28 | ifeq ($(origin .RECIPEPREFIX), undefined)
29 | $(error GNU Make 4.0 or later required)
30 | endif
31 | .RECIPEPREFIX := >
32 |
33 | SHELL := bash
34 | .SHELLFLAGS := -o nounset -o errexit -o pipefail -c
35 |
36 | MAKEFLAGS += --no-builtin-rules
37 | MAKEFLAGS += --warn-undefined-variables
38 |
39 | .DEFAULT_GOAL := build
40 |
41 | ifeq ($(MODE), cabal)
42 | GHC_VERSION ?= $(shell ghc --version | sed 's/.* //')
43 | CABAL_ARGS := --with-ghc ghc-$(GHC_VERSION)
44 | ifneq ($(origin PROJECT_FILE), undefined)
45 | CABAL_ARGS += "--project-file=$(PROJECT_FILE)"
46 | else
47 | PROJECT_FILE_AUTO := cabal-$(GHC_VERSION).project
48 | ifneq (,$(wildcard $(PROJECT_FILE_AUTO)))
49 | CABAL_ARGS += "--project-file=$(PROJECT_FILE_AUTO)"
50 | endif
51 | endif
52 | else ifeq ($(MODE), stack)
53 | STACK_ARGS :=
54 | ifneq ($(origin CONFIG), undefined)
55 | STACK_ARGS += --stack-yaml "$(CONFIG)"
56 | endif
57 | ifneq ($(origin RESOLVER), undefined)
58 | STACK_ARGS += --resolver "$(RESOLVER)"
59 | endif
60 | else
61 | $(error unknown MODE: $(MODE))
62 | endif
63 |
64 | BINDIR := $(DESTDIR)$(PREFIX)/bin
65 | DATAROOTDIR := $(DESTDIR)$(PREFIX)/share
66 | DOCDIR := $(DATAROOTDIR)/doc/$(PROJECT)
67 | MAN1DIR := $(DATAROOTDIR)/man/man1
68 |
69 | ##############################################################################
70 | # Functions
71 |
72 | define all_files
73 | find . -not -path '*/\.*' -type f
74 | endef
75 |
76 | define checksum_files
77 | find . -maxdepth 1 -type f -not -path './*SUMS' | sed 's,^\./,,' | sort
78 | endef
79 |
80 | define die
81 | (echo "error: $(1)" ; false)
82 | endef
83 |
84 | define get_version
85 | $(shell grep '^version:' $(if $(origin 1) == undefined,$(CABAL_FILE),$(1)) \
86 | | sed 's/^version: *//')
87 | endef
88 |
89 | define hs_files
90 | find . -not -path '*/\.*' -type f -name '*.hs'
91 | endef
92 |
93 | define newline
94 |
95 |
96 | endef
97 |
98 | ##############################################################################
99 | # Rules
100 |
101 | build: hr
102 | build: # build package *
103 | ifeq ($(MODE), cabal)
104 | > cabal v2-build $(CABAL_ARGS) --enable-tests --enable-benchmarks
105 | else
106 | > stack build $(STACK_ARGS) --test --bench --no-run-tests --no-run-benchmarks
107 | endif
108 | .PHONY: build
109 |
110 | checksums: # calculate checksums of build artifacts
111 | > @cd build && $(call checksum_files) | xargs md5sum > MD5SUMS
112 | > @cd build && $(call checksum_files) | xargs sha1sum > SHA1SUMS
113 | > @cd build && $(call checksum_files) | xargs sha256sum > SHA256SUMS
114 | > @cd build && $(call checksum_files) | xargs sha512sum > SHA512SUMS
115 | .PHONY: checksums
116 |
117 | clean: # clean package
118 | ifeq ($(MODE), cabal)
119 | > @rm -rf dist-newstyle
120 | else
121 | > @stack clean
122 | endif
123 | .PHONY: clean
124 |
125 | clean-all: clean
126 | clean-all: # clean package and remove artifacts
127 | > @rm -rf .stack-work
128 | > @rm -rf build
129 | > @rm -rf dist-newstyle
130 | > @rm -f *.yaml.lock
131 | > @rm -f cabal.project.local
132 | > @rm -f result*
133 | .PHONY: clean-all
134 |
135 | coverage: hr
136 | coverage: # run tests with code coverage *
137 | ifeq ($(MODE), cabal)
138 | > cabal v2-test $(CABAL_ARGS) \
139 | > --enable-coverage --enable-tests --test-show-details=always
140 | else
141 | > stack test $(STACK_ARGS) --coverage
142 | > stack hpc report .
143 | endif
144 | .PHONY: coverage
145 |
146 | deb: # build .deb package for VERSION in a Debian container
147 | > $(eval VERSION := $(call get_version))
148 | > $(eval SRC := $(PROJECT)-$(VERSION).tar.xz)
149 | > @test -f build/$(SRC) || $(call die,"build/$(SRC) not found")
150 | > @docker run --rm -it \
151 | > -e DEBFULLNAME="$(MAINTAINER_NAME)" \
152 | > -e DEBEMAIL="$(MAINTAINER_EMAIL)" \
153 | > -v $(PWD)/build:/host \
154 | > $(DEB_CONTAINER) \
155 | > /home/docker/bin/make-deb.sh "$(SRC)"
156 | .PHONY: deb
157 |
158 | deb-test: # run a Debian container to test .deb package for VERSION
159 | > $(eval VERSION := $(call get_version))
160 | > $(eval PKG := "$(PROJECT)_$(VERSION)-1_$(TEST_DEB_ARCH).deb")
161 | > @test -f build/$(PKG) || $(call die,"build/$(PKG) not found")
162 | > @docker run --rm -it \
163 | > -v $(PWD)/build/$(PKG):/tmp/$(PKG):ro \
164 | > $(TEST_DEB_CONTAINER) \
165 | > /bin/bash
166 | .PHONY: deb-test
167 |
168 | doc-api: hr
169 | doc-api: # build API documentation *
170 | ifeq ($(MODE), cabal)
171 | > cabal v2-haddock $(CABAL_ARGS)
172 | else
173 | > stack haddock $(STACK_ARGS)
174 | endif
175 | .PHONY: doc-api
176 |
177 | examples: hr
178 | examples: # build examples *
179 | ifeq ($(MODE), cabal)
180 | > cabal v2-build $(CABAL_ARGS) exe:literatex -f examples
181 | else
182 | > stack build $(STACK_ARGS) --flag literatex:examples
183 | endif
184 | .PHONY: examples
185 |
186 | grep: # grep all non-hidden files for expression E
187 | > $(eval E:= "")
188 | > @test -n "$(E)" || $(call die,"usage: make grep E=expression")
189 | > @$(call all_files) | xargs grep -Hn '$(E)' || true
190 | .PHONY: grep
191 |
192 | help: # show this help
193 | > @if command -v column >/dev/null 2>&1 \
194 | > ; then \
195 | > grep '^[a-zA-Z0-9_-]\+:[^#]*# ' $(MAKEFILE_LIST) \
196 | > | sed 's/^\([^:]\+\):[^#]*# \(.*\)/make \1\t\2/' \
197 | > | column -t -s $$'\t' \
198 | > ; else \
199 | > grep '^[a-zA-Z0-9_-]\+:[^#]*# ' $(MAKEFILE_LIST) \
200 | > | sed 's/^\([^:]\+\):[^#]*# \(.*\)/make \1\t\2/' \
201 | > ; fi
202 | > @echo
203 | > @echo "Cabal mode (MODE=cabal)"
204 | > @echo " * Set GHC_VERSION to specify a GHC version."
205 | > @echo " * Set PROJECT_FILE to specify a cabal.project file."
206 | > @echo
207 | > @echo "Stack mode (MODE=stack)"
208 | > @echo " * Set CONFIG to specify a stack.yaml file."
209 | > @echo " * Set RESOLVER to specify a Stack resolver."
210 | .PHONY: help
211 |
212 | hlint: # run hlint on all Haskell source
213 | > @$(call hs_files) | xargs hlint
214 | .PHONY: hlint
215 |
216 | hr: #internal# display a horizontal rule
217 | > @command -v hr >/dev/null 2>&1 && hr -t || true
218 | .PHONY: hr
219 |
220 | hsgrep: # grep all Haskell source for expression E
221 | > $(eval E := "")
222 | > @test -n "$(E)" || $(call die,"usage: make hsgrep E=expression")
223 | > @$(call hs_files) | xargs grep -Hn '$(E)' || true
224 | .PHONY: hsgrep
225 |
226 | hsrecent: # show N most recently modified Haskell files
227 | > $(eval N := "10")
228 | > @find . -not -path '*/\.*' -type f -name '*.hs' -printf '%T+ %p\n' \
229 | > | sort --reverse \
230 | > | head -n $(N)
231 | .PHONY: hsrecent
232 |
233 | hssloc: # count lines of Haskell source
234 | > @$(call hs_files) | xargs wc -l | tail -n 1 | sed 's/^ *\([0-9]*\).*$$/\1/'
235 | .PHONY: hssloc
236 |
237 | ignored: # list files ignored by git
238 | > @git ls-files . --ignored --exclude-standard --others
239 | .PHONY: ignored
240 |
241 | install: install-bin
242 | install: install-man
243 | install: install-doc
244 | install: # install everything (*)
245 | .PHONY: install
246 |
247 | install-bin: build
248 | install-bin: # install executable(s) (*)
249 | > @mkdir -p "$(BINDIR)"
250 | ifeq ($(MODE), cabal)
251 | > $(foreach EXE,$(EXECUTABLES), \
252 | @install -m 0755 \
253 | "$(shell cabal list-bin $(CABAL_ARGS) $(EXE))" \
254 | "$(BINDIR)/$(EXE)" $(newline) \
255 | )
256 | else
257 | > $(eval LIROOT := $(shell stack path --local-install-root))
258 | > $(foreach EXE,$(EXECUTABLES), \
259 | @install -m 0755 "$(LIROOT)/bin/$(EXE)" "$(BINDIR)/$(EXE)" $(newline) \
260 | )
261 | endif
262 | .PHONY: install-bin
263 |
264 | install-doc: # install documentation
265 | > @mkdir -p "$(DOCDIR)"
266 | > @install -m 0644 README.md "$(DOCDIR)"
267 | > @gzip "$(DOCDIR)/README.md"
268 | > @install -m 0644 -T CHANGELOG.md "$(DOCDIR)/changelog"
269 | > @gzip "$(DOCDIR)/changelog"
270 | > @install -m 0644 LICENSE "$(DOCDIR)"
271 | > @gzip "$(DOCDIR)/LICENSE"
272 | .PHONY: install-doc
273 |
274 | install-man: # install man page(s)
275 | > @mkdir -p "$(MAN1DIR)"
276 | > $(foreach EXE,$(EXECUTABLES), \
277 | @install -m 0644 "doc/$(EXE).1" "$(MAN1DIR)" $(newline) \
278 | @gzip "$(MAN1DIR)/$(EXE).1" $(newline) \
279 | )
280 | .PHONY: install-man
281 |
282 | man: # build man page
283 | > $(eval VERSION := $(call get_version))
284 | > $(eval DATE := $(shell date --rfc-3339=date))
285 | > $(foreach EXE,$(EXECUTABLES), \
286 | @pandoc -s -t man -o doc/$(EXE).1 \
287 | --variable header="$(EXE) Manual" \
288 | --variable footer="$(PROJECT) $(VERSION) ($(DATE))" \
289 | doc/$(EXE).1.md $(newline) \
290 | )
291 | .PHONY: man
292 |
293 | recent: # show N most recently modified files
294 | > $(eval N := "10")
295 | > @find . -not -path '*/\.*' -type f -printf '%T+ %p\n' \
296 | > | sort --reverse \
297 | > | head -n $(N)
298 | .PHONY: recent
299 |
300 | repl: # enter a REPL *
301 | ifeq ($(MODE), cabal)
302 | > cabal repl $(CABAL_ARGS)
303 | else
304 | > stack exec $(STACK_ARGS) ghci
305 | endif
306 | .PHONY: repl
307 |
308 | rpm: # build .rpm package for VERSION in a Fedora container
309 | > $(eval VERSION := $(call get_version))
310 | > $(eval SRC := $(PROJECT)-$(VERSION).tar.xz)
311 | > @test -f build/$(SRC) || $(call die,"build/$(SRC) not found")
312 | > @docker run --rm -it \
313 | > -e RPMFULLNAME="$(MAINTAINER_NAME)" \
314 | > -e RPMEMAIL="$(MAINTAINER_EMAIL)" \
315 | > -v $(PWD)/build:/host \
316 | > $(RPM_CONTAINER) \
317 | > /home/docker/bin/make-rpm.sh "$(SRC)"
318 | .PHONY: rpm
319 |
320 | rpm-test: # run a Fedora container to test .rpm package for VERSION
321 | > $(eval VERSION := $(call get_version))
322 | > $(eval PKG := "$(PROJECT)-$(VERSION)-1.$(TEST_RPM_OS).$(TEST_RPM_ARCH).rpm")
323 | > @test -f build/$(PKG) || $(call die,"build/$(PKG) not found")
324 | > @docker run --rm -it \
325 | > -v $(PWD)/build/$(PKG):/tmp/$(PKG):ro \
326 | > $(TEST_RPM_CONTAINER) \
327 | > /bin/bash
328 | .PHONY: rpm-test
329 |
330 | sdist: # create source tarball for Hackage
331 | > $(eval BRANCH := $(shell git rev-parse --abbrev-ref HEAD))
332 | > @test "${BRANCH}" = "main" || $(call die,"not in main branch")
333 | ifeq ($(MODE), cabal)
334 | > @cabal sdist
335 | else
336 | > @stack sdist
337 | endif
338 | .PHONY: sdist
339 |
340 | source-git: # create source tarball of git TREE
341 | > $(eval TREE := "HEAD")
342 | > $(eval BRANCH := $(shell git rev-parse --abbrev-ref $(TREE)))
343 | > @test "$(BRANCH)" = "main" || echo "WARNING: Not in main branch!" >&2
344 | > $(eval DIRTY := $(shell git diff --shortstat | wc -l))
345 | > @test "$(DIRTY)" = "0" \
346 | > || echo "WARNING: Not including non-committed changes!" >&2
347 | > $(eval UNTRACKED := $(shell \
348 | git ls-files --other --directory --no-empty-directory --exclude-standard \
349 | | wc -l))
350 | > @test "$(UNTRACKED)" = "0" \
351 | > || echo "WARNING: Not including untracked files!" >&2
352 | > $(eval VERSION := $(call get_version))
353 | > @mkdir -p build
354 | > @git archive --format=tar --prefix=$(PROJECT)-$(VERSION)/ $(TREE) \
355 | > | xz \
356 | > > build/$(PROJECT)-$(VERSION).tar.xz
357 | .PHONY: source-git
358 |
359 | source-tar: # create source tarball using tar
360 | > $(eval DIRTY := $(shell git diff --shortstat | wc -l))
361 | > @test "$(DIRTY)" = "0" \
362 | > || echo "WARNING: Including non-committed changes!" >&2
363 | > $(eval UNTRACKED := $(shell \
364 | git ls-files --other --directory --no-empty-directory --exclude-standard \
365 | | wc -l))
366 | > @test "$(UNTRACKED)" = "0" \
367 | > || echo "WARNING: Including untracked files!" >&2
368 | > $(eval VERSION := $(call get_version))
369 | > @mkdir -p build
370 | > @sed -e 's,^/,./,' -e 's,/$$,,' .gitignore > build/.gitignore
371 | > @tar \
372 | > --exclude-vcs \
373 | > --exclude-ignore-recursive=build/.gitignore \
374 | > --transform "s,^\.,$(PROJECT)-$(VERSION)," \
375 | > -Jcf build/$(PROJECT)-$(VERSION).tar.xz \
376 | > .
377 | > @rm -f build/.gitignore
378 | .PHONY: source-tar
379 |
380 | test: hr
381 | test: # run tests, optionally for pattern P *
382 | > $(eval P := "")
383 | ifeq ($(MODE), cabal)
384 | > @test -z "$(P)" \
385 | > && cabal v2-test $(CABAL_ARGS) --enable-tests --test-show-details=always \
386 | > || cabal v2-test $(CABAL_ARGS) --enable-tests --test-show-details=always \
387 | > --test-option '--pattern=$(P)'
388 | else
389 | > @test -z "$(P)" \
390 | > && stack test $(STACK_ARGS) \
391 | > || stack test $(STACK_ARGS) --test-arguments '--pattern $(P)'
392 | endif
393 | .PHONY: test
394 |
395 | test-all: # run all configured tests and build examples using MODE
396 | > @./test-all.sh "$(MODE)"
397 | .PHONY: test-all
398 |
399 | test-bounds-lower: # test lower bounds (Cabal only)
400 | ifeq ($(MODE), stack)
401 | > $(error test-bounds-lower not supported in Stack mode)
402 | endif
403 | > @make test-build CABAL_ARGS="--project-file=cabal-bounds-lower.project"
404 | .PHONY: test-bounds-lower
405 |
406 | test-bounds-upper: # test upper bounds (Cabal only)
407 | ifeq ($(MODE), stack)
408 | > $(error test-bounds-upper not supported in Stack mode)
409 | endif
410 | > @make test-build CABAL_ARGS="--project-file=cabal-bounds-upper.project"
411 | .PHONY: test-bounds-upper
412 |
413 | test-build: hr
414 | test-build: build
415 | test-build: test
416 | test-build: doc-api
417 | test-build: examples
418 | test-build: # build, run tests, build API documentation, build examples *
419 | .PHONY: test-build
420 |
421 | test-nightly: # run tests for the latest Stackage nightly release (Stack only)
422 | ifeq ($(MODE), cabal)
423 | > $(error test-nightly not supported in Cabal mode)
424 | endif
425 | > @make test RESOLVER=nightly
426 | .PHONY: test-nightly
427 |
428 | todo: # search for TODO items
429 | > @find . -type f \
430 | > -not -path '*/\.*' \
431 | > -not -path './build/*' \
432 | > -not -path './project/*' \
433 | > -not -path ./Makefile \
434 | > | xargs grep -Hn TODO \
435 | > | grep -v '^Binary file ' \
436 | > || true
437 | .PHONY: todo
438 |
439 | version: # show current version
440 | > @echo "$(PROJECT) $(call get_version)"
441 | .PHONY: version
442 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LiterateX
2 |
3 | [](https://www.repostatus.org/#active)
4 | [](https://github.com/ExtremaIS/literatex-haskell/actions)
5 | [](https://hackage.haskell.org/package/literatex)
6 | [](https://stackage.org/package/literatex)
7 | [](https://stackage.org/nightly/package/literatex)
8 |
9 | 
10 |
11 | * [Overview](#overview)
12 | * [Source Formats](#source-formats)
13 | * [Double-Dash Comments](#double-dash-comments)
14 | * [Double-Slash Comments](#double-slash-comments)
15 | * [Hash Comments](#hash-comments)
16 | * [Lisp Semicolon Comments](#lisp-semicolon-comments)
17 | * [Literate Haskell](#literate-haskell)
18 | * [Percent Comments](#percent-comments)
19 | * [CLI](#cli)
20 | * [Requirements](#requirements)
21 | * [Installation](#installation)
22 | * [`.deb` Package Installation](#deb-package-installation)
23 | * [`.rpm` Package Installation](#rpm-package-installation)
24 | * [Installation From Hackage](#installation-from-hackage)
25 | * [Installation From Stackage](#installation-from-stackage)
26 | * [Usage](#usage)
27 | * [Library](#library)
28 | * [Related Work](#related-work)
29 | * [Project](#project)
30 | * [Links](#links)
31 | * [Tags](#tags)
32 | * [Contribution](#contribution)
33 | * [License](#license)
34 |
35 | ## Overview
36 |
37 | LiterateX transforms literate source code to Markdown. Write documentation in
38 | Markdown format in the comments of source code, and LiterateX can transform
39 | the file to Markdown, optionally including the source code with syntax
40 | highlighting and line numbers. Many [source formats](#source-formats) are
41 | supported, and the following target formats are supported:
42 |
43 | * [Pandoc Markdown][]
44 | * [GitHub Flavored Markdown][]
45 | * [mdBook Markdown][], which does not support per-block line numbering
46 |
47 | LiterateX can be used to document code that is particularly important or
48 | difficult to understand. For example, documentation can be written in the SQL
49 | file that defines the schema for a complex database. LiterateX can translate
50 | the `.sql` file to a Markdown file, which can then be converted to HTML, PDF,
51 | or even EPUB with [Pandoc][].
52 |
53 | LiterateX can also be used in the publishing of blog entries, magazine
54 | articles, or books. Use the [command-line utility](#cli) or integrate the
55 | Haskell [library](#library) into your own software.
56 |
57 | LiterateX has support for **source code rules**, comment lines that are used
58 | to visually separate sections of source code. Since source code rules are
59 | only used to make the source code easier to scan quickly, they are ignored
60 | (treated as a blank line) when translating to Markdown.
61 |
62 | LiterateX also has support for [shebang][] lines at the start of the file.
63 | They can be ignored so that they do not precede the documentation.
64 |
65 | [Pandoc Markdown]:
66 | [GitHub Flavored Markdown]:
67 | [mdBook Markdown]:
68 | [Pandoc]:
69 | [shebang]:
70 |
71 | ## Source Formats
72 |
73 | LiterateX supports a number of source formats. With the exception of
74 | [literate Haskell](#literate-haskell), documentation is written in line
75 | comments in the source language. Note that multi-line comments are treated as
76 | code, not documentation.
77 |
78 | ### Double-Dash Comments
79 |
80 | Documentation is parsed from lines that begin with two dashes immediately
81 | followed by a space (`-- `). Lines that only contain two dashes are treated
82 | as blank lines in the documentation. Lines that contain three or more dashes
83 | are treated as source code rules and are ignored.
84 |
85 | ``` haskell
86 | -- # Haskell Example
87 | --
88 | -- Executables are implemented using a `Main` module that exposes a function
89 | -- named `main`.
90 |
91 | module Main (main) where
92 |
93 | -- The `main` function is run when the program is executed.
94 |
95 | main :: IO ()
96 | main = putStrLn "Hello!"
97 |
98 | -- This simple example just prints "Hello!" to the screen.
99 | ```
100 |
101 | Languages that use double-dash comments include the following:
102 |
103 | * [Elm][]
104 | * [Haskell][]
105 | * [Idris][]
106 | * [Lua][]
107 | * [SQL][]
108 |
109 | [Elm]:
110 | [Haskell]:
111 | [Idris]:
112 | [Lua]:
113 | [SQL]:
114 |
115 | ### Double-Slash Comments
116 |
117 | Documentation is parsed from lines that begin with two slashes immediately
118 | followed by a space (`// `). Lines that only contain two slashes are treated
119 | as blank lines in the documentation. Lines that contain three or more slashes
120 | are treated as source code rules and are ignored.
121 |
122 | ``` rust
123 | // # Rust Example
124 | //
125 | // The `main` function is run when the program is executed.
126 |
127 | fn main() {
128 | println!("Hello!");
129 | }
130 |
131 | // This simple example just prints "Hello!" to the screen.
132 | ```
133 |
134 | Languages that use double-slash comments include the following:
135 |
136 | * [C][]
137 | * [CSS][]
138 | * [Go][]
139 | * [Java][]
140 | * [JavaScript][]
141 | * [Kotlin][]
142 | * [PHP][]
143 | * [Rust][]
144 | * [Scala][]
145 | * [TypeScript][]
146 |
147 | [C]:
148 | [CSS]:
149 | [Go]:
150 | [Java]:
151 | [JavaScript]:
152 | [Kotlin]:
153 | [PHP]:
154 | [Rust]:
155 | [Scala]:
156 | [TypeScript]:
157 |
158 | ### Hash Comments
159 |
160 | Documentation is parsed from lines that begin with a hash character
161 | immediately followed by a space (`# `). Lines that only contain a hash
162 | character are treated as blank lines in the documentation. Lines that contain
163 | two or more hash characters are treated as source code rules and are ignored.
164 |
165 | ``` python
166 | # # Python Example
167 | #
168 | # A quick-and-dirty Python script can include commands at the top level!
169 |
170 | print("Hello!")
171 |
172 | # This simple example just prints "Hello!" to the screen.
173 | ```
174 |
175 | Languages that use hash comments include the following:
176 |
177 | * [Bash][]
178 | * [Elixir][]
179 | * [Perl][]
180 | * [Python][]
181 | * [R][]
182 | * [Ruby][]
183 |
184 | [Bash]:
185 | [Elixir]:
186 | [Perl]:
187 | [Python]:
188 | [R]:
189 | [Ruby]:
190 |
191 | ### Lisp Semicolon Comments
192 |
193 | Lisp languages use a semicolon (`;`) for line comments, but there is a
194 | special convention to use a different number of semicolons according to the
195 | context. Documentation is parsed from lines that begin with one to four
196 | semicolons immediately followed by a space (`; `, `;; `, `;;; `, or `;;;; `).
197 | Lines that only contain one to four semicolons are treated as blank lines in
198 | the documentation. Lines that contain more than four semicolons are treated
199 | as source code rules and are ignored.
200 |
201 | ``` scheme
202 | ; # Racket Example
203 | ;
204 | ; Racket programs must declare the language to use.
205 |
206 | #lang racket/base
207 |
208 | ; Racket can also include commands at the top level!
209 |
210 | (println "Hello!")
211 |
212 | ; This simple example just prints "Hello!" to the screen.
213 | ```
214 |
215 | Languages that use Lisp semicolon comments include the following:
216 |
217 | * [Clojure][]
218 | * [Common Lisp][]
219 | * [Racket][]
220 | * [Scheme][]
221 |
222 | [Clojure]:
223 | [Common Lisp]:
224 | [Racket]:
225 | [Scheme]:
226 |
227 | ### Literate Haskell
228 |
229 | [GHC][] has special support for
230 | [literate programming](https://wiki.haskell.org/Literate_programming).
231 | Haskell source code is usually written with documentation in comments, in
232 | files with a `.hs` extension. Literate Haskell source code gives
233 | documentation the leading role and prefixes code with a greater-than sign and
234 | space (`> `), in files with a `.lhs` extension. The documentation can be in
235 | any format, and [Markdown][] is a popular choice.
236 |
237 | Unfortunately, there is a [bug][] that causes problems when using
238 | [ATX-style headings][] (`#` characters before the heading text). Any easy
239 | workaround is to use [setext-style headings][] (underlines) instead, but this
240 | limits the number of heading levels. See the
241 | [Literate Haskell Markdown Headings][] blog post for more information and an
242 | example workaround.
243 |
244 | This source format does not support source code rules.
245 |
246 | ``` lhaskell
247 | Literate Haskell Example
248 | ========================
249 |
250 | Executables are implemented using a `Main` module that exposes a function
251 | named `main`.
252 |
253 | > module Main (main) where
254 |
255 | The `main` function is run when the program is executed.
256 |
257 | > main :: IO ()
258 | > main = putStrLn "Hello!"
259 |
260 | This simple example just prints "Hello!" to the screen.
261 | ```
262 |
263 | [GHC]:
264 | [Markdown]:
265 | [bug]:
266 | [ATX-style headings]:
267 | [setext-style headings]:
268 | [Literate Haskell Markdown Headings]:
269 |
270 | ### Percent Comments
271 |
272 | Documentation is parsed from lines that begin with a percent character
273 | immediately followed by a space (`% `). Lines that only contain a percent
274 | character are treated as blank lines in the documentation. Lines that contain
275 | two or more percent characters are treated as source code rules and are
276 | ignored.
277 |
278 | ``` erlang
279 | % # Erlang Example
280 | %
281 | % Programs are implemented using a `main` module that exports a `start/0`
282 | % function.
283 |
284 | -module(main).
285 | -export([start/0]).
286 |
287 | % The `start` function is run when the program is executed.
288 |
289 | start() -> io.fwrite("Hello!\n").
290 |
291 | % This simple example just prints "Hello!" to the screen.
292 | ```
293 |
294 | Languages that use percent comments include the following:
295 |
296 | * [Erlang][]
297 | * [LaTeX][]
298 |
299 | [Erlang]:
300 | [LaTeX]:
301 |
302 | ## CLI
303 |
304 | LiterateX may be used via a command-line utility named `literatex`.
305 |
306 | ### Requirements
307 |
308 | `literatex` has only been tested on Linux. It *might* work on Windows and
309 | macOS.
310 |
311 | ### Installation
312 |
313 | #### `.deb` Package Installation
314 |
315 | Check the [Releases][] page for `.deb` packages.
316 |
317 | [Releases]:
318 |
319 | #### `.rpm` Package Installation
320 |
321 | Check the [Releases][] page for `.rpm` packages.
322 |
323 | #### Installation From Hackage
324 |
325 | Install `literatex` from [Hackage][] using [Cabal][] as follows:
326 |
327 | ```
328 | $ cabal v2-install literatex
329 | ```
330 |
331 | [Hackage]:
332 | [Cabal]:
333 |
334 | #### Installation From Stackage
335 |
336 | Install `literatex` from [Stackage][] using [Stack][] as follows:
337 |
338 | ```
339 | $ stack install literatex
340 | ```
341 |
342 | [Stackage]:
343 | [Stack]:
344 |
345 | ### Usage
346 |
347 | See the [`literatex` man page][] for usage information.
348 |
349 | [`literatex` man page]:
350 |
351 | ## Library
352 |
353 | The [LiterateX Haskell library][] provides an API for integrating LiterateX
354 | functionality in your own software.
355 |
356 | [LiterateX Haskell library]:
357 |
358 | ## Related Work
359 |
360 | [Literate programming][] is a style of programming introduced by
361 | [Donald Knuth][] in which [the main idea][] is "to regard a program as a
362 | communication to human beings rather than as a set of instructions to a
363 | computer." LiterateX is faithful to this idea in that it is used for
364 | communication to human beings. Note, however, that LiterateX does *not*
365 | support another core aspect of Knuth's literate programming: the ability to
366 | write source code in the order best for human understanding. Since LiterateX
367 | transforms actual source code files, the source code has to be written in
368 | whatever order is required by the language. Those interested in writing code
369 | in different order are encouraged to check out [noweb][] and [CWEB][].
370 |
371 | The [lhs2tex][] utility is used to work with literate Haskell and LaTeX.
372 |
373 | The [markdown-unlit][] utility is used to extract Haskell code from Markdown
374 | files. This is useful in cases where the Markdown file is displayed on
375 | GitHub.
376 |
377 | The [src2md][] utility, written in Common Lisp, also supports multiple source
378 | formats. It outputs Markdown that includes HTML, which limits the usefulness
379 | of the Markdown.
380 |
381 | The [extract-documentation-comments][] utility, written in JavaScript,
382 | extracts documentation from multi-line JavaScript comments.
383 |
384 | [mlp.clj][], written in Clojure, is a [babashka][] script that transforms
385 | literate Clojure source code to Markdown, including HTML. The author uses it
386 | to implement a [live preview][] of literate Clojure documentation while using
387 | the [Notepad++][] (Windows editor).
388 |
389 | [Literate programming]:
390 | [Donald Knuth]:
391 | [the main idea]:
392 | [noweb]:
393 | [CWEB]:
394 | [lhs2tex]:
395 | [markdown-unlit]:
396 | [src2md]:
397 | [extract-documentation-comments]:
398 | [mlp.clj]:
399 | [babashka]:
400 | [live preview]:
401 | [Notepad++]:
402 |
403 | ## Project
404 |
405 | ### Links
406 |
407 | * Hackage:
408 | * Stackage:
409 | * Flora:
410 | * GitHub:
411 | * GitHub Actions CI:
412 |
413 | ### Branches
414 |
415 | The `main` branch is reserved for releases. It may be considered stable, and
416 | `HEAD` is always the latest release.
417 |
418 | The `develop` branch is the primary development branch. It contains changes
419 | that have not yet been released, and it is not necessarily stable.
420 |
421 | [Hackage revisions][] are made for metadata changes, such as relaxation of
422 | constraints when new versions of dependencies are released. The
423 | `literatex.cabal` metadata in the `main` branch may therefore not match that
424 | of Hackage. The `literatex.cabal` metadata in the `develop` branch may match,
425 | *unless* work is being done on a new release that contains other changes.
426 |
427 | [Hackage revisions]:
428 |
429 | ### Tags
430 |
431 | All releases are tagged in the `main` branch. Release tags are signed using
432 | the [`security@extrema.is` GPG key][].
433 |
434 | [`security@extrema.is` GPG key]:
435 |
436 | ### Contribution
437 |
438 | Issues and feature requests are tracked on GitHub:
439 |
440 |
441 | Issues may also be submitted via email to .
442 |
443 | ### License
444 |
445 | This project is released under the [MIT License][] as specified in the
446 | [`LICENSE`][] file.
447 |
448 | [MIT License]:
449 | [`LICENSE`]:
450 |
--------------------------------------------------------------------------------
/Setup.hs:
--------------------------------------------------------------------------------
1 | import Distribution.Simple
2 | main = defaultMain
3 |
--------------------------------------------------------------------------------
/app/LibOA.hs:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------
2 | -- |
3 | -- Module : LibOA
4 | -- Description : supplementary functions for optparse-applicative
5 | -- Copyright : Copyright (c) 2019-2025 Travis Cardwell
6 | -- License : MIT
7 | --
8 | -- This is a collection of functions that I often use with
9 | -- @optparse-applicative@. I do not feel that it is worth maintaining yet
10 | -- another helper package on Hackage, so I just copy the code to different
11 | -- projects as required. If the library grows to a substantial size or others
12 | -- want to use it, I will reconsider.
13 | --
14 | -- Revision: 2023-05-26
15 | ------------------------------------------------------------------------------
16 |
17 | {-# LANGUAGE CPP #-}
18 | {-# LANGUAGE LambdaCase #-}
19 | {-# LANGUAGE TupleSections #-}
20 |
21 | #if defined(MIN_VERSION_ansi_wl_pprint)
22 | #if MIN_VERSION_ansi_wl_pprint (1,0,2)
23 | {-# OPTIONS_GHC -Wno-warnings-deprecations #-}
24 | #endif
25 | #endif
26 |
27 | module LibOA
28 | ( -- * Options
29 | -- $Options
30 | helper
31 | , versioner
32 | -- * Utilities
33 | , commands
34 | -- * Help
35 | , (<||>)
36 | , section
37 | , section'
38 | , table
39 | , table_
40 | , vspace
41 | -- ** Compatibility
42 | , Doc
43 | , (<$$>)
44 | , empty
45 | , string
46 | , vcat
47 | ) where
48 |
49 | -- https://hackage.haskell.org/package/ansi-wl-pprint
50 | #if !MIN_VERSION_optparse_applicative (0,18,0)
51 | import qualified Text.PrettyPrint.ANSI.Leijen as Doc
52 | import Text.PrettyPrint.ANSI.Leijen (Doc)
53 | #endif
54 |
55 | -- https://hackage.haskell.org/package/base
56 | import Data.List (intersperse, transpose)
57 | import Data.Maybe (fromMaybe)
58 | #if !MIN_VERSION_base (4,11,0)
59 | import Data.Monoid ((<>))
60 | #endif
61 |
62 | -- https://hackage.haskell.org/package/optparse-applicative
63 | import qualified Options.Applicative as OA
64 | #if MIN_VERSION_optparse_applicative (0,16,0)
65 | import qualified Options.Applicative.Builder.Internal as OABI
66 | #endif
67 | import qualified Options.Applicative.Common as OAC
68 | #if MIN_VERSION_optparse_applicative (0,18,0)
69 | import Options.Applicative.Help.Pretty (Doc)
70 | #endif
71 | import qualified Options.Applicative.Types as OAT
72 |
73 | -- https://hackage.haskell.org/package/prettyprinter
74 | #if MIN_VERSION_optparse_applicative (0,18,0)
75 | import qualified Prettyprinter as Doc
76 | #endif
77 |
78 | ------------------------------------------------------------------------------
79 | -- $Options
80 | --
81 | -- Option descriptions are not capitalized.
82 |
83 | -- | A hidden @-h@ / @--help@ option that always fails, showing the help
84 | --
85 | -- This is the same as 'OA.helper' except that it has a different help
86 | -- message.
87 | helper :: OA.Parser (a -> a)
88 | #if MIN_VERSION_optparse_applicative (0,16,0)
89 | helper = OA.option helpReader $ mconcat
90 | [ OA.short 'h'
91 | , OA.long "help"
92 | , OA.value id
93 | , OA.metavar ""
94 | , OABI.noGlobal
95 | , OA.noArgError (OA.ShowHelpText Nothing)
96 | , OA.help "show this help text"
97 | , OA.hidden
98 | ]
99 | where
100 | helpReader = do
101 | potentialCommand <- OAT.readerAsk
102 | OA.readerAbort $ OA.ShowHelpText (Just potentialCommand)
103 | #else
104 | helper = OA.abortOption OA.ShowHelpText $ mconcat
105 | [ OA.short 'h'
106 | , OA.long "help"
107 | , OA.help "show help and exit"
108 | , OA.hidden
109 | ]
110 | #endif
111 |
112 | -- | A hidden @--version@ option that always fails, showing the version
113 | versioner
114 | :: String -- ^ version string
115 | -> OA.Parser (a -> a)
116 | versioner verStr = OA.infoOption verStr $ mconcat
117 | [ OA.long "version"
118 | , OA.help "show version and exit"
119 | , OA.hidden
120 | ]
121 |
122 | ------------------------------------------------------------------------------
123 | -- $Utilities
124 |
125 | -- | Get a list of commands for a parser
126 | commands :: OA.Parser a -> [String]
127 | commands =
128 | let go _ opt = case OAT.optMain opt of
129 | #if MIN_VERSION_optparse_applicative (0,18,0)
130 | OAT.CmdReader _ cmdPs -> reverse $ fst <$> cmdPs
131 | #else
132 | OAT.CmdReader _ cmds _ -> reverse cmds
133 | #endif
134 | _otherReader -> []
135 | in concat . OAC.mapParser go
136 |
137 | ------------------------------------------------------------------------------
138 | -- $Help
139 |
140 | -- | Insert a blank line between two documents
141 | (<||>) :: Doc -> Doc -> Doc
142 | d1 <||> d2 = d1 <> Doc.line <> Doc.line <> d2
143 | infixr 5 <||>
144 |
145 | -- | Create a section with a title and body indented by 2 spaces
146 | section :: String -> Doc -> Doc
147 | section = section' 2
148 |
149 | -- | Create a section with a title and body indented by specified spaces
150 | section' :: Int -> String -> Doc -> Doc
151 | section' numSpaces title = (string title <$$>) . Doc.indent numSpaces
152 |
153 | -- | Create a table, with formatting
154 | table :: Int -> [[(String, Doc -> Doc)]] -> Doc
155 | table sep rows = Doc.vcat $
156 | map (fromMaybe empty . foldr go Nothing . zip lengths) rows
157 | where
158 | lengths :: [Int]
159 | lengths = map ((+) sep . maximum . map (length . fst)) $ transpose rows
160 |
161 | go :: (Int, (String, Doc -> Doc)) -> Maybe Doc -> Maybe Doc
162 | go (len, (s, f)) = Just . \case
163 | Just doc -> Doc.fill len (f $ string s) <> doc
164 | Nothing -> f $ string s
165 |
166 | -- | Create a table, without formatting
167 | table_ :: Int -> [[String]] -> Doc
168 | table_ sep = table sep . (map . map) (, id)
169 |
170 | -- | Vertically space documents with blank lines between them
171 | vspace :: [Doc] -> Doc
172 | vspace = mconcat . intersperse (Doc.line <> Doc.line)
173 |
174 | ------------------------------------------------------------------------------
175 | -- $Compatibility
176 |
177 | (<$$>) :: Doc -> Doc -> Doc
178 | #if MIN_VERSION_optparse_applicative (0,18,0)
179 | l <$$> r = l <> Doc.line <> r
180 | #else
181 | (<$$>) = (Doc.<$$>)
182 | #endif
183 |
184 | empty :: Doc
185 | #if MIN_VERSION_optparse_applicative (0,18,0)
186 | empty = Doc.emptyDoc
187 | #else
188 | empty = Doc.empty
189 | #endif
190 |
191 | string :: String -> Doc
192 | #if MIN_VERSION_optparse_applicative (0,18,0)
193 | string = Doc.pretty
194 | #else
195 | string = Doc.string
196 | #endif
197 |
198 | vcat :: [Doc] -> Doc
199 | vcat = Doc.vcat
200 |
--------------------------------------------------------------------------------
/app/Main.hs:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------
2 | -- |
3 | -- Module : Main
4 | -- Description : literatex command-line utility
5 | -- Copyright : Copyright (c) 2021-2025 Travis Cardwell
6 | -- License : MIT
7 | ------------------------------------------------------------------------------
8 |
9 | {-# LANGUAGE CPP #-}
10 | {-# LANGUAGE RecordWildCards #-}
11 |
12 | #if defined(MIN_VERSION_ansi_wl_pprint)
13 | #if MIN_VERSION_ansi_wl_pprint (1,0,2)
14 | {-# OPTIONS_GHC -Wno-warnings-deprecations #-}
15 | #endif
16 | #endif
17 |
18 | module Main (main) where
19 |
20 | -- https://hackage.haskell.org/package/base
21 | import Control.Applicative (optional)
22 | import System.Exit (ExitCode(ExitFailure), exitWith)
23 | import System.IO (hPutStrLn, stderr, stdin, stdout)
24 |
25 | -- https://hackage.haskell.org/package/optparse-applicative
26 | import qualified Options.Applicative as OA
27 |
28 | -- https://hackage.haskell.org/package/ttc
29 | import qualified Data.TTC as TTC
30 |
31 | -- (literatex)
32 | import qualified LiterateX
33 | import qualified LiterateX.Renderer as Renderer
34 | import qualified LiterateX.SourceDefaults as SourceDefaults
35 | import LiterateX.Types (CodeLanguage, SourceFormat, TargetFormat)
36 | import qualified LiterateX.Types.SourceFormat as SourceFormat
37 | import qualified LiterateX.Types.TargetFormat as TargetFormat
38 |
39 | -- (literatex:executable)
40 | import qualified LibOA
41 |
42 | ------------------------------------------------------------------------------
43 | -- $Defaults
44 |
45 | defaultTargetFormat :: TargetFormat
46 | defaultTargetFormat = TargetFormat.PandocMarkdown
47 |
48 | ------------------------------------------------------------------------------
49 | -- $Options
50 |
51 | data Options
52 | = Options
53 | { formatOpt :: !(Maybe SourceFormat)
54 | , rendererOpts :: !Renderer.Options
55 | , noHighlightOpt :: !Bool
56 | , inputOpt :: !(Maybe FilePath)
57 | , outputOpt :: !(Maybe FilePath)
58 | }
59 |
60 | sourceFormatOption :: OA.Parser SourceFormat
61 | sourceFormatOption = OA.option (OA.eitherReader TTC.parse) $ mconcat
62 | [ OA.long "source"
63 | , OA.short 's'
64 | , OA.metavar "SOURCE"
65 | , OA.help "source format"
66 | ]
67 |
68 | targetFormatOption :: OA.Parser TargetFormat
69 | targetFormatOption = OA.option (OA.eitherReader TTC.parse) $ mconcat
70 | [ OA.long "target"
71 | , OA.short 't'
72 | , OA.metavar "TARGET"
73 | , OA.value defaultTargetFormat
74 | , OA.showDefaultWith TTC.render
75 | , OA.help "target format"
76 | ]
77 |
78 | languageOption :: OA.Parser CodeLanguage
79 | languageOption = OA.option (OA.eitherReader TTC.parse) $ mconcat
80 | [ OA.long "language"
81 | , OA.short 'l'
82 | , OA.metavar "LANGUAGE"
83 | , OA.help "code language"
84 | ]
85 |
86 | ignoreShebangOption :: OA.Parser Bool
87 | ignoreShebangOption = OA.switch $ mconcat
88 | [ OA.long "ignore-shebang"
89 | , OA.help "ignore shebangs"
90 | ]
91 |
92 | noCodeOption :: OA.Parser Bool
93 | noCodeOption = OA.switch $ mconcat
94 | [ OA.long "no-code"
95 | , OA.help "do not display code"
96 | ]
97 |
98 | noNumbersOption :: OA.Parser Bool
99 | noNumbersOption = OA.switch $ mconcat
100 | [ OA.long "no-numbers"
101 | , OA.help "do not number code lines"
102 | ]
103 |
104 | renderOptions :: OA.Parser Renderer.Options
105 | renderOptions = Renderer.Options
106 | <$> targetFormatOption
107 | <*> optional languageOption
108 | <*> ignoreShebangOption
109 | <*> fmap not noCodeOption
110 | <*> fmap not noNumbersOption
111 |
112 | noHighlightOption :: OA.Parser Bool
113 | noHighlightOption = OA.switch $ mconcat
114 | [ OA.long "no-highlight"
115 | , OA.help "do not highlight code"
116 | ]
117 |
118 | inputOption :: OA.Parser FilePath
119 | inputOption = OA.strOption $ mconcat
120 | [ OA.long "input"
121 | , OA.short 'i'
122 | , OA.metavar "FILE"
123 | , OA.help "input file (default: STDIN)"
124 | ]
125 |
126 | outputOption :: OA.Parser FilePath
127 | outputOption = OA.strOption $ mconcat
128 | [ OA.long "output"
129 | , OA.short 'o'
130 | , OA.metavar "FILE"
131 | , OA.help "output file (default: STDOUT)"
132 | ]
133 |
134 | options :: OA.Parser Options
135 | options = Options
136 | <$> optional sourceFormatOption
137 | <*> renderOptions
138 | <*> noHighlightOption
139 | <*> optional inputOption
140 | <*> optional outputOption
141 |
142 | ------------------------------------------------------------------------------
143 | -- $Library
144 |
145 | errorExit :: String -> IO a
146 | errorExit message = do
147 | hPutStrLn stderr $ "error: " ++ message
148 | exitWith $ ExitFailure 1
149 |
150 | ------------------------------------------------------------------------------
151 | -- $Main
152 |
153 | main :: IO ()
154 | main = do
155 | Options{..} <- OA.execParser pinfo
156 | let defaultOpts = SourceDefaults.defaultsFor =<< inputOpt
157 | format <- case (formatOpt, defaultOpts, inputOpt) of
158 | (Just format, _, _) -> pure format
159 | (Nothing, Just (format, _), _) -> pure format
160 | (Nothing, Nothing, Just{}) ->
161 | errorExit "source format not specified and unknown input filename"
162 | (Nothing, Nothing, Nothing) ->
163 | errorExit "source format not specified and no input filename"
164 | let codeLanguage = Renderer.codeLanguage rendererOpts
165 | opts = rendererOpts
166 | { Renderer.codeLanguage =
167 | case (noHighlightOpt, codeLanguage, defaultOpts) of
168 | (True, _, _) -> Nothing
169 | (False, opt@Just{}, _) -> opt
170 | (False, Nothing, Just (_, lang)) -> Just lang
171 | (False, Nothing, Nothing) -> Nothing
172 | }
173 | case (inputOpt, outputOpt) of
174 | (Just input, Just output) ->
175 | LiterateX.transformFileToFile format opts input output
176 | (Just input, Nothing) ->
177 | LiterateX.transformFileToHandle format opts input stdout
178 | (Nothing, Just output) ->
179 | LiterateX.transformHandleToFile format opts stdin output
180 | (Nothing, Nothing) ->
181 | LiterateX.transformHandleToHandle format opts stdin stdout
182 | where
183 | pinfo :: OA.ParserInfo Options
184 | pinfo
185 | = OA.info
186 | (LibOA.helper <*> LibOA.versioner LiterateX.version <*> options)
187 | $ mconcat
188 | [ OA.fullDesc
189 | , OA.progDesc "transform literate source code to Markdown"
190 | , OA.failureCode 2
191 | , OA.footerDoc . Just $ LibOA.vspace
192 | [ sourceFormatHelp
193 | , targetFormatHelp
194 | , defaultsHelp
195 | ]
196 | ]
197 |
198 | sourceFormatHelp :: LibOA.Doc
199 | sourceFormatHelp = LibOA.section "SOURCE options:" $ LibOA.table_ 2
200 | [ [TTC.render format, SourceFormat.describe format]
201 | | format <- SourceFormat.list
202 | ]
203 |
204 | targetFormatHelp :: LibOA.Doc
205 | targetFormatHelp = LibOA.section "TARGET options:" $ LibOA.table_ 2
206 | [ [TTC.render format, TargetFormat.describe format]
207 | | format <- TargetFormat.list
208 | ]
209 |
210 | defaultsHelp :: LibOA.Doc
211 | defaultsHelp = LibOA.section "Default options:" $ LibOA.table_ 2
212 | [ [ext, TTC.render lang ++ " (" ++ TTC.render format ++ ")"]
213 | | (ext, (format, lang)) <- SourceDefaults.extensionDefaults
214 | ]
215 |
--------------------------------------------------------------------------------
/cabal-bounds-lower.project:
--------------------------------------------------------------------------------
1 | packages: ./literatex.cabal
2 |
3 | with-compiler: ghc-8.8.4
4 |
5 | constraints:
6 | -- https://hackage.haskell.org/package/ansi-wl-pprint
7 | ansi-wl-pprint == 0.6.9
8 |
9 | -- https://hackage.haskell.org/package/base
10 | -- GHC boot library
11 | , base == 4.13.0.0
12 |
13 | -- https://hackage.haskell.org/package/bytestring
14 | -- GHC boot library
15 | , bytestring == 0.10.10.1
16 |
17 | -- https://hackage.haskell.org/package/conduit
18 | , conduit == 1.3.4
19 |
20 | -- https://hackage.haskell.org/package/filepath
21 | , filepath == 1.4.2.1
22 |
23 | -- https://hackage.haskell.org/package/optparse-applicative
24 | , optparse-applicative == 0.15.1.0
25 |
26 | -- https://hackage.haskell.org/package/tasty
27 | , tasty == 1.2.3
28 |
29 | -- https://hackage.haskell.org/package/tasty-hunit
30 | , tasty-hunit == 0.10.0.3
31 |
32 | -- https://hackage.haskell.org/package/text
33 | -- GHC boot library
34 | , text == 1.2.4.0
35 |
36 | -- https://hackage.haskell.org/package/ttc
37 | , ttc == 0.4.0.0
38 |
39 | -- https://hackage.haskell.org/package/unliftio
40 | , unliftio == 0.2.13.1
41 |
--------------------------------------------------------------------------------
/cabal-bounds-upper.project:
--------------------------------------------------------------------------------
1 | packages: ./literatex.cabal
2 |
3 | with-compiler: ghc-9.12.1
4 |
5 | constraints:
6 | -- https://hackage.haskell.org/package/base
7 | -- GHC boot library
8 | base == 4.21.0.0
9 |
10 | -- https://hackage.haskell.org/package/bytestring
11 | -- GHC boot library
12 | , bytestring == 0.12.2.0
13 |
14 | -- https://hackage.haskell.org/package/conduit
15 | , conduit == 1.3.6
16 |
17 | -- https://hackage.haskell.org/package/filepath
18 | -- GHC boot library
19 | , filepath == 1.5.4.0
20 |
21 | -- https://hackage.haskell.org/package/optparse-applicative
22 | , optparse-applicative == 0.18.1.0
23 |
24 | -- https://hackage.haskell.org/package/prettyprinter
25 | , prettyprinter == 1.7.1
26 |
27 | -- https://hackage.haskell.org/package/tasty
28 | , tasty == 1.5.2
29 |
30 | -- https://hackage.haskell.org/package/tasty-hunit
31 | , tasty-hunit == 0.10.2
32 |
33 | -- https://hackage.haskell.org/package/text
34 | -- (GHC boot library from GHC 8.4)
35 | , text == 2.1.2
36 |
37 | -- https://hackage.haskell.org/package/ttc
38 | , ttc == 1.5.0.0
39 |
40 | -- https://hackage.haskell.org/package/unliftio
41 | , unliftio == 0.2.25.0
42 |
--------------------------------------------------------------------------------
/cabal.project:
--------------------------------------------------------------------------------
1 | packages: ./literatex.cabal
2 |
--------------------------------------------------------------------------------
/doc/literatex.1:
--------------------------------------------------------------------------------
1 | .\" Automatically generated by Pandoc 3.0.1
2 | .\"
3 | .\" Define V font for inline verbatim, using C font in formats
4 | .\" that render this, and otherwise B font.
5 | .ie "\f[CB]x\f[]"x" \{\
6 | . ftr V B
7 | . ftr VI BI
8 | . ftr VB B
9 | . ftr VBI BI
10 | .\}
11 | .el \{\
12 | . ftr V CR
13 | . ftr VI CI
14 | . ftr VB CB
15 | . ftr VBI CBI
16 | .\}
17 | .TH "LITERATEX" "1" "" "literatex-haskell 0.4.0.0 (2025-01-03)" "literatex Manual"
18 | .nh
19 | .SH NAME
20 | .PP
21 | \f[V]literatex\f[R] - transform literate source code to Markdown
22 | .SH SYNOPSIS
23 | .PP
24 | \f[V]literatex\f[R] [\f[I]OPTIONS\f[R]]
25 | .SH DESCRIPTION
26 | .PP
27 | LiterateX transforms literate source code to Markdown.
28 | Write documentation in Markdown format in the comments of source code,
29 | and LiterateX can transform the file to Markdown, optionally including
30 | the source code with syntax highlighting and line numbers.
31 | .SH OPTIONS
32 | .TP
33 | -h, --help
34 | show help and exit
35 | .TP
36 | --version
37 | show version and exit
38 | .TP
39 | -s, --source \f[I]SOURCE\f[R]
40 | source format
41 | .RS
42 | .PP
43 | The following source formats are supported:
44 | .IP \[bu] 2
45 | \f[V]ddash\f[R] - \f[V]--\f[R] comments
46 | .IP \[bu] 2
47 | \f[V]dslash\f[R] - \f[V]//\f[R] comments
48 | .IP \[bu] 2
49 | \f[V]hash\f[R] - \f[V]#\f[R] comments
50 | .IP \[bu] 2
51 | \f[V]lisp\f[R] - Lisp semicolon comments
52 | .IP \[bu] 2
53 | \f[V]lhs\f[R] - literate Haskell
54 | .IP \[bu] 2
55 | \f[V]percent\f[R] - \f[V]%\f[R] comments
56 | .RE
57 | .TP
58 | -t, --target \f[I]TARGET\f[R]
59 | target format (default: \f[V]pandoc\f[R])
60 | .RS
61 | .PP
62 | The following target formats are supported:
63 | .IP \[bu] 2
64 | \f[V]pandoc\f[R] - Pandoc Markdown
65 | .IP \[bu] 2
66 | \f[V]github\f[R] - GitHub Flavored Markdown
67 | .IP \[bu] 2
68 | \f[V]mdbook\f[R] - mdBook Markdown
69 | .RE
70 | .TP
71 | -l, --language \f[I]LANGUAGE\f[R]
72 | code language
73 | .TP
74 | --ignore-shebang
75 | ignore shebangs
76 | .TP
77 | --no-code
78 | do not display code
79 | .TP
80 | --no-numbers
81 | do not number code lines
82 | .TP
83 | --no-highlight
84 | do not highlight code
85 | .TP
86 | -i, --input \f[I]FILE\f[R]
87 | input file (default: \f[V]STDIN\f[R])
88 | .TP
89 | -o, --output \f[I]FILE\f[R]
90 | output file (default: \f[V]STDOUT\f[R])
91 | .SH Default Options
92 | .TP
93 | \f[V].c\f[R]
94 | c (dslash)
95 | .TP
96 | \f[V].clj\f[R]
97 | clojure (lisp)
98 | .TP
99 | \f[V].css\f[R]
100 | css (dslash)
101 | .TP
102 | \f[V].elm\f[R]
103 | elm (ddash)
104 | .TP
105 | \f[V].erl\f[R]
106 | erlang (percent)
107 | .TP
108 | \f[V].ex\f[R]
109 | elixir (hash)
110 | .TP
111 | \f[V].exs\f[R]
112 | elixir (hash)
113 | .TP
114 | \f[V].go\f[R]
115 | go (dslash)
116 | .TP
117 | \f[V].hs\f[R]
118 | haskell (ddash)
119 | .TP
120 | \f[V].idr\f[R]
121 | idris (ddash)
122 | .TP
123 | \f[V].java\f[R]
124 | java (dslash)
125 | .TP
126 | \f[V].js\f[R]
127 | javascript (dslash)
128 | .TP
129 | \f[V].kt\f[R]
130 | kotlin (dslash)
131 | .TP
132 | \f[V].lhs\f[R]
133 | haskell (lhs)
134 | .TP
135 | \f[V].lisp\f[R]
136 | commonlisp (lisp)
137 | .TP
138 | \f[V].lua\f[R]
139 | lua (ddash)
140 | .TP
141 | \f[V].php\f[R]
142 | php (dslash)
143 | .TP
144 | \f[V].pl\f[R]
145 | perl (hash)
146 | .TP
147 | \f[V].py\f[R]
148 | python (hash)
149 | .TP
150 | \f[V].r\f[R]
151 | r (hash)
152 | .TP
153 | \f[V].rb\f[R]
154 | ruby (hash)
155 | .TP
156 | \f[V].rkt\f[R]
157 | scheme (lisp)
158 | .TP
159 | \f[V].rs\f[R]
160 | rust (dslash)
161 | .TP
162 | \f[V].sc\f[R]
163 | scala (dslash)
164 | .TP
165 | \f[V].scm\f[R]
166 | scheme (lisp)
167 | .TP
168 | \f[V].sh\f[R]
169 | bash (hash)
170 | .TP
171 | \f[V].sql\f[R]
172 | sql (ddash)
173 | .TP
174 | \f[V].tex\f[R]
175 | latex (percent)
176 | .TP
177 | \f[V].ts\f[R]
178 | typescript (dslash)
179 | .SH PROJECT
180 | .TP
181 | GitHub:
182 |
183 | .TP
184 | Reporting issues:
185 | GitHub:
186 | .RS
187 | .PP
188 | Email:
189 | .RE
190 | .TP
191 | Copyright
192 | Copyright (c) 2021-2025 Travis Cardwell
193 | .TP
194 | License
195 | The MIT License
196 |
--------------------------------------------------------------------------------
/doc/literatex.1.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: LITERATEX
3 | section: 1
4 | hyphenate: false
5 | ...
6 |
7 | # NAME
8 |
9 | `literatex` - transform literate source code to Markdown
10 |
11 | # SYNOPSIS
12 |
13 | `literatex` [*OPTIONS*]
14 |
15 | # DESCRIPTION
16 |
17 | LiterateX transforms literate source code to Markdown. Write documentation in
18 | Markdown format in the comments of source code, and LiterateX can transform
19 | the file to Markdown, optionally including the source code with syntax
20 | highlighting and line numbers.
21 |
22 | # OPTIONS
23 |
24 | -h, \--help
25 | : show help and exit
26 |
27 | \--version
28 | : show version and exit
29 |
30 | -s, \--source *SOURCE*
31 | : source format
32 |
33 | The following source formats are supported:
34 |
35 | * `ddash` - `--` comments
36 | * `dslash` - `//` comments
37 | * `hash` - `#` comments
38 | * `lisp` - Lisp semicolon comments
39 | * `lhs` - literate Haskell
40 | * `percent` - `%` comments
41 |
42 | -t, \--target *TARGET*
43 | : target format (default: `pandoc`)
44 |
45 | The following target formats are supported:
46 |
47 | * `pandoc` - Pandoc Markdown
48 | * `github` - GitHub Flavored Markdown
49 | * `mdbook` - mdBook Markdown
50 |
51 | -l, \--language *LANGUAGE*
52 | : code language
53 |
54 | \--ignore-shebang
55 | : ignore shebangs
56 |
57 | \--no-code
58 | : do not display code
59 |
60 | \--no-numbers
61 | : do not number code lines
62 |
63 | \--no-highlight
64 | : do not highlight code
65 |
66 | -i, \--input *FILE*
67 | : input file (default: `STDIN`)
68 |
69 | -o, \--output *FILE*
70 | : output file (default: `STDOUT`)
71 |
72 | # Default Options
73 |
74 | `.c`
75 | : c (dslash)
76 |
77 | `.clj`
78 | : clojure (lisp)
79 |
80 | `.css`
81 | : css (dslash)
82 |
83 | `.elm`
84 | : elm (ddash)
85 |
86 | `.erl`
87 | : erlang (percent)
88 |
89 | `.ex`
90 | : elixir (hash)
91 |
92 | `.exs`
93 | : elixir (hash)
94 |
95 | `.go`
96 | : go (dslash)
97 |
98 | `.hs`
99 | : haskell (ddash)
100 |
101 | `.idr`
102 | : idris (ddash)
103 |
104 | `.java`
105 | : java (dslash)
106 |
107 | `.js`
108 | : javascript (dslash)
109 |
110 | `.kt`
111 | : kotlin (dslash)
112 |
113 | `.lhs`
114 | : haskell (lhs)
115 |
116 | `.lisp`
117 | : commonlisp (lisp)
118 |
119 | `.lua`
120 | : lua (ddash)
121 |
122 | `.php`
123 | : php (dslash)
124 |
125 | `.pl`
126 | : perl (hash)
127 |
128 | `.py`
129 | : python (hash)
130 |
131 | `.r`
132 | : r (hash)
133 |
134 | `.rb`
135 | : ruby (hash)
136 |
137 | `.rkt`
138 | : scheme (lisp)
139 |
140 | `.rs`
141 | : rust (dslash)
142 |
143 | `.sc`
144 | : scala (dslash)
145 |
146 | `.scm`
147 | : scheme (lisp)
148 |
149 | `.sh`
150 | : bash (hash)
151 |
152 | `.sql`
153 | : sql (ddash)
154 |
155 | `.tex`
156 | : latex (percent)
157 |
158 | `.ts`
159 | : typescript (dslash)
160 |
161 | # PROJECT
162 |
163 | GitHub:
164 | :
165 |
166 | Reporting issues:
167 | : GitHub:
168 |
169 | Email:
170 |
171 | Copyright
172 | : Copyright (c) 2021-2025 Travis Cardwell
173 |
174 | License
175 | : The MIT License
176 |
--------------------------------------------------------------------------------
/examples/highlevel.lhs:
--------------------------------------------------------------------------------
1 | LiterateX High-Level Example
2 | ============================
3 |
4 | This is a simple example of using the [LiterateX][] high-level API to
5 | transform Haskell source code to [Pandoc Markdown][].
6 |
7 | [LiterateX]:
8 | [Pandoc Markdown]:
9 |
10 | Code
11 | ----
12 |
13 | When executed, this example program reads this source code and transforms it
14 | to Markdown, writing the output to `STDOUT`. Note that the path is
15 | hard-coded, so be sure to execute the program from the project directory.
16 |
17 | The `OverloadedStrings` extension is used to set the code language below.
18 |
19 | > {-# LANGUAGE OverloadedStrings #-}
20 | >
21 | > module Main (main) where
22 |
23 | Imports from [base](https://hackage.haskell.org/package/base):
24 |
25 | > import System.IO (stdout)
26 |
27 | Imports from [literatex](https://hackage.haskell.org/package/literatex):
28 |
29 | > import qualified LiterateX
30 | > import qualified LiterateX.Renderer as Renderer
31 | > import qualified LiterateX.Types.SourceFormat as SourceFormat
32 |
33 | The `main` function is just a call to `LiterateX.transformFileToHandle` with
34 | appropriate options.
35 |
36 | > main :: IO ()
37 | > main = LiterateX.transformFileToHandle
38 | > SourceFormat.LiterateHaskell
39 | > (Renderer.defaultOptionsFor "haskell")
40 | > "examples/highlevel.lhs"
41 | > stdout
42 |
--------------------------------------------------------------------------------
/examples/lowlevel.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE OverloadedStrings #-}
2 |
3 | module Main (main) where
4 |
5 | -- https://hackage.haskell.org/package/base
6 | import System.IO (stdout)
7 |
8 | -- https://hackage.haskell.org/package/literatex
9 | import qualified LiterateX
10 | import qualified LiterateX.Renderer as Renderer
11 | import qualified LiterateX.Types.SourceFormat as SourceFormat
12 |
13 | ------------------------------------------------------------------------------
14 |
15 | demo :: String
16 | demo = unlines
17 | [ "-- Demo"
18 | , "-- ===="
19 | , "--"
20 | , "-- This source code is embedded in the example, but it could come from"
21 | , "-- a database or API call."
22 | , ""
23 | , "module Main (main) where"
24 | , ""
25 | , "main :: IO ()"
26 | , "main = putStrLn \"Hello!\""
27 | ]
28 |
29 | ------------------------------------------------------------------------------
30 |
31 | main :: IO ()
32 | main = LiterateX.runIO
33 | SourceFormat.DoubleDash
34 | (Renderer.defaultOptionsFor "haskell")
35 | (LiterateX.sourceString demo)
36 | (LiterateX.sinkHandle stdout)
37 |
--------------------------------------------------------------------------------
/literatex.cabal:
--------------------------------------------------------------------------------
1 | cabal-version: 3.0
2 | name: literatex
3 | version: 0.4.0.0
4 | synopsis: transform literate source code to Markdown
5 | description:
6 | This package provides a library as well as a command-line utility that
7 | transforms literate source code to Markdown. Please see the README on
8 | GitHub at .
9 | homepage: https://github.com/ExtremaIS/literatex-haskell#readme
10 | bug-reports: https://github.com/ExtremaIS/literatex-haskell/issues
11 | license: MIT
12 | license-file: LICENSE
13 | author: Travis Cardwell
14 | maintainer: Travis Cardwell
15 | copyright: Copyright (c) 2021-2025 Travis Cardwell
16 | category: Utils
17 | build-type: Simple
18 |
19 | extra-doc-files:
20 | CHANGELOG.md
21 | README.md
22 |
23 | tested-with:
24 | GHC ==8.8.4
25 | || ==8.10.7
26 | || ==9.0.2
27 | || ==9.2.8
28 | || ==9.4.8
29 | || ==9.6.6
30 | || ==9.8.4
31 | || ==9.10.1
32 | || ==9.12.1
33 |
34 | source-repository head
35 | type: git
36 | location: https://github.com/ExtremaIS/literatex-haskell.git
37 |
38 | flag examples
39 | description: build examples
40 | default: False
41 |
42 | -- This flag is referenced in the Stack build-constraints.yaml configuration.
43 | flag optparse-applicative_ge_0_18
44 | description: Use optparse-applicative 0.18 or newer
45 | default: False
46 | manual: False
47 |
48 | library
49 | hs-source-dirs: src
50 | exposed-modules:
51 | LiterateX
52 | , LiterateX.Parser
53 | , LiterateX.Renderer
54 | , LiterateX.SourceDefaults
55 | , LiterateX.Types
56 | , LiterateX.Types.CodeLanguage
57 | , LiterateX.Types.SourceFormat
58 | , LiterateX.Types.SourceLine
59 | , LiterateX.Types.TargetFormat
60 | other-modules:
61 | Paths_literatex
62 | autogen-modules:
63 | Paths_literatex
64 | build-depends:
65 | base >=4.13.0.0 && <4.22
66 | , bytestring >=0.10.10.1 && <0.13
67 | , conduit >=1.3.4 && <1.4
68 | , text >=1.2.4.0 && <2.2
69 | , ttc >=0.4.0.0 && <1.6
70 | , unliftio >=0.2.13.1 && <0.3
71 | default-language: Haskell2010
72 | default-extensions:
73 | OverloadedStrings
74 | ghc-options: -Wall
75 |
76 | executable literatex
77 | hs-source-dirs: app
78 | main-is: Main.hs
79 | other-modules:
80 | LibOA
81 | build-depends:
82 | base
83 | , literatex
84 | , ttc
85 | if flag(optparse-applicative_ge_0_18)
86 | build-depends:
87 | optparse-applicative >=0.18 && <0.19
88 | , prettyprinter >=1.7.1 && <1.8
89 | else
90 | build-depends:
91 | ansi-wl-pprint >=0.6.9 && <1.1
92 | , optparse-applicative >=0.15.1.0 && <0.18
93 | default-language: Haskell2010
94 | ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
95 |
96 | executable example-highlevel
97 | if flag(examples)
98 | buildable: True
99 | else
100 | buildable: False
101 | hs-source-dirs: examples
102 | main-is: highlevel.lhs
103 | build-depends:
104 | base
105 | , literatex
106 | default-language: Haskell2010
107 | ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
108 |
109 | executable example-lowlevel
110 | if flag(examples)
111 | buildable: True
112 | else
113 | buildable: False
114 | hs-source-dirs: examples
115 | main-is: lowlevel.hs
116 | build-depends:
117 | base
118 | , literatex
119 | default-language: Haskell2010
120 | ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
121 |
122 | test-suite literatex-test
123 | type: exitcode-stdio-1.0
124 | hs-source-dirs: test
125 | main-is: Spec.hs
126 | other-modules:
127 | LiterateX.Test.API
128 | , LiterateX.Test.SourceFormat
129 | , LiterateX.Test.SourceFormat.DoubleDash
130 | , LiterateX.Test.SourceFormat.DoubleSlash
131 | , LiterateX.Test.SourceFormat.Hash
132 | , LiterateX.Test.SourceFormat.LispSemicolons
133 | , LiterateX.Test.SourceFormat.LiterateHaskell
134 | , LiterateX.Test.SourceFormat.Percent
135 | , LiterateX.Test.TargetFormat
136 | build-depends:
137 | base
138 | , bytestring
139 | , filepath >=1.4.2.1 && <1.6
140 | , literatex
141 | , tasty >=1.2.3 && <1.6
142 | , tasty-hunit >=0.10.0.3 && <0.11
143 | , text
144 | , ttc
145 | , unliftio
146 | default-language: Haskell2010
147 | ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
148 |
--------------------------------------------------------------------------------
/pkg/deb/Makefile:
--------------------------------------------------------------------------------
1 | DESTDIR ?=
2 | prefix ?= /usr
3 |
4 | bindir := $(DESTDIR)$(prefix)/bin
5 | datarootdir := $(DESTDIR)$(prefix)/share
6 | docdir := $(datarootdir)/doc/literatex-haskell
7 | man1dir := $(datarootdir)/man/man1
8 |
9 | SHELL := bash
10 |
11 | build:
12 | @stack build
13 | .PHONY: build
14 |
15 | install:
16 | $(eval LIROOT := $(shell stack path --local-install-root))
17 | @mkdir -p "$(bindir)"
18 | @install -m 0755 "$(LIROOT)/bin/literatex" "$(bindir)/literatex"
19 | @mkdir -p "$(man1dir)"
20 | @install -m 0644 doc/literatex.1 "$(man1dir)"
21 | @gzip "$(man1dir)/literatex.1"
22 | @mkdir -p "$(docdir)"
23 | @install -m 0644 README.md "$(docdir)"
24 | @gzip "$(docdir)/README.md"
25 | @install -m 0644 -T CHANGELOG.md "$(docdir)/changelog"
26 | @gzip "$(docdir)/changelog"
27 | @install -m 0644 LICENSE "$(docdir)"
28 | @gzip "$(docdir)/LICENSE"
29 | .PHONY: install
30 |
31 | test:
32 | @stack test
33 | .PHONY: test
34 |
--------------------------------------------------------------------------------
/pkg/deb/control:
--------------------------------------------------------------------------------
1 | Source: literatex-haskell
2 | Section: text
3 | Priority: optional
4 | Maintainer: Travis Cardwell
5 | Build-Depends: debhelper (>= 9)
6 | Standards-Version: 3.9.8
7 | Vcs-Git: git://github.com/ExtremaIS/literatex-haskell.git
8 | Vcs-browser: https://github.com/ExtremaIS/literatex-haskell
9 | Homepage: https://github.com/ExtremaIS/literatex-haskell#literatex
10 |
11 | Package: literatex-haskell
12 | Architecture: {{ARCH}}
13 | Depends: ${shlibs:Depends}, ${misc:Depends}
14 | Description: Transform literate source code to Markdown
15 |
--------------------------------------------------------------------------------
/pkg/deb/copyright:
--------------------------------------------------------------------------------
1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2 | Upstream-Name: literatex-haskell
3 | Upstream-Contact: Travis Cardwell
4 | Copyright: Copyright (c) 2021-2025 Travis Cardwell
5 | License: MIT
6 |
--------------------------------------------------------------------------------
/pkg/rpm/literatex-haskell.spec:
--------------------------------------------------------------------------------
1 | Name: literatex-haskell
2 | Version: {{VERSION}}
3 | Release: 1%{?dist}
4 | Summary: Transform literate source code to Markdown
5 | License: MIT
6 | URL: https://github.com/ExtremaIS/literatex-haskell
7 | Source0: literatex-haskell-{{VERSION}}.tar.xz
8 | BuildArch: {{ARCH}}
9 | BuildRequires: make
10 | Requires: glibc,gmp
11 | #ExcludeArch:
12 |
13 | %description
14 | LiterateX transforms literate source code to Markdown. Write documentation in
15 | Markdown format in the comments of source code, and LiterateX can transform
16 | the file to Markdown, optionally including the source code with syntax
17 | highlighting and line numbers.
18 |
19 | %global debug_package %{nil}
20 |
21 | %prep
22 | %setup -q
23 |
24 | %build
25 |
26 | %install
27 | make install DESTDIR=%{buildroot} PREFIX=/usr
28 |
29 | %check
30 | make test
31 |
32 | %files
33 | %{_bindir}/literatex
34 | %{_mandir}/man1/literatex.1.gz
35 | %{_datadir}/doc/%{name}/
36 |
37 | %changelog
38 | * {{DATE}} {{RPMFULLNAME}} <{{RPMEMAIL}}> - {{VERSION}}-1
39 | - Release {{VERSION}}
40 |
--------------------------------------------------------------------------------
/project/TODO.md:
--------------------------------------------------------------------------------
1 | # `literatex-haskell` TODO
2 |
3 | ## Functionality
4 |
5 | * Add special support for handling pragmas?
6 |
7 | ## Tests
8 |
9 | ## Compatibility
10 |
11 | ## Documentation
12 |
13 | ## Examples
14 |
15 | ## Project
16 |
17 | * Test examples with Stack in CI (currently disabled because setting the
18 | `examples` flag on the command line disables the
19 | `optparse-applicative_ge_0_18` flag when set in the `stack.yaml`
20 | configuration)
21 | * Rewrite animation with [reanimate](https://github.com/reanimate/reanimate)?
22 |
--------------------------------------------------------------------------------
/project/animation/.gitignore:
--------------------------------------------------------------------------------
1 | # cabal
2 | /dist-newstyle/
3 |
4 | # nix
5 | result*
6 |
7 | # vi
8 | .*.swp
9 |
--------------------------------------------------------------------------------
/project/animation/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2021-2025 Travis Cardwell
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/project/animation/Main.hs:
--------------------------------------------------------------------------------
1 | module Main (main) where
2 |
3 | -- https://hackage.haskell.org/package/diagrams-cairo
4 | import Diagrams.Backend.Cairo.CmdLine
5 |
6 | -- https://hackage.haskell.org/package/diagrams-lib
7 | import Diagrams.Prelude
8 |
9 | ------------------------------------------------------------------------------
10 |
11 | langs :: [String]
12 | langs =
13 | [ "Bash"
14 | , "C"
15 | , "Clojure"
16 | , "Erlang"
17 | , "Haskell"
18 | , "Idris"
19 | , "JavaScript"
20 | , "Python"
21 | , "Racket"
22 | , "SQL"
23 | , "..."
24 | ]
25 |
26 | ------------------------------------------------------------------------------
27 |
28 | animation :: [(Diagram B, Int)]
29 | animation = (mkFrame "X", 300) : [(mkFrame lang, 50) | lang <- langs]
30 | where
31 | mkFrame :: String -> Diagram B
32 | mkFrame lang
33 | = baselineText ("Literate" ++ lang)
34 | # font "Noto Sans"
35 | # fontSizeL 0.5
36 | # fc black
37 | # translateX (-3)
38 | # translateY (-0.25)
39 | <> rect 6.5 1 # lw none # fc white # bg white
40 |
41 | ------------------------------------------------------------------------------
42 |
43 | main :: IO ()
44 | main = mainWith animation
45 |
--------------------------------------------------------------------------------
/project/animation/Makefile:
--------------------------------------------------------------------------------
1 | literatex.gif: Main.hs
2 | cabal v2-run -- literatex-animation --width 400 --output literatex.render.gif
3 | gifsicle --crop 0,1+400x60 --colors 8 --output literatex.gif literatex.render.gif
4 | rm literatex.render.gif
5 |
--------------------------------------------------------------------------------
/project/animation/README.md:
--------------------------------------------------------------------------------
1 | # LiterateX Animation
2 |
3 | This directory contains the source code for generating the [LiterateX][]
4 | animation using [Diagrams][].
5 |
6 | [LiterateX]:
7 | [Diagrams]:
8 |
--------------------------------------------------------------------------------
/project/animation/default.nix:
--------------------------------------------------------------------------------
1 | # Nix configuration for `literatex-animation`
2 | #
3 | # Usage:
4 | #
5 | # * Build the program with the default compiler:
6 | #
7 | # $ nix-build
8 |
9 | { # This string argument specifies the compiler (example: "ghc8104"). When
10 | # not specified, the default compiler (configured below) is used.
11 | compiler ? null
12 | # This path argument specifies the packages to use. When not specified and
13 | # the default compiler is selected, a known good revision is used.
14 | # Otherwise, the packages configured on the filesystem are used.
15 | , nixpkgs ? null
16 | # This boolean argument is used by `shell.nix`. When `True`, build tools
17 | # are added to the derivation.
18 | , isShell ? false
19 | }:
20 |
21 | let
22 |
23 | # This string defines the default compiler version.
24 | defaultCompiler = "ghc8104";
25 |
26 | # This string defines a known good revision for the default compiler.
27 | defaultRevision = "4d4fdc329285e0d0c1c1a2b65947d651b8ba6b29";
28 |
29 | # This function fetches the specified nixpkgs revision.
30 | nixpkgsTarball = rev:
31 | builtins.fetchTarball {
32 | url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
33 | };
34 |
35 | # The compiler is explicitly specified or the default.
36 | compiler' = if isNull compiler then defaultCompiler else compiler;
37 |
38 | # Packages are explicitly specified, those for a known good revision for the
39 | # default compiler, or those configured on the filesystem.
40 | pkgs = if isNull nixpkgs
41 | then if isNull compiler
42 | then import (nixpkgsTarball defaultRevision) {}
43 | else import {}
44 | else nixpkgs;
45 |
46 | # This does not work with old revisions.
47 | gitIgnore = pkgs.nix-gitignore.gitignoreSourcePure;
48 |
49 | in
50 |
51 | # Configure the development environment for the package using the selected
52 | # packages and compiler.
53 | pkgs.haskell.packages.${compiler'}.developPackage {
54 | root = gitIgnore [./.gitignore] ./.;
55 | name = "literatex-animation";
56 | modifier = drv:
57 | if isShell
58 | then pkgs.haskell.lib.addBuildTools drv
59 | [ pkgs.cabal-install pkgs.gifsicle pkgs.noto-fonts
60 | ]
61 | else drv;
62 | }
63 |
--------------------------------------------------------------------------------
/project/animation/literatex-animation.cabal:
--------------------------------------------------------------------------------
1 | name: literatex-animation
2 | version: 0.0.0.0
3 | synopsis: LiterateX animation
4 | description:
5 | This package contains the source for the LiterateX animation.
6 |
7 | homepage: https://github.com/ExtremaIS/literatex-haskell#readme
8 | bug-reports: https://github.com/ExtremaIS/literatex-haskell/issues
9 | author: Travis Cardwell
10 | maintainer: Travis Cardwell
11 | copyright: Copyright (c) 2021-2025 Travis Cardwell
12 | license: MIT
13 | license-file: LICENSE
14 |
15 | cabal-version: 1.24
16 | build-type: Simple
17 |
18 | extra-source-files:
19 | README.md
20 |
21 | executable literatex-animation
22 | hs-source-dirs: .
23 | main-is: Main.hs
24 | build-depends:
25 | base
26 | , diagrams-cairo
27 | , diagrams-lib
28 | default-language: Haskell2010
29 | ghc-options: -Wall
30 |
--------------------------------------------------------------------------------
/project/animation/literatex.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExtremaIS/literatex-haskell/4b84a28f777a51564cd92a8c5781ced9f17b7e9a/project/animation/literatex.gif
--------------------------------------------------------------------------------
/project/animation/shell.nix:
--------------------------------------------------------------------------------
1 | # Nix shell configuration for `literatex-animation` development
2 | #
3 | # Usage:
4 | #
5 | # * Run a Nix shell with the default compiler:
6 | #
7 | # $ nix-shell
8 |
9 | { # This string argument specifies the compiler (example: "ghc8104"). When
10 | # not specified, the default compiler is used.
11 | compiler ? null
12 | # This path argument specifies the packages to use. When not specified and
13 | # the default compiler is selected, a known good revision is used.
14 | # Otherwise, the packages configured on the filesystem are used.
15 | , nixpkgs ? null
16 | }@args:
17 |
18 | import ./default.nix (args // { isShell = true; })
19 |
--------------------------------------------------------------------------------
/project/log/20210523-design.md:
--------------------------------------------------------------------------------
1 | # LiterateX Design Log
2 |
3 | Author
4 | : Travis Cardwell
5 |
6 | Date
7 | : 2021-05-23
8 |
9 | ## History
10 |
11 | I have been writing Markdown in source code comments for many years. I have
12 | found it especially useful for documenting SQL schema definitions! The
13 | database design can be very enlightening when learning about a complex system,
14 | so I think it is worthwhile to document it well. It is also beneficial to
15 | document the intended purposes of indexes and optimizations, as well as
16 | intentional limitations of a database design.
17 |
18 | Since parsing line-based comments is so simple, I have often just used `sed`
19 | to do so. The implementation is generally so short that it can easily fit in
20 | a `Makefile`. I decided to go ahead and write a program for the task,
21 | however, in order to have a simple utility that supports features that I
22 | usually skip when using `sed` for a wide variety of source languages. I also
23 | wanted to have an easy way to author blog entries and articles using literate
24 | source code on [my website](https://www.extrema.is).
25 |
26 | ## Design Goals
27 |
28 | I am trying to keep the implementation simple. I considered adding support
29 | for multi-line comments but think that the simplicity of only supporting line
30 | comments is worthwhile.
31 |
32 | The initial implementation supports two "flavors" of Markdown, but I use
33 | module and option names ("target formats") that allow for the addition of
34 | non-Markdown target formats in the future. I only plan on supporting Markdown
35 | at this time, however.
36 |
37 | ## Implementation
38 |
39 | The initial prototype utilized lazy evaluation, through lazy `Text` and lists.
40 | It was very easy to understand and was aligned with my goal of simplicity. I
41 | have run into memory/performance issues with such implementations in other
42 | projects, however, so I decided to use [conduit][], a streaming library that
43 | manages resources well.
44 |
45 | [conduit]:
46 |
47 | Performance of decoding and encoding of UTF-8 in Haskell varies according to
48 | the data types used. I wrote some blog articles about some benchmarks that I
49 | experimented with during the implementation of LiterateX:
50 |
51 | * [Decoding UTF-8 in Haskell](https://www.extrema.is/blog/2021/05/10/decoding-utf8-in-haskell)
52 | * [Decoding UTF-8 in Haskell (Part 2)](https://www.extrema.is/blog/2021/05/11/decoding-utf8-in-haskell-part-2)
53 | * [Encoding UTF-8 in Haskell](https://www.extrema.is/blog/2021/05/14/encoding-utf8-in-haskell)
54 |
55 | I implemented a number of different APIs in an attempt to create an API that
56 | is simple yet does not limit users with respect to performance. I ended up
57 | providing a high-level API that does not expose use of `conduit` as well as a
58 | low-level API that does. Since the low-level API provides ways to process
59 | many "textual" types, the high-level API could be kept simple.
60 |
61 | In the implementation of the tests, source format tests are high-level: they
62 | test example source code for each supported source format, not the
63 | implementation. While this results in repetition, I think it is worthwhile.
64 |
--------------------------------------------------------------------------------
/project/release/literatex-haskell-0.1.0.0.md:
--------------------------------------------------------------------------------
1 | # `literatex-haskell` `0.1.0.0` Release Notes
2 |
3 | Date
4 | : 2021-05-26
5 |
6 | ## Overview
7 |
8 | This is the first public release of `literatex-haskell`.
9 |
--------------------------------------------------------------------------------
/project/release/literatex-haskell-0.1.0.1.md:
--------------------------------------------------------------------------------
1 | # `literatex-haskell` `0.1.0.1` Release Notes
2 |
3 | Date
4 | : 2021-06-03
5 |
6 | ## Overview
7 |
8 | This patch release of LiterateX bumps the TTC dependency version upper bound.
9 | There are no changes to the code in this release.
10 |
--------------------------------------------------------------------------------
/project/release/literatex-haskell-0.1.0.2.md:
--------------------------------------------------------------------------------
1 | # `literatex-haskell` `0.1.0.2` Release Notes
2 |
3 | Date
4 | : 2021-06-10
5 |
6 | ## Overview
7 |
8 | This patch release of LiterateX bumps the TTC dependency version upper bound.
9 | There are no changes to the code in this release.
10 |
--------------------------------------------------------------------------------
/project/release/literatex-haskell-0.2.0.0.md:
--------------------------------------------------------------------------------
1 | # `literatex-haskell` `0.2.0.0` Release Notes
2 |
3 | Date
4 | : 2021-06-25
5 |
6 | ## Overview
7 |
8 | This release of LiterateX fixes a bug and makes changes to the [Nix][]
9 | configuration. There are no changes to the LiterateX API.
10 |
11 | [Nix]:
12 |
13 | ### Bug Fix
14 |
15 | This release includes a fix for a bug that broke `--help` output. The issue
16 | only affected builds using `optparse-applicative` `0.16`, so none of the
17 | published builds were affected.
18 |
19 | ### Nix Configuration
20 |
21 | The Nix configuration now supports testing against the following GHC versions
22 | using known working `nixpkgs` revisions:
23 |
24 | * GHC 8.2.2
25 | * GHC 8.4.4
26 | * GHC 8.6.5
27 | * GHC 8.8.4
28 | * GHC 8.10.4
29 | * GHC 9.0.1
30 |
--------------------------------------------------------------------------------
/project/release/literatex-haskell-0.2.0.1.md:
--------------------------------------------------------------------------------
1 | # `literatex-haskell` `0.2.0.1` Release Notes
2 |
3 | Date
4 | : 2021-12-25
5 |
6 | ## Overview
7 |
8 | This patch release of LiterateX bumps the [text][] dependency version upper
9 | bound. There are no changes to the code in this release.
10 |
11 | [text]:
12 |
--------------------------------------------------------------------------------
/project/release/literatex-haskell-0.2.0.2.md:
--------------------------------------------------------------------------------
1 | # `literatex-haskell` `0.2.0.2` Release Notes
2 |
3 | Date
4 | : 2022-02-05
5 |
6 | ## Overview
7 |
8 | This patch release of LiterateX bumps the [optparse-applicative][] dependency
9 | version upper bound. There are no changes to the code in this release.
10 |
11 | [optparse-applicative]:
12 |
--------------------------------------------------------------------------------
/project/release/literatex-haskell-0.2.1.0.md:
--------------------------------------------------------------------------------
1 | # `literatex-haskell` `0.2.1.0` Release Notes
2 |
3 | Date
4 | : 2023-03-21
5 |
6 | ## Overview
7 |
8 | LiterateX transforms literate source code to Markdown. Write documentation in
9 | Markdown format in the comments of source code, and LiterateX can transform
10 | the file to Markdown, optionally including the source code with syntax
11 | highlighting and line numbers.
12 |
13 | See the [README][] for details.
14 |
15 | [README]:
16 |
17 | ## This Release
18 |
19 | This release adds support for the [mdBook Markdown][] output format.
20 |
21 | LiterateX `0.2.0.2` was released more than one year ago, and a number of
22 | updates to dependency constraints have since been registered as
23 | [Hackage revisions][]. This release also updates the package (tarball and
24 | `main` branch) to the latest state.
25 |
26 | This release also includes changes to the project management infrastructure.
27 | One important change is that both lower and upper bounds of dependencies are
28 | now tested in CI.
29 |
30 | [mdBook Markdown]:
31 | [Hackage revisions]:
32 |
33 | ### mdBook Markdown support
34 |
35 | The [mdBook Markdown][] format does not support per-block line numbering.
36 | When using this format, line numbering is not configured even when requested.
37 |
38 | ### Compatibility
39 |
40 | Build software:
41 |
42 | | Software | LiterateX 0.2.0.1 | LiterateX 0.2.1.0 |
43 | | ----------------- | ----------------- | ----------------- |
44 | | [GHC][] | 8.2.2 ~ 9.2.1 | 8.2.2 ~ 9.6.1 |
45 | | [cabal-install][] | 1.24 ~ 3.6 | 1.24 ~ 3.10 |
46 |
47 | Library dependencies:
48 |
49 | | Package | LiterateX 0.2.0.1 | LiterateX 0.2.1.0 |
50 | | -------------- | ------------------- | ------------------- |
51 | | [base][] | `>=4.7 && <5` | `>=4.10.1 && <4.19` |
52 | | [bytestring][] | `>=0.10.8 && <0.12` | `>=0.10.8 && <0.12` |
53 | | [conduit][] | `>=1.3 && <1.4` | `>=1.3 && <1.4` |
54 | | [text][] | `>=1.2.3 && <2.1` | `>=1.2.3 && <2.1` |
55 | | [ttc][] | `>=0.4 && <1.2` | `>=0.4 && <1.3` |
56 | | [unliftio][] | `>=0.2 && <0.3` | `>=0.2 && <0.3` |
57 |
58 | Executable dependencies:
59 |
60 | | Package | LiterateX 0.2.0.1 | LiterateX 0.2.1.0 |
61 | | ------------------------ | ----------------- | ----------------- |
62 | | [ansi-wl-pprint][] | `>=0.6 && <0.7` | `>=0.6.8 && <0.7` |
63 | | [optparse-applicative][] | `>=0.14 && <0.18` | `>=0.13 && <0.18` |
64 |
65 | Test dependencies:
66 |
67 | | Package | LiterateX 0.2.0.1 | LiterateX 0.2.1.0 |
68 | | --------------- | ----------------- | ------------------- |
69 | | [filepath][] | (unconstrained) | `>=1.4.1.2 && <1.5` |
70 | | [tasty][] | (unconstrained) | `>=0.12 && <1.5` |
71 | | [tasty-hunit][] | (unconstrained) | `>=0.8 && <0.11` |
72 |
73 | To use this release with a Stackage snapshot that does not include it, add
74 | the following to your `stack.yaml` configuration:
75 |
76 | ```yaml
77 | extra-deps:
78 | - literatex-0.2.1.0
79 | ```
80 |
81 | [GHC]:
82 | [cabal-install]:
83 | [base]:
84 | [bytestring]:
85 | [conduit]:
86 | [text]:
87 | [ttc]:
88 | [unliftio]:
89 | [ansi-wl-pprint]:
90 | [optparse-applicative]:
91 | [filepath]:
92 | [tasty]:
93 | [tasty-hunit]:
94 |
95 | ### Issues
96 |
97 | There are no known issues at this time.
98 |
--------------------------------------------------------------------------------
/project/release/literatex-haskell-0.3.0.0.md:
--------------------------------------------------------------------------------
1 | # `literatex-haskell` `0.3.0.0` Release Notes
2 |
3 | Date
4 | : 2023-05-28
5 |
6 | ## Overview
7 |
8 | LiterateX transforms literate source code to Markdown. Write documentation in
9 | Markdown format in the comments of source code, and LiterateX can transform
10 | the file to Markdown, optionally including the source code with syntax
11 | highlighting and line numbers.
12 |
13 | See the [README][] for details.
14 |
15 | [README]:
16 |
17 | ## This Release
18 |
19 | This release adds compatibility with the latest version of the
20 | `optparse-applicative` library.
21 |
22 | There are no changes to the API or CLI.
23 |
24 | ### Compatibility
25 |
26 | To use this release with a Stackage snapshot that does not include it, add
27 | the following to your `stack.yaml` configuration:
28 |
29 | ```yaml
30 | extra-deps:
31 | - literatex-0.3.0.0
32 | ```
33 |
34 | ### Issues
35 |
36 | There are no known issues at this time.
37 |
--------------------------------------------------------------------------------
/project/release/literatex-haskell-0.4.0.0.md:
--------------------------------------------------------------------------------
1 | # `literatex-haskell` `0.4.0.0` Release Notes
2 |
3 | Date
4 | : 2025-01-03
5 |
6 | ## Overview
7 |
8 | LiterateX transforms literate source code to Markdown. Write documentation in
9 | Markdown format in the comments of source code, and LiterateX can transform
10 | the file to Markdown, optionally including the source code with syntax
11 | highlighting and line numbers.
12 |
13 | See the [README][] for details.
14 |
15 | [README]:
16 |
17 | ## This Release
18 |
19 | This release adds compatibility with the latest releases of GHC and removes
20 | support for versions of GHC that were released more than five years ago. GHC
21 | versions 8.8.4 through 9.10.1 are supported. Cabal version 3.0 through
22 | 3.12.1.0 are supported.
23 |
24 | There are no changes to the API or CLI.
25 |
26 | ### Compatibility
27 |
28 | To use this release with a Stackage snapshot that does not include it, add
29 | the following to your `stack.yaml` configuration:
30 |
31 | ```yaml
32 | extra-deps:
33 | - literatex-0.4.0.0
34 | ```
35 |
36 | ### Issues
37 |
38 | There are no known issues at this time.
39 |
--------------------------------------------------------------------------------
/src/LiterateX.hs:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------
2 | -- |
3 | -- Module : LiterateX
4 | -- Description : API
5 | -- Copyright : Copyright (c) 2021-2025 Travis Cardwell
6 | -- License : MIT
7 | --
8 | -- This module provides high-level as well as low-level API functions for
9 | -- transforming literate source code.
10 | ------------------------------------------------------------------------------
11 |
12 | {-# LANGUAGE FlexibleInstances #-}
13 |
14 | module LiterateX
15 | ( -- * Constants
16 | version
17 | -- * API
18 | -- ** High-Level
19 | -- $HighLevelAPI
20 | , transformTextToText
21 | , transformTextToHandle
22 | , transformTextToFile
23 | , transformHandleToText
24 | , transformHandleToHandle
25 | , transformHandleToFile
26 | , transformFileToText
27 | , transformFileToHandle
28 | , transformFileToFile
29 | -- ** Low-Level
30 | -- $LowLevelAPI
31 | , runPure
32 | , runIO
33 | , runResource
34 | -- *** Producers
35 | , sourceString
36 | , sourceText
37 | , sourceLazyText
38 | , sourceByteString
39 | , sourceLazyByteString
40 | , sourceHandle
41 | , sourceFile
42 | -- *** Consumers
43 | , sinkString
44 | , sinkText
45 | , sinkLazyText
46 | , sinkByteString
47 | , sinkLazyByteString
48 | , sinkHandle
49 | , sinkFile
50 | -- *** Transformers
51 | , transform
52 | ) where
53 |
54 | -- https://hackage.haskell.org/package/base
55 | import Control.Monad.IO.Class (MonadIO)
56 | import Data.Version (showVersion)
57 | import System.IO (Handle)
58 |
59 | -- https://hackage.haskell.org/package/bytestring
60 | import qualified Data.ByteString as BS
61 | import qualified Data.ByteString.Lazy as BSL
62 |
63 | -- https://hackage.haskell.org/package/conduit
64 | import qualified Conduit as C
65 | import Data.Conduit ((.|))
66 | import qualified Data.Conduit.Combinators as CC
67 | import qualified Data.Conduit.List as CL
68 |
69 | -- https://hackage.haskell.org/package/text
70 | import qualified Data.Text as T
71 | import qualified Data.Text.Encoding as TE
72 | import qualified Data.Text.Encoding.Error as TEE
73 | import qualified Data.Text.Lazy as TL
74 |
75 | -- https://hackage.haskell.org/package/unliftio
76 | import UnliftIO (MonadUnliftIO)
77 |
78 | -- (literatex)
79 | import qualified LiterateX.Parser as Parser
80 | import qualified LiterateX.Renderer as Renderer
81 | import LiterateX.Types (SourceFormat)
82 |
83 | -- (literatex:cabal)
84 | import qualified Paths_literatex as Project
85 |
86 | ------------------------------------------------------------------------------
87 | -- $Constants
88 |
89 | -- | LiterateX version string (\"@literatex-haskell X.X.X@\")
90 | --
91 | -- @since 0.0.1.0
92 | version :: String
93 | version = "literatex-haskell " ++ showVersion Project.version
94 |
95 | ------------------------------------------------------------------------------
96 | -- $HighLevelAPI
97 | --
98 | -- This high-level API provides functions for transforming literate source
99 | -- code. These functions provide support for transforming from/to lazy
100 | -- 'TL.Text', 'Handle's, and files.
101 | --
102 | -- Example usage:
103 | --
104 | -- @
105 | -- {-\# LANGUAGE OverloadedStrings \#-}
106 | --
107 | -- module Main (main) where
108 | --
109 | -- -- https://hackage.haskell.org/package/literatex
110 | -- import qualified LiterateX
111 | -- import qualified LiterateX.Renderer as Renderer
112 | -- import qualified LiterateX.Types.SourceFormat as SourceFormat
113 | --
114 | -- main :: IO ()
115 | -- main = LiterateX.transformFileToFile
116 | -- SourceFormat.LiterateHaskell
117 | -- (Renderer.defaultOptionsFor "haskell")
118 | -- "demo.lhs"
119 | -- "demo.md"
120 | -- @
121 |
122 | -- | Transform from lazy 'TL.Text' to lazy 'TL.Text'
123 | --
124 | -- @since 0.0.1.0
125 | transformTextToText
126 | :: SourceFormat
127 | -> Renderer.Options
128 | -> TL.Text
129 | -> TL.Text
130 | transformTextToText sourceFormat rendererOpts source =
131 | runPure sourceFormat rendererOpts (sourceLazyText source) sinkLazyText
132 |
133 | -- | Transform from lazy 'TL.Text' to a 'Handle'
134 | --
135 | -- @since 0.0.1.0
136 | transformTextToHandle
137 | :: MonadIO m
138 | => SourceFormat
139 | -> Renderer.Options
140 | -> TL.Text
141 | -> Handle
142 | -> m ()
143 | transformTextToHandle sourceFormat rendererOpts source target =
144 | runIO sourceFormat rendererOpts
145 | (sourceLazyText source)
146 | (sinkHandle target)
147 |
148 | -- | Transform from lazy 'TL.Text' to a file
149 | --
150 | -- @since 0.0.1.0
151 | transformTextToFile
152 | :: MonadUnliftIO m
153 | => SourceFormat
154 | -> Renderer.Options
155 | -> TL.Text
156 | -> FilePath
157 | -> m ()
158 | transformTextToFile sourceFormat rendererOpts source target =
159 | runResource sourceFormat rendererOpts
160 | (sourceLazyText source)
161 | (sinkFile target)
162 |
163 | -- | Transform from a 'Handle' to lazy 'TL.Text'
164 | --
165 | -- @since 0.0.1.0
166 | transformHandleToText
167 | :: MonadIO m
168 | => SourceFormat
169 | -> Renderer.Options
170 | -> Handle
171 | -> m TL.Text
172 | transformHandleToText sourceFormat rendererOpts source =
173 | runIO sourceFormat rendererOpts (sourceHandle source) sinkLazyText
174 |
175 | -- | Transform from a 'Handle' to a 'Handle'
176 | --
177 | -- @since 0.0.1.0
178 | transformHandleToHandle
179 | :: MonadIO m
180 | => SourceFormat
181 | -> Renderer.Options
182 | -> Handle
183 | -> Handle
184 | -> m ()
185 | transformHandleToHandle sourceFormat rendererOpts source target =
186 | runIO sourceFormat rendererOpts (sourceHandle source) (sinkHandle target)
187 |
188 | -- | Transform from a 'Handle' to a file
189 | --
190 | -- @since 0.0.1.0
191 | transformHandleToFile
192 | :: MonadUnliftIO m
193 | => SourceFormat
194 | -> Renderer.Options
195 | -> Handle
196 | -> FilePath
197 | -> m ()
198 | transformHandleToFile sourceFormat rendererOpts source target =
199 | runResource sourceFormat rendererOpts
200 | (sourceHandle source)
201 | (sinkFile target)
202 |
203 | -- | Transform from a file to lazy 'TL.Text'
204 | --
205 | -- @since 0.0.1.0
206 | transformFileToText
207 | :: MonadUnliftIO m
208 | => SourceFormat
209 | -> Renderer.Options
210 | -> FilePath
211 | -> m TL.Text
212 | transformFileToText sourceFormat rendererOpts source =
213 | runResource sourceFormat rendererOpts (sourceFile source) sinkLazyText
214 |
215 | -- | Transform from a file to a 'Handle'
216 | --
217 | -- @since 0.0.1.0
218 | transformFileToHandle
219 | :: MonadUnliftIO m
220 | => SourceFormat
221 | -> Renderer.Options
222 | -> FilePath
223 | -> Handle
224 | -> m ()
225 | transformFileToHandle sourceFormat rendererOpts source target =
226 | runResource sourceFormat rendererOpts
227 | (sourceFile source)
228 | (sinkHandle target)
229 |
230 | -- | Transform from a file to a file
231 | --
232 | -- @since 0.0.1.0
233 | transformFileToFile
234 | :: MonadUnliftIO m
235 | => SourceFormat
236 | -> Renderer.Options
237 | -> FilePath
238 | -> FilePath
239 | -> m ()
240 | transformFileToFile sourceFormat rendererOpts source target =
241 | runResource sourceFormat rendererOpts
242 | (sourceFile source)
243 | (sinkFile target)
244 |
245 | ------------------------------------------------------------------------------
246 | -- $LowLevelAPI
247 | --
248 | -- This low-level API provides more control over transforming literate source
249 | -- code, using "Conduit". The 'transform' transformer implements the
250 | -- transformation, transforming lines of input 'T.Text' to lines of output
251 | -- 'T.Text'. Various producers are provided to produce lines of input
252 | -- 'T.Text' from common sources, and various consumers are provided to consume
253 | -- lines of output 'T.Text' to common sinks. These can be used separately if
254 | -- necessary, but some run functions are provided for common usage.
255 | --
256 | -- Example usage:
257 | --
258 | -- @
259 | -- {-\# LANGUAGE OverloadedStrings \#-}
260 | --
261 | -- module Main (main) where
262 | --
263 | -- -- https://hackage.haskell.org/package/base
264 | -- import System.IO (stdout)
265 | --
266 | -- -- https://hackage.haskell.org/package/literatex
267 | -- import qualified LiterateX
268 | -- import qualified LiterateX.Renderer as Renderer
269 | -- import qualified LiterateX.Types.SourceFormat as SourceFormat
270 | --
271 | -- main :: IO ()
272 | -- main = do
273 | -- let demoBS = "..."
274 | -- LiterateX.runIO
275 | -- SourceFormat.LiterateHaskell
276 | -- (Renderer.defaultOptionsFor "haskell")
277 | -- (LiterateX.sourceByteString demoBS)
278 | -- (LiterateX.sinkHandle stdout)
279 | -- @
280 |
281 | -- | Run a pure LiterateX transformation
282 | --
283 | -- This function works with the following input line producers:
284 | --
285 | -- * 'sourceString'
286 | -- * 'sourceText'
287 | -- * 'sourceLazyText'
288 | -- * 'sourceByteString'
289 | -- * 'sourceLazyByteString'
290 | --
291 | -- This function works with the following output line consumers:
292 | --
293 | -- * 'sinkString'
294 | -- * 'sinkText'
295 | -- * 'sinkLazyText'
296 | -- * 'sinkByteString'
297 | -- * 'sinkLazyByteString'
298 | --
299 | -- @since 0.0.1.0
300 | runPure
301 | :: SourceFormat
302 | -> Renderer.Options
303 | -> C.ConduitT () T.Text C.Identity () -- ^ input line producer
304 | -> C.ConduitT T.Text C.Void C.Identity r -- ^ output line consumer
305 | -> r
306 | runPure sourceFormat rendererOpts source sink = C.runConduitPure $
307 | source .| transform sourceFormat rendererOpts .| sink
308 |
309 | -- | Run a LiterateX transformation using IO
310 | --
311 | -- This function works with the following input line producers:
312 | --
313 | -- * 'sourceString'
314 | -- * 'sourceText'
315 | -- * 'sourceLazyText'
316 | -- * 'sourceByteString'
317 | -- * 'sourceLazyByteString'
318 | -- * 'LiterateX.sourceHandle'
319 | --
320 | -- This function works with the following output line consumers:
321 | --
322 | -- * 'sinkString'
323 | -- * 'sinkText'
324 | -- * 'sinkLazyText'
325 | -- * 'sinkByteString'
326 | -- * 'sinkLazyByteString'
327 | -- * 'LiterateX.sinkHandle'
328 | --
329 | -- @since 0.0.1.0
330 | runIO
331 | :: MonadIO m
332 | => SourceFormat
333 | -> Renderer.Options
334 | -> C.ConduitT () T.Text m () -- ^ input line producer
335 | -> C.ConduitT T.Text C.Void m r -- ^ output line consumer
336 | -> m r
337 | runIO sourceFormat rendererOpts source sink = C.runConduit $
338 | source .| transform sourceFormat rendererOpts .| sink
339 |
340 | -- | Run a LiterateX transformation using resource management
341 | --
342 | -- This function works with the following input line producers:
343 | --
344 | -- * 'sourceString'
345 | -- * 'sourceText'
346 | -- * 'sourceLazyText'
347 | -- * 'sourceByteString'
348 | -- * 'sourceLazyByteString'
349 | -- * 'LiterateX.sourceHandle'
350 | -- * 'LiterateX.sourceFile'
351 | --
352 | -- This function works with the following output line consumers:
353 | --
354 | -- * 'sinkString'
355 | -- * 'sinkText'
356 | -- * 'sinkLazyText'
357 | -- * 'sinkByteString'
358 | -- * 'sinkLazyByteString'
359 | -- * 'LiterateX.sinkHandle'
360 | -- * 'LiterateX.sinkFile'
361 | --
362 | -- @since 0.0.1.0
363 | runResource
364 | :: MonadUnliftIO m
365 | => SourceFormat
366 | -> Renderer.Options
367 | -> C.ConduitT () T.Text (C.ResourceT m) () -- ^ input line producer
368 | -> C.ConduitT T.Text C.Void (C.ResourceT m) r -- ^ output line consumer
369 | -> m r
370 | runResource sourceFormat rendererOpts source sink = C.runConduitRes $
371 | source .| transform sourceFormat rendererOpts .| sink
372 |
373 | ------------------------------------------------------------------------------
374 | -- $Producers
375 |
376 | -- | Produce input lines from a 'String' source
377 | --
378 | -- @since 0.0.1.0
379 | sourceString
380 | :: Monad m
381 | => String
382 | -> C.ConduitT i T.Text m ()
383 | sourceString = CC.yieldMany . map T.pack . lines
384 |
385 | -- | Produce input lines from a 'T.Text' source
386 | --
387 | -- @since 0.0.1.0
388 | sourceText
389 | :: Monad m
390 | => T.Text
391 | -> C.ConduitT i T.Text m ()
392 | sourceText source = C.yield source .| CC.linesUnbounded
393 |
394 | -- | Produce input lines from a lazy 'TL.Text' source
395 | --
396 | -- @since 0.0.1.0
397 | sourceLazyText
398 | :: Monad m
399 | => TL.Text
400 | -> C.ConduitT i T.Text m ()
401 | sourceLazyText source = CC.sourceLazy source .| CC.linesUnbounded
402 |
403 | -- | Produce input lines from a 'BS.ByteString' source
404 | --
405 | -- @since 0.0.1.0
406 | sourceByteString
407 | :: Monad m
408 | => BS.ByteString
409 | -> C.ConduitT i T.Text m ()
410 | sourceByteString source =
411 | C.yield source .| CC.linesUnboundedAscii .| decodeUtf8LinesLenient
412 |
413 | -- | Produce input lines from a lazy 'BS.ByteString' source
414 | --
415 | -- @since 0.0.1.0
416 | sourceLazyByteString
417 | :: Monad m
418 | => BSL.ByteString
419 | -> C.ConduitT i T.Text m ()
420 | sourceLazyByteString source =
421 | CC.sourceLazy source .| CC.linesUnboundedAscii .| decodeUtf8LinesLenient
422 |
423 | -- | Produce input lines from a 'Handle' source
424 | --
425 | -- @since 0.0.1.0
426 | sourceHandle
427 | :: MonadIO m
428 | => Handle
429 | -> C.ConduitT i T.Text m ()
430 | sourceHandle source =
431 | CC.sourceHandle source .| CC.linesUnboundedAscii .| decodeUtf8LinesLenient
432 |
433 | -- | Produce input lines from a file source
434 | --
435 | -- @since 0.0.1.0
436 | sourceFile
437 | :: C.MonadResource m
438 | => FilePath
439 | -> C.ConduitT i T.Text m ()
440 | sourceFile source =
441 | CC.sourceFile source .| CC.linesUnboundedAscii .| decodeUtf8LinesLenient
442 |
443 | ------------------------------------------------------------------------------
444 | -- $Consumers
445 |
446 | -- | Consume output lines, returning a 'String'
447 | --
448 | -- @since 0.0.1.0
449 | sinkString
450 | :: Monad m
451 | => C.ConduitT T.Text o m String
452 | sinkString = fmap TL.unpack $ CC.unlines .| CC.sinkLazy
453 |
454 | -- | Consume output lines, returning 'T.Text'
455 | --
456 | -- @since 0.0.1.0
457 | sinkText
458 | :: Monad m
459 | => C.ConduitT T.Text o m T.Text
460 | sinkText = fmap TL.toStrict $ CC.unlines .| CC.sinkLazy
461 |
462 | -- | Consume output lines, returning lazy 'T.Text'
463 | --
464 | -- @since 0.0.1.0
465 | sinkLazyText
466 | :: Monad m
467 | => C.ConduitT T.Text o m TL.Text
468 | sinkLazyText = CC.unlines .| CC.sinkLazy
469 |
470 | -- | Consume output lines, returning a 'BS.ByteString'
471 | --
472 | -- @since 0.0.1.0
473 | sinkByteString
474 | :: Monad m
475 | => C.ConduitT T.Text o m BS.ByteString
476 | sinkByteString =
477 | fmap BSL.toStrict $ CC.unlines .| CC.encodeUtf8 .| CC.sinkLazy
478 |
479 | -- | Consume output lines, returning a lazy 'BS.ByteString'
480 | --
481 | -- @since 0.0.1.0
482 | sinkLazyByteString
483 | :: Monad m
484 | => C.ConduitT T.Text o m BSL.ByteString
485 | sinkLazyByteString = CC.unlines .| CC.encodeUtf8 .| CC.sinkLazy
486 |
487 | -- | Consume output lines, writing to a 'Handle'
488 | --
489 | -- @since 0.0.1.0
490 | sinkHandle
491 | :: MonadIO m
492 | => Handle
493 | -> C.ConduitT T.Text o m ()
494 | sinkHandle target = CC.encodeUtf8 .| CC.unlinesAscii .| CC.sinkHandle target
495 |
496 | -- | Consume output lines, writing to a file
497 | --
498 | -- @since 0.0.1.0
499 | sinkFile
500 | :: C.MonadResource m
501 | => FilePath
502 | -> C.ConduitT T.Text o m ()
503 | sinkFile target = CC.encodeUtf8 .| CC.unlinesAscii .| CC.sinkFile target
504 |
505 | ------------------------------------------------------------------------------
506 | -- $Transformers
507 |
508 | -- | Transform input lines to output lines
509 | --
510 | -- @since 0.0.1.0
511 | transform
512 | :: Monad m
513 | => SourceFormat
514 | -> Renderer.Options
515 | -> C.ConduitT T.Text T.Text m ()
516 | transform sourceFormat rendererOpts
517 | = Parser.parse sourceFormat
518 | .| CL.concatMapAccum (\x n -> (n + 1, [(n, x)])) 1
519 | .| Renderer.render rendererOpts
520 |
521 | ------------------------------------------------------------------------------
522 | -- $Internal
523 |
524 | -- | Decode UTF-8 'BS.ByteString' lines to 'T.Text'
525 | --
526 | -- Note that 'CC.decodeUtf8Lenient' decodes UTF-8 streams and therefore
527 | -- discards empty strings. This version decodes UTF-8 lines and does not
528 | -- discard empty lines.
529 | decodeUtf8LinesLenient
530 | :: Monad m
531 | => C.ConduitT BS.ByteString T.Text m ()
532 | decodeUtf8LinesLenient =
533 | C.awaitForever $ C.yield . TE.decodeUtf8With TEE.lenientDecode
534 |
--------------------------------------------------------------------------------
/src/LiterateX/Parser.hs:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------
2 | -- |
3 | -- Module : LiterateX.Parser
4 | -- Description : source parser
5 | -- Copyright : Copyright (c) 2021-2025 Travis Cardwell
6 | -- License : MIT
7 | --
8 | -- This module implements the source parser.
9 | ------------------------------------------------------------------------------
10 |
11 | {-# LANGUAGE LambdaCase #-}
12 | {-# LANGUAGE OverloadedStrings #-}
13 |
14 | module LiterateX.Parser
15 | ( -- * API
16 | parse
17 | ) where
18 |
19 | -- https://hackage.haskell.org/package/conduit
20 | import qualified Data.Conduit as C
21 | import Data.Conduit (ConduitT)
22 |
23 | -- https://hackage.haskell.org/package/text
24 | import qualified Data.Text as T
25 | import Data.Text (Text)
26 |
27 | -- (literatex)
28 | import LiterateX.Types (SourceFormat, SourceLine)
29 | import qualified LiterateX.Types.SourceFormat as SourceFormat
30 | import qualified LiterateX.Types.SourceLine as SourceLine
31 |
32 | ------------------------------------------------------------------------------
33 | -- $API
34 |
35 | -- | Create a "Conduit" transformer that parses the specified source format
36 | --
37 | -- The transformer consumes lines of the input and produces a 'SourceLine' for
38 | -- each line of input.
39 | --
40 | -- @since 0.0.1.0
41 | parse
42 | :: Monad m
43 | => SourceFormat
44 | -> ConduitT Text SourceLine m ()
45 | parse sourceFormat = do
46 | let parseLine' = parseLine sourceFormat
47 | mLine <- C.await
48 | case mLine of
49 | Just line -> do
50 | C.yield $ if "#!" `T.isPrefixOf` line
51 | then SourceLine.Shebang line
52 | else parseLine' line
53 | C.awaitForever $ C.yield . parseLine'
54 | Nothing -> return ()
55 |
56 | ------------------------------------------------------------------------------
57 | -- $Internal
58 |
59 | -- | Parse a source line according to the source format
60 | parseLine :: SourceFormat -> Text -> SourceLine
61 | parseLine = \case
62 | SourceFormat.DoubleDash -> parseLineCommentLine '-' 2
63 | SourceFormat.DoubleSlash -> parseLineCommentLine '/' 2
64 | SourceFormat.Hash -> parseLineCommentLine '#' 1
65 | SourceFormat.LiterateHaskell -> parseLiterateHaskellLine
66 | SourceFormat.Percent -> parseLineCommentLine '%' 1
67 | SourceFormat.LispSemicolons -> parseLispCommentLine
68 |
69 | ------------------------------------------------------------------------------
70 |
71 | -- | Parse a source line using line-based comments
72 | parseLineCommentLine
73 | :: Char -- ^ comment character
74 | -> Int -- ^ number of comment characters to create line comment
75 | -> Text -- ^ source line
76 | -> SourceLine
77 | parseLineCommentLine char count line
78 | | T.null line = SourceLine.CodeBlank
79 | | otherwise = case T.uncons <$> T.span (== char) line of
80 | ("", _) -> SourceLine.Code line
81 | (_, Nothing) -> case T.compareLength line count of
82 | EQ -> SourceLine.DocBlank
83 | GT -> SourceLine.Rule
84 | LT -> SourceLine.Code line
85 | (l, Just (' ', r)) | T.compareLength l count == EQ -> SourceLine.Doc r
86 | _otherwise -> SourceLine.Code line
87 |
88 | ------------------------------------------------------------------------------
89 |
90 | -- | Parse a source line using Lisp-style comments
91 | --
92 | -- Lisp-style comments begin with one or more semicolons.
93 | parseLispCommentLine
94 | :: Text -- ^ source line
95 | -> SourceLine
96 | parseLispCommentLine line
97 | | T.null line = SourceLine.CodeBlank
98 | | otherwise = case T.uncons <$> T.span (== ';') line of
99 | ("", _) -> SourceLine.Code line
100 | (_, Nothing)
101 | | T.compareLength line 4 == GT -> SourceLine.Rule
102 | | otherwise -> SourceLine.DocBlank
103 | (_, Just (' ', r)) -> SourceLine.Doc r
104 | _otherwise -> SourceLine.Code line
105 |
106 | ------------------------------------------------------------------------------
107 |
108 | -- | Parse a Literate Haskell source line
109 | parseLiterateHaskellLine
110 | :: Text -- ^ source line
111 | -> SourceLine
112 | parseLiterateHaskellLine line = case T.uncons line of
113 | Nothing -> SourceLine.DocBlank
114 | Just ('>', r1) -> case T.uncons r1 of
115 | Nothing -> SourceLine.CodeBlank
116 | Just (' ', r2) -> SourceLine.Code r2
117 | _otherwise -> SourceLine.Doc line
118 | _otherwise -> SourceLine.Doc line
119 |
--------------------------------------------------------------------------------
/src/LiterateX/Renderer.hs:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------
2 | -- |
3 | -- Module : LiterateX.Renderer
4 | -- Description : target renderer
5 | -- Copyright : Copyright (c) 2021-2025 Travis Cardwell
6 | -- License : MIT
7 | --
8 | -- This module implements the target renderer.
9 | ------------------------------------------------------------------------------
10 |
11 | {-# LANGUAGE BangPatterns #-}
12 | {-# LANGUAGE RecordWildCards #-}
13 | {-# LANGUAGE ScopedTypeVariables #-}
14 |
15 | module LiterateX.Renderer
16 | ( -- * Types
17 | Options(..)
18 | -- * API
19 | , defaultOptions
20 | , defaultOptionsFor
21 | , render
22 | ) where
23 |
24 | -- https://hackage.haskell.org/package/base
25 | import Control.Monad (replicateM_)
26 |
27 | -- https://hackage.haskell.org/package/conduit
28 | import qualified Data.Conduit as C
29 | import Data.Conduit (ConduitT)
30 |
31 | -- https://hackage.haskell.org/package/text
32 | import qualified Data.Text as T
33 | import Data.Text (Text)
34 |
35 | -- (literatex)
36 | import LiterateX.Types (CodeLanguage, SourceLine, TargetFormat)
37 | import qualified LiterateX.Types.SourceLine as SourceLine
38 | import qualified LiterateX.Types.TargetFormat as TargetFormat
39 |
40 | ------------------------------------------------------------------------------
41 | -- $Types
42 |
43 | -- | Renderer options determine how output is rendered
44 | --
45 | -- @since 0.0.1.0
46 | data Options
47 | = Options
48 | { targetFormat :: !TargetFormat
49 | , codeLanguage :: !(Maybe CodeLanguage)
50 | , ignoreShebang :: !Bool -- ^ 'True' to ignore shebangs
51 | , renderCode :: !Bool -- ^ 'True' to render code
52 | , numberCodeLines :: !Bool -- ^ 'True' to number code lines
53 | }
54 | deriving Show
55 |
56 | ------------------------------------------------------------------------------
57 | -- $API
58 |
59 | -- | Default options
60 | --
61 | -- * @targetFormat@: 'TargetFormat.PandocMarkdown'
62 | -- * @codeLanguage@: 'Nothing'
63 | -- * @ignoreShebang@: 'True'
64 | -- * @renderCode@: 'True'
65 | -- * @numberCodeLines@: 'True'
66 | --
67 | -- @since 0.0.1.0
68 | defaultOptions :: Options
69 | defaultOptions = Options
70 | { targetFormat = TargetFormat.PandocMarkdown
71 | , codeLanguage = Nothing
72 | , ignoreShebang = True
73 | , renderCode = True
74 | , numberCodeLines = True
75 | }
76 |
77 | -- | Default options for the specified code language
78 | --
79 | -- @since 0.0.1.0
80 | defaultOptionsFor :: CodeLanguage -> Options
81 | defaultOptionsFor lang = defaultOptions
82 | { codeLanguage = Just lang
83 | }
84 |
85 | -- | Create a "Conduit" transformer that renders using the specified options
86 | --
87 | -- The transformer consumes 'SourceLine' values annotated with the line number
88 | -- and produces lines of output.
89 | --
90 | -- @since 0.0.1.0
91 | render
92 | :: forall m. Monad m
93 | => Options
94 | -> ConduitT (Int, SourceLine) Text m ()
95 | render Options{..} = C.await >>= sStart
96 | where
97 | -- Start state
98 | sStart
99 | :: Maybe (Int, SourceLine)
100 | -> ConduitT (Int, SourceLine) Text m ()
101 | sStart (Just (lineNum, sourceLine)) = case sourceLine of
102 | -- render shebang if enabled, otherwise skip
103 | SourceLine.Shebang line
104 | | renderCode && not ignoreShebang -> do
105 | C.yield $ startShebang lineNum
106 | C.yield line
107 | C.yield endCode
108 | C.await >>= sBlank
109 | | otherwise -> C.await >>= sStart
110 | -- start rendering documentation
111 | SourceLine.Doc line -> do
112 | C.yield line
113 | C.await >>= sDoc
114 | -- start rendering code if enabled, otherwise skip
115 | SourceLine.Code line
116 | | renderCode -> do
117 | C.yield $ startCode lineNum
118 | C.yield line
119 | C.await >>= sCode 0
120 | | otherwise -> C.await >>= sStart
121 | -- skip blank lines and rules
122 | _BlankOrRule -> C.await >>= sStart
123 | sStart Nothing = return ()
124 |
125 | -- Code state
126 | sCode
127 | :: Int -- ^ number of pending (code) blank lines
128 | -> Maybe (Int, SourceLine)
129 | -> ConduitT (Int, SourceLine) Text m ()
130 | sCode !numBlanks (Just (_lineNum, sourceLine)) = case sourceLine of
131 | -- render any pending blank lines and code
132 | SourceLine.Code line -> do
133 | replicateM_ numBlanks $ C.yield T.empty
134 | C.yield line
135 | C.await >>= sCode 0
136 | -- increment number of pending blank lines
137 | SourceLine.CodeBlank -> C.await >>= sCode (numBlanks + 1)
138 | -- end code, add blank line, start rendering documentation
139 | SourceLine.Doc line -> do
140 | C.yield endCode
141 | C.yield T.empty
142 | C.yield line
143 | C.await >>= sDoc
144 | -- end code, enter blank state
145 | SourceLine.DocBlank -> do
146 | C.yield endCode
147 | C.await >>= sBlank
148 | -- end code, enter blank state
149 | SourceLine.Rule -> do
150 | C.yield endCode
151 | C.await >>= sBlank
152 | -- shebang only possible on first line (seen in start state)
153 | SourceLine.Shebang _line -> error "impossible: Shebang in sCode"
154 | sCode _numBlanks Nothing = C.yield endCode
155 |
156 | -- Documentation state
157 | sDoc
158 | :: Maybe (Int, SourceLine)
159 | -> ConduitT (Int, SourceLine) Text m ()
160 | sDoc (Just (lineNum, sourceLine)) = case sourceLine of
161 | -- render documentation
162 | SourceLine.Doc line -> do
163 | C.yield line
164 | C.await >>= sDoc
165 | -- start rendering code if enabled, otherwise enter blank state
166 | SourceLine.Code line
167 | | renderCode -> do
168 | C.yield T.empty
169 | C.yield $ startCode lineNum
170 | C.yield line
171 | C.await >>= sCode 0
172 | | otherwise -> C.await >>= sBlank
173 | -- shebang only possible on first line (seen in start state)
174 | SourceLine.Shebang _line -> error "impossible: Shebang in sDoc"
175 | -- blank lines and rules transition to blank state
176 | _BlankOrRule -> C.await >>= sBlank
177 | sDoc Nothing = return ()
178 |
179 | -- Blank state
180 | sBlank
181 | :: Maybe (Int, SourceLine)
182 | -> ConduitT (Int, SourceLine) Text m ()
183 | sBlank (Just (lineNum, sourceLine)) = case sourceLine of
184 | -- start rendering code if enabled, otherwise skip
185 | SourceLine.Code line
186 | | renderCode -> do
187 | C.yield T.empty
188 | C.yield $ startCode lineNum
189 | C.yield line
190 | C.await >>= sCode 0
191 | | otherwise -> C.await >>= sBlank
192 | -- render blank line, start rendering documentation
193 | SourceLine.Doc line -> do
194 | C.yield T.empty
195 | C.yield line
196 | C.await >>= sDoc
197 | -- shebang only possible on first line (seen in start state)
198 | SourceLine.Shebang _line -> error "impossible: Shebang in sBlank"
199 | -- skip additional blank lines and rules
200 | _BlankOrRule -> C.await >>= sBlank
201 | sBlank Nothing = return ()
202 |
203 | -- start code line for code starting at given line number
204 | startCode :: Int -> Text
205 | startCode =
206 | TargetFormat.mkBeginCode targetFormat codeLanguage numberCodeLines
207 |
208 | -- shebang start code line does not use syntax highlighting
209 | startShebang :: Int -> Text
210 | startShebang =
211 | TargetFormat.mkBeginCode targetFormat Nothing numberCodeLines
212 |
213 | -- end code line
214 | endCode :: Text
215 | endCode = TargetFormat.mkEndCode targetFormat
216 | {-# ANN render ("HLint: ignore Reduce duplication" :: String) #-}
217 |
--------------------------------------------------------------------------------
/src/LiterateX/SourceDefaults.hs:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------
2 | -- |
3 | -- Module : LiterateX.SourceDefaults
4 | -- Description : default options by source extension
5 | -- Copyright : Copyright (c) 2021-2025 Travis Cardwell
6 | -- License : MIT
7 | --
8 | -- This module provides some default options for various sources.
9 | ------------------------------------------------------------------------------
10 |
11 | module LiterateX.SourceDefaults
12 | ( -- * API
13 | defaultsFor
14 | , extensionDefaults
15 | ) where
16 |
17 | -- https://hackage.haskell.org/package/base
18 | import Data.List (find, isSuffixOf)
19 |
20 | -- (literatex)
21 | import LiterateX.Types (CodeLanguage, SourceFormat)
22 | import qualified LiterateX.Types.SourceFormat as SourceFormat
23 |
24 | ------------------------------------------------------------------------------
25 | -- $API
26 |
27 | -- | Get the default source format and code language for the given filename
28 | --
29 | -- @since 0.0.1.0
30 | defaultsFor :: FilePath -> Maybe (SourceFormat, CodeLanguage)
31 | defaultsFor path =
32 | fmap snd . flip find extensionDefaults $ flip isSuffixOf path . fst
33 |
34 | ------------------------------------------------------------------------------
35 |
36 | -- | List of default options for various filename extensions
37 | --
38 | -- @since 0.0.1.0
39 | extensionDefaults :: [(String, (SourceFormat, CodeLanguage))]
40 | extensionDefaults =
41 | [ (".c", (SourceFormat.DoubleSlash, "c"))
42 | , (".clj", (SourceFormat.LispSemicolons, "clojure"))
43 | , (".css", (SourceFormat.DoubleSlash, "css"))
44 | , (".elm", (SourceFormat.DoubleDash, "elm"))
45 | , (".erl", (SourceFormat.Percent, "erlang"))
46 | , (".ex", (SourceFormat.Hash, "elixir"))
47 | , (".exs", (SourceFormat.Hash, "elixir"))
48 | , (".go", (SourceFormat.DoubleSlash, "go"))
49 | , (".hs", (SourceFormat.DoubleDash, "haskell"))
50 | , (".idr", (SourceFormat.DoubleDash, "idris"))
51 | , (".java", (SourceFormat.DoubleSlash, "java"))
52 | , (".js", (SourceFormat.DoubleSlash, "javascript"))
53 | , (".kt", (SourceFormat.DoubleSlash, "kotlin"))
54 | , (".lhs", (SourceFormat.LiterateHaskell, "haskell"))
55 | , (".lisp", (SourceFormat.LispSemicolons, "commonlisp"))
56 | , (".lua", (SourceFormat.DoubleDash, "lua"))
57 | , (".php", (SourceFormat.DoubleSlash, "php"))
58 | , (".pl", (SourceFormat.Hash, "perl"))
59 | , (".py", (SourceFormat.Hash, "python"))
60 | , (".r", (SourceFormat.Hash, "r"))
61 | , (".rb", (SourceFormat.Hash, "ruby"))
62 | , (".rkt", (SourceFormat.LispSemicolons, "scheme"))
63 | , (".rs", (SourceFormat.DoubleSlash, "rust"))
64 | , (".sc", (SourceFormat.DoubleSlash, "scala"))
65 | , (".scm", (SourceFormat.LispSemicolons, "scheme"))
66 | , (".sh", (SourceFormat.Hash, "bash"))
67 | , (".sql", (SourceFormat.DoubleDash, "sql"))
68 | , (".tex", (SourceFormat.Percent, "latex"))
69 | , (".ts", (SourceFormat.DoubleSlash, "typescript"))
70 | ]
71 |
--------------------------------------------------------------------------------
/src/LiterateX/Types.hs:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------
2 | -- |
3 | -- Module : LiterateX.Types
4 | -- Description : type re-exports for convenience
5 | -- Copyright : Copyright (c) 2021-2025 Travis Cardwell
6 | -- License : MIT
7 | --
8 | -- The type modules are generally imported qualified, so the types are
9 | -- re-exported by this module for convenience.
10 | ------------------------------------------------------------------------------
11 |
12 | module LiterateX.Types
13 | ( module LiterateX.Types.CodeLanguage
14 | , module LiterateX.Types.SourceFormat
15 | , module LiterateX.Types.SourceLine
16 | , module LiterateX.Types.TargetFormat
17 | ) where
18 |
19 | -- (literatex)
20 | import LiterateX.Types.CodeLanguage (CodeLanguage)
21 | import LiterateX.Types.SourceFormat (SourceFormat)
22 | import LiterateX.Types.SourceLine (SourceLine)
23 | import LiterateX.Types.TargetFormat (TargetFormat)
24 |
--------------------------------------------------------------------------------
/src/LiterateX/Types/CodeLanguage.hs:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------
2 | -- |
3 | -- Module : LiterateX.Types.CodeLanguage
4 | -- Description : source code language type
5 | -- Copyright : Copyright (c) 2021-2025 Travis Cardwell
6 | -- License : MIT
7 | ------------------------------------------------------------------------------
8 |
9 | module LiterateX.Types.CodeLanguage
10 | ( -- * Type
11 | CodeLanguage
12 | ) where
13 |
14 | -- https://hackage.haskell.org/package/base
15 | import Control.Monad (when)
16 | import Data.Bifunctor (first)
17 | import Data.Char (isSpace)
18 | import Data.String (IsString(fromString))
19 |
20 | -- https://hackage.haskell.org/package/text
21 | import qualified Data.Text as T
22 | import Data.Text (Text)
23 |
24 | -- https://hackage.haskell.org/package/ttc
25 | import qualified Data.TTC as TTC
26 |
27 | ------------------------------------------------------------------------------
28 | -- $Type
29 |
30 | -- | Source code language
31 | --
32 | -- This string is used for syntax highlighting of source code.
33 | --
34 | -- When using Pandoc Markdown, run the following command to see a list of
35 | -- supported languages:
36 | --
37 | -- @
38 | -- \$ pandoc --list-highlight-languages
39 | -- @
40 | --
41 | -- When using GitHub Flavored Markdown, check the following file for supported
42 | -- languages:
43 | --
44 | --
45 | --
46 | -- @since 0.0.1.0
47 | newtype CodeLanguage = CodeLanguage { unCodeLanguage :: Text }
48 | deriving (Eq, Ord, Show)
49 |
50 | instance IsString CodeLanguage where
51 | fromString = either error id . TTC.parse
52 |
53 | instance TTC.Parse CodeLanguage where
54 | parse = TTC.asT $ \t -> first TTC.fromS $ do
55 | when (T.null t) $ Left "invalid code language: empty"
56 | when (T.any isSpace t) $ Left "invalid code language: contains whitespace"
57 | return $ CodeLanguage t
58 |
59 | instance TTC.Render CodeLanguage where
60 | render = TTC.fromT . unCodeLanguage
61 |
--------------------------------------------------------------------------------
/src/LiterateX/Types/SourceFormat.hs:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------
2 | -- |
3 | -- Module : LiterateX.Types.SourceFormat
4 | -- Description : source format type
5 | -- Copyright : Copyright (c) 2021-2025 Travis Cardwell
6 | -- License : MIT
7 | ------------------------------------------------------------------------------
8 |
9 | {-# LANGUAGE LambdaCase #-}
10 |
11 | module LiterateX.Types.SourceFormat
12 | ( -- * Type
13 | SourceFormat(..)
14 | -- * API
15 | , describe
16 | , list
17 | ) where
18 |
19 | -- https://hackage.haskell.org/package/ttc
20 | import qualified Data.TTC as TTC
21 |
22 | ------------------------------------------------------------------------------
23 | -- $Type
24 |
25 | -- | Source format
26 | --
27 | -- This sum type defines the supported source formats.
28 | --
29 | -- @since 0.0.1.0
30 | data SourceFormat
31 | = DoubleDash -- ^ \-- comments
32 | | DoubleSlash -- ^ // comments
33 | | Hash -- ^ # comments
34 | | LispSemicolons -- ^ Lisp semicolon comments
35 | | LiterateHaskell -- ^ literate Haskell
36 | | Percent -- ^ % comments
37 | deriving (Bounded, Enum, Eq, Ord, Show)
38 |
39 | instance TTC.Parse SourceFormat where
40 | parse = TTC.parseEnum' "source format" True False
41 |
42 | instance TTC.Render SourceFormat where
43 | render = TTC.fromS . \case
44 | DoubleDash -> "ddash"
45 | DoubleSlash -> "dslash"
46 | Hash -> "hash"
47 | LispSemicolons -> "lisp"
48 | LiterateHaskell -> "lhs"
49 | Percent -> "percent"
50 |
51 | ------------------------------------------------------------------------------
52 | -- $API
53 |
54 | -- | Get a description of a source format
55 | --
56 | -- @since 0.0.1.0
57 | describe :: SourceFormat -> String
58 | describe = \case
59 | DoubleDash -> "-- comments"
60 | DoubleSlash -> "// comments"
61 | Hash -> "# comments"
62 | LispSemicolons -> "Lisp semicolon comments"
63 | LiterateHaskell -> "literate Haskell"
64 | Percent -> "% comments"
65 |
66 | ------------------------------------------------------------------------------
67 |
68 | -- | List of all supported source formats
69 | --
70 | -- @since 0.0.1.0
71 | list :: [SourceFormat]
72 | list = [minBound ..]
73 |
--------------------------------------------------------------------------------
/src/LiterateX/Types/SourceLine.hs:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------
2 | -- |
3 | -- Module : LiterateX.Types.SourceLine
4 | -- Description : source line type
5 | -- Copyright : Copyright (c) 2021-2025 Travis Cardwell
6 | -- License : MIT
7 | ------------------------------------------------------------------------------
8 |
9 | module LiterateX.Types.SourceLine
10 | ( -- * Type
11 | SourceLine(..)
12 | ) where
13 |
14 | -- https://hackage.haskell.org/package/text
15 | import Data.Text (Text)
16 |
17 | ------------------------------------------------------------------------------
18 | -- $Type
19 |
20 | -- | Parsed source line
21 | --
22 | -- @since 0.0.1.0
23 | data SourceLine
24 | = Shebang !Text -- ^ script shebang on first line
25 | | CodeBlank -- ^ code blank line
26 | | DocBlank -- ^ documentation blank line
27 | | Rule -- ^ comment rule used to organize source code
28 | | Doc !Text -- ^ documentation line
29 | | Code !Text -- ^ code line
30 | deriving Show
31 |
--------------------------------------------------------------------------------
/src/LiterateX/Types/TargetFormat.hs:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------
2 | -- |
3 | -- Module : LiterateX.Types.TargetFormat
4 | -- Description : target format type
5 | -- Copyright : Copyright (c) 2021-2025 Travis Cardwell
6 | -- License : MIT
7 | ------------------------------------------------------------------------------
8 |
9 | {-# LANGUAGE LambdaCase #-}
10 | {-# LANGUAGE OverloadedStrings #-}
11 |
12 | module LiterateX.Types.TargetFormat
13 | ( -- * Type
14 | TargetFormat(..)
15 | -- * API
16 | , describe
17 | , list
18 | , mkEndCode
19 | , mkBeginCode
20 | ) where
21 |
22 | -- https://hackage.haskell.org/package/text
23 | import qualified Data.Text as T
24 | import Data.Text (Text)
25 |
26 | -- https://hackage.haskell.org/package/ttc
27 | import qualified Data.TTC as TTC
28 |
29 | -- (literatex)
30 | import LiterateX.Types.CodeLanguage (CodeLanguage)
31 |
32 | ------------------------------------------------------------------------------
33 | -- $Type
34 |
35 | -- | Target format
36 | --
37 | -- This sum type defines the supported target formats.
38 | --
39 | -- Documentation:
40 | --
41 | -- * [Pandoc Markdown](https://pandoc.org/MANUAL.html#pandocs-markdown)
42 | -- * [GitHub Flavored Markdown](https://github.github.com/gfm/)
43 | -- * [mdBook Markdown](https://rust-lang.github.io/mdBook/)
44 | --
45 | -- @since 0.2.1.0
46 | data TargetFormat
47 | = PandocMarkdown
48 | | GitHubFlavoredMarkdown
49 | | MdBook
50 | deriving (Bounded, Enum, Eq, Ord, Show)
51 |
52 | instance TTC.Parse TargetFormat where
53 | parse = TTC.parseEnum' "target format" True False
54 |
55 | instance TTC.Render TargetFormat where
56 | render = TTC.fromS . \case
57 | PandocMarkdown -> "pandoc"
58 | GitHubFlavoredMarkdown -> "github"
59 | MdBook -> "mdbook"
60 |
61 | ------------------------------------------------------------------------------
62 | -- $API
63 |
64 | -- | Get a description of a target format
65 | --
66 | -- @since 0.0.1.0
67 | describe :: TargetFormat -> String
68 | describe = \case
69 | PandocMarkdown -> "Pandoc Markdown"
70 | GitHubFlavoredMarkdown -> "GitHub Flavored Markdown"
71 | MdBook -> "mdBook Markdown"
72 |
73 | ------------------------------------------------------------------------------
74 |
75 | -- | List of all supported target formats
76 | --
77 | -- @since 0.0.1.0
78 | list :: [TargetFormat]
79 | list = [minBound ..]
80 |
81 | ------------------------------------------------------------------------------
82 |
83 | -- | Make line in the target format to end a block of source code
84 | --
85 | -- @since 0.0.1.0
86 | mkEndCode
87 | :: TargetFormat
88 | -> Text
89 | mkEndCode _anyFormat = "```"
90 |
91 | ------------------------------------------------------------------------------
92 |
93 | -- | Make line in the target format to begin a block of code
94 | --
95 | -- This function is written to indicate how it is used. Given the target
96 | -- format, optional code language, and line numbering flag, this function
97 | -- returns a function that takes a line number and returns a line.
98 | --
99 | -- The 'MdBook' format does not support per-block line numbering, so the line
100 | -- numbering flag is ignored for that format.
101 | --
102 | -- @since 0.0.1.0
103 | mkBeginCode
104 | :: TargetFormat
105 | -> Maybe CodeLanguage
106 | -> Bool -- ^ 'True' to number code lines
107 | -> (Int -> Text) -- ^ make line for code starting at specified line number
108 | mkBeginCode PandocMarkdown (Just lang) True = \lineNum -> T.concat
109 | [ "``` {.", TTC.render lang, " .numberSource startFrom=\""
110 | , TTC.renderWithShow lineNum, "\"}"
111 | ]
112 | mkBeginCode PandocMarkdown (Just lang) False = const $ T.concat
113 | [ "```", TTC.render lang
114 | ]
115 | mkBeginCode PandocMarkdown Nothing True = \lineNum -> T.concat
116 | [ "``` {.numberSource startFrom=\"", TTC.renderWithShow lineNum, "\"}"
117 | ]
118 | mkBeginCode GitHubFlavoredMarkdown (Just lang) True = \lineNum -> T.concat
119 | [ "``` ", TTC.render lang, " startline=", TTC.renderWithShow lineNum
120 | ]
121 | mkBeginCode GitHubFlavoredMarkdown (Just lang) False = const $ T.concat
122 | [ "``` ", TTC.render lang
123 | ]
124 | mkBeginCode GitHubFlavoredMarkdown Nothing True = \lineNum -> T.concat
125 | [ "``` startline=", TTC.renderWithShow lineNum
126 | ]
127 | mkBeginCode MdBook (Just lang) _isNumbered = const $ T.concat
128 | [ "```", TTC.render lang
129 | ]
130 | mkBeginCode _anyFormat Nothing _isNumbered = const "```"
131 |
--------------------------------------------------------------------------------
/stack-8.10.7.yaml:
--------------------------------------------------------------------------------
1 | resolver: lts-18.28
2 |
3 | packages:
4 | - .
5 |
--------------------------------------------------------------------------------
/stack-8.8.4.yaml:
--------------------------------------------------------------------------------
1 | resolver: lts-16.31
2 |
3 | packages:
4 | - .
5 |
6 | extra-deps:
7 | - ttc-1.2.1.0
8 |
--------------------------------------------------------------------------------
/stack-9.0.2.yaml:
--------------------------------------------------------------------------------
1 | resolver: lts-19.33
2 |
3 | packages:
4 | - .
5 |
--------------------------------------------------------------------------------
/stack-9.10.1.yaml:
--------------------------------------------------------------------------------
1 | resolver: nightly-2025-01-02
2 |
3 | packages:
4 | - .
5 |
6 | flags:
7 | literatex:
8 | optparse-applicative_ge_0_18: true
9 |
--------------------------------------------------------------------------------
/stack-9.12.1.yaml:
--------------------------------------------------------------------------------
1 | resolver: ghc-9.12.1
2 |
3 | packages:
4 | - .
5 |
6 | flags:
7 | literatex:
8 | optparse-applicative_ge_0_18: true
9 |
10 | extra-deps:
11 | - ansi-terminal-1.1.2
12 | - ansi-terminal-types-1.1
13 | - async-2.2.5
14 | - bitvec-1.1.5.0
15 | - call-stack-0.4.0
16 | - colour-2.3.6
17 | - conduit-1.3.6
18 | - hashable-1.5.0.0
19 | - mono-traversable-1.0.21.0
20 | - optparse-applicative-0.18.1.0
21 | - prettyprinter-1.7.1
22 | - prettyprinter-ansi-terminal-1.1.3
23 | - primitive-0.9.0.0
24 | - random-1.2.1.3
25 | - resourcet-1.3.0
26 | - safe-exceptions-0.1.7.4
27 | - split-0.2.5
28 | - splitmix-0.1.1
29 | - tagged-0.8.9
30 | - tasty-1.5.2
31 | - tasty-hunit-0.10.2
32 | - text-short-0.1.6
33 | - transformers-compat-0.7.2
34 | - ttc-1.5.0.0
35 | - unliftio-0.2.25.0
36 | - unliftio-core-0.2.1.0
37 | - unordered-containers-0.2.20
38 | - vector-0.13.2.0
39 | - vector-algorithms-0.9.0.3
40 | - vector-stream-0.1.0.1
41 |
--------------------------------------------------------------------------------
/stack-9.2.8.yaml:
--------------------------------------------------------------------------------
1 | resolver: lts-20.26
2 |
3 | packages:
4 | - .
5 |
--------------------------------------------------------------------------------
/stack-9.4.8.yaml:
--------------------------------------------------------------------------------
1 | resolver: lts-21.25
2 |
3 | packages:
4 | - .
5 |
--------------------------------------------------------------------------------
/stack-9.6.6.yaml:
--------------------------------------------------------------------------------
1 | resolver: lts-22.43
2 |
3 | packages:
4 | - .
5 |
6 | flags:
7 | literatex:
8 | optparse-applicative_ge_0_18: true
9 |
--------------------------------------------------------------------------------
/stack-9.8.4.yaml:
--------------------------------------------------------------------------------
1 | resolver: lts-23.2
2 |
3 | packages:
4 | - .
5 |
6 | flags:
7 | literatex:
8 | optparse-applicative_ge_0_18: true
9 |
--------------------------------------------------------------------------------
/stack.yaml:
--------------------------------------------------------------------------------
1 | stack-9.8.4.yaml
--------------------------------------------------------------------------------
/test-all.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -o errexit
4 | set -o nounset
5 | set -o pipefail
6 | #set -o xtrace
7 |
8 | if [ "$#" -ne "1" ] ; then
9 | echo "Usage: test-all.sh COMMAND" 1>&2
10 | echo 1>&2
11 | echo "Commands:" 1>&2
12 | echo " cabal test all GHC versions using Cabal" 1>&2
13 | echo " github output GitHub actions matrix configuration" 1>&2
14 | echo " stack test all GHC versions using Stack" 1>&2
15 | exit 2
16 | fi
17 |
18 | ghcvers() {
19 | awk '/^tested-with:$/{p=1;next}/^$/{p=0}p' ./*.cabal | sed 's/^[^=]*==//'
20 | }
21 |
22 | ghcvers_lower() {
23 | grep ^with-compiler: cabal-bounds-lower.project | sed 's/.*-//'
24 | }
25 |
26 | ghcvers_upper() {
27 | grep ^with-compiler: cabal-bounds-upper.project | sed 's/.*-//'
28 | }
29 |
30 | mockhr="$(command -v hr >/dev/null 2>&1 && echo "0" || echo "1")"
31 | hr() {
32 | echo "--{ $* }--------------------------------------"
33 | }
34 | test "${mockhr}" -eq "1" || unset -f hr
35 |
36 | fail() {
37 | echo
38 | echo "Fail!"
39 | exit 1
40 | }
41 |
42 | cabaltest() {
43 | local ghcver="$1"
44 | local projfile="cabal-${ghcver}.project"
45 | hr "${ghcver}"
46 | declare -a args
47 | args+=("--with-ghc=ghc-${ghcver}")
48 | if [ -e "${projfile}" ] ; then
49 | args+=("--project-file=${projfile}")
50 | fi
51 | args+=("--enable-tests")
52 | args+=("--enable-benchmarks")
53 | cabal v2-build "${args[@]}" || fail
54 | cabal v2-test "${args[@]}" --test-show-details=always || fail
55 | cabal v2-haddock "${args[@]}" || fail
56 | cabal v2-build "${args[@]}" exe:literatex -f examples || fail
57 | }
58 |
59 | stacktest() {
60 | local ghcver="$1"
61 | local config="stack-${ghcver}.yaml"
62 | hr "${ghcver}"
63 | declare -a args
64 | args+=("--stack-yaml=${config}")
65 | stack build "${args[@]}" --test --bench --no-run-tests --no-run-benchmarks \
66 | || fail
67 | stack test "${args[@]}" || fail
68 | stack haddock "${args[@]}" || fail
69 | stack build "${args[@]}" --flag literatex:examples || fail
70 | }
71 |
72 | case "$1" in
73 | "cabal" )
74 | while IFS=$'\n' read -r ghcver ; do
75 | cabaltest "${ghcver}"
76 | done < <(ghcvers)
77 | ;;
78 | "github" )
79 | echo "ghcvers=$(ghcvers | jq -Rnc '[inputs]')"
80 | echo "ghcvers_lower=$(ghcvers_lower | jq -Rnc '[inputs]')"
81 | echo "ghcvers_upper=$(ghcvers_upper | jq -Rnc '[inputs]')"
82 | exit 0
83 | ;;
84 | "stack" )
85 | while IFS=$'\n' read -r ghcver ; do
86 | stacktest "${ghcver}"
87 | done < <(ghcvers)
88 | ;;
89 | * )
90 | echo "error: unknown command: $1" 1>&2
91 | exit 2
92 | ;;
93 | esac
94 |
95 | echo
96 | echo "Success!"
97 |
--------------------------------------------------------------------------------
/test/LiterateX/Test/API.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE OverloadedStrings #-}
2 |
3 | module LiterateX.Test.API (tests) where
4 |
5 | -- https://hackage.haskell.org/package/bytestring
6 | import qualified Data.ByteString as BS
7 | import qualified Data.ByteString.Lazy as BSL
8 |
9 | -- https://hackage.haskell.org/package/filepath
10 | import System.FilePath ((>))
11 |
12 | -- https://hackage.haskell.org/package/tasty
13 | import Test.Tasty (TestTree, testGroup)
14 |
15 | -- https://hackage.haskell.org/package/tasty-hunit
16 | import Test.Tasty.HUnit ((@=?), testCase)
17 |
18 | -- https://hackage.haskell.org/package/text
19 | import qualified Data.Text as T
20 | import qualified Data.Text.Lazy as TL
21 |
22 | -- https://hackage.haskell.org/package/ttc
23 | import qualified Data.TTC as TTC
24 |
25 | -- https://hackage.haskell.org/package/unliftio
26 | import qualified UnliftIO.IO as IO
27 | import UnliftIO.Temporary (withSystemTempDirectory)
28 |
29 | -- (literatex)
30 | import qualified LiterateX
31 | import qualified LiterateX.Renderer as Renderer
32 | import qualified LiterateX.Types.SourceFormat as SourceFormat
33 | import qualified LiterateX.Types.TargetFormat as TargetFormat
34 |
35 | ------------------------------------------------------------------------------
36 |
37 | sourceS :: String
38 | sourceS = unlines
39 | [ "#!/usr/bin/env python"
40 | , ""
41 | , "# # Example Python Program"
42 | , "#"
43 | , "# This is an example program."
44 | , ""
45 | , ""
46 | , "######################################################################"
47 | , ""
48 | , "# ## Function `greet`"
49 | , "#"
50 | , "# This function prints a greeting."
51 | , "def greet(name: str) -> None:"
52 | , " if name == '':"
53 | , " print('Hello!')"
54 | , " return"
55 | , ""
56 | , " print(f'Hello {name}!')"
57 | , ""
58 | , ""
59 | , "if __name__ == '__main__':"
60 | , " greet()"
61 | ]
62 |
63 | sourceT :: T.Text
64 | sourceT = TTC.fromS sourceS
65 |
66 | sourceTL :: TL.Text
67 | sourceTL = TTC.fromS sourceS
68 |
69 | sourceBS :: BS.ByteString
70 | sourceBS = TTC.fromS sourceS
71 |
72 | sourceBSL :: BSL.ByteString
73 | sourceBSL = TTC.fromS sourceS
74 |
75 | ------------------------------------------------------------------------------
76 |
77 | targetS :: String
78 | targetS = unlines
79 | [ "# Example Python Program"
80 | , ""
81 | , "This is an example program."
82 | , ""
83 | , "## Function `greet`"
84 | , ""
85 | , "This function prints a greeting."
86 | , ""
87 | , "``` {.python .numberSource startFrom=\"13\"}"
88 | , "def greet(name: str) -> None:"
89 | , " if name == '':"
90 | , " print('Hello!')"
91 | , " return"
92 | , ""
93 | , " print(f'Hello {name}!')"
94 | , ""
95 | , ""
96 | , "if __name__ == '__main__':"
97 | , " greet()"
98 | , "```"
99 | ]
100 |
101 | targetT :: T.Text
102 | targetT = TTC.fromS targetS
103 |
104 | targetTL :: TL.Text
105 | targetTL = TTC.fromS targetS
106 |
107 | targetBS :: BS.ByteString
108 | targetBS = TTC.fromS targetS
109 |
110 | targetBSL :: BSL.ByteString
111 | targetBSL = TTC.fromS targetS
112 |
113 | ------------------------------------------------------------------------------
114 |
115 | withSourceHandle
116 | :: String -- ^ content
117 | -> (IO.Handle -> IO a)
118 | -> IO a
119 | withSourceHandle content action =
120 | withSystemTempDirectory "literatex-test-" $ \tmpDirPath -> do
121 | let sourcePath = tmpDirPath > "source"
122 | writeFile sourcePath content
123 | IO.withFile sourcePath IO.ReadMode action
124 |
125 | ------------------------------------------------------------------------------
126 |
127 | withTargetHandle
128 | :: (IO.Handle -> IO ())
129 | -> IO String
130 | withTargetHandle action =
131 | withSystemTempDirectory "literatex-test-" $ \tmpDirPath -> do
132 | let targetPath = tmpDirPath > "target"
133 | IO.withFile targetPath IO.WriteMode action
134 | readFile targetPath
135 |
136 | ------------------------------------------------------------------------------
137 |
138 | withSourceFile
139 | :: String -- ^ content
140 | -> (FilePath -> IO a)
141 | -> IO a
142 | withSourceFile content action =
143 | withSystemTempDirectory "literatex-test-" $ \tmpDirPath -> do
144 | let sourcePath = tmpDirPath > "source"
145 | writeFile sourcePath content
146 | action sourcePath
147 |
148 | ------------------------------------------------------------------------------
149 |
150 | withTargetFile
151 | :: (FilePath -> IO ())
152 | -> IO String
153 | withTargetFile action =
154 | withSystemTempDirectory "literatex-test-" $ \tmpDirPath -> do
155 | let targetPath = tmpDirPath > "target"
156 | action targetPath
157 | readFile targetPath
158 |
159 | ------------------------------------------------------------------------------
160 |
161 | rendererOpts :: Renderer.Options
162 | rendererOpts = Renderer.Options
163 | { Renderer.targetFormat = TargetFormat.PandocMarkdown
164 | , Renderer.codeLanguage = Just "python"
165 | , Renderer.ignoreShebang = True
166 | , Renderer.renderCode = True
167 | , Renderer.numberCodeLines = True
168 | }
169 |
170 | ------------------------------------------------------------------------------
171 |
172 | testTransformTextToText :: TestTree
173 | testTransformTextToText = testCase "transformTextToText" $
174 | targetTL @=?
175 | LiterateX.transformTextToText SourceFormat.Hash rendererOpts sourceTL
176 |
177 | ------------------------------------------------------------------------------
178 |
179 | testTransformTextToHandle :: TestTree
180 | testTransformTextToHandle = testCase "transformTextToHandle" $ do
181 | result <- withTargetHandle $
182 | LiterateX.transformTextToHandle SourceFormat.Hash rendererOpts sourceTL
183 | targetS @=? result
184 |
185 | ------------------------------------------------------------------------------
186 |
187 | testTransformTextToFile :: TestTree
188 | testTransformTextToFile = testCase "transformTextToFile" $ do
189 | result <- withTargetFile $
190 | LiterateX.transformTextToFile SourceFormat.Hash rendererOpts sourceTL
191 | targetS @=? result
192 |
193 | ------------------------------------------------------------------------------
194 |
195 | testTransformHandleToText :: TestTree
196 | testTransformHandleToText = testCase "transformHandleToText" $ do
197 | result <- withSourceHandle sourceS $
198 | LiterateX.transformHandleToText SourceFormat.Hash rendererOpts
199 | targetTL @=? result
200 |
201 | ------------------------------------------------------------------------------
202 |
203 | testTransformHandleToHandle :: TestTree
204 | testTransformHandleToHandle = testCase "transformHandleToHandle" $ do
205 | result <- withSourceHandle sourceS $ withTargetHandle .
206 | LiterateX.transformHandleToHandle SourceFormat.Hash rendererOpts
207 | targetS @=? result
208 |
209 | ------------------------------------------------------------------------------
210 |
211 | testTransformHandleToFile :: TestTree
212 | testTransformHandleToFile = testCase "transformHandleToFile" $ do
213 | result <- withSourceHandle sourceS $ withTargetFile .
214 | LiterateX.transformHandleToFile SourceFormat.Hash rendererOpts
215 | targetS @=? result
216 |
217 | ------------------------------------------------------------------------------
218 |
219 | testTransformFileToText :: TestTree
220 | testTransformFileToText = testCase "transformFileToText" $ do
221 | result <- withSourceFile sourceS $
222 | LiterateX.transformFileToText SourceFormat.Hash rendererOpts
223 | targetTL @=? result
224 |
225 | ------------------------------------------------------------------------------
226 |
227 | testTransformFileToHandle :: TestTree
228 | testTransformFileToHandle = testCase "transformFileToHandle" $ do
229 | result <- withSourceFile sourceS $ withTargetHandle .
230 | LiterateX.transformFileToHandle SourceFormat.Hash rendererOpts
231 | targetS @=? result
232 |
233 | ------------------------------------------------------------------------------
234 |
235 | testTransformFileToFile :: TestTree
236 | testTransformFileToFile = testCase "transformFileToFile" $ do
237 | result <- withSourceFile sourceS $ withTargetFile .
238 | LiterateX.transformFileToFile SourceFormat.Hash rendererOpts
239 | targetS @=? result
240 |
241 | ------------------------------------------------------------------------------
242 |
243 | testRunPure :: TestTree
244 | testRunPure = testGroup "runPure"
245 | [ testCase "sourceString" $
246 | targetTL @=? LiterateX.runPure SourceFormat.Hash rendererOpts
247 | (LiterateX.sourceString sourceS)
248 | LiterateX.sinkLazyText
249 | , testCase "sourceText" $
250 | targetTL @=? LiterateX.runPure SourceFormat.Hash rendererOpts
251 | (LiterateX.sourceText sourceT)
252 | LiterateX.sinkLazyText
253 | , testCase "sourceLazyText" $
254 | targetTL @=? LiterateX.runPure SourceFormat.Hash rendererOpts
255 | (LiterateX.sourceLazyText sourceTL)
256 | LiterateX.sinkLazyText
257 | , testCase "sourceByteString" $
258 | targetTL @=? LiterateX.runPure SourceFormat.Hash rendererOpts
259 | (LiterateX.sourceByteString sourceBS)
260 | LiterateX.sinkLazyText
261 | , testCase "sourceLazyByteString" $
262 | targetTL @=? LiterateX.runPure SourceFormat.Hash rendererOpts
263 | (LiterateX.sourceLazyByteString sourceBSL)
264 | LiterateX.sinkLazyText
265 | , testCase "sinkString" $
266 | targetS @=? LiterateX.runPure SourceFormat.Hash rendererOpts
267 | (LiterateX.sourceLazyText sourceTL)
268 | LiterateX.sinkString
269 | , testCase "sinkText" $
270 | targetT @=? LiterateX.runPure SourceFormat.Hash rendererOpts
271 | (LiterateX.sourceLazyText sourceTL)
272 | LiterateX.sinkText
273 | , testCase "sinkLazyText" $
274 | targetTL @=? LiterateX.runPure SourceFormat.Hash rendererOpts
275 | (LiterateX.sourceLazyText sourceTL)
276 | LiterateX.sinkLazyText
277 | , testCase "sinkByteString" $
278 | targetBS @=? LiterateX.runPure SourceFormat.Hash rendererOpts
279 | (LiterateX.sourceLazyText sourceTL)
280 | LiterateX.sinkByteString
281 | , testCase "sinkLazyByteString" $
282 | targetBSL @=? LiterateX.runPure SourceFormat.Hash rendererOpts
283 | (LiterateX.sourceLazyText sourceTL)
284 | LiterateX.sinkLazyByteString
285 | ]
286 |
287 | ------------------------------------------------------------------------------
288 |
289 | testRunIO :: TestTree
290 | testRunIO = testGroup "runIO"
291 | [ testCase "TextToHandle" $ do
292 | result <- withTargetHandle $ \target ->
293 | LiterateX.runIO SourceFormat.Hash rendererOpts
294 | (LiterateX.sourceLazyText sourceTL)
295 | (LiterateX.sinkHandle target)
296 | targetS @=? result
297 | , testCase "HandleToText" $ do
298 | result <- withSourceHandle sourceS $ \source ->
299 | LiterateX.runIO SourceFormat.Hash rendererOpts
300 | (LiterateX.sourceHandle source)
301 | LiterateX.sinkLazyText
302 | targetTL @=? result
303 | , testCase "HandleToHandle" $ do
304 | result <- withSourceHandle sourceS $ \source ->
305 | withTargetHandle $ \target ->
306 | LiterateX.runIO SourceFormat.Hash rendererOpts
307 | (LiterateX.sourceHandle source)
308 | (LiterateX.sinkHandle target)
309 | targetS @=? result
310 | ]
311 |
312 | ------------------------------------------------------------------------------
313 |
314 | testRunResource :: TestTree
315 | testRunResource = testGroup "runResource"
316 | [ testCase "TextToFile" $ do
317 | result <- withTargetFile $ \target ->
318 | LiterateX.runResource SourceFormat.Hash rendererOpts
319 | (LiterateX.sourceLazyText sourceTL)
320 | (LiterateX.sinkFile target)
321 | targetS @=? result
322 | , testCase "HandleToFile" $ do
323 | result <- withSourceHandle sourceS $ \source ->
324 | withTargetFile $ \target ->
325 | LiterateX.runResource SourceFormat.Hash rendererOpts
326 | (LiterateX.sourceHandle source)
327 | (LiterateX.sinkFile target)
328 | targetS @=? result
329 | , testCase "FileToText" $ do
330 | result <- withSourceFile sourceS $ \source ->
331 | LiterateX.runResource SourceFormat.Hash rendererOpts
332 | (LiterateX.sourceFile source)
333 | LiterateX.sinkLazyText
334 | targetTL @=? result
335 | , testCase "FileToHandle" $ do
336 | result <- withSourceFile sourceS $ \source ->
337 | withTargetHandle $ \target ->
338 | LiterateX.runResource SourceFormat.Hash rendererOpts
339 | (LiterateX.sourceFile source)
340 | (LiterateX.sinkHandle target)
341 | targetS @=? result
342 | , testCase "FileToFile" $ do
343 | result <- withSourceFile sourceS $ \source ->
344 | withTargetFile $ \target ->
345 | LiterateX.runResource SourceFormat.Hash rendererOpts
346 | (LiterateX.sourceFile source)
347 | (LiterateX.sinkFile target)
348 | targetS @=? result
349 | ]
350 |
351 | ------------------------------------------------------------------------------
352 |
353 | tests :: TestTree
354 | tests = testGroup "LiterateX.Test.API"
355 | [ testTransformTextToText
356 | , testTransformTextToHandle
357 | , testTransformTextToFile
358 | , testTransformHandleToText
359 | , testTransformHandleToHandle
360 | , testTransformHandleToFile
361 | , testTransformFileToText
362 | , testTransformFileToHandle
363 | , testTransformFileToFile
364 | , testRunPure
365 | , testRunIO
366 | , testRunResource
367 | ]
368 |
--------------------------------------------------------------------------------
/test/LiterateX/Test/SourceFormat.hs:
--------------------------------------------------------------------------------
1 | module LiterateX.Test.SourceFormat (tests) where
2 |
3 | -- https://hackage.haskell.org/package/tasty
4 | import Test.Tasty (TestTree, testGroup)
5 |
6 | -- (literatex:test)
7 | import qualified LiterateX.Test.SourceFormat.DoubleDash
8 | import qualified LiterateX.Test.SourceFormat.DoubleSlash
9 | import qualified LiterateX.Test.SourceFormat.Hash
10 | import qualified LiterateX.Test.SourceFormat.LispSemicolons
11 | import qualified LiterateX.Test.SourceFormat.LiterateHaskell
12 | import qualified LiterateX.Test.SourceFormat.Percent
13 |
14 | ------------------------------------------------------------------------------
15 |
16 | tests :: TestTree
17 | tests = testGroup "LiterateX.Test.SourceFormat"
18 | [ LiterateX.Test.SourceFormat.DoubleDash.tests
19 | , LiterateX.Test.SourceFormat.DoubleSlash.tests
20 | , LiterateX.Test.SourceFormat.Hash.tests
21 | , LiterateX.Test.SourceFormat.LispSemicolons.tests
22 | , LiterateX.Test.SourceFormat.LiterateHaskell.tests
23 | , LiterateX.Test.SourceFormat.Percent.tests
24 | ]
25 |
--------------------------------------------------------------------------------
/test/LiterateX/Test/SourceFormat/Hash.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE OverloadedStrings #-}
2 |
3 | module LiterateX.Test.SourceFormat.Hash (tests) where
4 |
5 | -- https://hackage.haskell.org/package/tasty
6 | import Test.Tasty (TestTree, testGroup)
7 |
8 | -- https://hackage.haskell.org/package/tasty-hunit
9 | import Test.Tasty.HUnit ((@=?), testCase)
10 |
11 | -- https://hackage.haskell.org/package/text
12 | import qualified Data.Text.Lazy as TL
13 | import Data.Text.Lazy (Text)
14 |
15 | -- (literatex)
16 | import qualified LiterateX
17 | import qualified LiterateX.Renderer as Renderer
18 | import qualified LiterateX.Types.SourceFormat as SourceFormat
19 | import qualified LiterateX.Types.TargetFormat as TargetFormat
20 |
21 | ------------------------------------------------------------------------------
22 |
23 | rendererOpts :: Renderer.Options
24 | rendererOpts = Renderer.Options
25 | { Renderer.targetFormat = TargetFormat.PandocMarkdown
26 | , Renderer.codeLanguage = Just "python"
27 | , Renderer.ignoreShebang = True
28 | , Renderer.renderCode = True
29 | , Renderer.numberCodeLines = True
30 | }
31 |
32 | run' :: Text -> Renderer.Options -> Text
33 | run' = flip $ LiterateX.transformTextToText SourceFormat.Hash
34 |
35 | run :: Text -> Text
36 | run source = run' source rendererOpts
37 |
38 | ------------------------------------------------------------------------------
39 |
40 | sourceStartShebangDoc :: Text
41 | sourceStartShebangDoc = TL.unlines
42 | [ "#!/usr/bin/env python"
43 | , ""
44 | , "# Test"
45 | , ""
46 | , "print('Hello!')"
47 | ]
48 |
49 | targetStartShebangDocIgnore :: Text
50 | targetStartShebangDocIgnore = TL.unlines
51 | [ "Test"
52 | , ""
53 | , "``` {.python .numberSource startFrom=\"5\"}"
54 | , "print('Hello!')"
55 | , "```"
56 | ]
57 |
58 | targetStartShebangDocNoIgnore :: Text
59 | targetStartShebangDocNoIgnore = TL.unlines
60 | [ "``` {.numberSource startFrom=\"1\"}"
61 | , "#!/usr/bin/env python"
62 | , "```"
63 | , ""
64 | , "Test"
65 | , ""
66 | , "``` {.python .numberSource startFrom=\"5\"}"
67 | , "print('Hello!')"
68 | , "```"
69 | ]
70 |
71 | targetStartShebangDocNoCode :: Text
72 | targetStartShebangDocNoCode = TL.unlines
73 | [ "Test"
74 | ]
75 |
76 | testStartShebangDoc :: TestTree
77 | testStartShebangDoc = testGroup "startShebangDoc"
78 | [ testCase "Ignore" $
79 | targetStartShebangDocIgnore @=? run sourceStartShebangDoc
80 | , testCase "NoIgnore" $
81 | targetStartShebangDocNoIgnore @=?
82 | run' sourceStartShebangDoc rendererOpts
83 | { Renderer.ignoreShebang = False
84 | }
85 | , testCase "NoCode" $
86 | targetStartShebangDocNoCode @=?
87 | run' sourceStartShebangDoc rendererOpts
88 | { Renderer.ignoreShebang = False
89 | , Renderer.renderCode = False
90 | }
91 | ]
92 |
93 | ------------------------------------------------------------------------------
94 |
95 | sourceStartShebangCode :: Text
96 | sourceStartShebangCode = TL.unlines
97 | [ "#!/usr/bin/env python"
98 | , ""
99 | , "print('Hello!')"
100 | , ""
101 | , "# Test"
102 | ]
103 |
104 | targetStartShebangCodeIgnore :: Text
105 | targetStartShebangCodeIgnore = TL.unlines
106 | [ "``` {.python .numberSource startFrom=\"3\"}"
107 | , "print('Hello!')"
108 | , "```"
109 | , ""
110 | , "Test"
111 | ]
112 |
113 | targetStartShebangCodeNoIgnore :: Text
114 | targetStartShebangCodeNoIgnore = TL.unlines
115 | [ "``` {.numberSource startFrom=\"1\"}"
116 | , "#!/usr/bin/env python"
117 | , "```"
118 | , ""
119 | , "``` {.python .numberSource startFrom=\"3\"}"
120 | , "print('Hello!')"
121 | , "```"
122 | , ""
123 | , "Test"
124 | ]
125 |
126 | targetStartShebangCodeNoCode :: Text
127 | targetStartShebangCodeNoCode = TL.unlines
128 | [ "Test"
129 | ]
130 |
131 | testStartShebangCode :: TestTree
132 | testStartShebangCode = testGroup "startShebangCode"
133 | [ testCase "Ignore" $
134 | targetStartShebangCodeIgnore @=? run sourceStartShebangCode
135 | , testCase "NoIgnore" $
136 | targetStartShebangCodeNoIgnore @=?
137 | run' sourceStartShebangCode rendererOpts
138 | { Renderer.ignoreShebang = False
139 | }
140 | , testCase "NoCode" $
141 | targetStartShebangCodeNoCode @=?
142 | run' sourceStartShebangCode rendererOpts
143 | { Renderer.ignoreShebang = False
144 | , Renderer.renderCode = False
145 | }
146 | ]
147 |
148 | ------------------------------------------------------------------------------
149 |
150 | sourceStartDoc :: Text
151 | sourceStartDoc = TL.unlines
152 | [ "# Test"
153 | , ""
154 | , "print('Hello!')"
155 | ]
156 |
157 | targetStartDoc :: Text
158 | targetStartDoc = TL.unlines
159 | [ "Test"
160 | , ""
161 | , "``` {.python .numberSource startFrom=\"3\"}"
162 | , "print('Hello!')"
163 | , "```"
164 | ]
165 |
166 | testStartDoc :: TestTree
167 | testStartDoc = testCase "startDoc" $
168 | targetStartDoc @=? run sourceStartDoc
169 |
170 | ------------------------------------------------------------------------------
171 |
172 | sourceStartCode :: Text
173 | sourceStartCode = TL.unlines
174 | [ "print('Hello!')"
175 | , ""
176 | , "# Test"
177 | ]
178 |
179 | targetStartCode :: Text
180 | targetStartCode = TL.unlines
181 | [ "``` {.python .numberSource startFrom=\"1\"}"
182 | , "print('Hello!')"
183 | , "```"
184 | , ""
185 | , "Test"
186 | ]
187 |
188 | testStartCode :: TestTree
189 | testStartCode = testCase "startCode" $
190 | targetStartCode @=? run sourceStartCode
191 |
192 | ------------------------------------------------------------------------------
193 |
194 | sourceStartDocBlank :: Text
195 | sourceStartDocBlank = TL.unlines
196 | [ "#"
197 | , "# Test"
198 | , ""
199 | , "print('Hello!')"
200 | ]
201 |
202 | targetStartDocBlank :: Text
203 | targetStartDocBlank = TL.unlines
204 | [ "Test"
205 | , ""
206 | , "``` {.python .numberSource startFrom=\"4\"}"
207 | , "print('Hello!')"
208 | , "```"
209 | ]
210 |
211 | testStartDocBlank :: TestTree
212 | testStartDocBlank = testCase "startDocBlank" $
213 | targetStartDocBlank @=? run sourceStartDocBlank
214 |
215 | ------------------------------------------------------------------------------
216 |
217 | sourceStartCodeBlank :: Text
218 | sourceStartCodeBlank = TL.unlines
219 | [ ""
220 | , "print('Hello!')"
221 | , ""
222 | , "# Test"
223 | ]
224 |
225 | targetStartCodeBlank :: Text
226 | targetStartCodeBlank = TL.unlines
227 | [ "``` {.python .numberSource startFrom=\"2\"}"
228 | , "print('Hello!')"
229 | , "```"
230 | , ""
231 | , "Test"
232 | ]
233 |
234 | testStartCodeBlank :: TestTree
235 | testStartCodeBlank = testCase "startCodeBlank" $
236 | targetStartCodeBlank @=? run sourceStartCodeBlank
237 |
238 | ------------------------------------------------------------------------------
239 |
240 | sourceStartRuleDoc :: Text
241 | sourceStartRuleDoc = TL.unlines
242 | [ "######################################################################"
243 | , "# Test"
244 | , ""
245 | , "print('Hello!')"
246 | ]
247 |
248 | targetStartRuleDoc :: Text
249 | targetStartRuleDoc = TL.unlines
250 | [ "Test"
251 | , ""
252 | , "``` {.python .numberSource startFrom=\"4\"}"
253 | , "print('Hello!')"
254 | , "```"
255 | ]
256 |
257 | testStartRuleDoc :: TestTree
258 | testStartRuleDoc = testCase "startRuleDoc" $
259 | targetStartRuleDoc @=? run sourceStartRuleDoc
260 |
261 | ------------------------------------------------------------------------------
262 |
263 | sourceStartRuleCode :: Text
264 | sourceStartRuleCode = TL.unlines
265 | [ "######################################################################"
266 | , ""
267 | , "print('Hello!')"
268 | , ""
269 | , "# Test"
270 | ]
271 |
272 | targetStartRuleCode :: Text
273 | targetStartRuleCode = TL.unlines
274 | [ "``` {.python .numberSource startFrom=\"3\"}"
275 | , "print('Hello!')"
276 | , "```"
277 | , ""
278 | , "Test"
279 | ]
280 |
281 | testStartRuleCode :: TestTree
282 | testStartRuleCode = testCase "startRuleCode" $
283 | targetStartRuleCode @=? run sourceStartRuleCode
284 |
285 | ------------------------------------------------------------------------------
286 |
287 | sourceCodeDoc :: Text
288 | sourceCodeDoc = TL.unlines
289 | [ "print('one')"
290 | , "# Test"
291 | ]
292 |
293 | targetCodeDoc :: Text
294 | targetCodeDoc = TL.unlines
295 | [ "``` {.python .numberSource startFrom=\"1\"}"
296 | , "print('one')"
297 | , "```"
298 | , ""
299 | , "Test"
300 | ]
301 |
302 | testCodeDoc :: TestTree
303 | testCodeDoc = testCase "codeDoc" $
304 | targetCodeDoc @=? run sourceCodeDoc
305 |
306 | ------------------------------------------------------------------------------
307 |
308 | sourceCodeCodeBlanksCode :: Text
309 | sourceCodeCodeBlanksCode = TL.unlines
310 | [ "print('one')"
311 | , ""
312 | , ""
313 | , "print('two')"
314 | ]
315 |
316 | targetCodeCodeBlanksCode :: Text
317 | targetCodeCodeBlanksCode = TL.unlines
318 | [ "``` {.python .numberSource startFrom=\"1\"}"
319 | , "print('one')"
320 | , ""
321 | , ""
322 | , "print('two')"
323 | , "```"
324 | ]
325 |
326 | testCodeCodeBlanksCode :: TestTree
327 | testCodeCodeBlanksCode = testCase "codeCodeBlanksCode" $
328 | targetCodeCodeBlanksCode @=? run sourceCodeCodeBlanksCode
329 |
330 | ------------------------------------------------------------------------------
331 |
332 | sourceCodeCodeBlanksDoc :: Text
333 | sourceCodeCodeBlanksDoc = TL.unlines
334 | [ "print('one')"
335 | , ""
336 | , ""
337 | , "# Test"
338 | ]
339 |
340 | targetCodeCodeBlanksDoc :: Text
341 | targetCodeCodeBlanksDoc = TL.unlines
342 | [ "``` {.python .numberSource startFrom=\"1\"}"
343 | , "print('one')"
344 | , "```"
345 | , ""
346 | , "Test"
347 | ]
348 |
349 | testCodeCodeBlanksDoc :: TestTree
350 | testCodeCodeBlanksDoc = testCase "codeCodeBlanksDoc" $
351 | targetCodeCodeBlanksDoc @=? run sourceCodeCodeBlanksDoc
352 |
353 | ------------------------------------------------------------------------------
354 |
355 | sourceCodeDocBlanksDoc :: Text
356 | sourceCodeDocBlanksDoc = TL.unlines
357 | [ "print('one')"
358 | , "#"
359 | , "#"
360 | , "# Test"
361 | ]
362 |
363 | targetCodeDocBlanksDoc :: Text
364 | targetCodeDocBlanksDoc = TL.unlines
365 | [ "``` {.python .numberSource startFrom=\"1\"}"
366 | , "print('one')"
367 | , "```"
368 | , ""
369 | , "Test"
370 | ]
371 |
372 | testCodeDocBlanksDoc :: TestTree
373 | testCodeDocBlanksDoc = testCase "codeDocBlanksDoc" $
374 | targetCodeDocBlanksDoc @=? run sourceCodeDocBlanksDoc
375 |
376 | ------------------------------------------------------------------------------
377 |
378 | sourceCodeDocBlanksCode :: Text
379 | sourceCodeDocBlanksCode = TL.unlines
380 | [ "print('one')"
381 | , "#"
382 | , "#"
383 | , "print('two')"
384 | ]
385 |
386 | targetCodeDocBlanksCode :: Text
387 | targetCodeDocBlanksCode = TL.unlines
388 | [ "``` {.python .numberSource startFrom=\"1\"}"
389 | , "print('one')"
390 | , "```"
391 | , ""
392 | , "``` {.python .numberSource startFrom=\"4\"}"
393 | , "print('two')"
394 | , "```"
395 | ]
396 |
397 | testCodeDocBlanksCode :: TestTree
398 | testCodeDocBlanksCode = testCase "codeDocBlanksCode" $
399 | targetCodeDocBlanksCode @=? run sourceCodeDocBlanksCode
400 |
401 | ------------------------------------------------------------------------------
402 |
403 | sourceCodeRuleCode :: Text
404 | sourceCodeRuleCode = TL.unlines
405 | [ "print('one')"
406 | , "######################################################################"
407 | , "print('two')"
408 | ]
409 |
410 | targetCodeRuleCode :: Text
411 | targetCodeRuleCode = TL.unlines
412 | [ "``` {.python .numberSource startFrom=\"1\"}"
413 | , "print('one')"
414 | , "```"
415 | , ""
416 | , "``` {.python .numberSource startFrom=\"3\"}"
417 | , "print('two')"
418 | , "```"
419 | ]
420 |
421 | testCodeRuleCode :: TestTree
422 | testCodeRuleCode = testCase "codeRuleCode" $
423 | targetCodeRuleCode @=? run sourceCodeRuleCode
424 |
425 | ------------------------------------------------------------------------------
426 |
427 | sourceCodeRuleDoc :: Text
428 | sourceCodeRuleDoc = TL.unlines
429 | [ "print('one')"
430 | , "######################################################################"
431 | , "# Test"
432 | ]
433 |
434 | targetCodeRuleDoc :: Text
435 | targetCodeRuleDoc = TL.unlines
436 | [ "``` {.python .numberSource startFrom=\"1\"}"
437 | , "print('one')"
438 | , "```"
439 | , ""
440 | , "Test"
441 | ]
442 |
443 | testCodeRuleDoc :: TestTree
444 | testCodeRuleDoc = testCase "codeRuleDoc" $
445 | targetCodeRuleDoc @=? run sourceCodeRuleDoc
446 |
447 | ------------------------------------------------------------------------------
448 |
449 | sourceDocCode :: Text
450 | sourceDocCode = TL.unlines
451 | [ "# Test"
452 | , "print('one')"
453 | ]
454 |
455 | targetDocCode :: Text
456 | targetDocCode = TL.unlines
457 | [ "Test"
458 | , ""
459 | , "``` {.python .numberSource startFrom=\"2\"}"
460 | , "print('one')"
461 | , "```"
462 | ]
463 |
464 | testDocCode :: TestTree
465 | testDocCode = testCase "docCode" $
466 | targetDocCode @=? run sourceDocCode
467 |
468 | ------------------------------------------------------------------------------
469 |
470 | sourceDocDocBlanksDoc :: Text
471 | sourceDocDocBlanksDoc = TL.unlines
472 | [ "# one"
473 | , "#"
474 | , "#"
475 | , "# two"
476 | ]
477 |
478 | targetDocDocBlanksDoc :: Text
479 | targetDocDocBlanksDoc = TL.unlines
480 | [ "one"
481 | , ""
482 | , "two"
483 | ]
484 |
485 | testDocDocBlanksDoc :: TestTree
486 | testDocDocBlanksDoc = testCase "docDocBlanksDoc" $
487 | targetDocDocBlanksDoc @=? run sourceDocDocBlanksDoc
488 |
489 | ------------------------------------------------------------------------------
490 |
491 | sourceDocDocBlanksCode :: Text
492 | sourceDocDocBlanksCode = TL.unlines
493 | [ "# Test"
494 | , "#"
495 | , "#"
496 | , "print('Hello!')"
497 | ]
498 |
499 | targetDocDocBlanksCode :: Text
500 | targetDocDocBlanksCode = TL.unlines
501 | [ "Test"
502 | , ""
503 | , "``` {.python .numberSource startFrom=\"4\"}"
504 | , "print('Hello!')"
505 | , "```"
506 | ]
507 |
508 | testDocDocBlanksCode :: TestTree
509 | testDocDocBlanksCode = testCase "docDocBlanksCode" $
510 | targetDocDocBlanksCode @=? run sourceDocDocBlanksCode
511 |
512 | ------------------------------------------------------------------------------
513 |
514 | sourceDocCodeBlanksCode :: Text
515 | sourceDocCodeBlanksCode = TL.unlines
516 | [ "# Test"
517 | , ""
518 | , ""
519 | , "print('Hello!')"
520 | ]
521 |
522 | targetDocCodeBlanksCode :: Text
523 | targetDocCodeBlanksCode = TL.unlines
524 | [ "Test"
525 | , ""
526 | , "``` {.python .numberSource startFrom=\"4\"}"
527 | , "print('Hello!')"
528 | , "```"
529 | ]
530 |
531 | testDocCodeBlanksCode :: TestTree
532 | testDocCodeBlanksCode = testCase "docCodeBlanksCode" $
533 | targetDocCodeBlanksCode @=? run sourceDocCodeBlanksCode
534 |
535 | ------------------------------------------------------------------------------
536 |
537 | sourceDocCodeBlanksDoc :: Text
538 | sourceDocCodeBlanksDoc = TL.unlines
539 | [ "# one"
540 | , ""
541 | , ""
542 | , "# two"
543 | ]
544 |
545 | targetDocCodeBlanksDoc :: Text
546 | targetDocCodeBlanksDoc = TL.unlines
547 | [ "one"
548 | , ""
549 | , "two"
550 | ]
551 |
552 | testDocCodeBlanksDoc :: TestTree
553 | testDocCodeBlanksDoc = testCase "docCodeBlanksDoc" $
554 | targetDocCodeBlanksDoc @=? run sourceDocCodeBlanksDoc
555 |
556 | ------------------------------------------------------------------------------
557 |
558 | sourceDocRuleDoc :: Text
559 | sourceDocRuleDoc = TL.unlines
560 | [ "# one"
561 | , "######################################################################"
562 | , "# two"
563 | ]
564 |
565 | targetDocRuleDoc :: Text
566 | targetDocRuleDoc = TL.unlines
567 | [ "one"
568 | , ""
569 | , "two"
570 | ]
571 |
572 | testDocRuleDoc :: TestTree
573 | testDocRuleDoc = testCase "docRuleDoc" $
574 | targetDocRuleDoc @=? run sourceDocRuleDoc
575 |
576 | ------------------------------------------------------------------------------
577 |
578 | sourceDocRuleCode :: Text
579 | sourceDocRuleCode = TL.unlines
580 | [ "# Test"
581 | , "######################################################################"
582 | , "print('Hello!')"
583 | ]
584 |
585 | targetDocRuleCode :: Text
586 | targetDocRuleCode = TL.unlines
587 | [ "Test"
588 | , ""
589 | , "``` {.python .numberSource startFrom=\"3\"}"
590 | , "print('Hello!')"
591 | , "```"
592 | ]
593 |
594 | testDocRuleCode :: TestTree
595 | testDocRuleCode = testCase "docRuleCode" $
596 | targetDocRuleCode @=? run sourceDocRuleCode
597 |
598 | ------------------------------------------------------------------------------
599 |
600 | sourceEndDoc :: Text
601 | sourceEndDoc = TL.unlines
602 | [ "# Test"
603 | ]
604 |
605 | targetEndDoc :: Text
606 | targetEndDoc = TL.unlines
607 | [ "Test"
608 | ]
609 |
610 | testEndDoc :: TestTree
611 | testEndDoc = testCase "endDoc" $
612 | targetEndDoc @=? run sourceEndDoc
613 |
614 | ------------------------------------------------------------------------------
615 |
616 | sourceEndDocBlanks :: Text
617 | sourceEndDocBlanks = TL.unlines
618 | [ "#"
619 | , "#"
620 | ]
621 |
622 | targetEndDocBlanks :: Text
623 | targetEndDocBlanks = ""
624 |
625 | testEndDocBlanks :: TestTree
626 | testEndDocBlanks = testCase "endDocBlanks" $
627 | targetEndDocBlanks @=? run sourceEndDocBlanks
628 |
629 | ------------------------------------------------------------------------------
630 |
631 | sourceEndCode :: Text
632 | sourceEndCode = TL.unlines
633 | [ "print('Hello!')"
634 | ]
635 |
636 | targetEndCode :: Text
637 | targetEndCode = TL.unlines
638 | [ "``` {.python .numberSource startFrom=\"1\"}"
639 | , "print('Hello!')"
640 | , "```"
641 | ]
642 |
643 | testEndCode :: TestTree
644 | testEndCode = testCase "endCode" $
645 | targetEndCode @=? run sourceEndCode
646 |
647 | ------------------------------------------------------------------------------
648 |
649 | sourceEndCodeBlanks :: Text
650 | sourceEndCodeBlanks = TL.unlines
651 | [ "print('Hello!')"
652 | , ""
653 | , ""
654 | ]
655 |
656 | targetEndCodeBlanks :: Text
657 | targetEndCodeBlanks = TL.unlines
658 | [ "``` {.python .numberSource startFrom=\"1\"}"
659 | , "print('Hello!')"
660 | , "```"
661 | ]
662 |
663 | testEndCodeBlanks :: TestTree
664 | testEndCodeBlanks = testCase "endCodeBlanks" $
665 | targetEndCodeBlanks @=? run sourceEndCodeBlanks
666 |
667 | ------------------------------------------------------------------------------
668 |
669 | sourceEmpty :: Text
670 | sourceEmpty = ""
671 |
672 | sourceOnlyRule :: Text
673 | sourceOnlyRule = "###########################################################"
674 |
675 | sourceOnlyDocBlanks :: Text
676 | sourceOnlyDocBlanks = TL.unlines
677 | [ "#"
678 | , "#"
679 | ]
680 |
681 | sourceOnlyCodeBlanks :: Text
682 | sourceOnlyCodeBlanks = TL.unlines
683 | [ ""
684 | , ""
685 | ]
686 |
687 | targetEmpty :: Text
688 | targetEmpty = ""
689 |
690 | testEmpty :: TestTree
691 | testEmpty = testCase "empty" $
692 | targetEmpty @=? run sourceEmpty
693 |
694 | testOnlyRule :: TestTree
695 | testOnlyRule = testCase "onlyRule" $
696 | targetEmpty @=? run sourceOnlyRule
697 |
698 | testOnlyDocBlanks :: TestTree
699 | testOnlyDocBlanks = testCase "onlyDocBlanks" $
700 | targetEmpty @=? run sourceOnlyDocBlanks
701 |
702 | testOnlyCodeBlanks :: TestTree
703 | testOnlyCodeBlanks = testCase "onlyCodeBlanks" $
704 | targetEmpty @=? run sourceOnlyCodeBlanks
705 |
706 | ------------------------------------------------------------------------------
707 |
708 | tests :: TestTree
709 | tests = testGroup "Hash"
710 | [ testStartShebangDoc
711 | , testStartShebangCode
712 | , testStartDoc
713 | , testStartCode
714 | , testStartDocBlank
715 | , testStartCodeBlank
716 | , testStartRuleDoc
717 | , testStartRuleCode
718 | , testCodeDoc
719 | , testCodeCodeBlanksCode
720 | , testCodeCodeBlanksDoc
721 | , testCodeDocBlanksDoc
722 | , testCodeDocBlanksCode
723 | , testCodeRuleCode
724 | , testCodeRuleDoc
725 | , testDocCode
726 | , testDocDocBlanksDoc
727 | , testDocDocBlanksCode
728 | , testDocCodeBlanksCode
729 | , testDocCodeBlanksDoc
730 | , testDocRuleDoc
731 | , testDocRuleCode
732 | , testEndDoc
733 | , testEndDocBlanks
734 | , testEndCode
735 | , testEndCodeBlanks
736 | , testEmpty
737 | , testOnlyRule
738 | , testOnlyDocBlanks
739 | , testOnlyCodeBlanks
740 | ]
741 |
--------------------------------------------------------------------------------
/test/LiterateX/Test/SourceFormat/LiterateHaskell.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE OverloadedStrings #-}
2 |
3 | module LiterateX.Test.SourceFormat.LiterateHaskell (tests) where
4 |
5 | -- https://hackage.haskell.org/package/tasty
6 | import Test.Tasty (TestTree, testGroup)
7 |
8 | -- https://hackage.haskell.org/package/tasty-hunit
9 | import Test.Tasty.HUnit ((@=?), testCase)
10 |
11 | -- https://hackage.haskell.org/package/text
12 | import qualified Data.Text.Lazy as TL
13 | import Data.Text.Lazy (Text)
14 |
15 | -- (literatex)
16 | import qualified LiterateX
17 | import qualified LiterateX.Renderer as Renderer
18 | import qualified LiterateX.Types.SourceFormat as SourceFormat
19 | import qualified LiterateX.Types.TargetFormat as TargetFormat
20 |
21 | ------------------------------------------------------------------------------
22 |
23 | rendererOpts :: Renderer.Options
24 | rendererOpts = Renderer.Options
25 | { Renderer.targetFormat = TargetFormat.PandocMarkdown
26 | , Renderer.codeLanguage = Just "haskell"
27 | , Renderer.ignoreShebang = True
28 | , Renderer.renderCode = True
29 | , Renderer.numberCodeLines = True
30 | }
31 |
32 | run' :: Text -> Renderer.Options -> Text
33 | run' = flip $ LiterateX.transformTextToText SourceFormat.LiterateHaskell
34 |
35 | run :: Text -> Text
36 | run source = run' source rendererOpts
37 |
38 | ------------------------------------------------------------------------------
39 |
40 | sourceStartShebangDoc :: Text
41 | sourceStartShebangDoc = TL.unlines
42 | [ "#!/usr/bin/env stack"
43 | , ""
44 | , "This results in warnings!"
45 | , ""
46 | , "> {- stack script --resolver lts-17.11 -}"
47 | , ">"
48 | , "> module Main (main) where"
49 | , ">"
50 | , "> main :: IO ()"
51 | , "> main = putStrLn \"Hello!\""
52 | ]
53 |
54 | targetStartShebangDocIgnore :: Text
55 | targetStartShebangDocIgnore = TL.unlines
56 | [ "This results in warnings!"
57 | , ""
58 | , "``` {.haskell .numberSource startFrom=\"5\"}"
59 | , "{- stack script --resolver lts-17.11 -}"
60 | , ""
61 | , "module Main (main) where"
62 | , ""
63 | , "main :: IO ()"
64 | , "main = putStrLn \"Hello!\""
65 | , "```"
66 | ]
67 |
68 | targetStartShebangDocNoIgnore :: Text
69 | targetStartShebangDocNoIgnore = TL.unlines
70 | [ "``` {.numberSource startFrom=\"1\"}"
71 | , "#!/usr/bin/env stack"
72 | , "```"
73 | , ""
74 | , "This results in warnings!"
75 | , ""
76 | , "``` {.haskell .numberSource startFrom=\"5\"}"
77 | , "{- stack script --resolver lts-17.11 -}"
78 | , ""
79 | , "module Main (main) where"
80 | , ""
81 | , "main :: IO ()"
82 | , "main = putStrLn \"Hello!\""
83 | , "```"
84 | ]
85 |
86 | targetStartShebangDocNoCode :: Text
87 | targetStartShebangDocNoCode = TL.unlines
88 | [ "This results in warnings!"
89 | ]
90 |
91 | testStartShebangDoc :: TestTree
92 | testStartShebangDoc = testGroup "startShebangDoc"
93 | [ testCase "Ignore" $
94 | targetStartShebangDocIgnore @=? run sourceStartShebangDoc
95 | , testCase "NoIgnore" $
96 | targetStartShebangDocNoIgnore @=?
97 | run' sourceStartShebangDoc rendererOpts
98 | { Renderer.ignoreShebang = False
99 | }
100 | , testCase "NoCode" $
101 | targetStartShebangDocNoCode @=?
102 | run' sourceStartShebangDoc rendererOpts
103 | { Renderer.ignoreShebang = False
104 | , Renderer.renderCode = False
105 | }
106 | ]
107 |
108 | ------------------------------------------------------------------------------
109 |
110 | sourceStartShebangCode :: Text
111 | sourceStartShebangCode = TL.unlines
112 | [ "#!/usr/bin/env stack"
113 | , "> {- stack script --resolver lts-17.11 -}"
114 | , ">"
115 | , "> module Main (main) where"
116 | , ">"
117 | , "> main :: IO ()"
118 | , "> main = putStrLn \"Hello!\""
119 | , ""
120 | , "Test"
121 | ]
122 |
123 | targetStartShebangCodeIgnore :: Text
124 | targetStartShebangCodeIgnore = TL.unlines
125 | [ "``` {.haskell .numberSource startFrom=\"2\"}"
126 | , "{- stack script --resolver lts-17.11 -}"
127 | , ""
128 | , "module Main (main) where"
129 | , ""
130 | , "main :: IO ()"
131 | , "main = putStrLn \"Hello!\""
132 | , "```"
133 | , ""
134 | , "Test"
135 | ]
136 |
137 | targetStartShebangCodeNoIgnore :: Text
138 | targetStartShebangCodeNoIgnore = TL.unlines
139 | [ "``` {.numberSource startFrom=\"1\"}"
140 | , "#!/usr/bin/env stack"
141 | , "```"
142 | , ""
143 | , "``` {.haskell .numberSource startFrom=\"2\"}"
144 | , "{- stack script --resolver lts-17.11 -}"
145 | , ""
146 | , "module Main (main) where"
147 | , ""
148 | , "main :: IO ()"
149 | , "main = putStrLn \"Hello!\""
150 | , "```"
151 | , ""
152 | , "Test"
153 | ]
154 |
155 | targetStartShebangCodeNoCode :: Text
156 | targetStartShebangCodeNoCode = TL.unlines
157 | [ "Test"
158 | ]
159 |
160 | testStartShebangCode :: TestTree
161 | testStartShebangCode = testGroup "startShebangCode"
162 | [ testCase "Ignore" $
163 | targetStartShebangCodeIgnore @=? run sourceStartShebangCode
164 | , testCase "NoIgnore" $
165 | targetStartShebangCodeNoIgnore @=?
166 | run' sourceStartShebangCode rendererOpts
167 | { Renderer.ignoreShebang = False
168 | }
169 | , testCase "NoCode" $
170 | targetStartShebangCodeNoCode @=?
171 | run' sourceStartShebangCode rendererOpts
172 | { Renderer.ignoreShebang = False
173 | , Renderer.renderCode = False
174 | }
175 | ]
176 |
177 | ------------------------------------------------------------------------------
178 |
179 | sourceStartDoc :: Text
180 | sourceStartDoc = TL.unlines
181 | [ "Test"
182 | , ""
183 | , "> module Main (main) where"
184 | , ">"
185 | , "> main :: IO ()"
186 | , "> main = putStrLn \"Hello!\""
187 | ]
188 |
189 | targetStartDoc :: Text
190 | targetStartDoc = TL.unlines
191 | [ "Test"
192 | , ""
193 | , "``` {.haskell .numberSource startFrom=\"3\"}"
194 | , "module Main (main) where"
195 | , ""
196 | , "main :: IO ()"
197 | , "main = putStrLn \"Hello!\""
198 | , "```"
199 | ]
200 |
201 | testStartDoc :: TestTree
202 | testStartDoc = testCase "startDoc" $
203 | targetStartDoc @=? run sourceStartDoc
204 |
205 | ------------------------------------------------------------------------------
206 |
207 | sourceStartCode :: Text
208 | sourceStartCode = TL.unlines
209 | [ "> module Main (main) where"
210 | , ">"
211 | , "> main :: IO ()"
212 | , "> main = putStrLn \"Hello!\""
213 | , ""
214 | , "Test"
215 | ]
216 |
217 | targetStartCode :: Text
218 | targetStartCode = TL.unlines
219 | [ "``` {.haskell .numberSource startFrom=\"1\"}"
220 | , "module Main (main) where"
221 | , ""
222 | , "main :: IO ()"
223 | , "main = putStrLn \"Hello!\""
224 | , "```"
225 | , ""
226 | , "Test"
227 | ]
228 |
229 | testStartCode :: TestTree
230 | testStartCode = testCase "startCode" $
231 | targetStartCode @=? run sourceStartCode
232 |
233 | ------------------------------------------------------------------------------
234 |
235 | sourceStartDocBlank :: Text
236 | sourceStartDocBlank = TL.unlines
237 | [ ""
238 | , "Test"
239 | , ""
240 | , "> module Main (main) where"
241 | , ">"
242 | , "> main :: IO ()"
243 | , "> main = putStrLn \"Hello!\""
244 | ]
245 |
246 | targetStartDocBlank :: Text
247 | targetStartDocBlank = TL.unlines
248 | [ "Test"
249 | , ""
250 | , "``` {.haskell .numberSource startFrom=\"4\"}"
251 | , "module Main (main) where"
252 | , ""
253 | , "main :: IO ()"
254 | , "main = putStrLn \"Hello!\""
255 | , "```"
256 | ]
257 |
258 | testStartDocBlank :: TestTree
259 | testStartDocBlank = testCase "startDocBlank" $
260 | targetStartDocBlank @=? run sourceStartDocBlank
261 |
262 | ------------------------------------------------------------------------------
263 |
264 | sourceStartCodeBlank :: Text
265 | sourceStartCodeBlank = TL.unlines
266 | [ ">"
267 | , "> module Main (main) where"
268 | , ">"
269 | , "> main :: IO ()"
270 | , "> main = putStrLn \"Hello!\""
271 | , ""
272 | , "Test"
273 | ]
274 |
275 | targetStartCodeBlank :: Text
276 | targetStartCodeBlank = TL.unlines
277 | [ "``` {.haskell .numberSource startFrom=\"2\"}"
278 | , "module Main (main) where"
279 | , ""
280 | , "main :: IO ()"
281 | , "main = putStrLn \"Hello!\""
282 | , "```"
283 | , ""
284 | , "Test"
285 | ]
286 |
287 | testStartCodeBlank :: TestTree
288 | testStartCodeBlank = testCase "startCodeBlank" $
289 | targetStartCodeBlank @=? run sourceStartCodeBlank
290 |
291 | ------------------------------------------------------------------------------
292 |
293 | sourceCodeDoc :: Text
294 | sourceCodeDoc = TL.unlines
295 | [ "> module Main (main) where"
296 | , ">"
297 | , "> main :: IO ()"
298 | , "> main = putStrLn \"Hello!\""
299 | , "Test"
300 | ]
301 |
302 | targetCodeDoc :: Text
303 | targetCodeDoc = TL.unlines
304 | [ "``` {.haskell .numberSource startFrom=\"1\"}"
305 | , "module Main (main) where"
306 | , ""
307 | , "main :: IO ()"
308 | , "main = putStrLn \"Hello!\""
309 | , "```"
310 | , ""
311 | , "Test"
312 | ]
313 |
314 | testCodeDoc :: TestTree
315 | testCodeDoc = testCase "codeDoc" $
316 | targetCodeDoc @=? run sourceCodeDoc
317 |
318 | ------------------------------------------------------------------------------
319 |
320 | sourceCodeCodeBlanksCode :: Text
321 | sourceCodeCodeBlanksCode = TL.unlines
322 | [ "> module Main (main) where"
323 | , ">"
324 | , ">"
325 | , "> main :: IO ()"
326 | , "> main = putStrLn \"Hello!\""
327 | ]
328 |
329 | targetCodeCodeBlanksCode :: Text
330 | targetCodeCodeBlanksCode = TL.unlines
331 | [ "``` {.haskell .numberSource startFrom=\"1\"}"
332 | , "module Main (main) where"
333 | , ""
334 | , ""
335 | , "main :: IO ()"
336 | , "main = putStrLn \"Hello!\""
337 | , "```"
338 | ]
339 |
340 | testCodeCodeBlanksCode :: TestTree
341 | testCodeCodeBlanksCode = testCase "codeCodeBlanksCode" $
342 | targetCodeCodeBlanksCode @=? run sourceCodeCodeBlanksCode
343 |
344 | ------------------------------------------------------------------------------
345 |
346 | sourceCodeCodeBlanksDoc :: Text
347 | sourceCodeCodeBlanksDoc = TL.unlines
348 | [ "> module Main (main) where"
349 | , ">"
350 | , "> main :: IO ()"
351 | , "> main = putStrLn \"Hello!\""
352 | , ">"
353 | , ">"
354 | , "Test"
355 | ]
356 |
357 | targetCodeCodeBlanksDoc :: Text
358 | targetCodeCodeBlanksDoc = TL.unlines
359 | [ "``` {.haskell .numberSource startFrom=\"1\"}"
360 | , "module Main (main) where"
361 | , ""
362 | , "main :: IO ()"
363 | , "main = putStrLn \"Hello!\""
364 | , "```"
365 | , ""
366 | , "Test"
367 | ]
368 |
369 | testCodeCodeBlanksDoc :: TestTree
370 | testCodeCodeBlanksDoc = testCase "codeCodeBlanksDoc" $
371 | targetCodeCodeBlanksDoc @=? run sourceCodeCodeBlanksDoc
372 |
373 | ------------------------------------------------------------------------------
374 |
375 | sourceCodeDocBlanksDoc :: Text
376 | sourceCodeDocBlanksDoc = TL.unlines
377 | [ "> module Main (main) where"
378 | , ">"
379 | , "> main :: IO ()"
380 | , "> main = putStrLn \"Hello!\""
381 | , ""
382 | , ""
383 | , "Test"
384 | ]
385 |
386 | targetCodeDocBlanksDoc :: Text
387 | targetCodeDocBlanksDoc = TL.unlines
388 | [ "``` {.haskell .numberSource startFrom=\"1\"}"
389 | , "module Main (main) where"
390 | , ""
391 | , "main :: IO ()"
392 | , "main = putStrLn \"Hello!\""
393 | , "```"
394 | , ""
395 | , "Test"
396 | ]
397 |
398 | testCodeDocBlanksDoc :: TestTree
399 | testCodeDocBlanksDoc = testCase "codeDocBlanksDoc" $
400 | targetCodeDocBlanksDoc @=? run sourceCodeDocBlanksDoc
401 |
402 | ------------------------------------------------------------------------------
403 |
404 | sourceCodeDocBlanksCode :: Text
405 | sourceCodeDocBlanksCode = TL.unlines
406 | [ "> module Main (main) where"
407 | , ""
408 | , ""
409 | , "> main :: IO ()"
410 | , "> main = putStrLn \"Hello!\""
411 | ]
412 |
413 | targetCodeDocBlanksCode :: Text
414 | targetCodeDocBlanksCode = TL.unlines
415 | [ "``` {.haskell .numberSource startFrom=\"1\"}"
416 | , "module Main (main) where"
417 | , "```"
418 | , ""
419 | , "``` {.haskell .numberSource startFrom=\"4\"}"
420 | , "main :: IO ()"
421 | , "main = putStrLn \"Hello!\""
422 | , "```"
423 | ]
424 |
425 | testCodeDocBlanksCode :: TestTree
426 | testCodeDocBlanksCode = testCase "codeDocBlanksCode" $
427 | targetCodeDocBlanksCode @=? run sourceCodeDocBlanksCode
428 |
429 | ------------------------------------------------------------------------------
430 |
431 | sourceDocCode :: Text
432 | sourceDocCode = TL.unlines
433 | [ "Test"
434 | , "> module Main (main) where"
435 | , ">"
436 | , "> main :: IO ()"
437 | , "> main = putStrLn \"Hello!\""
438 | ]
439 |
440 | targetDocCode :: Text
441 | targetDocCode = TL.unlines
442 | [ "Test"
443 | , ""
444 | , "``` {.haskell .numberSource startFrom=\"2\"}"
445 | , "module Main (main) where"
446 | , ""
447 | , "main :: IO ()"
448 | , "main = putStrLn \"Hello!\""
449 | , "```"
450 | ]
451 |
452 | testDocCode :: TestTree
453 | testDocCode = testCase "docCode" $
454 | targetDocCode @=? run sourceDocCode
455 |
456 | ------------------------------------------------------------------------------
457 |
458 | sourceDocDocBlanksDoc :: Text
459 | sourceDocDocBlanksDoc = TL.unlines
460 | [ "one"
461 | , ""
462 | , ""
463 | , "two"
464 | ]
465 |
466 | targetDocDocBlanksDoc :: Text
467 | targetDocDocBlanksDoc = TL.unlines
468 | [ "one"
469 | , ""
470 | , "two"
471 | ]
472 |
473 | testDocDocBlanksDoc :: TestTree
474 | testDocDocBlanksDoc = testCase "docDocBlanksDoc" $
475 | targetDocDocBlanksDoc @=? run sourceDocDocBlanksDoc
476 |
477 | ------------------------------------------------------------------------------
478 |
479 | sourceDocDocBlanksCode :: Text
480 | sourceDocDocBlanksCode = TL.unlines
481 | [ "Test"
482 | , ""
483 | , ""
484 | , "> module Main (main) where"
485 | , ">"
486 | , "> main :: IO ()"
487 | , "> main = putStrLn \"Hello!\""
488 | ]
489 |
490 | targetDocDocBlanksCode :: Text
491 | targetDocDocBlanksCode = TL.unlines
492 | [ "Test"
493 | , ""
494 | , "``` {.haskell .numberSource startFrom=\"4\"}"
495 | , "module Main (main) where"
496 | , ""
497 | , "main :: IO ()"
498 | , "main = putStrLn \"Hello!\""
499 | , "```"
500 | ]
501 |
502 | testDocDocBlanksCode :: TestTree
503 | testDocDocBlanksCode = testCase "docDocBlanksCode" $
504 | targetDocDocBlanksCode @=? run sourceDocDocBlanksCode
505 |
506 | ------------------------------------------------------------------------------
507 |
508 | sourceDocCodeBlanksCode :: Text
509 | sourceDocCodeBlanksCode = TL.unlines
510 | [ "Test"
511 | , ">"
512 | , ">"
513 | , "> module Main (main) where"
514 | , ">"
515 | , "> main :: IO ()"
516 | , "> main = putStrLn \"Hello!\""
517 | ]
518 |
519 | targetDocCodeBlanksCode :: Text
520 | targetDocCodeBlanksCode = TL.unlines
521 | [ "Test"
522 | , ""
523 | , "``` {.haskell .numberSource startFrom=\"4\"}"
524 | , "module Main (main) where"
525 | , ""
526 | , "main :: IO ()"
527 | , "main = putStrLn \"Hello!\""
528 | , "```"
529 | ]
530 |
531 | testDocCodeBlanksCode :: TestTree
532 | testDocCodeBlanksCode = testCase "docCodeBlanksCode" $
533 | targetDocCodeBlanksCode @=? run sourceDocCodeBlanksCode
534 |
535 | ------------------------------------------------------------------------------
536 |
537 | sourceDocCodeBlanksDoc :: Text
538 | sourceDocCodeBlanksDoc = TL.unlines
539 | [ "one"
540 | , ">"
541 | , ">"
542 | , "two"
543 | ]
544 |
545 | targetDocCodeBlanksDoc :: Text
546 | targetDocCodeBlanksDoc = TL.unlines
547 | [ "one"
548 | , ""
549 | , "two"
550 | ]
551 |
552 | testDocCodeBlanksDoc :: TestTree
553 | testDocCodeBlanksDoc = testCase "docCodeBlanksDoc" $
554 | targetDocCodeBlanksDoc @=? run sourceDocCodeBlanksDoc
555 |
556 | ------------------------------------------------------------------------------
557 |
558 | sourceEndDoc :: Text
559 | sourceEndDoc = TL.unlines
560 | [ "Test"
561 | ]
562 |
563 | targetEndDoc :: Text
564 | targetEndDoc = TL.unlines
565 | [ "Test"
566 | ]
567 |
568 | testEndDoc :: TestTree
569 | testEndDoc = testCase "endDoc" $
570 | targetEndDoc @=? run sourceEndDoc
571 |
572 | ------------------------------------------------------------------------------
573 |
574 | sourceEndDocBlanks :: Text
575 | sourceEndDocBlanks = TL.unlines
576 | [ ""
577 | , ""
578 | ]
579 |
580 | targetEndDocBlanks :: Text
581 | targetEndDocBlanks = ""
582 |
583 | testEndDocBlanks :: TestTree
584 | testEndDocBlanks = testCase "endDocBlanks" $
585 | targetEndDocBlanks @=? run sourceEndDocBlanks
586 |
587 | ------------------------------------------------------------------------------
588 |
589 | sourceEndCode :: Text
590 | sourceEndCode = TL.unlines
591 | [ "> module Main (main) where"
592 | , ">"
593 | , "> main :: IO ()"
594 | , "> main = putStrLn \"Hello!\""
595 | ]
596 |
597 | targetEndCode :: Text
598 | targetEndCode = TL.unlines
599 | [ "``` {.haskell .numberSource startFrom=\"1\"}"
600 | , "module Main (main) where"
601 | , ""
602 | , "main :: IO ()"
603 | , "main = putStrLn \"Hello!\""
604 | , "```"
605 | ]
606 |
607 | testEndCode :: TestTree
608 | testEndCode = testCase "endCode" $
609 | targetEndCode @=? run sourceEndCode
610 |
611 | ------------------------------------------------------------------------------
612 |
613 | sourceEndCodeBlanks :: Text
614 | sourceEndCodeBlanks = TL.unlines
615 | [ "> module Main (main) where"
616 | , ">"
617 | , "> main :: IO ()"
618 | , "> main = putStrLn \"Hello!\""
619 | , ">"
620 | , ">"
621 | ]
622 |
623 | targetEndCodeBlanks :: Text
624 | targetEndCodeBlanks = TL.unlines
625 | [ "``` {.haskell .numberSource startFrom=\"1\"}"
626 | , "module Main (main) where"
627 | , ""
628 | , "main :: IO ()"
629 | , "main = putStrLn \"Hello!\""
630 | , "```"
631 | ]
632 |
633 | testEndCodeBlanks :: TestTree
634 | testEndCodeBlanks = testCase "endCodeBlanks" $
635 | targetEndCodeBlanks @=? run sourceEndCodeBlanks
636 |
637 | ------------------------------------------------------------------------------
638 |
639 | sourceEmpty :: Text
640 | sourceEmpty = ""
641 |
642 | sourceOnlyDocBlanks :: Text
643 | sourceOnlyDocBlanks = TL.unlines
644 | [ ""
645 | , ""
646 | ]
647 |
648 | sourceOnlyCodeBlanks :: Text
649 | sourceOnlyCodeBlanks = TL.unlines
650 | [ ">"
651 | , ">"
652 | ]
653 |
654 | targetEmpty :: Text
655 | targetEmpty = ""
656 |
657 | testEmpty :: TestTree
658 | testEmpty = testCase "empty" $
659 | targetEmpty @=? run sourceEmpty
660 |
661 | testOnlyDocBlanks :: TestTree
662 | testOnlyDocBlanks = testCase "onlyDocBlanks" $
663 | targetEmpty @=? run sourceOnlyDocBlanks
664 |
665 | testOnlyCodeBlanks :: TestTree
666 | testOnlyCodeBlanks = testCase "onlyCodeBlanks" $
667 | targetEmpty @=? run sourceOnlyCodeBlanks
668 |
669 | ------------------------------------------------------------------------------
670 |
671 | tests :: TestTree
672 | tests = testGroup "LiterateHaskell"
673 | [ testStartShebangDoc
674 | , testStartShebangCode
675 | , testStartDoc
676 | , testStartCode
677 | , testStartDocBlank
678 | , testStartCodeBlank
679 | , testCodeDoc
680 | , testCodeCodeBlanksCode
681 | , testCodeCodeBlanksDoc
682 | , testCodeDocBlanksDoc
683 | , testCodeDocBlanksCode
684 | , testDocCode
685 | , testDocDocBlanksDoc
686 | , testDocDocBlanksCode
687 | , testDocCodeBlanksCode
688 | , testDocCodeBlanksDoc
689 | , testEndDoc
690 | , testEndDocBlanks
691 | , testEndCode
692 | , testEndCodeBlanks
693 | , testEmpty
694 | , testOnlyDocBlanks
695 | , testOnlyCodeBlanks
696 | ]
697 |
--------------------------------------------------------------------------------
/test/LiterateX/Test/TargetFormat.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE OverloadedStrings #-}
2 |
3 | module LiterateX.Test.TargetFormat (tests) where
4 |
5 | -- https://hackage.haskell.org/package/tasty
6 | import Test.Tasty (TestTree, testGroup)
7 |
8 | -- https://hackage.haskell.org/package/tasty-hunit
9 | import Test.Tasty.HUnit ((@=?), testCase)
10 |
11 | -- https://hackage.haskell.org/package/text
12 | import qualified Data.Text.Lazy as TL
13 | import Data.Text.Lazy (Text)
14 |
15 | -- (literatex)
16 | import qualified LiterateX
17 | import qualified LiterateX.Renderer as Renderer
18 | import qualified LiterateX.Types.SourceFormat as SourceFormat
19 | import qualified LiterateX.Types.TargetFormat as TargetFormat
20 |
21 | ------------------------------------------------------------------------------
22 |
23 | sourceTL :: Text
24 | sourceTL = TL.unlines
25 | [ "#!/usr/bin/env python"
26 | , ""
27 | , "# Test"
28 | , ""
29 | , "print('Hello!')"
30 | ]
31 |
32 | ------------------------------------------------------------------------------
33 |
34 | pandocLangNum :: Text
35 | pandocLangNum = TL.unlines
36 | [ "Test"
37 | , ""
38 | , "``` {.python .numberSource startFrom=\"5\"}"
39 | , "print('Hello!')"
40 | , "```"
41 | ]
42 |
43 | pandocLangNoNum :: Text
44 | pandocLangNoNum = TL.unlines
45 | [ "Test"
46 | , ""
47 | , "```python"
48 | , "print('Hello!')"
49 | , "```"
50 | ]
51 |
52 | pandocNoLangNum :: Text
53 | pandocNoLangNum = TL.unlines
54 | [ "Test"
55 | , ""
56 | , "``` {.numberSource startFrom=\"5\"}"
57 | , "print('Hello!')"
58 | , "```"
59 | ]
60 |
61 | pandocNoLangNoNum :: Text
62 | pandocNoLangNoNum = TL.unlines
63 | [ "Test"
64 | , ""
65 | , "```"
66 | , "print('Hello!')"
67 | , "```"
68 | ]
69 |
70 | ------------------------------------------------------------------------------
71 |
72 | githubLangNum :: Text
73 | githubLangNum = TL.unlines
74 | [ "Test"
75 | , ""
76 | , "``` python startline=5"
77 | , "print('Hello!')"
78 | , "```"
79 | ]
80 |
81 | githubLangNoNum :: Text
82 | githubLangNoNum = TL.unlines
83 | [ "Test"
84 | , ""
85 | , "``` python"
86 | , "print('Hello!')"
87 | , "```"
88 | ]
89 |
90 | githubNoLangNum :: Text
91 | githubNoLangNum = TL.unlines
92 | [ "Test"
93 | , ""
94 | , "``` startline=5"
95 | , "print('Hello!')"
96 | , "```"
97 | ]
98 |
99 | githubNoLangNoNum :: Text
100 | githubNoLangNoNum = TL.unlines
101 | [ "Test"
102 | , ""
103 | , "```"
104 | , "print('Hello!')"
105 | , "```"
106 | ]
107 |
108 | ------------------------------------------------------------------------------
109 |
110 | rendererOpts :: Renderer.Options
111 | rendererOpts = Renderer.Options
112 | { Renderer.targetFormat = TargetFormat.PandocMarkdown
113 | , Renderer.codeLanguage = Just "python"
114 | , Renderer.ignoreShebang = True
115 | , Renderer.renderCode = True
116 | , Renderer.numberCodeLines = True
117 | }
118 |
119 | rendererOptsGFM :: Renderer.Options
120 | rendererOptsGFM = rendererOpts
121 | { Renderer.targetFormat = TargetFormat.GitHubFlavoredMarkdown
122 | }
123 |
124 | run' :: Renderer.Options -> Text
125 | run' opts = LiterateX.transformTextToText SourceFormat.Hash opts sourceTL
126 |
127 | ------------------------------------------------------------------------------
128 |
129 | testPandocMarkdown :: TestTree
130 | testPandocMarkdown = testGroup "PandocMarkdown"
131 | [ testCase "LangNum" $ pandocLangNum @=? run' rendererOpts
132 | , testCase "LangNoNum" $ pandocLangNoNum @=? run' rendererOpts
133 | { Renderer.numberCodeLines = False
134 | }
135 | , testCase "NoLangNum" $ pandocNoLangNum @=? run' rendererOpts
136 | { Renderer.codeLanguage = Nothing
137 | }
138 | , testCase "NoLangNoNum" $ pandocNoLangNoNum @=? run' rendererOpts
139 | { Renderer.codeLanguage = Nothing
140 | , Renderer.numberCodeLines = False
141 | }
142 | ]
143 |
144 | ------------------------------------------------------------------------------
145 |
146 | testGitHubFlavoredMarkdown :: TestTree
147 | testGitHubFlavoredMarkdown = testGroup "GitHubFlavoredMarkdown"
148 | [ testCase "LangNum" $ githubLangNum @=? run' rendererOptsGFM
149 | , testCase "LangNoNum" $ githubLangNoNum @=? run' rendererOptsGFM
150 | { Renderer.numberCodeLines = False
151 | }
152 | , testCase "NoLangNum" $ githubNoLangNum @=? run' rendererOptsGFM
153 | { Renderer.codeLanguage = Nothing
154 | }
155 | , testCase "NoLangNoNum" $ githubNoLangNoNum @=? run' rendererOptsGFM
156 | { Renderer.codeLanguage = Nothing
157 | , Renderer.numberCodeLines = False
158 | }
159 | ]
160 |
161 | ------------------------------------------------------------------------------
162 |
163 | tests :: TestTree
164 | tests = testGroup "LiterateX.Test.TargetFormat"
165 | [ testPandocMarkdown
166 | , testGitHubFlavoredMarkdown
167 | ]
168 |
--------------------------------------------------------------------------------
/test/Spec.hs:
--------------------------------------------------------------------------------
1 | module Main (main) where
2 |
3 | -- https://hackage.haskell.org/package/tasty
4 | import Test.Tasty (defaultMain, testGroup)
5 |
6 | -- (literatex)
7 | import qualified LiterateX.Test.API
8 | import qualified LiterateX.Test.SourceFormat
9 | import qualified LiterateX.Test.TargetFormat
10 |
11 | ------------------------------------------------------------------------------
12 |
13 | main :: IO ()
14 | main = defaultMain $ testGroup "test"
15 | [ LiterateX.Test.API.tests
16 | , LiterateX.Test.SourceFormat.tests
17 | , LiterateX.Test.TargetFormat.tests
18 | ]
19 |
--------------------------------------------------------------------------------