pretty-0.12.3/.cargo_vcs_info.json0000644000000001360000000000100124460ustar { "git": { "sha1": "c1c2af60cd6a1e20dc7563acdf00f716b759a13b" }, "path_in_vcs": "" }pretty-0.12.3/.clog.toml000064400000000000000000000001561046102023000131370ustar 00000000000000[clog] repository = "https://github.com/Marwes/pretty.rs" changelog = "CHANGELOG.md" from-latest-tag = true pretty-0.12.3/.github/workflows/build.yml000064400000000000000000000006241046102023000164570ustar 00000000000000name: Build on: push: branches: [ master ] pull_request: branches: [ master ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Build run: cargo build - name: Run tests run: cargo test --no-default-features - name: Run benchmarks run: cargo test --all-features --examples --benches pretty-0.12.3/.gitignore000064400000000000000000000067141046102023000132360ustar 00000000000000### https://raw.github.com/github/gitignore/f93202c42e947f3be10b3bd6912b48e30e7e9781/Global/Emacs.gitignore # -*- mode: gitignore; -*- *~ \#*\# /.emacs.desktop /.emacs.desktop.lock *.elc auto-save-list tramp .\#* # Org-mode .org-id-locations *_archive # flymake-mode *_flymake.* # eshell files /eshell/history /eshell/lastdir # elpa packages /elpa/ # reftex files *.rel # AUCTeX auto folder /auto/ # cask packages .cask/ dist/ # Flycheck flycheck_*.el # server auth directory /server/ # projectiles files .projectile ### https://raw.github.com/github/gitignore/f93202c42e947f3be10b3bd6912b48e30e7e9781/Global/Linux.gitignore *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* ### https://raw.github.com/github/gitignore/f93202c42e947f3be10b3bd6912b48e30e7e9781/Global/OSX.gitignore *.DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### https://raw.github.com/github/gitignore/f93202c42e947f3be10b3bd6912b48e30e7e9781/Rust.gitignore # Generated by Cargo # will have compiled files and executables /target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock Cargo.lock ### https://raw.github.com/github/gitignore/f93202c42e947f3be10b3bd6912b48e30e7e9781/Global/SublimeText.gitignore # cache files for sublime text *.tmlanguage.cache *.tmPreferences.cache *.stTheme.cache # workspace files are user-specific *.sublime-workspace # project files should be checked into the repository, unless a significant # proportion of contributors will probably not be using SublimeText # *.sublime-project # sftp configuration file sftp-config.json # Package control specific files Package Control.last-run Package Control.ca-list Package Control.ca-bundle Package Control.system-ca-bundle Package Control.cache/ Package Control.ca-certs/ bh_unicode_properties.cache # Sublime-github package stores a github token in this file # https://packagecontrol.io/packages/sublime-github GitHub.sublime-settings ### https://raw.github.com/github/gitignore/f93202c42e947f3be10b3bd6912b48e30e7e9781/Global/Tags.gitignore # Ignore tags created by etags, ctags, gtags (GNU global) and cscope TAGS .TAGS !TAGS/ tags .tags !tags/ gtags.files GTAGS GRTAGS GPATH cscope.files cscope.out cscope.in.out cscope.po.out ### https://raw.github.com/github/gitignore/f93202c42e947f3be10b3bd6912b48e30e7e9781/Global/Vim.gitignore # swap [._]*.s[a-w][a-z] [._]s[a-w][a-z] # session Session.vim # temporary .netrwhist *~ # auto-generated tag files tags ### https://raw.github.com/github/gitignore/f93202c42e947f3be10b3bd6912b48e30e7e9781/Global/VisualStudioCode.gitignore .vscode ### https://raw.github.com/github/gitignore/f93202c42e947f3be10b3bd6912b48e30e7e9781/Global/Windows.gitignore # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # Windows shortcuts *.lnk pretty-0.12.3/CHANGELOG.md000064400000000000000000000113051046102023000130470ustar 00000000000000 ### v0.12.3 (2023-09-21) #### Features * Implement Pretty for Cow ([5897df33](https://github.com/Marwes/pretty.rs/commit/5897df33008890d434d2948f6c3c0f692c70eeae)) ### v0.12.2 (2023-09-21) #### Bug Fixes * CJK characters display width ([d1cdc4ef](https://github.com/Marwes/pretty.rs/commit/d1cdc4ef961864743e66bd245985549d58228aaf)) ### v0.12.1 (2023-04-19) #### Bug Fixes * Don't leak the indentation of earlier documents into latter ones ([430bb457](https://github.com/Marwes/pretty.rs/commit/430bb4571f77362c45451063aba728d232503677)) ## v0.12.0 (2023-03-30) #### Features * Add the BlockDoc formatter ([d4106e4e](https://github.com/Marwes/pretty.rs/commit/d4106e4e28d826755d36028d0cc57769d1261e44)) #### Bug Fixes * Make `RcDoc` a wrapper around `Rc`, not `Box` ### v0.11.3 (2022-04-21) ### v0.11.2 (2021-12-15) #### Features * Implement Add/AddAssign on DocBuilder ([45e085a4](https://github.com/Marwes/pretty.rs/commit/45e085a4bd5737c66be8654ab30dae2fd6aa5a08)) ### v0.11.1 (2021-12-10) #### Bug Fixes * Allow &String in the docs! macro again ([e02bdc8f](https://github.com/Marwes/pretty.rs/commit/e02bdc8f24f251a496529fc79fcb243da816ddee)) ## v0.11.0 (2021-12-10) #### Features * Use display width during rendering ([b88f123a](https://github.com/Marwes/pretty.rs/commit/b88f123a3dba6f2f269d4c9f38961765238983a8), closes [#67](https://github.com/Marwes/pretty.rs/issues/67)) * Introduce the Pretty trait ([84a41d3d](https://github.com/Marwes/pretty.rs/commit/84a41d3daf2adfb6d3d8aa5806e12ff26bd4e3b7)) * Implement Deref for DocBuilder ([97602f36](https://github.com/Marwes/pretty.rs/commit/97602f36aff8c0a3a5895a9d6348fea424a5fb2c)) * Implement Debug on DocBuilder ([05bc8b76](https://github.com/Marwes/pretty.rs/commit/05bc8b76e05fc8dd0fbd425987c5597dab8a860c)) * Debug print Line documents ([063052d5](https://github.com/Marwes/pretty.rs/commit/063052d5ac4ef484ab3988547fa373824ed975fd)) * Debug print sotfline_ docs in a shortform ([b64b8b37](https://github.com/Marwes/pretty.rs/commit/b64b8b37f5cb23830ce37ce0ee475af5a28998ed)) * Debug print sotfline docs in a shortform ([86915fea](https://github.com/Marwes/pretty.rs/commit/86915fea79ddcb02f1cc32f6f23229a3dbd9fee5)) #### Performance * convert empty text to Nil docs ([91931342](https://github.com/Marwes/pretty.rs/commit/91931342315c0500f93cfbf3f4f97f774d2192ae)) * No need to group on individual text components ([9a67247b](https://github.com/Marwes/pretty.rs/commit/9a67247b725380607d315e8117cf757eca5f0b82)) ## v0.7.0 (2019-12-01) #### Breaking Changes * Rename space to line and newline to hardline ([a011c1b0](https://github.com/Marwes/pretty.rs/commit/a011c1b05d26257c5b529eaa973073d94522225c), breaks [#](https://github.com/Marwes/pretty.rs/issues/)) #### Features * Add RcDoc ([f7c675fc](https://github.com/Marwes/pretty.rs/commit/f7c675fc9d6eb2142f71033cffa4bfac5749b1bc)) * Add convenience combinators for enclosing documents ([5358b6ed](https://github.com/Marwes/pretty.rs/commit/5358b6edd562b082ec9f1f89503f2eea7f2f8aaa)) * Add softline ([3802a856](https://github.com/Marwes/pretty.rs/commit/3802a8566c51ec2819ab485b194d0e14f6ccc1c0)) * Rename space to line and newline to hardline ([a011c1b0](https://github.com/Marwes/pretty.rs/commit/a011c1b05d26257c5b529eaa973073d94522225c), breaks [#](https://github.com/Marwes/pretty.rs/issues/)) * Add nesting and align ([713f5a98](https://github.com/Marwes/pretty.rs/commit/713f5a984d8bea0afa0f82af92b0b5148f853b4b)) * Add the width document ([927583e9](https://github.com/Marwes/pretty.rs/commit/927583e948f979966626d0819acffbba1522acda)) * Introduce the Column document ([f78cd2ea](https://github.com/Marwes/pretty.rs/commit/f78cd2ea9b366a9d2d1ae1a2fd93adf1807ff20b)) #### Bug Fixes * Allow usize::max_value as a width ([340f6685](https://github.com/Marwes/pretty.rs/commit/340f6685ca08cd7adaee1599efaec8b4b403c137), closes [#53](https://github.com/Marwes/pretty.rs/issues/53)) ## v0.5.0 (2018-06-16) #### Breaking Changes * Change the type parameter order so attributes can be defaulted ([ba08cedc](https://github.com/Marwes/pretty.rs/commit/ba08cedcdfe2ce117d757ab5bc0fcfb4d2a7a6b6), breaks [#](https://github.com/Marwes/pretty.rs/issues/)) #### Features * Allow custom attributes to be rendered ([07c8ac03](https://github.com/Marwes/pretty.rs/commit/07c8ac03178c00a3d28a02b7395701b59d6abe4d)) * Permit newlines in text documents ([d11ad4be](https://github.com/Marwes/pretty.rs/commit/d11ad4bee656f67fba42fcc50988d7aa7a271a7e)) pretty-0.12.3/Cargo.lock0000644000000507400000000000100104270ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" dependencies = [ "memchr", ] [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "arrayvec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "bumpalo" version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "libc", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "ciborium" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" [[package]] name = "ciborium-ll" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "bitflags 1.3.2", "clap_lex", "indexmap", "textwrap", ] [[package]] name = "clap_lex" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] [[package]] name = "criterion" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" dependencies = [ "anes", "atty", "cast", "ciborium", "clap", "criterion-plot", "itertools", "lazy_static", "num-traits", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", ] [[package]] name = "crossbeam-deque" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] [[package]] name = "difference" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" [[package]] name = "dissimilar" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632" [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "errno" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", "windows-sys", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "expect-test" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d9eafeadd538e68fb28016364c9732d78e420b9ff8853fa5e4058861e9f8d3" dependencies = [ "dissimilar", "once_cell", ] [[package]] name = "fastrand" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", ] [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "linux-raw-sys" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" [[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "memoffset" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] name = "num-traits" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "oorandom" version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "os_str_bytes" version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] name = "plotters" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] [[package]] name = "pretty" version = "0.12.3" dependencies = [ "arrayvec", "criterion", "difference", "expect-test", "tempfile", "termcolor", "typed-arena", "unicode-width", ] [[package]] name = "proc-macro2" version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "rustix" version = "0.38.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" dependencies = [ "bitflags 2.4.0", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "syn" version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", "redox_syscall", "rustix", "windows-sys", ] [[package]] name = "termcolor" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" dependencies = [ "winapi-util", ] [[package]] name = "textwrap" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "typed-arena" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-width" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "walkdir" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasm-bindgen" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" pretty-0.12.3/Cargo.toml0000644000000027570000000000100104570ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "pretty" version = "0.12.3" authors = [ "Jonathan Sterling ", "Darin Morrison ", "Markus Westerlind ", ] description = "Wadler-style pretty-printing combinators in Rust" documentation = "https://docs.rs/pretty/" readme = "README.md" keywords = [ "console", "functional", "pretty-printing", ] license = "MIT" repository = "https://github.com/Marwes/pretty.rs" [package.metadata.docs.rs] features = ["termcolor"] [[example]] name = "trees" [[example]] name = "colored" required-features = ["termcolor"] [[bench]] name = "trees" harness = false [dependencies.arrayvec] version = "0.5" [dependencies.termcolor] version = "1.1.0" optional = true [dependencies.typed-arena] version = "2.0.0" [dependencies.unicode-width] version = "0.1" [dev-dependencies.criterion] version = "0.4" [dev-dependencies.difference] version = "2" [dev-dependencies.expect-test] version = "1" [dev-dependencies.tempfile] version = "3.1.0" pretty-0.12.3/Cargo.toml.orig000064400000000000000000000020511046102023000141230ustar 00000000000000[package] name = "pretty" version = "0.12.3" authors = [ "Jonathan Sterling ", "Darin Morrison ", "Markus Westerlind "] description = "Wadler-style pretty-printing combinators in Rust" documentation = "https://docs.rs/pretty/" keywords = ["console", "functional", "pretty-printing"] license = "MIT" readme = "README.md" repository = "https://github.com/Marwes/pretty.rs" edition = "2018" [package.metadata.docs.rs] features = ["termcolor"] [dependencies] # The newer versions of arrayvec are larger due to forced u32 lengths, would need to drop down Smalltext to 16 bytes # to not see any size increase so I'd rather stick with the older version for now arrayvec = "0.5" typed-arena = "2.0.0" termcolor = { version = "1.1.0", optional = true } unicode-width = "0.1" [dev-dependencies] criterion = "0.4" difference = "2" expect-test = "1" tempfile = "3.1.0" [[example]] name = "trees" [[example]] name = "colored" required-features = ["termcolor"] [[bench]] name = "trees" harness = false pretty-0.12.3/LICENSE000064400000000000000000000021161046102023000122430ustar 00000000000000The MIT License (MIT) Copyright (c) 2014 Jonathan Sterling and Darin Morrison Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pretty-0.12.3/README.md000064400000000000000000000031161046102023000125160ustar 00000000000000# pretty.rs [![build](https://github.com/Marwes/pretty.rs/actions/workflows/build.yml/badge.svg)](https://github.com/Marwes/pretty.rs/actions/workflows/build.yml) [![Docs](https://docs.rs/pretty/badge.svg)](https://docs.rs/pretty) Pretty printing combinators for Rust ## Synopsis This crate provides functionality for defining pretty printers. It is particularly useful for printing structured recursive data like trees. The implementation was originally based on Larsen's SML translation (https://github.com/kfl/wpp) of Wadler's Haskell pretty printer (https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf). It has since been modified in various ways to better fit Rust's programming model. In particular, it uses iteration rather than recursion and provides streaming output. ## Documentation See the generated API documentation [here](https://docs.rs/pretty). ## Requirements 1. [Rust](https://www.rust-lang.org/) 2. [Cargo](https://crates.io/) You can install both with the following: ``` $ curl -s https://static.rust-lang.org/rustup.sh | sudo sh ``` See [Installation](https://doc.rust-lang.org/book/ch01-01-installation.html) for further details. ## Usage ``` $ cargo build ## build library and binary $ cargo run --example trees ## run the example (pretty trees) $ cargo run --example colored --features termcolor ## run the example (pretty colored output) $ cargo bench ## run benchmarks $ cargo test ## run tests ``` pretty-0.12.3/benches/trees.rs000064400000000000000000000072411046102023000143410ustar 00000000000000use std::io; use criterion::{criterion_group, criterion_main, Bencher, Criterion}; use crate::trees::Tree; use pretty::{Arena, BoxAllocator}; #[path = "../examples/trees.rs"] mod trees; macro_rules! bench_trees { ($b:expr, $out:expr, $allocator:expr, $size:expr) => {{ let arena = typed_arena::Arena::new(); let b = $b; let mut out = $out; let size = $size; let mut example = Tree::node("aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); for _ in 0..size { let bbbbbbs = arena.alloc_extend([example, Tree::node("dd")].iter().cloned()); let ffffs = arena.alloc_extend( [Tree::node("gg"), Tree::node("hhh"), Tree::node("ii")] .iter() .cloned(), ); let aaas = arena.alloc_extend( [ Tree::node_with_forest("bbbbbb", bbbbbbs), Tree::node("eee"), Tree::node_with_forest("ffff", ffffs), ] .iter() .cloned(), ); example = Tree::node_with_forest("aaa", aaas); } let allocator = $allocator; b.iter(|| { example .pretty::<_, ()>(&allocator) .1 .render(70, &mut out) .unwrap(); }); }}; } fn bench_sink_box(b: &mut Bencher<'_>) { bench_trees!(b, io::sink(), BoxAllocator, 1) } fn bench_sink_arena(b: &mut Bencher<'_>) { bench_trees!(b, io::sink(), Arena::new(), 1) } fn bench_vec_box(b: &mut Bencher<'_>) { bench_trees!(b, Vec::new(), BoxAllocator, 1) } fn bench_vec_arena(b: &mut Bencher<'_>) { bench_trees!(b, Vec::new(), Arena::new(), 1) } fn bench_io_box(b: &mut Bencher<'_>) { let out = tempfile::tempfile().unwrap(); bench_trees!(b, io::BufWriter::new(out), BoxAllocator, 1) } fn bench_io_arena(b: &mut Bencher<'_>) { let out = tempfile::tempfile().unwrap(); bench_trees!(b, io::BufWriter::new(out), Arena::new(), 1) } fn bench_large_sink_box(b: &mut Bencher<'_>) { bench_trees!(b, io::sink(), BoxAllocator, 50) } fn bench_large_sink_arena(b: &mut Bencher<'_>) { bench_trees!(b, io::sink(), Arena::new(), 50) } fn bench_large_vec_box(b: &mut Bencher<'_>) { bench_trees!(b, Vec::new(), BoxAllocator, 50) } fn bench_large_vec_arena(b: &mut Bencher<'_>) { bench_trees!(b, Vec::new(), Arena::new(), 50) } fn bench_large_io_box(b: &mut Bencher<'_>) { let out = tempfile::tempfile().unwrap(); bench_trees!(b, io::BufWriter::new(out), BoxAllocator, 50) } fn bench_large_io_arena(b: &mut Bencher<'_>) { let out = tempfile::tempfile().unwrap(); bench_trees!(b, io::BufWriter::new(out), Arena::new(), 50) } fn bench_pretty(c: &mut Criterion) { { let mut group = c.benchmark_group("small"); group.bench_function("sink_box", bench_sink_box); group.bench_function("sink_arena", bench_sink_arena); group.bench_function("vec_box", bench_vec_box); group.bench_function("vec_arena", bench_vec_arena); group.bench_function("io_box", bench_io_box); group.bench_function("io_arena", bench_io_arena); } { let mut group = c.benchmark_group("large"); group.bench_function("sink_box", bench_large_sink_box); group.bench_function("sink_arena", bench_large_sink_arena); group.bench_function("vec_box", bench_large_vec_box); group.bench_function("vec_arena", bench_large_vec_arena); group.bench_function("io_box", bench_large_io_box); group.bench_function("io_arena", bench_large_io_arena); } } criterion_group!(benches, bench_pretty); criterion_main!(benches); pretty-0.12.3/examples/colored.rs000064400000000000000000000015531046102023000150550ustar 00000000000000use pretty::termcolor::{Color, ColorChoice, ColorSpec, StandardStream}; use pretty::{Arena, DocAllocator}; fn main() { let arena = Arena::new(); let red = arena .text("red") .annotate(ColorSpec::new().set_fg(Some(Color::Red)).clone()); let blue = arena .text("blue") .annotate(ColorSpec::new().set_fg(Some(Color::Blue)).clone()); let bold = arena .text("bold") .annotate(ColorSpec::new().set_bold(true).clone()); let intense = arena .text("intense") .annotate(ColorSpec::new().set_intense(true).clone()); red.append(arena.space()) .append(blue) .append(arena.space()) .append(bold) .append(arena.space()) .append(intense) .group() .1 .render_colored(80, StandardStream::stdout(ColorChoice::Auto)) .unwrap(); } pretty-0.12.3/examples/trees.rs000064400000000000000000000060631046102023000145510ustar 00000000000000use pretty::{BoxAllocator, DocAllocator, DocBuilder}; use std::io; use std::str; #[derive(Clone, Debug)] pub struct Forest<'a>(&'a [Tree<'a>]); impl<'a> Forest<'a> { fn forest(forest: &'a [Tree<'a>]) -> Forest<'a> { Forest(forest) } fn nil() -> Forest<'a> { Forest(&[]) } fn bracket<'b, D, A>(&'b self, allocator: &'b D) -> DocBuilder<'b, D, A> where D: DocAllocator<'b, A>, D::Doc: Clone, A: Clone, { if (self.0).is_empty() { allocator.nil() } else { allocator .text("[") .append(allocator.hardline().append(self.pretty(allocator)).nest(2)) .append(allocator.hardline()) .append(allocator.text("]")) } } fn pretty<'b, D, A>(&'b self, allocator: &'b D) -> DocBuilder<'b, D, A> where D: DocAllocator<'b, A>, D::Doc: Clone, A: Clone, { let forest = self.0; let separator = allocator.text(",").append(allocator.hardline()); allocator.intersperse(forest.iter().map(|tree| tree.pretty(allocator)), separator) } } #[derive(Clone, Debug)] pub struct Tree<'a> { node: String, forest: Forest<'a>, } impl<'a> Tree<'a> { pub fn node(node: &str) -> Tree<'a> { Tree { node: node.to_string(), forest: Forest::nil(), } } pub fn node_with_forest(node: &str, forest: &'a [Tree<'a>]) -> Tree<'a> { Tree { node: node.to_string(), forest: Forest::forest(forest), } } pub fn pretty<'b, D, A>(&'b self, allocator: &'b D) -> DocBuilder<'b, D, A> where D: DocAllocator<'b, A>, D::Doc: Clone, A: Clone, { allocator .text(&self.node[..]) .append((self.forest).bracket(allocator)) .group() } } #[allow(dead_code)] pub fn main() { let allocator = BoxAllocator; let bbbbbbs = [Tree::node("ccc"), Tree::node("dd")]; let ffffs = [Tree::node("gg"), Tree::node("hhh"), Tree::node("ii")]; let aaas = [ Tree::node_with_forest("bbbbbb", &bbbbbbs), Tree::node("eee"), Tree::node_with_forest("ffff", &ffffs), ]; let example = Tree::node_with_forest("aaaa", &aaas); let err_msg = ""; // try writing to stdout { print!("\nwriting to stdout directly:\n"); let mut out = io::stdout(); example.pretty::<_, ()>(&allocator).1.render(70, &mut out) // try writing to memory } .and_then(|()| { print!("\nwriting to string then printing:\n"); let mut mem = Vec::new(); example .pretty::<_, ()>(&allocator) .1 .render(70, &mut mem) // print to console from memory .map(|()| { let res = str::from_utf8(&mem).unwrap_or(err_msg); println!("{}", res) }) // print an error if anything failed }) .unwrap_or_else(|err| println!("error: {}", err)); } pretty-0.12.3/release.sh000075500000000000000000000003231046102023000132130ustar 00000000000000#!/bin/sh set -ex LEVEL=$1 if [ -z "$LEVEL" ]; then echo "Expected patch, minor or major" exit 1 fi clog --$LEVEL git add CHANGELOG.md git commit -m "Update changelog" cargo release $LEVEL --execute pretty-0.12.3/src/block.rs000064400000000000000000000132631046102023000134720ustar 00000000000000//! Document formatting of "blocks" such as where some number of prefixes and suffixes would //! ideally be layed out onto a single line instead of breaking them up into multiple lines. See //! `BlockDoc` for an example use crate::{docs, Doc, DocAllocator, DocBuilder}; pub struct Affixes<'doc, D, A> where D: DocAllocator<'doc, A>, { prefix: DocBuilder<'doc, D, A>, suffix: DocBuilder<'doc, D, A>, nest: bool, } impl<'a, D, A> Clone for Affixes<'a, D, A> where A: Clone, D: DocAllocator<'a, A> + 'a, D::Doc: Clone, { fn clone(&self) -> Self { Affixes { prefix: self.prefix.clone(), suffix: self.suffix.clone(), nest: self.nest, } } } impl<'doc, D, A> Affixes<'doc, D, A> where D: DocAllocator<'doc, A>, { pub fn new(prefix: DocBuilder<'doc, D, A>, suffix: DocBuilder<'doc, D, A>) -> Self { Affixes { prefix, suffix, nest: false, } } pub fn nest(mut self) -> Self { self.nest = true; self } } /// Formats a set of `prefix` and `suffix` documents around a `body` /// /// The following document split into the prefixes [\x y ->, \z ->, {], suffixes [nil, nil, }] and /// body [result: x + y - z] will try to be formatted /// /// ```gluon /// \x y -> \z -> { result: x + y - z } /// ``` /// /// ```gluon /// \x y -> \z -> { /// result: x + y - z /// } /// ``` /// /// ```gluon /// \x y -> \z -> /// { /// result: x + y - z /// } /// ``` /// /// ```gluon /// \x y -> /// \z -> /// { /// result: x + y - z /// } /// ``` pub struct BlockDoc<'doc, D, A> where D: DocAllocator<'doc, A>, { pub affixes: Vec>, pub body: DocBuilder<'doc, D, A>, } impl<'doc, D, A> BlockDoc<'doc, D, A> where D: DocAllocator<'doc, A>, D::Doc: Clone, A: Clone, { pub fn format(self, nest: isize) -> DocBuilder<'doc, D, A> { let arena = self.body.0; let fail_on_multi_line = arena.fail().flat_alt(arena.nil()); (1..self.affixes.len() + 1) .rev() .map(|split| { let (before, after) = self.affixes.split_at(split); let last = before.len() == 1; docs![ arena, docs![ arena, arena.concat(before.iter().map(|affixes| affixes.prefix.clone())), if last { arena.nil() } else { fail_on_multi_line.clone() } ] .group(), docs![ arena, after.iter().rev().cloned().fold( docs![ arena, self.body.clone(), // If there is no prefix then we must not allow the body to laid out on multiple // lines without nesting if !last && before .iter() .all(|affixes| matches!(&*affixes.prefix.1, Doc::Nil)) { fail_on_multi_line.clone() } else { arena.nil() }, ] .nest(nest) .append( arena.concat(after.iter().map(|affixes| affixes.suffix.clone())) ), |acc, affixes| { let mut doc = affixes.prefix.append(acc); if affixes.nest { doc = doc.nest(nest); } doc.group() }, ), arena.concat(before.iter().map(|affixes| affixes.suffix.clone())), ] .group(), ] }) .fold(None::>, |acc, doc| { Some(match acc { None => doc, Some(acc) => acc.union(doc), }) }) .unwrap_or(self.body) } } #[cfg(test)] mod tests { use super::*; use crate::Arena; #[test] fn format_block() { let arena = &Arena::<()>::new(); let mk_doc = || BlockDoc { affixes: vec![ Affixes::new(docs![arena, "\\x y ->"], arena.nil()).nest(), Affixes::new(docs![arena, arena.line(), "\\z ->"], arena.nil()).nest(), Affixes::new( docs![arena, arena.line(), "{"], docs![arena, arena.line(), "}"], ) .nest(), ], body: docs![arena, arena.line(), "result"], }; expect_test::expect![[r#"\x y -> \z -> { result }"#]] .assert_eq(&mk_doc().format(4).1.pretty(40).to_string()); expect_test::expect![[r#" \x y -> \z -> { result }"#]] .assert_eq(&mk_doc().format(4).1.pretty(15).to_string()); expect_test::expect![[r#" \x y -> \z -> { result }"#]] .assert_eq(&mk_doc().format(4).1.pretty(14).to_string()); expect_test::expect![[r#" \x y -> \z -> { result }"#]] .assert_eq(&mk_doc().format(4).1.pretty(12).to_string()); } } pretty-0.12.3/src/lib.rs000064400000000000000000001700471046102023000131520ustar 00000000000000//! This crate defines a //! [Wadler-style](http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf) //! pretty-printing API. //! //! Start with the static functions of [Doc](enum.Doc.html). //! //! ## Quick start //! //! Let's pretty-print simple sexps! We want to pretty print sexps like //! //! ```lisp //! (1 2 3) //! ``` //! or, if the line would be too long, like //! //! ```lisp //! ((1) //! (2 3) //! (4 5 6)) //! ``` //! //! A _simple symbolic expression_ consists of a numeric _atom_ or a nested ordered _list_ of //! symbolic expression children. //! //! ```rust //! # use pretty::*; //! enum SExp { //! Atom(u32), //! List(Vec), //! } //! use SExp::*; //! # fn main() { } //! ``` //! //! We define a simple conversion to a [Doc](enum.Doc.html). Atoms are rendered as strings; lists //! are recursively rendered, with spaces between children where appropriate. Children are //! [nested]() and [grouped](), allowing them to be laid out in a single line as appropriate. //! //! ```rust //! # use pretty::*; //! # enum SExp { //! # Atom(u32), //! # List(Vec), //! # } //! # use SExp::*; //! impl SExp { //! /// Return a pretty printed format of self. //! pub fn to_doc(&self) -> RcDoc<()> { //! match *self { //! Atom(ref x) => RcDoc::as_string(x), //! List(ref xs) => //! RcDoc::text("(") //! .append(RcDoc::intersperse(xs.into_iter().map(|x| x.to_doc()), Doc::line()).nest(1).group()) //! .append(RcDoc::text(")")) //! } //! } //! } //! # fn main() { } //! ``` //! //! Next, we convert the [Doc](enum.Doc.html) to a plain old string. //! //! ```rust //! # use pretty::*; //! # enum SExp { //! # Atom(u32), //! # List(Vec), //! # } //! # use SExp::*; //! # impl SExp { //! # /// Return a pretty printed format of self. //! # pub fn to_doc(&self) -> BoxDoc<()> { //! # match *self { //! # Atom(ref x) => BoxDoc::as_string(x), //! # List(ref xs) => //! # BoxDoc::text("(") //! # .append(BoxDoc::intersperse(xs.into_iter().map(|x| x.to_doc()), Doc::line()).nest(1).group()) //! # .append(BoxDoc::text(")")) //! # } //! # } //! # } //! impl SExp { //! pub fn to_pretty(&self, width: usize) -> String { //! let mut w = Vec::new(); //! self.to_doc().render(width, &mut w).unwrap(); //! String::from_utf8(w).unwrap() //! } //! } //! # fn main() { } //! ``` //! //! And finally we can test that the nesting and grouping behaves as we expected. //! //! ```rust //! # use pretty::*; //! # enum SExp { //! # Atom(u32), //! # List(Vec), //! # } //! # use SExp::*; //! # impl SExp { //! # /// Return a pretty printed format of self. //! # pub fn to_doc(&self) -> BoxDoc<()> { //! # match *self { //! # Atom(ref x) => BoxDoc::as_string(x), //! # List(ref xs) => //! # BoxDoc::text("(") //! # .append(BoxDoc::intersperse(xs.into_iter().map(|x| x.to_doc()), Doc::line()).nest(1).group()) //! # .append(BoxDoc::text(")")) //! # } //! # } //! # } //! # impl SExp { //! # pub fn to_pretty(&self, width: usize) -> String { //! # let mut w = Vec::new(); //! # self.to_doc().render(width, &mut w).unwrap(); //! # String::from_utf8(w).unwrap() //! # } //! # } //! # fn main() { //! let atom = SExp::Atom(5); //! assert_eq!("5", atom.to_pretty(10)); //! let list = SExp::List(vec![SExp::Atom(1), SExp::Atom(2), SExp::Atom(3)]); //! assert_eq!("(1 2 3)", list.to_pretty(10)); //! assert_eq!("\ //! (1 //! 2 //! 3)", list.to_pretty(5)); //! # } //! ``` //! //! ## Advanced usage //! //! There's a more efficient pattern that uses the [DocAllocator](trait.DocAllocator.html) trait, as //! implemented by [BoxAllocator](struct.BoxAllocator.html), to allocate //! [DocBuilder](struct.DocBuilder.html) instances. See //! [examples/trees.rs](https://github.com/freebroccolo/pretty.rs/blob/master/examples/trees.rs#L39) //! for this approach. #[cfg(feature = "termcolor")] pub extern crate termcolor; use std::{ borrow::Cow, convert::TryInto, fmt, io, ops::{Add, AddAssign, Deref}, rc::Rc, }; #[cfg(feature = "termcolor")] use termcolor::{ColorSpec, WriteColor}; pub mod block; mod render; pub use self::block::{Affixes, BlockDoc}; #[cfg(feature = "termcolor")] pub use self::render::TermColored; pub use self::render::{FmtWrite, IoWrite, Render, RenderAnnotated}; /// The concrete document type. This type is not meant to be used directly. Instead use the static /// functions on `Doc` or the methods on an `DocAllocator`. /// /// The `T` parameter is used to abstract over pointers to `Doc`. See `RefDoc` and `BoxDoc` for how /// it is used #[derive(Clone)] pub enum Doc<'a, T, A = ()> where T: DocPtr<'a, A>, { Nil, Append(T, T), Group(T), FlatAlt(T, T), Nest(isize, T), Hardline, // Stores the length of a string document that is not just ascii RenderLen(usize, T), OwnedText(Box), BorrowedText(&'a str), SmallText(SmallText), Annotated(A, T), Union(T, T), Column(T::ColumnFn), Nesting(T::ColumnFn), Fail, } impl<'a, T, A> Default for Doc<'a, T, A> where T: DocPtr<'a, A>, { fn default() -> Self { Self::Nil } } pub type SmallText = arrayvec::ArrayString<[u8; 22]>; fn append_docs<'a, 'd, T, A>( mut doc: &'d Doc<'a, T, A>, consumer: &mut impl FnMut(&'d Doc<'a, T, A>), ) where T: DocPtr<'a, A>, { loop { match doc { Doc::Append(l, r) => { append_docs(l, consumer); doc = r; } _ => break consumer(doc), } } } impl<'a, T, A> fmt::Debug for Doc<'a, T, A> where T: DocPtr<'a, A> + fmt::Debug, A: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let is_line = |doc: &Doc<'a, T, A>| match doc { Doc::FlatAlt(x, y) => { matches!((&**x, &**y), (Doc::Hardline, Doc::BorrowedText(" "))) } _ => false, }; let is_line_ = |doc: &Doc<'a, T, A>| match doc { Doc::FlatAlt(x, y) => { matches!((&**x, &**y), (Doc::Hardline, Doc::Nil)) } _ => false, }; match self { Doc::Nil => f.debug_tuple("Nil").finish(), Doc::Append(..) => { let mut f = f.debug_list(); append_docs(self, &mut |doc| { f.entry(doc); }); f.finish() } _ if is_line(self) => f.debug_tuple("Line").finish(), _ if is_line_(self) => f.debug_tuple("Line_").finish(), Doc::FlatAlt(ref x, ref y) => f.debug_tuple("FlatAlt").field(x).field(y).finish(), Doc::Group(ref doc) => { if is_line(self) { return f.debug_tuple("SoftLine").finish(); } if is_line_(self) { return f.debug_tuple("SoftLine_").finish(); } f.debug_tuple("Group").field(doc).finish() } Doc::Nest(off, ref doc) => f.debug_tuple("Nest").field(&off).field(doc).finish(), Doc::Hardline => f.debug_tuple("Hardline").finish(), Doc::RenderLen(_, d) => d.fmt(f), Doc::OwnedText(ref s) => s.fmt(f), Doc::BorrowedText(ref s) => s.fmt(f), Doc::SmallText(ref s) => s.fmt(f), Doc::Annotated(ref ann, ref doc) => { f.debug_tuple("Annotated").field(ann).field(doc).finish() } Doc::Union(ref l, ref r) => f.debug_tuple("Union").field(l).field(r).finish(), Doc::Column(_) => f.debug_tuple("Column(..)").finish(), Doc::Nesting(_) => f.debug_tuple("Nesting(..)").finish(), Doc::Fail => f.debug_tuple("Fail").finish(), } } } macro_rules! impl_doc { ($name: ident, $ptr: ident, $allocator: ident) => { #[derive(Clone)] pub struct $name<'a, A = ()>($ptr, A>>); impl<'a, A> fmt::Debug for $name<'a, A> where A: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl<'a, A> $name<'a, A> { pub fn new(doc: Doc<'a, $name<'a, A>, A>) -> $name<'a, A> { $name($ptr::new(doc)) } } impl<'a, A> From> for $name<'a, A> { fn from(doc: Doc<'a, $name<'a, A>, A>) -> $name<'a, A> { $name::new(doc) } } impl<'a, A> Deref for $name<'a, A> { type Target = Doc<'a, $name<'a, A>, A>; fn deref(&self) -> &Self::Target { &self.0 } } impl<'a, A> DocAllocator<'a, A> for $allocator where A: 'a, { type Doc = $name<'a, A>; #[inline] fn alloc(&'a self, doc: Doc<'a, Self::Doc, A>) -> Self::Doc { $name::new(doc) } fn alloc_column_fn( &'a self, f: impl Fn(usize) -> Self::Doc + 'a, ) -> >::ColumnFn { Rc::new(f) } fn alloc_width_fn( &'a self, f: impl Fn(isize) -> Self::Doc + 'a, ) -> >::WidthFn { Rc::new(f) } } impl<'a, A> DocPtr<'a, A> for $name<'a, A> { type ColumnFn = std::rc::Rc Self + 'a>; type WidthFn = std::rc::Rc Self + 'a>; } impl<'a, A> StaticDoc<'a, A> for $name<'a, A> { type Allocator = $allocator; const ALLOCATOR: &'static Self::Allocator = &$allocator; } impl_doc_methods!($name ('a, A) where () where ()); impl<'a, A> $name<'a, A> { /// The text `t.to_string()`. /// /// The given text must not contain line breaks. #[inline] pub fn as_string(data: U) -> Self { $allocator.as_string(data).into_doc() } /// The given text, which must not contain line breaks. #[inline] pub fn text>>(data: U) -> Self { $allocator.text(data).into_doc() } /// Append the given document after this document. #[inline] pub fn append(self, that: D) -> Self where D: Pretty<'a, $allocator, A>, { DocBuilder(&$allocator, self.into()).append(that).into_doc() } /// A single document concatenating all the given documents. #[inline] pub fn concat(docs: I) -> Self where I: IntoIterator, I::Item: Pretty<'a, $allocator, A>, { $allocator.concat(docs).into_doc() } /// A single document interspersing the given separator `S` between the given documents. For /// example, if the documents are `[A, B, C, ..., Z]`, this yields `[A, S, B, S, C, S, ..., S, Z]`. /// /// Compare [the `intersperse` method from the `itertools` crate](https://docs.rs/itertools/0.5.9/itertools/trait.Itertools.html#method.intersperse). /// /// NOTE: The separator type, `S` may need to be cloned. Consider using cheaply cloneable ptr /// like `RefDoc` or `RcDoc` #[inline] pub fn intersperse(docs: I, separator: S) -> Self where I: IntoIterator, I::Item: Pretty<'a, $allocator, A>, S: Pretty<'a, $allocator, A> + Clone, A: Clone, { $allocator.intersperse(docs, separator).into_doc() } /// Acts as `self` when laid out on multiple lines and acts as `that` when laid out on a single line. #[inline] pub fn flat_alt(self, doc: D) -> Self where D: Pretty<'a, $allocator, A>, { DocBuilder(&$allocator, self.into()) .flat_alt(doc) .into_doc() } /// Mark this document as a group. /// /// Groups are layed out on a single line if possible. Within a group, all basic documents with /// several possible layouts are assigned the same layout, that is, they are all layed out /// horizontally and combined into a one single line, or they are each layed out on their own /// line. #[inline] pub fn group(self) -> Self { DocBuilder(&$allocator, self.into()).group().into_doc() } /// Increase the indentation level of this document. #[inline] pub fn nest(self, offset: isize) -> Self { DocBuilder(&$allocator, self.into()).nest(offset).into_doc() } #[inline] pub fn annotate(self, ann: A) -> Self { DocBuilder(&$allocator, self.into()) .annotate(ann) .into_doc() } #[inline] pub fn union(self, other: D) -> Self where D: Into>, { DocBuilder(&$allocator, self.into()).union(other).into_doc() } #[inline] pub fn softline() -> Self { Self::line().group() } /// A `softline_` acts like `nil` if the document fits the page, otherwise like `line_` #[inline] pub fn softline_() -> Self { Self::line_().group() } #[inline] pub fn column(f: impl Fn(usize) -> Self + 'static) -> Self { DocBuilder(&$allocator, Doc::Column($allocator.alloc_column_fn(f)).into()).into_doc() } #[inline] pub fn nesting(f: impl Fn(usize) -> Self + 'static) -> Self { DocBuilder(&$allocator, Doc::Nesting($allocator.alloc_column_fn(f)).into()).into_doc() } } }; } enum FmtText { Small(SmallText), Large(String), } impl fmt::Write for FmtText { fn write_str(&mut self, s: &str) -> fmt::Result { match self { FmtText::Small(buf) => { if buf.try_push_str(s).is_err() { let mut new_str = String::with_capacity(buf.len() + s.len()); new_str.push_str(buf); new_str.push_str(s); *self = FmtText::Large(new_str); } } FmtText::Large(buf) => buf.push_str(s), } Ok(()) } } macro_rules! impl_doc_methods { ($name: ident ( $($params: tt)* ) where ( $($where_: tt)* ) where ( $($where_2: tt)* )) => { impl< $($params)* > $name< $($params)* > where $($where_)* { /// An empty document. #[inline] pub fn nil() -> Self { Doc::Nil.into() } /// A single hardline. #[inline] pub fn hardline() -> Self { Doc::Hardline.into() } #[inline] pub fn space() -> Self { Doc::BorrowedText(" ").into() } #[inline] pub fn fail() -> Self { Doc::Fail.into() } } impl< $($params)* > $name< $($params)* > where $($where_2)* { /// A line acts like a `\n` but behaves like `space` if it is grouped on a single line. #[inline] pub fn line() -> Self { Self::hardline().flat_alt(Self::space()).into() } /// Acts like `line` but behaves like `nil` if grouped on a single line #[inline] pub fn line_() -> Self { Self::hardline().flat_alt(Self::nil()).into() } } }; } impl_doc!(BoxDoc, Box, BoxAllocator); impl_doc!(RcDoc, Rc, RcAllocator); impl_doc_methods!(Doc ('a, D, A) where (D: DocPtr<'a, A>) where (D: StaticDoc<'a, A>)); impl_doc_methods!(BuildDoc ('a, D, A) where (D: DocPtr<'a, A>) where (D: StaticDoc<'a, A>)); pub struct BoxAllocator; pub struct RcAllocator; impl<'a, T, A> BuildDoc<'a, T, A> where T: StaticDoc<'a, A>, { /// The text `t.to_string()`. /// /// The given text must not contain line breaks. #[inline] pub fn as_string(data: U) -> Self { T::ALLOCATOR.as_string(data).1 } /// The given text, which must not contain line breaks. #[inline] pub fn text>>(data: U) -> Self { T::ALLOCATOR.text(data).1 } fn flat_alt(self, doc: D) -> Self where D: Pretty<'a, T::Allocator, A>, { DocBuilder(T::ALLOCATOR, self).flat_alt(doc).1 } } impl<'a, T, A> Doc<'a, T, A> where T: StaticDoc<'a, A>, { /// The text `t.to_string()`. /// /// The given text must not contain line breaks. #[inline] pub fn as_string(data: U) -> Self { T::ALLOCATOR.as_string(data).into_plain_doc() } /// The given text, which must not contain line breaks. #[inline] pub fn text>>(data: U) -> Self { T::ALLOCATOR.text(data).into_plain_doc() } fn flat_alt(self, doc: D) -> Self where D: Pretty<'a, T::Allocator, A>, { DocBuilder(T::ALLOCATOR, self.into()) .flat_alt(doc) .into_plain_doc() } } pub trait StaticDoc<'a, A>: DocPtr<'a, A> where A: 'a, { type Allocator: DocAllocator<'a, A, Doc = Self> + 'static; const ALLOCATOR: &'static Self::Allocator; } impl<'a, T, A, S> From for Doc<'a, T, A> where T: StaticDoc<'a, A>, S: Into>, { fn from(s: S) -> Doc<'a, T, A> { Doc::text(s) } } pub struct PrettyFmt<'a, 'd, T, A> where A: 'a, T: DocPtr<'a, A> + 'a, { doc: &'d Doc<'a, T, A>, width: usize, } impl<'a, T, A> fmt::Display for PrettyFmt<'a, '_, T, A> where T: DocPtr<'a, A>, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.doc.render_fmt(self.width, f) } } impl<'a, T, A> Doc<'a, T, A> where T: DocPtr<'a, A> + 'a, { /// Writes a rendered document to a `std::io::Write` object. #[inline] pub fn render(&self, width: usize, out: &mut W) -> io::Result<()> where W: ?Sized + io::Write, { self.render_raw(width, &mut IoWrite::new(out)) } /// Writes a rendered document to a `std::fmt::Write` object. #[inline] pub fn render_fmt(&self, width: usize, out: &mut W) -> fmt::Result where W: ?Sized + fmt::Write, { self.render_raw(width, &mut FmtWrite::new(out)) } /// Writes a rendered document to a `RenderAnnotated` object. #[inline] pub fn render_raw(&self, width: usize, out: &mut W) -> Result<(), W::Error> where for<'b> W: render::RenderAnnotated<'b, A>, W: ?Sized, { render::best(self, width, out) } /// Returns a value which implements `std::fmt::Display` /// /// ``` /// use pretty::{Doc, BoxDoc}; /// let doc = BoxDoc::<()>::group( /// BoxDoc::text("hello").append(Doc::line()).append(Doc::text("world")) /// ); /// assert_eq!(format!("{}", doc.pretty(80)), "hello world"); /// ``` #[inline] pub fn pretty<'d>(&'d self, width: usize) -> PrettyFmt<'a, 'd, T, A> { PrettyFmt { doc: self, width } } } #[cfg(feature = "termcolor")] impl<'a, T> Doc<'a, T, ColorSpec> where T: DocPtr<'a, ColorSpec> + 'a, { #[inline] pub fn render_colored(&self, width: usize, out: W) -> io::Result<()> where W: WriteColor, { render::best(self, width, &mut TermColored::new(out)) } } /// The `DocBuilder` type allows for convenient appending of documents even for arena allocated /// documents by storing the arena inline. pub struct DocBuilder<'a, D, A = ()>(pub &'a D, pub BuildDoc<'a, D::Doc, A>) where D: ?Sized + DocAllocator<'a, A>; impl<'a, D, A, P> Add

for DocBuilder<'a, D, A> where D: ?Sized + DocAllocator<'a, A>, P: Pretty<'a, D, A>, { type Output = DocBuilder<'a, D, A>; fn add(self, other: P) -> Self::Output { self.append(other) } } impl<'a, D, A, P> AddAssign

