├── .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 | [![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) 4 | [![GitHub CI](https://github.com/ExtremaIS/literatex-haskell/workflows/CI/badge.svg?branch=main)](https://github.com/ExtremaIS/literatex-haskell/actions) 5 | [![Hackage](https://img.shields.io/hackage/v/literatex.svg)](https://hackage.haskell.org/package/literatex) 6 | [![Stackage LTS](https://stackage.org/package/literatex/badge/lts)](https://stackage.org/package/literatex) 7 | [![Stackage Nightly](https://stackage.org/package/literatex/badge/nightly)](https://stackage.org/nightly/package/literatex) 8 | 9 | ![LiterateX: LiterateBash, LiterateC, LiterateClojure, LiterateErlang, LiterateHaskell, LiterateIdris, LiterateJavaScript, LiteratePython, LiterateRacket, LiterateSQL, Literate...](project/animation/literatex.gif) 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 | --------------------------------------------------------------------------------