for DocBuilder<'a, D, A> where D: ?Sized + DocAllocator<'a, A>, P: Pretty<'a, D, A>, { fn add_assign(&mut self, other: P) { *self = DocBuilder(self.0, std::mem::take(&mut self.1)).append(other) } } impl<'a, D, A> Deref for DocBuilder<'a, D, A> where D: ?Sized + DocAllocator<'a, A>, { type Target = Doc<'a, D::Doc, A>; fn deref(&self) -> &Self::Target { match &self.1 { BuildDoc::DocPtr(d) => d, BuildDoc::Doc(d) => d, } } } impl<'a, D, A> fmt::Debug for DocBuilder<'a, D, A> where D: ?Sized + DocAllocator<'a, A>, D::Doc: fmt::Debug, A: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.1.fmt(f) } } impl<'a, A, D> Clone for DocBuilder<'a, D, A> where A: Clone, D: DocAllocator<'a, A> + 'a, D::Doc: Clone, { fn clone(&self) -> Self { DocBuilder(self.0, self.1.clone()) } } impl<'a, D, A> From> for BuildDoc<'a, D::Doc, A> where D: ?Sized + DocAllocator<'a, A>, { fn from(val: DocBuilder<'a, D, A>) -> Self { val.1 } } pub trait DocPtr<'a, A>: Deref> + Sized where A: 'a, { type ColumnFn: Deref Self + 'a> + Clone + 'a; type WidthFn: Deref Self + 'a> + Clone + 'a; } impl<'a, A> DocPtr<'a, A> for RefDoc<'a, A> { type ColumnFn = &'a (dyn Fn(usize) -> Self + 'a); type WidthFn = &'a (dyn Fn(isize) -> Self + 'a); } /// Trait for types which can be converted to a `Document` pub trait Pretty<'a, D, A = ()> where A: 'a, D: ?Sized + DocAllocator<'a, A>, { /// Converts `self` into a document fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A>; } impl<'a, A> Pretty<'a, BoxAllocator, A> for BoxDoc<'a, A> where A: 'a, { fn pretty(self, allocator: &'a BoxAllocator) -> DocBuilder<'a, BoxAllocator, A> { DocBuilder(allocator, self.into()) } } impl<'a, A> Pretty<'a, RcAllocator, A> for RcDoc<'a, A> where A: 'a, { fn pretty(self, allocator: &'a RcAllocator) -> DocBuilder<'a, RcAllocator, A> { DocBuilder(allocator, self.into()) } } impl<'a, A> Pretty<'a, Arena<'a, A>, A> for RefDoc<'a, A> where A: 'a, { fn pretty(self, allocator: &'a Arena<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A> { DocBuilder(allocator, self.into()) } } impl<'a, D, A> Pretty<'a, D, A> for BuildDoc<'a, D::Doc, A> where A: 'a, D: ?Sized + DocAllocator<'a, A>, { fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { DocBuilder(allocator, self) } } impl<'a, D, A> Pretty<'a, D, A> for Doc<'a, D::Doc, A> where A: 'a, D: ?Sized + DocAllocator<'a, A>, { fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { DocBuilder(allocator, self.into()) } } impl<'a, D, A> Pretty<'a, D, A> for DocBuilder<'a, D, A> where A: 'a, D: ?Sized + DocAllocator<'a, A>, { fn pretty(self, _: &'a D) -> DocBuilder<'a, D, A> { self } } impl<'a, D, A, T> Pretty<'a, D, A> for Option where A: 'a, D: ?Sized + DocAllocator<'a, A>, T: Pretty<'a, D, A>, { fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { match self { Some(x) => x.pretty(allocator), None => allocator.nil(), } } } impl<'a, D, A> Pretty<'a, D, A> for &'a str where A: 'a, D: ?Sized + DocAllocator<'a, A>, { fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { allocator.text(self) } } impl<'a, D, A> Pretty<'a, D, A> for &'a String where A: 'a, D: ?Sized + DocAllocator<'a, A>, { fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { self[..].pretty(allocator) } } impl<'a, D, A> Pretty<'a, D, A> for String where A: 'a, D: ?Sized + DocAllocator<'a, A>, { fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { allocator.text(self) } } impl<'a, D, A, S> Pretty<'a, D, A> for Cow<'a, S> where A: 'a, D: ?Sized + DocAllocator<'a, A>, S: ?Sized + ToOwned, &'a S: Pretty<'a, D, A>, S::Owned: Pretty<'a, D, A>, { fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { match self { Cow::Borrowed(s) => s.pretty(allocator), Cow::Owned(s) => s.pretty(allocator), } } } /// The `DocAllocator` trait abstracts over a type which can allocate (pointers to) `Doc`. pub trait DocAllocator<'a, A = ()> where A: 'a, { type Doc: DocPtr<'a, A>; fn alloc(&'a self, doc: Doc<'a, Self::Doc, A>) -> Self::Doc; fn alloc_column_fn( &'a self, f: impl Fn(usize) -> Self::Doc + 'a, ) -> >::ColumnFn; fn alloc_width_fn( &'a self, f: impl Fn(isize) -> Self::Doc + 'a, ) -> >::WidthFn; fn alloc_cow(&'a self, doc: BuildDoc<'a, Self::Doc, A>) -> Self::Doc { match doc { BuildDoc::DocPtr(d) => d, BuildDoc::Doc(d) => self.alloc(d), } } /// Allocate an empty document. #[inline] fn nil(&'a self) -> DocBuilder<'a, Self, A> { DocBuilder(self, Doc::Nil.into()) } /// Fails document rendering immediately. /// /// Primarily used to abort rendering inside the left side of `Union` #[inline] fn fail(&'a self) -> DocBuilder<'a, Self, A> { DocBuilder(self, Doc::Fail.into()) } /// Allocate a single hardline. #[inline] fn hardline(&'a self) -> DocBuilder<'a, Self, A> { DocBuilder(self, Doc::Hardline.into()) } #[inline] fn space(&'a self) -> DocBuilder<'a, Self, A> { self.text(" ") } /// A line acts like a `\n` but behaves like `space` if it is grouped on a single line. #[inline] fn line(&'a self) -> DocBuilder<'a, Self, A> { self.hardline().flat_alt(self.space()) } /// Acts like `line` but behaves like `nil` if grouped on a single line /// /// ``` /// use pretty::{Doc, RcDoc}; /// /// let doc = RcDoc::<()>::group( /// RcDoc::text("(") /// .append( /// RcDoc::line_() /// .append(Doc::text("test")) /// .append(Doc::line()) /// .append(Doc::text("test")) /// .nest(2), /// ) /// .append(Doc::line_()) /// .append(Doc::text(")")), /// ); /// assert_eq!(doc.pretty(5).to_string(), "(\n test\n test\n)"); /// assert_eq!(doc.pretty(100).to_string(), "(test test)"); /// ``` #[inline] fn line_(&'a self) -> DocBuilder<'a, Self, A> { self.hardline().flat_alt(self.nil()) } /// A `softline` acts like `space` if the document fits the page, otherwise like `line` #[inline] fn softline(&'a self) -> DocBuilder<'a, Self, A> { self.line().group() } /// A `softline_` acts like `nil` if the document fits the page, otherwise like `line_` #[inline] fn softline_(&'a self) -> DocBuilder<'a, Self, A> { self.line_().group() } /// Allocate a document containing the text `t.to_string()`. /// /// The given text must not contain line breaks. #[inline] fn as_string(&'a self, data: U) -> DocBuilder<'a, Self, A> { use std::fmt::Write; let mut buf = FmtText::Small(SmallText::new()); write!(buf, "{}", data).unwrap(); let doc = match buf { FmtText::Small(b) => Doc::SmallText(b), FmtText::Large(b) => Doc::OwnedText(b.into()), }; DocBuilder(self, doc.into()).with_utf8_len() } /// Allocate a document containing the given text. /// /// The given text must not contain line breaks. #[inline] fn text>>(&'a self, data: U) -> DocBuilder<'a, Self, A> { let data: Cow<_> = data.into(); let doc = if data.is_empty() { Doc::Nil.into() } else { match data { Cow::Owned(t) => Doc::OwnedText(t.into()).into(), Cow::Borrowed(t) => Doc::BorrowedText(t).into(), } }; DocBuilder(self, doc).with_utf8_len() } /// Allocate a document concatenating the given documents. #[inline] fn concat(&'a self, docs: I) -> DocBuilder<'a, Self, A> where I: IntoIterator, I::Item: Pretty<'a, Self, A>, { docs.into_iter().fold(self.nil(), |a, b| a.append(b)) } /// Allocate a document that intersperses the given separator `S` between the given documents /// `[A, B, C, ..., Z]`, yielding `[A, S, B, S, C, S, ..., S, Z]`. /// /// Compare [the `intersperse` method from the `itertools` crate](https://docs.rs/itertools/0.5.9/itertools/trait.Itertools.html#method.intersperse). /// /// NOTE: The separator type, `S` may need to be cloned. Consider using cheaply cloneable ptr /// like `RefDoc` or `RcDoc` #[inline] fn intersperse(&'a self, docs: I, separator: S) -> DocBuilder<'a, Self, A> where I: IntoIterator, I::Item: Pretty<'a, Self, A>, S: Pretty<'a, Self, A> + Clone, { let mut result = self.nil(); let mut iter = docs.into_iter(); if let Some(first) = iter.next() { result = result.append(first); for doc in iter { result = result.append(separator.clone()); result = result.append(doc); } } result } /// Allocate a document that acts differently based on the position and page layout /// /// ```rust /// use pretty::DocAllocator; /// /// let arena = pretty::Arena::<()>::new(); /// let doc = arena.text("prefix ") /// .append(arena.column(|l| { /// arena.text("| <- column ").append(arena.as_string(l)).into_doc() /// })); /// assert_eq!(doc.1.pretty(80).to_string(), "prefix | <- column 7"); /// ``` #[inline] fn column(&'a self, f: impl Fn(usize) -> Self::Doc + 'a) -> DocBuilder<'a, Self, A> { DocBuilder(self, Doc::Column(self.alloc_column_fn(f)).into()) } /// Allocate a document that acts differently based on the current nesting level /// /// ```rust /// use pretty::DocAllocator; /// /// let arena = pretty::Arena::<()>::new(); /// let doc = arena.text("prefix ") /// .append(arena.nesting(|l| { /// arena.text("[Nested: ").append(arena.as_string(l)).append("]").into_doc() /// }).nest(4)); /// assert_eq!(doc.1.pretty(80).to_string(), "prefix [Nested: 4]"); /// ``` #[inline] fn nesting(&'a self, f: impl Fn(usize) -> Self::Doc + 'a) -> DocBuilder<'a, Self, A> { DocBuilder(self, Doc::Nesting(self.alloc_column_fn(f)).into()) } /// Reflows `text` inserting `softline` in place of any whitespace #[inline] fn reflow(&'a self, text: &'a str) -> DocBuilder<'a, Self, A> where Self: Sized, Self::Doc: Clone, A: Clone, { self.intersperse(text.split(char::is_whitespace), self.softline()) } } /// Either a `Doc` or a pointer to a `Doc` (`D`) #[derive(Clone)] pub enum BuildDoc<'a, D, A> where D: DocPtr<'a, A>, { DocPtr(D), Doc(Doc<'a, D, A>), } impl<'a, D, A> Default for BuildDoc<'a, D, A> where D: DocPtr<'a, A>, { fn default() -> Self { Self::Doc(Doc::default()) } } impl<'a, D, A> fmt::Debug for BuildDoc<'a, D, A> where D: DocPtr<'a, A> + fmt::Debug, A: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { (**self).fmt(f) } } impl<'a, D, A> Deref for BuildDoc<'a, D, A> where D: DocPtr<'a, A>, { type Target = Doc<'a, D, A>; fn deref(&self) -> &Self::Target { match self { BuildDoc::DocPtr(d) => d, BuildDoc::Doc(d) => d, } } } impl<'a, A> From> for BuildDoc<'a, RefDoc<'a, A>, A> { fn from(s: RefDoc<'a, A>) -> Self { BuildDoc::DocPtr(s) } } impl<'a, A> From> for BuildDoc<'a, BoxDoc<'a, A>, A> { fn from(s: BoxDoc<'a, A>) -> Self { BuildDoc::DocPtr(s) } } impl<'a, A> From> for BuildDoc<'a, RcDoc<'a, A>, A> { fn from(s: RcDoc<'a, A>) -> Self { BuildDoc::DocPtr(s) } } impl<'a, T, A> From> for BuildDoc<'a, T, A> where T: DocPtr<'a, A>, { fn from(s: Doc<'a, T, A>) -> Self { BuildDoc::Doc(s) } } impl<'a, T, A> From for BuildDoc<'a, T, A> where T: StaticDoc<'a, A>, { fn from(s: String) -> Self { BuildDoc::Doc(Doc::text(s)) } } impl<'a, T, A> From<&'a str> for BuildDoc<'a, T, A> where T: StaticDoc<'a, A>, { fn from(s: &'a str) -> Self { BuildDoc::Doc(Doc::text(s)) } } impl<'a, T, A> From<&'a String> for BuildDoc<'a, T, A> where T: StaticDoc<'a, A>, { fn from(s: &'a String) -> Self { BuildDoc::Doc(Doc::text(s)) } } impl<'a, T, A, S> From> for BuildDoc<'a, T, A> where T: DocPtr<'a, A>, S: Into>, { fn from(s: Option) -> Self { match s { Some(s) => s.into(), None => BuildDoc::Doc(Doc::Nil), } } } /// Concatenates a number of documents (or values that can be converted into a document via the /// `Pretty` trait, like `&str`) /// /// ``` /// use pretty::{docs, Arena, DocAllocator}; /// let arena = &Arena::<()>::new(); /// let doc = docs![ /// arena, /// "let", /// arena.softline(), /// "x", /// arena.softline(), /// "=", /// arena.softline(), /// Some("123"), /// ]; /// assert_eq!(doc.1.pretty(80).to_string(), "let x = 123"); /// ``` #[macro_export] macro_rules! docs { ($alloc: expr, $first: expr $(,)?) => { $crate::Pretty::pretty($first, $alloc) }; ($alloc: expr, $first: expr $(, $rest: expr)+ $(,)?) => {{ let mut doc = $crate::Pretty::pretty($first, $alloc); $( doc = doc.append($rest); )* doc }} } impl<'a, D, A> DocBuilder<'a, D, A> where A: 'a, D: ?Sized + DocAllocator<'a, A>, { fn with_utf8_len(self) -> Self { let s = match &*self { Doc::OwnedText(s) => &s[..], Doc::BorrowedText(s) => s, Doc::SmallText(s) => s, _ => return self, }; if s.is_ascii() { self } else { let display_width = unicode_width::UnicodeWidthStr::width(s); let DocBuilder(allocator, _) = self; DocBuilder( allocator, Doc::RenderLen(display_width, self.into_doc()).into(), ) } } /// Append the given document after this document. #[inline] pub fn append(self, that: E) -> DocBuilder<'a, D, A> where E: Pretty<'a, D, A>, { let DocBuilder(allocator, _) = self; let that = that.pretty(allocator); match (&*self, &*that) { (Doc::Nil, _) => that, (_, Doc::Nil) => self, _ => DocBuilder( allocator, Doc::Append( allocator.alloc_cow(self.into()), allocator.alloc_cow(that.into()), ) .into(), ), } } /// Acts as `self` when laid out on multiple lines and acts as `that` when laid out on a single line. /// /// ``` /// use pretty::{Arena, DocAllocator}; /// /// let arena = Arena::<()>::new(); /// let body = arena.line().append("x"); /// let doc = arena.text("let") /// .append(arena.line()) /// .append("x") /// .group() /// .append( /// body.clone() /// .flat_alt( /// arena.line() /// .append("in") /// .append(body) /// ) /// ) /// .group(); /// /// assert_eq!(doc.1.pretty(100).to_string(), "let x in x"); /// assert_eq!(doc.1.pretty(8).to_string(), "let x\nx"); /// ``` #[inline] pub fn flat_alt(self, that: E) -> DocBuilder<'a, D, A> where E: Pretty<'a, D, A>, { let DocBuilder(allocator, this) = self; let that = that.pretty(allocator); DocBuilder( allocator, Doc::FlatAlt(allocator.alloc_cow(this), allocator.alloc_cow(that.into())).into(), ) } /// Mark this document as a group. /// /// Groups are layed out on a single line if possible. Within a group, all basic documents with /// several possible layouts are assigned the same layout, that is, they are all layed out /// horizontally and combined into a one single line, or they are each layed out on their own /// line. #[inline] pub fn group(self) -> DocBuilder<'a, D, A> { match *self.1 { Doc::Group(_) | Doc::OwnedText(_) | Doc::BorrowedText(_) | Doc::SmallText(_) | Doc::Nil => self, _ => { let DocBuilder(allocator, this) = self; DocBuilder(allocator, Doc::Group(allocator.alloc_cow(this)).into()) } } } /// Increase the indentation level of this document. #[inline] pub fn nest(self, offset: isize) -> DocBuilder<'a, D, A> { if let Doc::Nil = &*self.1 { return self; } if offset == 0 { return self; } let DocBuilder(allocator, this) = self; DocBuilder( allocator, Doc::Nest(offset, allocator.alloc_cow(this)).into(), ) } #[inline] pub fn annotate(self, ann: A) -> DocBuilder<'a, D, A> { let DocBuilder(allocator, this) = self; DocBuilder( allocator, Doc::Annotated(ann, allocator.alloc_cow(this)).into(), ) } #[inline] pub fn union(self, other: E) -> DocBuilder<'a, D, A> where E: Into>, { let DocBuilder(allocator, this) = self; let other = other.into(); let doc = Doc::Union(allocator.alloc_cow(this), allocator.alloc_cow(other)); DocBuilder(allocator, doc.into()) } /// Lays out `self` so with the nesting level set to the current column /// /// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr /// like `RefDoc` or `RcDoc` /// /// ```rust /// use pretty::{docs, DocAllocator}; /// /// let arena = &pretty::Arena::<()>::new(); /// let doc = docs![ /// arena, /// "lorem", /// " ", /// arena.intersperse(["ipsum", "dolor"].iter().cloned(), arena.line_()).align(), /// arena.hardline(), /// "next", /// ]; /// assert_eq!(doc.1.pretty(80).to_string(), "lorem ipsum\n dolor\nnext"); /// ``` #[inline] pub fn align(self) -> DocBuilder<'a, D, A> where DocBuilder<'a, D, A>: Clone, { let allocator = self.0; allocator.column(move |col| { let self_ = self.clone(); allocator .nesting(move |nest| self_.clone().nest(col as isize - nest as isize).into_doc()) .into_doc() }) } /// Lays out `self` with a nesting level set to the current level plus `adjust`. /// /// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr /// like `RefDoc` or `RcDoc` /// /// ```rust /// use pretty::DocAllocator; /// /// let arena = pretty::Arena::<()>::new(); /// let doc = arena.text("prefix").append(arena.text(" ")) /// .append(arena.reflow("Indenting these words with nest").hang(4)); /// assert_eq!( /// doc.1.pretty(24).to_string(), /// "prefix Indenting these\n words with\n nest", /// ); /// ``` #[inline] pub fn hang(self, adjust: isize) -> DocBuilder<'a, D, A> where DocBuilder<'a, D, A>: Clone, { self.nest(adjust).align() } /// Indents `self` by `adjust` spaces from the current cursor position /// /// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr /// like `RefDoc` or `RcDoc` /// /// ```rust /// use pretty::DocAllocator; /// /// let arena = pretty::Arena::<()>::new(); /// let doc = arena.text("prefix").append(arena.text(" ")) /// .append(arena.reflow("The indent function indents these words!").indent(4)); /// assert_eq!( /// doc.1.pretty(24).to_string(), /// " /// prefix The indent /// function /// indents these /// words!".trim_start(), /// ); /// ``` #[inline] pub fn indent(self, adjust: usize) -> DocBuilder<'a, D, A> where DocBuilder<'a, D, A>: Clone, { let spaces = { use crate::render::SPACES; let DocBuilder(allocator, _) = self; let mut doc = allocator.nil(); let mut remaining = adjust; while remaining != 0 { let i = SPACES.len().min(remaining); remaining -= i; doc = doc.append(allocator.text(&SPACES[..i])) } doc }; spaces.append(self).hang(adjust.try_into().unwrap()) } /// Lays out `self` and provides the column width of it available to `f` /// /// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr /// like `RefDoc` or `RcDoc` /// /// ```rust /// use pretty::DocAllocator; /// /// let arena = pretty::Arena::<()>::new(); /// let doc = arena.text("prefix ") /// .append(arena.column(|l| { /// arena.text("| <- column ").append(arena.as_string(l)).into_doc() /// })); /// assert_eq!(doc.1.pretty(80).to_string(), "prefix | <- column 7"); /// ``` #[inline] pub fn width(self, f: impl Fn(isize) -> D::Doc + 'a) -> DocBuilder<'a, D, A> where BuildDoc<'a, D::Doc, A>: Clone, { let DocBuilder(allocator, this) = self; let f = allocator.alloc_width_fn(f); allocator.column(move |start| { let f = f.clone(); DocBuilder(allocator, this.clone()) .append(allocator.column(move |end| f(end as isize - start as isize))) .into_doc() }) } /// Puts `self` between `before` and `after` #[inline] pub fn enclose(self, before: E, after: F) -> DocBuilder<'a, D, A> where E: Pretty<'a, D, A>, F: Pretty<'a, D, A>, { let DocBuilder(allocator, _) = self; DocBuilder(allocator, before.pretty(allocator).1) .append(self) .append(after) } pub fn single_quotes(self) -> DocBuilder<'a, D, A> { self.enclose("'", "'") } pub fn double_quotes(self) -> DocBuilder<'a, D, A> { self.enclose("\"", "\"") } pub fn parens(self) -> DocBuilder<'a, D, A> { self.enclose("(", ")") } pub fn angles(self) -> DocBuilder<'a, D, A> { self.enclose("<", ">") } pub fn braces(self) -> DocBuilder<'a, D, A> { self.enclose("{", "}") } pub fn brackets(self) -> DocBuilder<'a, D, A> { self.enclose("[", "]") } pub fn into_doc(self) -> D::Doc { match self.1 { BuildDoc::DocPtr(d) => d, BuildDoc::Doc(d) => self.0.alloc(d), } } fn into_plain_doc(self) -> Doc<'a, D::Doc, A> { match self.1 { BuildDoc::DocPtr(_) => unreachable!(), BuildDoc::Doc(d) => d, } } } /// Newtype wrapper for `&Doc` pub struct RefDoc<'a, A = ()>(pub &'a Doc<'a, RefDoc<'a, A>, A>); impl Copy for RefDoc<'_, A> {} impl Clone for RefDoc<'_, A> { fn clone(&self) -> Self { *self } } impl<'a, A> fmt::Debug for RefDoc<'a, A> where A: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl<'a, A> Deref for RefDoc<'a, A> { type Target = Doc<'a, RefDoc<'a, A>, A>; fn deref(&self) -> &Self::Target { self.0 } } trait DropT {} impl DropT for T {} /// An arena which can be used to allocate `Doc` values. pub struct Arena<'a, A = ()> { docs: typed_arena::Arena, A>>, column_fns: typed_arena::Arena>, } impl Default for Arena<'_, A> { fn default() -> Self { Self::new() } } impl<'a, A> Arena<'a, A> { pub fn new() -> Self { Arena { docs: typed_arena::Arena::new(), column_fns: Default::default(), } } fn alloc_any(&'a self, f: T) -> &'a T where T: 'a, { let f = Box::new(f); let f_ptr = &*f as *const T; // Until #[may_dangle] https://github.com/rust-lang/rust/issues/34761 is stabilized (or // equivalent) we need to use unsafe to cast away the lifetime of the function as we do not // have any other way of asserting that the `typed_arena::Arena` destructor does not touch // `'a` // // Since `'a` is used elsewhere in our `Arena` type we still have all the other lifetime // checks in place (the other arena stores no `Drop` value which touches `'a` which lets it // compile) unsafe { self.column_fns .alloc(std::mem::transmute::, Box>(f)); &*f_ptr } } } impl<'a, D, A> DocAllocator<'a, A> for &'a D where D: ?Sized + DocAllocator<'a, A>, A: 'a, { type Doc = D::Doc; #[inline] fn alloc(&'a self, doc: Doc<'a, Self::Doc, A>) -> Self::Doc { (**self).alloc(doc) } fn alloc_column_fn( &'a self, f: impl Fn(usize) -> Self::Doc + 'a, ) -> >::ColumnFn { (**self).alloc_column_fn(f) } fn alloc_width_fn( &'a self, f: impl Fn(isize) -> Self::Doc + 'a, ) -> >::WidthFn { (**self).alloc_width_fn(f) } } impl<'a, A> DocAllocator<'a, A> for Arena<'a, A> { type Doc = RefDoc<'a, A>; #[inline] fn alloc(&'a self, doc: Doc<'a, Self::Doc, A>) -> Self::Doc { RefDoc(match doc { // Return 'static references for common variants to avoid some allocations Doc::Nil => &Doc::Nil, Doc::Hardline => &Doc::Hardline, Doc::Fail => &Doc::Fail, // line() Doc::FlatAlt(RefDoc(Doc::Hardline), RefDoc(Doc::BorrowedText(" "))) => { &Doc::FlatAlt(RefDoc(&Doc::Hardline), RefDoc(&Doc::BorrowedText(" "))) } // line_() Doc::FlatAlt(RefDoc(Doc::Hardline), RefDoc(Doc::Nil)) => { &Doc::FlatAlt(RefDoc(&Doc::Hardline), RefDoc(&Doc::Nil)) } // softline() Doc::Group(RefDoc(Doc::FlatAlt( RefDoc(Doc::Hardline), RefDoc(Doc::BorrowedText(" ")), ))) => &Doc::Group(RefDoc(&Doc::FlatAlt( RefDoc(&Doc::Hardline), RefDoc(&Doc::BorrowedText(" ")), ))), // softline_() Doc::Group(RefDoc(Doc::FlatAlt(RefDoc(Doc::Hardline), RefDoc(Doc::Nil)))) => { &Doc::Group(RefDoc(&Doc::FlatAlt( RefDoc(&Doc::Hardline), RefDoc(&Doc::Nil), ))) } _ => self.docs.alloc(doc), }) } fn alloc_column_fn( &'a self, f: impl Fn(usize) -> Self::Doc + 'a, ) -> >::ColumnFn { self.alloc_any(f) } fn alloc_width_fn( &'a self, f: impl Fn(isize) -> Self::Doc + 'a, ) -> >::WidthFn { self.alloc_any(f) } } #[cfg(test)] mod tests { use super::*; macro_rules! chain { ($first: expr $(, $rest: expr)* $(,)?) => {{ #[allow(unused_mut)] let mut doc = DocBuilder(&BoxAllocator, $first.into()); $( doc = doc.append($rest); )* doc.into_doc() }} } #[cfg(target_pointer_width = "64")] #[test] fn doc_size() { // Safeguard against accidentally growing Doc assert_eq!(8 * 3, std::mem::size_of::>()); } macro_rules! test { ($size:expr, $actual:expr, $expected:expr) => { let mut s = String::new(); $actual.render_fmt($size, &mut s).unwrap(); difference::assert_diff!(&s, $expected, "\n", 0); }; ($actual:expr, $expected:expr) => { test!(70, $actual, $expected) }; } #[test] fn box_doc_inference() { let doc: BoxDoc<()> = BoxDoc::group( BoxDoc::text("test") .append(BoxDoc::line()) .append(BoxDoc::text("test")), ); test!(doc, "test test"); } #[test] fn newline_in_text() { let doc: BoxDoc<()> = BoxDoc::group( BoxDoc::text("test").append( BoxDoc::line() .append(BoxDoc::text("\"test\n test\"")) .nest(4), ), ); test!(5, doc, "test\n \"test\n test\""); } #[test] fn forced_newline() { let doc: BoxDoc<()> = BoxDoc::group( BoxDoc::text("test") .append(BoxDoc::hardline()) .append(BoxDoc::text("test")), ); test!(doc, "test\ntest"); } #[test] fn space_do_not_reset_pos() { let doc: BoxDoc<()> = BoxDoc::group(BoxDoc::text("test").append(BoxDoc::line())) .append(BoxDoc::text("test")) .append(BoxDoc::group(BoxDoc::line()).append(BoxDoc::text("test"))); test!(9, doc, "test test\ntest"); } // Tests that the `BoxDoc::hardline()` does not cause the rest of document to think that it fits on // a single line but instead breaks on the `BoxDoc::line()` to fit with 6 columns #[test] fn newline_does_not_cause_next_line_to_be_to_long() { let doc: RcDoc<()> = RcDoc::group( RcDoc::text("test").append(RcDoc::hardline()).append( RcDoc::text("test") .append(RcDoc::line()) .append(RcDoc::text("test")), ), ); test!(6, doc, "test\ntest\ntest"); } #[test] fn newline_after_group_does_not_affect_it() { let arena = Arena::<()>::new(); let doc = arena.text("x").append(arena.line()).append("y").group(); test!(100, doc.append(arena.hardline()).1, "x y\n"); } #[test] fn block() { let doc: RcDoc<()> = RcDoc::group( RcDoc::text("{") .append( RcDoc::line() .append(RcDoc::text("test")) .append(RcDoc::line()) .append(RcDoc::text("test")) .nest(2), ) .append(RcDoc::line()) .append(RcDoc::text("}")), ); test!(5, doc, "{\n test\n test\n}"); } #[test] fn block_with_hardline() { let doc: RcDoc<()> = RcDoc::group( RcDoc::text("{") .append( RcDoc::line() .append(RcDoc::text("test")) .append(RcDoc::hardline()) .append(RcDoc::text("test")) .nest(2), ) .append(RcDoc::line()) .append(RcDoc::text("}")), ); test!(10, doc, "{\n test\n test\n}"); } #[test] fn block_with_hardline_negative_nest() { let doc: RcDoc<()> = RcDoc::group( RcDoc::text("{") .append( RcDoc::line() .append(RcDoc::text("test")) .append(RcDoc::hardline()) .append(RcDoc::text("test")) .nest(-2), ) .append(RcDoc::line()) .append(RcDoc::text("}")), ); test!(10, doc, "{\ntest\ntest\n}"); } #[test] fn line_comment() { let doc: BoxDoc<()> = BoxDoc::group( BoxDoc::text("{") .append( BoxDoc::line() .append(BoxDoc::text("test")) .append(BoxDoc::line()) .append(BoxDoc::text("// a").append(BoxDoc::hardline())) .append(BoxDoc::text("test")) .nest(2), ) .append(BoxDoc::line()) .append(BoxDoc::text("}")), ); test!(14, doc, "{\n test\n // a\n test\n}"); } #[test] fn annotation_no_panic() { let doc: BoxDoc<()> = BoxDoc::group( BoxDoc::text("test") .annotate(()) .append(BoxDoc::hardline()) .annotate(()) .append(BoxDoc::text("test")), ); test!(doc, "test\ntest"); } fn nest_on_line(doc: BoxDoc<'static, ()>) -> BoxDoc<'static, ()> { BoxDoc::softline().append(BoxDoc::nesting(move |n| { let doc = doc.clone(); BoxDoc::column(move |c| { if n == c { BoxDoc::text(" ").append(doc.clone()).nest(2) } else { doc.clone() } }) })) } #[test] fn hang_lambda1() { let doc = chain![ chain!["let", BoxDoc::line(), "x", BoxDoc::line(), "="].group(), nest_on_line(chain![ "\\y ->", chain![BoxDoc::line(), "y"].nest(2).group() ]), ] .group(); test!(doc, "let x = \\y -> y"); test!( 8, doc, r"let x = \y -> y" ); test!( 14, doc, r"let x = \y -> y" ); } #[test] fn hang_comment() { let body = chain!["y"].nest(2).group(); let doc = chain![ chain!["let", BoxDoc::line(), "x", BoxDoc::line(), "="].group(), nest_on_line(chain![ "\\y ->", nest_on_line(chain!["// abc", BoxDoc::hardline(), body]) ]), ] .group(); test!(8, doc, "let x =\n \\y ->\n // abc\n y"); test!(14, doc, "let x = \\y ->\n // abc\n y"); } #[test] fn union() { let doc = chain![ chain!["let", BoxDoc::line(), "x", BoxDoc::line(), "="].group(), nest_on_line(chain![ "(", chain![ BoxDoc::line_(), chain!["x", ","].group(), BoxDoc::line(), chain!["1234567890", ","].group() ] .nest(2) .group(), BoxDoc::line_().append(")"), ]) ] .group(); test!(doc, "let x = (x, 1234567890,)"); test!(8, doc, "let x =\n (\n x,\n 1234567890,\n )"); test!(14, doc, "let x = (\n x,\n 1234567890,\n)"); } fn hang2( from: BoxDoc<'static, ()>, body_whitespace: BoxDoc<'static, ()>, body: BoxDoc<'static, ()>, trailer: BoxDoc<'static, ()>, ) -> BoxDoc<'static, ()> { let body1 = body_whitespace .append(body.clone()) .nest(2) .group() .append(trailer.clone()); let body2 = BoxDoc::hardline() .append(body.clone()) .nest(2) .group() .append(trailer.clone()); let single = from.clone().append(body1.clone()).group(); let hang = from.clone().append(body2).group(); let break_all = from.append(body1).group().nest(2); BoxDoc::group(single.union(hang.union(break_all))) } #[test] fn hang_lambda2() { let from = chain![ chain!["let", BoxDoc::line(), "x", BoxDoc::line(), "="].group(), BoxDoc::line(), "\\y ->", ] .group(); let body = chain!["y"].group(); let trailer = BoxDoc::nil(); let doc = hang2(from, BoxDoc::line(), body, trailer); eprintln!("{:#?}", doc); test!(doc, "let x = \\y -> y"); test!(14, doc, "let x = \\y ->\n y"); } #[test] fn union2() { let from = chain![ chain!["let", BoxDoc::line(), "x", BoxDoc::line(), "="].group(), BoxDoc::line(), "(", ] .group(); let body = chain![ chain!["x", ","].group(), BoxDoc::line(), chain!["1234567890", ","].group() ] .group(); let trailer = BoxDoc::line_().append(")"); let doc = hang2(from, BoxDoc::line_(), body, trailer); test!(doc, "let x = (x, 1234567890,)"); test!(14, doc, "let x = (\n x,\n 1234567890,\n)"); } #[test] fn usize_max_value() { let doc: BoxDoc<()> = BoxDoc::group( BoxDoc::text("test") .append(BoxDoc::line()) .append(BoxDoc::text("test")), ); test!(usize::max_value(), doc, "test test"); } #[test] fn fail() { let fail_break: BoxDoc<()> = BoxDoc::fail().flat_alt(Doc::nil()); let doc = fail_break.append(Doc::text("12345")).group().union("abc"); test!(5, doc, "12345"); test!(4, doc, "abc"); } pub struct TestWriter { upstream: W, } impl TestWriter { pub fn new(upstream: W) -> Self { Self { upstream } } } impl Render for TestWriter where W: Render, { type Error = W::Error; fn write_str(&mut self, s: &str) -> Result { self.upstream.write_str(s) } fn write_str_all(&mut self, s: &str) -> Result<(), W::Error> { self.upstream.write_str_all(s) } fn fail_doc(&self) -> Self::Error { self.upstream.fail_doc() } } impl RenderAnnotated<'_, ()> for TestWriter where W: Render, { fn push_annotation(&mut self, _: &()) -> Result<(), Self::Error> { self.upstream.write_str_all("[") } fn pop_annotation(&mut self) -> Result<(), Self::Error> { self.upstream.write_str_all("]") } } #[test] fn annotations() { let actual = BoxDoc::text("abc").annotate(()).annotate(()); let mut s = String::new(); actual .render_raw(70, &mut TestWriter::new(FmtWrite::new(&mut s))) .unwrap(); difference::assert_diff!(&s, "[[abc]]", "\n", 0); } #[test] fn non_ascii_is_not_byte_length() { let doc: BoxDoc<()> = BoxDoc::group( BoxDoc::text("ÅÄÖ") .append(BoxDoc::line()) .append(BoxDoc::text("test")), ); test!(8, doc, "ÅÄÖ test"); } #[test] fn cjk_display_width() { let arena = Arena::<()>::new(); let doc = arena .text("你好") .append(arena.line().append(arena.text("abc")).align()) .into_doc(); test!(doc, "你好\n abc"); } #[test] fn pretty_cow() { let doc: BoxDoc<()> = docs![ &BoxAllocator, Cow::::Borrowed("abc"), BoxDoc::line(), Cow::::Owned("123".to_string()), ] .group() .into_doc(); test!(8, doc, "abc 123"); } } pretty-0.12.3/src/render.rs000064400000000000000000000410311046102023000136510ustar 00000000000000use std::{cmp, fmt, io}; #[cfg(feature = "termcolor")] use termcolor::{ColorSpec, WriteColor}; use crate::{Doc, DocPtr}; /// Trait representing the operations necessary to render a document pub trait Render { type Error; fn write_str(&mut self, s: &str) -> Result; fn write_str_all(&mut self, mut s: &str) -> Result<(), Self::Error> { while !s.is_empty() { let count = self.write_str(s)?; s = &s[count..]; } Ok(()) } fn fail_doc(&self) -> Self::Error; } /// Writes to something implementing `std::io::Write` pub struct IoWrite { upstream: W, } impl IoWrite { pub fn new(upstream: W) -> IoWrite { IoWrite { upstream } } } impl Render for IoWrite where W: io::Write, { type Error = io::Error; fn write_str(&mut self, s: &str) -> io::Result { self.upstream.write(s.as_bytes()) } fn write_str_all(&mut self, s: &str) -> io::Result<()> { self.upstream.write_all(s.as_bytes()) } fn fail_doc(&self) -> Self::Error { io::Error::new(io::ErrorKind::Other, "Document failed to render") } } /// Writes to something implementing `std::fmt::Write` pub struct FmtWrite { upstream: W, } impl FmtWrite { pub fn new(upstream: W) -> FmtWrite { FmtWrite { upstream } } } impl Render for FmtWrite where W: fmt::Write, { type Error = fmt::Error; fn write_str(&mut self, s: &str) -> Result { self.write_str_all(s).map(|_| s.len()) } fn write_str_all(&mut self, s: &str) -> fmt::Result { self.upstream.write_str(s) } fn fail_doc(&self) -> Self::Error { fmt::Error } } /// Trait representing the operations necessary to write an annotated document. pub trait RenderAnnotated<'a, A>: Render { fn push_annotation(&mut self, annotation: &'a A) -> Result<(), Self::Error>; fn pop_annotation(&mut self) -> Result<(), Self::Error>; } impl RenderAnnotated<'_, A> for IoWrite where W: io::Write, { fn push_annotation(&mut self, _: &A) -> Result<(), Self::Error> { Ok(()) } fn pop_annotation(&mut self) -> Result<(), Self::Error> { Ok(()) } } impl RenderAnnotated<'_, A> for FmtWrite where W: fmt::Write, { fn push_annotation(&mut self, _: &A) -> Result<(), Self::Error> { Ok(()) } fn pop_annotation(&mut self) -> Result<(), Self::Error> { Ok(()) } } #[cfg(feature = "termcolor")] pub struct TermColored { color_stack: Vec, upstream: W, } #[cfg(feature = "termcolor")] impl TermColored { pub fn new(upstream: W) -> TermColored { TermColored { color_stack: Vec::new(), upstream, } } } #[cfg(feature = "termcolor")] impl Render for TermColored where W: io::Write, { type Error = io::Error; fn write_str(&mut self, s: &str) -> io::Result { self.upstream.write(s.as_bytes()) } fn write_str_all(&mut self, s: &str) -> io::Result<()> { self.upstream.write_all(s.as_bytes()) } fn fail_doc(&self) -> Self::Error { io::Error::new(io::ErrorKind::Other, "Document failed to render") } } #[cfg(feature = "termcolor")] impl RenderAnnotated<'_, ColorSpec> for TermColored where W: WriteColor, { fn push_annotation(&mut self, color: &ColorSpec) -> Result<(), Self::Error> { self.color_stack.push(color.clone()); self.upstream.set_color(color) } fn pop_annotation(&mut self) -> Result<(), Self::Error> { self.color_stack.pop(); match self.color_stack.last() { Some(previous) => self.upstream.set_color(previous), None => self.upstream.reset(), } } } enum Annotation<'a, A> { Push(&'a A), Pop, } struct BufferWrite<'a, A> { buffer: String, annotations: Vec<(usize, Annotation<'a, A>)>, } impl<'a, A> BufferWrite<'a, A> { fn new() -> Self { BufferWrite { buffer: String::new(), annotations: Vec::new(), } } fn render(&mut self, render: &mut W) -> Result<(), W::Error> where W: RenderAnnotated<'a, A>, W: ?Sized, { let mut start = 0; for (end, annotation) in &self.annotations { let s = &self.buffer[start..*end]; if !s.is_empty() { render.write_str_all(s)?; } start = *end; match annotation { Annotation::Push(a) => render.push_annotation(a)?, Annotation::Pop => render.pop_annotation()?, } } let s = &self.buffer[start..]; if !s.is_empty() { render.write_str_all(s)?; } Ok(()) } } impl Render for BufferWrite<'_, A> { type Error = (); fn write_str(&mut self, s: &str) -> Result { self.buffer.push_str(s); Ok(s.len()) } fn write_str_all(&mut self, s: &str) -> Result<(), Self::Error> { self.buffer.push_str(s); Ok(()) } fn fail_doc(&self) -> Self::Error {} } impl<'a, A> RenderAnnotated<'a, A> for BufferWrite<'a, A> { fn push_annotation(&mut self, a: &'a A) -> Result<(), Self::Error> { self.annotations .push((self.buffer.len(), Annotation::Push(a))); Ok(()) } fn pop_annotation(&mut self) -> Result<(), Self::Error> { self.annotations.push((self.buffer.len(), Annotation::Pop)); Ok(()) } } macro_rules! make_spaces { () => { "" }; ($s: tt $($t: tt)*) => { concat!(" ", make_spaces!($($t)*)) }; } pub(crate) const SPACES: &str = make_spaces!(,,,,,,,,,,); fn append_docs2<'a, 'd, T, A>( ldoc: &'d Doc<'a, T, A>, rdoc: &'d Doc<'a, T, A>, mut consumer: impl FnMut(&'d Doc<'a, T, A>), ) -> &'d Doc<'a, T, A> where T: DocPtr<'a, A>, { let d = append_docs(rdoc, &mut consumer); consumer(d); append_docs(ldoc, &mut consumer) } fn append_docs<'a, 'd, T, A>( mut doc: &'d Doc<'a, T, A>, consumer: &mut impl FnMut(&'d Doc<'a, T, A>), ) -> &'d Doc<'a, T, A> where T: DocPtr<'a, A>, { loop { // Since appended documents often appear in sequence on the left side we // gain a slight performance increase by batching these pushes (avoiding // to push and directly pop `Append` documents) match doc { Doc::Append(l, r) => { let d = append_docs(r, consumer); consumer(d); doc = l; } _ => return doc, } } } pub fn best<'a, W, T, A>(doc: &Doc<'a, T, A>, width: usize, out: &mut W) -> Result<(), W::Error> where T: DocPtr<'a, A> + 'a, for<'b> W: RenderAnnotated<'b, A>, W: ?Sized, { let temp_arena = &typed_arena::Arena::new(); Best { pos: 0, bcmds: vec![(0, Mode::Break, doc)], fcmds: vec![], annotation_levels: vec![], width, temp_arena, } .best(0, out)?; Ok(()) } #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] enum Mode { Break, Flat, } type Cmd<'d, 'a, T, A> = (usize, Mode, &'d Doc<'a, T, A>); fn write_newline(ind: usize, out: &mut W) -> Result<(), W::Error> where W: ?Sized + Render, { out.write_str_all("\n")?; write_spaces(ind, out) } fn write_spaces(spaces: usize, out: &mut W) -> Result<(), W::Error> where W: ?Sized + Render, { let mut inserted = 0; while inserted < spaces { let insert = cmp::min(SPACES.len(), spaces - inserted); inserted += out.write_str(&SPACES[..insert])?; } Ok(()) } struct Best<'d, 'a, T, A> where T: DocPtr<'a, A> + 'a, { pos: usize, bcmds: Vec>, fcmds: Vec<&'d Doc<'a, T, A>>, annotation_levels: Vec, width: usize, temp_arena: &'d typed_arena::Arena, } impl<'d, 'a, T, A> Best<'d, 'a, T, A> where T: DocPtr<'a, A> + 'a, { fn fitting(&mut self, next: &'d Doc<'a, T, A>, mut pos: usize, ind: usize) -> bool { let mut bidx = self.bcmds.len(); self.fcmds.clear(); // clear from previous calls from best self.fcmds.push(next); let mut mode = Mode::Flat; loop { let mut doc = match self.fcmds.pop() { None => { if bidx == 0 { // All commands have been processed return true; } else { bidx -= 1; mode = Mode::Break; self.bcmds[bidx].2 } } Some(cmd) => cmd, }; loop { match *doc { Doc::Nil => {} Doc::Append(ref ldoc, ref rdoc) => { doc = append_docs2(ldoc, rdoc, |doc| self.fcmds.push(doc)); continue; } // Newlines inside the group makes it not fit, but those outside lets it // fit on the current line Doc::Hardline => return mode == Mode::Break, Doc::RenderLen(len, _) => { pos += len; if pos > self.width { return false; } } Doc::BorrowedText(str) => { pos += str.len(); if pos > self.width { return false; } } Doc::OwnedText(ref str) => { pos += str.len(); if pos > self.width { return false; } } Doc::SmallText(ref str) => { pos += str.len(); if pos > self.width { return false; } } Doc::FlatAlt(ref b, ref f) => { doc = match mode { Mode::Break => b, Mode::Flat => f, }; continue; } Doc::Column(ref f) => { doc = self.temp_arena.alloc(f(pos)); continue; } Doc::Nesting(ref f) => { doc = self.temp_arena.alloc(f(ind)); continue; } Doc::Nest(_, ref next) | Doc::Group(ref next) | Doc::Annotated(_, ref next) | Doc::Union(_, ref next) => { doc = next; continue; } Doc::Fail => return false, } break; } } } fn best(&mut self, top: usize, out: &mut W) -> Result where W: RenderAnnotated<'d, A>, W: ?Sized, { let mut fits = true; while top < self.bcmds.len() { let mut cmd = self.bcmds.pop().unwrap(); loop { let (ind, mode, doc) = cmd; match *doc { Doc::Nil => {} Doc::Append(ref ldoc, ref rdoc) => { cmd.2 = append_docs2(ldoc, rdoc, |doc| self.bcmds.push((ind, mode, doc))); continue; } Doc::FlatAlt(ref b, ref f) => { cmd.2 = match mode { Mode::Break => b, Mode::Flat => f, }; continue; } Doc::Group(ref doc) => { if let Mode::Break = mode { if self.fitting(doc, self.pos, ind) { cmd.1 = Mode::Flat; } } cmd.2 = doc; continue; } Doc::Nest(off, ref doc) => { // Once https://doc.rust-lang.org/std/primitive.usize.html#method.saturating_add_signed is stable // this can be replaced let new_ind = if off >= 0 { ind.saturating_add(off as usize) } else { ind.saturating_sub(off.unsigned_abs()) }; cmd = (new_ind, mode, doc); continue; } Doc::Hardline => { // The next document may have different indentation so we should use it if // we can if let Some(next) = self.bcmds.pop() { write_newline(next.0, out)?; self.pos = next.0; cmd = next; continue; } else { write_newline(ind, out)?; self.pos = ind; } } Doc::RenderLen(len, ref doc) => match **doc { Doc::OwnedText(ref s) => { out.write_str_all(s)?; self.pos += len; fits &= self.pos <= self.width; } Doc::BorrowedText(s) => { out.write_str_all(s)?; self.pos += len; fits &= self.pos <= self.width; } Doc::SmallText(ref s) => { out.write_str_all(s)?; self.pos += len; fits &= self.pos <= self.width; } _ => unreachable!(), }, Doc::OwnedText(ref s) => { out.write_str_all(s)?; self.pos += s.len(); fits &= self.pos <= self.width; } Doc::BorrowedText(s) => { out.write_str_all(s)?; self.pos += s.len(); fits &= self.pos <= self.width; } Doc::SmallText(ref s) => { out.write_str_all(s)?; self.pos += s.len(); fits &= self.pos <= self.width; } Doc::Annotated(ref ann, ref doc) => { out.push_annotation(ann)?; self.annotation_levels.push(self.bcmds.len()); cmd.2 = doc; continue; } Doc::Union(ref l, ref r) => { let pos = self.pos; let annotation_levels = self.annotation_levels.len(); let bcmds = self.bcmds.len(); self.bcmds.push((ind, mode, l)); let mut buffer = BufferWrite::new(); match self.best(bcmds, &mut buffer) { Ok(true) => buffer.render(out)?, Ok(false) | Err(()) => { self.pos = pos; self.bcmds.truncate(bcmds); self.annotation_levels.truncate(annotation_levels); cmd.2 = r; continue; } } } Doc::Column(ref f) => { cmd.2 = self.temp_arena.alloc(f(self.pos)); continue; } Doc::Nesting(ref f) => { cmd.2 = self.temp_arena.alloc(f(ind)); continue; } Doc::Fail => return Err(out.fail_doc()), } break; } while self.annotation_levels.last() == Some(&self.bcmds.len()) { self.annotation_levels.pop(); out.pop_annotation()?; } } Ok(fits) } }