pax_global_header00006660000000000000000000000064152366641670014532gustar00rootroot0000000000000052 comment=bebf6c5d0be367a8f7835b92fc44d4cdeeb986d0 OpenVoxProject-clj-kitchensink-6f5334f/000077500000000000000000000000001523666416700200775ustar00rootroot00000000000000OpenVoxProject-clj-kitchensink-6f5334f/.clj-kondo/000077500000000000000000000000001523666416700220355ustar00rootroot00000000000000OpenVoxProject-clj-kitchensink-6f5334f/.clj-kondo/config.edn000066400000000000000000000003711523666416700237730ustar00rootroot00000000000000{:linters {:refer-all {:exclude [clojure.test slingshot.test]} :deprecated-var {:exclude {puppetlabs.kitchensink.core/cn-for-dn {:namespaces [puppetlabs.kitchensink.core puppetlabs.kitchensink.core-test]}}}} :output {:linter-name true}} OpenVoxProject-clj-kitchensink-6f5334f/.github/000077500000000000000000000000001523666416700214375ustar00rootroot00000000000000OpenVoxProject-clj-kitchensink-6f5334f/.github/dependabot.yml000066400000000000000000000003231523666416700242650ustar00rootroot00000000000000version: 2 updates: # Maintain dependencies for GitHub Actions - package-ecosystem: github-actions directory: "/" schedule: interval: daily time: "13:00" open-pull-requests-limit: 10 OpenVoxProject-clj-kitchensink-6f5334f/.github/release.yml000066400000000000000000000013231523666416700236010ustar00rootroot00000000000000--- # https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes changelog: exclude: labels: - duplicate - invalid - modulesync - question - skip-changelog - wont-fix - wontfix categories: - title: Breaking Changes 🛠 labels: - backwards-incompatible - title: New Features 🎉 labels: - enhancement - title: Bug Fixes 🐛 labels: - bug - title: Documentation Updates 📚 labels: - documentation - docs - title: Dependency Updates ⬆️ labels: - dependencies - title: Other Changes labels: - "*" OpenVoxProject-clj-kitchensink-6f5334f/.github/workflows/000077500000000000000000000000001523666416700234745ustar00rootroot00000000000000OpenVoxProject-clj-kitchensink-6f5334f/.github/workflows/pr-testing.yaml000066400000000000000000000034021523666416700264530ustar00rootroot00000000000000--- name: lein_test on: pull_request: {} push: branches: - main # minimal permissions permissions: contents: read jobs: run-lein-tests: name: lein test - Java ${{ matrix.java }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: java: - '25' - '21' - '17' steps: - name: Check out repository code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup java uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: distribution: temurin java-version: ${{ matrix.java }} - name: Install Clojure tools uses: DeLaGuardo/setup-clojure@4c7a6f613e5089821bb3bb2a33a3ee115578580d # 13.6.1 with: cli: latest # Clojure CLI based on tools.deps lein: latest # Leiningen boot: latest # Boot.clj bb: latest # Babashka clj-kondo: latest # Clj-kondo cljstyle: latest # cljstyle zprint: latest # zprint - name: kondo lint run: clj-kondo --lint src test - name: eastwood lint run: | java -version lein eastwood - name: Run lein tests with update profile run: | set -x set -e lein version echo "Running tests" lein -U test tests: if: always() needs: - run-lein-tests runs-on: ubuntu-24.04 name: Test suite steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} OpenVoxProject-clj-kitchensink-6f5334f/.github/workflows/release.yaml000066400000000000000000000007411523666416700260020ustar00rootroot00000000000000--- name: Release and publish package to clojars.org permissions: contents: write packages: write on: workflow_dispatch: jobs: release: uses: 'openvoxproject/shared-actions/.github/workflows/clojure_release.yml@main' secrets: github_pat: ${{ secrets.OPENVOXBOT_COMMIT_AND_PRS }} ssh_private_key: ${{ secrets.OPENVOXBOT_SSH_PRIVATE_KEY }} clojars_username: ${{ secrets.CLOJARS_USERNAME }} clojars_password: ${{ secrets.CLOJARS_PASSWORD }} OpenVoxProject-clj-kitchensink-6f5334f/.gitignore000066400000000000000000000001411523666416700220630ustar00rootroot00000000000000pom.xml *jar /lib/ /classes/ /target/ .lein-deps-sum .lein-failures .clj-kondo/.cache/ .eastwood OpenVoxProject-clj-kitchensink-6f5334f/CHANGELOG.md000066400000000000000000000170031523666416700217110ustar00rootroot00000000000000## unreleased ## 3.4.1 * Migrate from Puppetlabs to OpenVoxProject ## 3.4.0 * add new JSON encoders for java.time Instant, LocalDate, and LocalDateTime ## 3.3.1 * add new functions for conversion of milliseconds to seconds, minutes, hours and days, along with a set of logical corresponding matrix of those units ## 3.3.0 * add new functions `duration-str->seconds` and `duration-str->Duration` to calculate duration values in the style of Puppet duration settings resulting in an integer and a java.time.Duration respectively * add new function `safe-parse-int` to convert a number or string representing a to a number * update clj-parent to 5.6.14 ## 3.2.5 * in `atomic-write` ensure the writer is closed if the `write-function` throws an exception. ## 3.2.4 * change use of java.security.cert.X509Certificate/getSubjectDN, which is now deprecated, to java.security.cert.X509Certificate/getSubjectX500Principal * add function `get-lein-project-version` to retrieve a given project version from the standard `lein` system properties. * mark deprecated interfaces with metadata deprecation * add 'unzip-file' routine to the files namespace to decompress zipped files * add 'untar-file' routine to the files namespace to decompress tar files * update clj-parent to 5.6.6 * remove reflection use in atomic-write * add function `delete-recursively` to the files namespace to recursively delete a directory ## 3.2.3 * add clj-kondo linting, eastwood linting, and PR testing * address issues identified by clj-kondo, and eastwood * add function equivalent of puppet's versioncmp function, and helper functions for to determine if strings have all numeric characters and if strings start with a leading zero. ## 3.2.2 * add conversion functions from string -> ZonedDateTime -> string to facilitate the removal of clj-time * update clj-parent to 5.3.7 ## 3.2.1 * Update ini4j to 0.5.4 to address a Denial of Service vulnerability (CVE-2022-41404). ## 3.2.0 This is a minor feature release. * Add key->str function to convert keywords to strings ## 3.1.3 * Add base-type function that returns the base type from an HTTP Content-Type header. ## 3.1.2 * Update clj-parent to resolve some outdated dependencies with security issues. ## 3.1.1 * Don't select an open port from the ephemeral range in open-port-num. ## 3.1.0 This is a minor feature release. * Add a function for atomic file writes ## 3.0.0 Maintenance: * Update dependencies to take up clj-parent 4 ## 2.5.2 This is a minor maintenance release. Maintenance: * Fix adding URLs to classpath under Java 9. ## 2.5.1 This is a minor maintenance release. Maintenance: * Fix symbol redef warnings under Clojure 1.9 ## 2.5.0 This is a minor feature release. Features: * add a `stream->sha256` function for hashing the contents of an InputStream ## 2.4.0 This is a minor feature and improvement release. Features: * add a `utf8-string->sha256` function, directly analogous to `utf8-string->sha1` * add a `file->sha256` function, equivalent to reading a file's contents as a UTF-8 string and hashing the result. Uses an InputStream internally to avoid reading the entire file into memory at once. Improvement: * the `open-port-num` function should now return a random port number from the entire traditional ephemeral port range of 49152 through 65535. ## 2.3.0 This is a minor feature release. Features: * add a parser for period strings (7d, 12h, etc) into Joda Periods * fix file connection leaks in the functions 'lines' and 'ini-to-map' ## 2.2.0 This is a minor feature release. Features: * Add an `assoc-if-new` macro, which associates a map key to a value only if the key does not already exist in the map. * Add a `deref-swap!` function, which behaves like deref but returns the old value instead of the new one. * Add a `rand-str` function, for generating random strings from various character sets. Maintenance: * Update to dynapath 0.2.5, to address some compatibility issues with Java 9. ## 2.1.1 The 2.1.1 release was burned and folded into 2.2.0. ## 2.1.0 This is a minor feature release. Features: * Add an `open-port-num` function, which returns a currently open port. Tests that bind services to ports can use this to guard against chance port collisions. ## 2.0.0 This is a bugfix release which contains one backward incompatible change. Bug fix: * Changes all of the maps in various slingshot errors thrown to use `:kind` and `:msg` in place of `:type` and `:message`, respectively. ## 1.4.0 This is a minor feature release, which also includes some bugfixes and maintenance work. Features: * Add `uuid?` predicate function for determining whether a string is a valid UUID. Bug fixes: * Fix an issue in `with-additional-classpath-entries` wherein it's pre/post-conditions were not handling arguments properly. Maintenance: * Reduce use of reflection * Remove unused plugins and jenkins scripts * Switch to `lein-parent` for managing dependency versions ## 1.3.1 This is a maintenance release. * Remove retired :flag option from cli tooling, to eliminate warnings on CLI invocations. * Bump to org.clojure/tools.cli 0.3.3. ## 1.3.0 This is a maintenance / minor feature release. * [TK-315](https://tickets.puppetlabs.com/browse/TK-315) - update to latest version of `raynes.fs` to reduce downstream dependency conflicts. * Add an `absolute-path` fn to replace the one that was removed from raynes.fs * Add a `normalized-path` fn to replace the one that was removed from raynes.fs ## 1.2.0 * Add `temp-file-name` function, which returns a unique name to a temporary file, but does not actually create the file. * Add `with-timeout` macro, which returns a default value if executing an arbitrary block of code takes longer than a specified timeout. ## 1.1.0 * Add new `walk-leaves` function for applying a function to all of the leaf nodes of a map * Add new `zipper?` predicate which can be used to assert that an object is a clojure zipper. * Add new `while-let` macro * Add new `rand-weighted-selection` function * Add new `to-sentence` variant of string join ## 1.0.0 * Promoting previous release to 1.0.0 so that we can be more deliberate about adhering to semver from now on. ## 0.7.3 * Add 'filter-map' function that can be used to filter maps ## 0.7.2 * Change `mkdirs!` to allow string as path arg (7097bb3) * Add a new `dissoc-in` function, for removing data from nested maps. ## 0.7.1 * Add a new `to-bool` function, which provides a more tolerant way to coerce data to booleans ## 0.7.0 * Upgrade fs dependency to 1.4.5 (to standardize across projects) * Add mkdirs! function to create parent directories with better failure reporting * Move temp file functions from testutils to core ## 0.6.0 * Remove SSL utility code, which is now available in [puppetlabs/certificate-authority](https://github.com/puppetlabs/jvm-certificate-authority). ## 0.5.4 * Upgrade cheshire dependency to version 5.3.1. ## 0.5.3 * .ini parsing utilities now throw an Exception if a key appears in the file(s) more than once. * Added a `with-no-jvm-shutdown-hooks` macro for running a block of code without any JVM shutdown hooks. ## 0.5.2 * Minor change to the cli! function so that, in addition to the data that it already returned, it now also returns a string representation of a banner/usage summary. Callers can use this to display a help message if additional validation of the cli args fails. * Utility functions added to ssl namespace that allow creation of an SSLContext or a KeyStore/TrustStore directly from the pem files. * Added some JSON utility functions * Added a deep-merge utility function OpenVoxProject-clj-kitchensink-6f5334f/LICENSE000066400000000000000000000260751523666416700211160ustar00rootroot00000000000000Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. OpenVoxProject-clj-kitchensink-6f5334f/README.md000066400000000000000000000017771523666416700213720ustar00rootroot00000000000000# org.openvoxproject/kitchensink A library of utility functions that are common to several Puppet Labs clojure projects. ## Installation Add the following dependency to your `project.clj` file: [![Clojars Project](https://img.shields.io/clojars/v/org.openvoxproject/kitchensink.svg)](https://clojars.org/org.openvoxproject/kitchensink) ## Using Our Test Utils Kitchensink provides [utility code](./test/puppetlabs/kitchensink/) for use in tests. The code is available in a separate "test" jar that you may depend on by using a classifier in your project dependencies. ```clojure (defproject yourproject "1.0.0" ... :profiles {:test {:dependencies [[org.openvoxproject/kitchensink "x.y.z" :classifier "test"]]}}) ``` ## License Copyright © 2013 Puppet Labs Copyright © 2025 Vox Pupuli Distributed under the [Apache License, version 2](http://www.apache.org/licenses/). ## Support Please log tickets and issues at our [GitHub issues tracker](https://github.com/OpenVoxProject/clj-kitchensink/issues). OpenVoxProject-clj-kitchensink-6f5334f/classpath-test/000077500000000000000000000000001523666416700230365ustar00rootroot00000000000000OpenVoxProject-clj-kitchensink-6f5334f/classpath-test/does-not-exist-anywhere-else000066400000000000000000000000001523666416700303770ustar00rootroot00000000000000OpenVoxProject-clj-kitchensink-6f5334f/dev-resources/000077500000000000000000000000001523666416700226655ustar00rootroot00000000000000OpenVoxProject-clj-kitchensink-6f5334f/dev-resources/fixtures/000077500000000000000000000000001523666416700245365ustar00rootroot00000000000000OpenVoxProject-clj-kitchensink-6f5334f/dev-resources/fixtures/plain-text.txt000066400000000000000000000362251523666416700273740ustar00rootroot00000000000000Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Dolor sed viverra ipsum nunc aliquet bibendum enim. Auctor elit sed vulputate mi sit amet mauris commodo quis. Senectus et netus et malesuada fames ac. Ultricies leo integer malesuada nunc vel risus commodo. Nibh tortor id aliquet lectus proin nibh. Varius quam quisque id diam vel quam elementum pulvinar etiam. Laoreet non curabitur gravida arcu ac tortor dignissim convallis aenean. Commodo elit at imperdiet dui accumsan. Molestie nunc non blandit massa enim. Mauris pellentesque pulvinar pellentesque habitant. Non enim praesent elementum facilisis leo vel fringilla est ullamcorper. Arcu non sodales neque sodales ut etiam. Faucibus a pellentesque sit amet porttitor eget dolor. Tincidunt nunc pulvinar sapien et ligula ullamcorper malesuada proin. Arcu bibendum at varius vel pharetra. Cras fermentum odio eu feugiat pretium. Massa placerat duis ultricies lacus. In mollis nunc sed id semper risus. Ut lectus arcu bibendum at. Dictum non consectetur a erat nam at lectus urna. Sed augue lacus viverra vitae congue eu consequat. Vel orci porta non pulvinar neque laoreet. Tellus mauris a diam maecenas sed enim ut. Lobortis mattis aliquam faucibus purus in massa tempor nec. Velit laoreet id donec ultrices tincidunt. Sollicitudin nibh sit amet commodo nulla facilisi. Hendrerit gravida rutrum quisque non tellus. Diam maecenas ultricies mi eget mauris pharetra et ultrices neque. Sem viverra aliquet eget sit amet tellus cras adipiscing. Aenean pharetra magna ac placerat vestibulum lectus. Mattis pellentesque id nibh tortor id aliquet lectus proin nibh. Neque laoreet suspendisse interdum consectetur libero id faucibus nisl. Nulla facilisi etiam dignissim diam quis enim lobortis scelerisque. Nec ultrices dui sapien eget. Enim nec dui nunc mattis enim ut tellus. Ultrices eros in cursus turpis massa tincidunt dui ut ornare. Interdum varius sit amet mattis vulputate enim nulla aliquet. Tortor consequat id porta nibh venenatis cras. Ac ut consequat semper viverra. Sollicitudin aliquam ultrices sagittis orci a scelerisque. Fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate sapien. Aliquet eget sit amet tellus cras adipiscing enim. Ut ornare lectus sit amet est placerat. Enim facilisis gravida neque convallis a cras. Sagittis vitae et leo duis ut. Ornare lectus sit amet est placerat. Adipiscing elit ut aliquam purus sit. Massa ultricies mi quis hendrerit. Adipiscing at in tellus integer feugiat. A cras semper auctor neque vitae tempus quam. Sed adipiscing diam donec adipiscing tristique risus. Tortor aliquam nulla facilisi cras fermentum odio. Quisque non tellus orci ac auctor. Cras tincidunt lobortis feugiat vivamus at. Suspendisse in est ante in nibh mauris cursus. Ut sem nulla pharetra diam. Enim facilisis gravida neque convallis. Euismod quis viverra nibh cras. Viverra suspendisse potenti nullam ac tortor vitae. Id diam maecenas ultricies mi eget mauris. Egestas egestas fringilla phasellus faucibus scelerisque eleifend donec pretium. Ultricies tristique nulla aliquet enim tortor at. Est sit amet facilisis magna. Hendrerit gravida rutrum quisque non tellus orci ac auctor. Mi tempus imperdiet nulla malesuada pellentesque elit eget. Habitant morbi tristique senectus et netus. Purus semper eget duis at tellus at urna condimentum mattis. Magna ac placerat vestibulum lectus mauris. Rutrum tellus pellentesque eu tincidunt tortor aliquam nulla facilisi. Lorem donec massa sapien faucibus et molestie. Mattis aliquam faucibus purus in massa tempor nec feugiat nisl. Ipsum nunc aliquet bibendum enim. Auctor eu augue ut lectus arcu bibendum at. Tempor nec feugiat nisl pretium fusce id velit. Magna fermentum iaculis eu non diam phasellus vestibulum lorem sed. Sit amet porttitor eget dolor morbi non arcu risus quis. Fringilla ut morbi tincidunt augue interdum velit. Mattis rhoncus urna neque viverra justo nec ultrices. Sem nulla pharetra diam sit amet nisl suscipit adipiscing. Sit amet cursus sit amet dictum sit amet. Libero nunc consequat interdum varius sit amet mattis vulputate. Lectus magna fringilla urna porttitor. Amet volutpat consequat mauris nunc congue nisi vitae suscipit. In massa tempor nec feugiat nisl pretium fusce id velit. Eu facilisis sed odio morbi quis commodo odio aenean sed. Accumsan tortor posuere ac ut consequat. Morbi tincidunt augue interdum velit. Massa tincidunt dui ut ornare lectus sit. Cursus eget nunc scelerisque viverra mauris. Et egestas quis ipsum suspendisse ultrices gravida dictum fusce ut. Urna molestie at elementum eu facilisis sed. Nisi est sit amet facilisis magna etiam tempor. Etiam sit amet nisl purus in mollis nunc sed. In vitae turpis massa sed elementum tempus egestas. Nibh ipsum consequat nisl vel pretium lectus. At ultrices mi tempus imperdiet nulla malesuada pellentesque elit eget. Ac auctor augue mauris augue neque. Elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi. Iaculis urna id volutpat lacus laoreet non curabitur gravida arcu. Malesuada nunc vel risus commodo viverra. Porttitor lacus luctus accumsan tortor posuere ac ut. Tempus imperdiet nulla malesuada pellentesque elit eget. Ornare massa eget egestas purus viverra accumsan in nisl nisi. Ligula ullamcorper malesuada proin libero. Tempor nec feugiat nisl pretium fusce id velit. Fermentum iaculis eu non diam phasellus vestibulum. Tellus mauris a diam maecenas sed. Nisi scelerisque eu ultrices vitae auctor eu augue. Morbi tristique senectus et netus et malesuada. Tortor aliquam nulla facilisi cras fermentum odio eu feugiat pretium. Pellentesque pulvinar pellentesque habitant morbi tristique senectus. Donec pretium vulputate sapien nec sagittis aliquam malesuada bibendum. Molestie at elementum eu facilisis sed. Tortor id aliquet lectus proin nibh nisl condimentum id venenatis. Sapien et ligula ullamcorper malesuada. Massa sapien faucibus et molestie ac feugiat. Scelerisque purus semper eget duis. Sit amet volutpat consequat mauris nunc congue nisi vitae. Donec massa sapien faucibus et molestie ac feugiat. Arcu felis bibendum ut tristique et. Facilisis magna etiam tempor orci eu. Nisi vitae suscipit tellus mauris a diam maecenas sed. Et netus et malesuada fames ac turpis egestas sed tempus. Justo donec enim diam vulputate. Cursus turpis massa tincidunt dui ut ornare lectus. Nullam non nisi est sit amet facilisis magna etiam. Tortor consequat id porta nibh venenatis cras sed felis. Eu turpis egestas pretium aenean pharetra magna ac. Id neque aliquam vestibulum morbi. Platea dictumst vestibulum rhoncus est pellentesque elit ullamcorper. Leo a diam sollicitudin tempor id. Sem fringilla ut morbi tincidunt. Non consectetur a erat nam at lectus urna duis. Neque viverra justo nec ultrices dui sapien eget mi. Fermentum leo vel orci porta non pulvinar neque. Sem fringilla ut morbi tincidunt augue interdum velit. Aliquam ut porttitor leo a. Scelerisque viverra mauris in aliquam. Elementum facilisis leo vel fringilla est ullamcorper eget nulla. Tortor vitae purus faucibus ornare suspendisse sed nisi. Aenean vel elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi. Leo vel orci porta non. Sed augue lacus viverra vitae congue eu. Quam nulla porttitor massa id neque aliquam vestibulum morbi. Justo donec enim diam vulputate. Tristique senectus et netus et malesuada fames ac turpis egestas. Aenean et tortor at risus viverra adipiscing at. Sed elementum tempus egestas sed. Eget egestas purus viverra accumsan in nisl. Scelerisque purus semper eget duis at tellus at. Amet tellus cras adipiscing enim eu turpis egestas pretium. Ullamcorper eget nulla facilisi etiam dignissim diam quis enim lobortis. Vel pharetra vel turpis nunc eget. Volutpat sed cras ornare arcu dui. Convallis aenean et tortor at. A arcu cursus vitae congue mauris rhoncus aenean vel. Elit at imperdiet dui accumsan sit. Egestas maecenas pharetra convallis posuere. Tortor at auctor urna nunc id cursus metus aliquam eleifend. Eget felis eget nunc lobortis mattis aliquam faucibus. Adipiscing elit duis tristique sollicitudin nibh sit amet commodo nulla. Aliquet eget sit amet tellus cras adipiscing enim. Dignissim convallis aenean et tortor. Dictumst vestibulum rhoncus est pellentesque elit ullamcorper. Sem viverra aliquet eget sit amet tellus. Phasellus egestas tellus rutrum tellus pellentesque eu. Mattis aliquam faucibus purus in massa tempor nec. Ac turpis egestas maecenas pharetra convallis posuere. Pellentesque habitant morbi tristique senectus. Faucibus ornare suspendisse sed nisi lacus sed viverra tellus in. Purus sit amet luctus venenatis. Sollicitudin ac orci phasellus egestas tellus rutrum tellus pellentesque eu. Blandit libero volutpat sed cras ornare. Et netus et malesuada fames ac turpis egestas sed tempus. Quis ipsum suspendisse ultrices gravida dictum fusce. Eros donec ac odio tempor orci. Ipsum suspendisse ultrices gravida dictum fusce ut placerat orci nulla. Cras sed felis eget velit aliquet sagittis. Risus quis varius quam quisque id diam vel quam. Leo duis ut diam quam nulla porttitor massa id neque. Nec nam aliquam sem et tortor. Nascetur ridiculus mus mauris vitae ultricies leo integer. Viverra vitae congue eu consequat ac felis donec et odio. Ac tortor vitae purus faucibus ornare suspendisse sed. Adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus urna. Aliquet lectus proin nibh nisl. Risus in hendrerit gravida rutrum quisque non. Nec feugiat in fermentum posuere urna. Sit amet est placerat in egestas. Nulla posuere sollicitudin aliquam ultrices sagittis. Morbi blandit cursus risus at. Aenean et tortor at risus viverra adipiscing at in. Sagittis orci a scelerisque purus semper eget. Dui id ornare arcu odio. Ultrices mi tempus imperdiet nulla malesuada pellentesque. Venenatis lectus magna fringilla urna porttitor rhoncus dolor purus non. Condimentum vitae sapien pellentesque habitant morbi tristique. Sed viverra ipsum nunc aliquet bibendum enim facilisis. Neque gravida in fermentum et sollicitudin ac. Justo eget magna fermentum iaculis eu non diam phasellus vestibulum. Consectetur libero id faucibus nisl tincidunt eget nullam. Pellentesque diam volutpat commodo sed egestas egestas fringilla phasellus faucibus. Gravida in fermentum et sollicitudin ac orci phasellus. Dolor sed viverra ipsum nunc aliquet bibendum enim facilisis gravida. Ipsum dolor sit amet consectetur adipiscing elit duis. Bibendum enim facilisis gravida neque convallis a cras semper. Habitant morbi tristique senectus et netus et malesuada fames ac. Eget lorem dolor sed viverra ipsum nunc aliquet. Facilisis gravida neque convallis a cras. Dolor sit amet consectetur adipiscing elit pellentesque. Nunc sed blandit libero volutpat sed cras ornare arcu dui. Aliquet nec ullamcorper sit amet risus nullam eget felis. Blandit massa enim nec dui nunc. Ut enim blandit volutpat maecenas. Varius duis at consectetur lorem donec massa sapien. Nunc non blandit massa enim nec dui nunc mattis. Nibh tellus molestie nunc non blandit. Purus in mollis nunc sed. Viverra orci sagittis eu volutpat odio facilisis mauris. Ultrices gravida dictum fusce ut placerat. Vitae et leo duis ut diam quam nulla porttitor massa. Et egestas quis ipsum suspendisse ultrices gravida dictum fusce. In eu mi bibendum neque egestas congue quisque egestas diam. Et odio pellentesque diam volutpat commodo sed egestas egestas fringilla. Blandit turpis cursus in hac habitasse platea dictumst. Proin libero nunc consequat interdum. Non blandit massa enim nec dui nunc mattis enim ut. Vel orci porta non pulvinar neque laoreet suspendisse interdum consectetur. Viverra maecenas accumsan lacus vel facilisis. Mauris cursus mattis molestie a iaculis. Vestibulum sed arcu non odio euismod lacinia at quis. Proin libero nunc consequat interdum. Cras fermentum odio eu feugiat pretium. Dolor morbi non arcu risus. Sit amet mauris commodo quis imperdiet massa tincidunt. Dolor sit amet consectetur adipiscing. Eget mauris pharetra et ultrices neque ornare aenean. Nulla aliquet porttitor lacus luctus. Nulla at volutpat diam ut venenatis tellus in metus vulputate. Ipsum nunc aliquet bibendum enim facilisis gravida neque convallis. Nulla pharetra diam sit amet. Et sollicitudin ac orci phasellus egestas tellus rutrum. Vehicula ipsum a arcu cursus vitae. Magna sit amet purus gravida quis blandit turpis cursus. Nunc congue nisi vitae suscipit tellus. Felis eget nunc lobortis mattis aliquam faucibus purus in massa. Id diam vel quam elementum pulvinar. Lacus viverra vitae congue eu consequat ac felis donec et. Facilisi cras fermentum odio eu feugiat pretium nibh ipsum. Duis at tellus at urna condimentum mattis. Ut faucibus pulvinar elementum integer enim neque volutpat ac tincidunt. Egestas diam in arcu cursus. Sollicitudin nibh sit amet commodo. Pharetra diam sit amet nisl suscipit. Et tortor consequat id porta nibh venenatis cras sed. At augue eget arcu dictum varius duis. Magna fermentum iaculis eu non diam phasellus. Facilisi nullam vehicula ipsum a arcu cursus vitae congue mauris. Varius quam quisque id diam vel quam elementum. Amet nisl suscipit adipiscing bibendum est ultricies integer. Id diam maecenas ultricies mi eget mauris pharetra et. Nulla porttitor massa id neque. Aliquet risus feugiat in ante metus. Etiam erat velit scelerisque in. Consequat id porta nibh venenatis cras sed felis eget. Ut tortor pretium viverra suspendisse potenti. Nunc sed blandit libero volutpat sed. Orci sagittis eu volutpat odio facilisis mauris sit amet. Ullamcorper malesuada proin libero nunc consequat interdum varius sit amet. Accumsan tortor posuere ac ut consequat semper viverra nam. Tincidunt ornare massa eget egestas purus. Mauris cursus mattis molestie a iaculis at. Sodales ut eu sem integer vitae justo eget magna fermentum. Purus in mollis nunc sed id. Mattis molestie a iaculis at erat. Et sollicitudin ac orci phasellus egestas. Et pharetra pharetra massa massa ultricies mi. Nisl condimentum id venenatis a. Libero id faucibus nisl tincidunt eget nullam. Tincidunt nunc pulvinar sapien et ligula. Vulputate mi sit amet mauris commodo quis. Suscipit tellus mauris a diam maecenas sed enim ut. Tincidunt eget nullam non nisi est sit amet. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. In aliquam sem fringilla ut morbi tincidunt augue. Nec feugiat nisl pretium fusce id. Nam libero justo laoreet sit amet cursus sit. Mi proin sed libero enim sed faucibus turpis in eu. At elementum eu facilisis sed odio morbi quis commodo. Sed odio morbi quis commodo. Lacus vel facilisis volutpat est velit egestas dui id ornare. Orci porta non pulvinar neque laoreet suspendisse. Sollicitudin nibh sit amet commodo nulla. Sit amet volutpat consequat mauris nunc congue. Fermentum leo vel orci porta non pulvinar. Risus ultricies tristique nulla aliquet enim tortor at auctor. Dolor magna eget est lorem ipsum dolor. Dictum varius duis at consectetur lorem donec. Sed cras ornare arcu dui vivamus arcu felis bibendum. Lacus sed turpis tincidunt id aliquet. Donec et odio pellentesque diam volutpat commodo sed egestas egestas. Est velit egestas dui id ornare arcu. Mauris in aliquam sem fringilla ut. Nisl nunc mi ipsum faucibus vitae. Platea dictumst vestibulum rhoncus est pellentesque elit ullamcorper. Massa tincidunt dui ut ornare lectus. Neque aliquam vestibulum morbi blandit cursus risus at ultrices.OpenVoxProject-clj-kitchensink-6f5334f/dev-resources/fixtures/plain-text.txt.gz000066400000000000000000000070071523666416700300070ustar00rootroot00000000000000P:eplain-text.txt[vF}Wۚx8آ: S{7HhJy% Kuխ[ U8}ʵ~OUOq>XP&G*jn]aSոx|'~Zw\Ot3t '? NbWEpKx>]hw}' e;csD!pdbwit^~h]z;ջ깙P=i?dW|'ThoUOP4Ї] 7˵$\w?Ʒ؜B\(OwUD:9T}8taC<8Pv'QMUh~4X :#^O/7Nl~Mr{:> Ev:gݠG ;H8F~7@(:\&xU? lҁkt;R$ˤ0C) Ҡ#w|spOW?}E$sB$򏍫fp^WG׮j{: NP= 73wwwCRPK;G94@6GP1Ox@p`lC4Bk93s B"aUΘi/c ah K,10_FD\L&(4+>#@Rn J0{˱%bdޖ!& 'J7CUMN`s`ߝp!:Czht@BG6,F-AxIfaogUbnĢ<)˧zpt^2<폒WϙA ? 7+5tƎρaQh P)1 F'C~״$ڔ~ 27K rYou&."peTEEy|ؽߥK!X:iFU~"p`fFsYY!N4c_13e2m~_7}W$ĶZ9GRN&T5%7~='pdu+ii\_[Vwl~2}kT=oR\$Htq^=x0̎+AyyVr_V 4&`6ఽG̉/!OOSyRa]Y3ĕ5[qb(gS7q ]8@d$W~ JDqgG߫Ƽ=R.i Zmhܽ6_=iUj!+xǴC |Z{iG9Dq.{ʋ ~&ͥcz<7[o|%*AŷǬ=!%JC W|:x,~!B䍔$ %ATzpd9b̀LT_+1mY>pķ&n\I"]KS(kM98}( sY瘀1)ջ@xՇ2P 2qųH?5 uFP%FJʸΆ:Dwj7Yi?|G^7X\dU;tMRwT&^iF'޴jVwC8\I#fI&Q-782|rpdeCFbꉭs0זc$cqe*Br$c̦[rUsV9RE캦(wTgoS ֭VpS*1Bo}mG)ahԪ-ˑôm"Ə9%9l6{{lHVjJfbݳdOYq/E)W E pkjk#<*})Ft͈֭I1'5$Po{/ˍk\y%q[fK{mk"l-*|.t,g]-難0)8 N\"-nh>ULbrlyhab xZw;ZښT.vm衉|Kys)Iw\nlOh`.ԧ kЬʅ$R2%yAhc4eÃ))77$UwRO,5렐Vʴ] =GeJ+]x8:W3q- 7--ɂl9xufWeqUl FFd}:DԼa%]gt#_SlTI|/\Vun쥜ije7v/ 'q\Fun#=O;v] Ek$Vgdɨ%ԟ\c!MRz8NDDn\l$崉yo}Hvi'sqʪrȋpϥ8&kYK˔-컝B' 10 6>^ޠ,#ppʢA6A667__(N͏ٗg"54D0^,V. ҌC)XGw̥ p×Պ'F&u:=> tip (iterate #(.getParent ^ClassLoader %)) (take-while boolean)))) (defn- modifiable-classloader? "Returns true iff the given ClassLoader is of a type that satisfies the dynapath.dynamic-classpath/DynamicClasspath protocol, and it can be modified." [cl] (dp/addable-classpath? cl)) (defn- ensure-modifiable-classloader "Check if there is a modifiable classloader in the current hierarchy, and add one if not." [] (let [classloader (.. Thread currentThread getContextClassLoader)] (when (not-any? modifiable-classloader? (classloader-hierarchy classloader)) (let [new-cl (clojure.lang.DynamicClassLoader. classloader)] (.. Thread currentThread (setContextClassLoader new-cl)))))) (defn add-classpath "A corollary to the (deprecated) `add-classpath` in clojure.core. This implementation requires a java.io.File or String path to a jar file or directory, and will attempt to add that path to the right classloader (with the search rooted at the current thread's context classloader). Because this function is a replacement for `add-classpath` in clojure.core, if you simply `:require` this namespace and then `:refer` to this function, you will get the following warning: WARNING: add-classpath already refers to: #'clojure.core/add-classpath in namespace: [...], being replaced by: #'puppetlabs.kitchensink.classpath/add-classpath You can avoid this by referencing this function through its namespace. This function is copied out of the 'pomegranate' library (https://github.com/cemerick/pomegranate)." ([jar-or-dir classloader] (when-not (dp/add-classpath-url classloader (.toURL (file jar-or-dir))) (throw (IllegalStateException. (str classloader " is not a modifiable classloader"))))) ([jar-or-dir] (ensure-modifiable-classloader) (let [classloaders (classloader-hierarchy)] (if-let [cl (last (filter modifiable-classloader? classloaders))] (add-classpath jar-or-dir cl) (throw (IllegalStateException. (str "Could not find a suitable classloader to modify from " classloaders))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; end of functions copied from pomegranate (see note above) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn jar-or-dir-to-url "Given the path to a jar file or a directory, return a `java.net.URL` object suitable for using with a `URLClassLoader`" [jar-or-dir] {:pre [(satisfies? Coercions jar-or-dir)] :post [(instance? URL %)]} ;; explicitly calling `getAbsoluteFile` causes relative file paths to ;; be evaluated relative to the system property `user.dir` (which is ;; usually set to the current working directory). This is useful for ;; tests and other code that wants to emulated changing the working ;; directory. (.. (file jar-or-dir) getAbsoluteFile toURL)) (defmacro with-additional-classpath-entries "This macro takes a list of paths as an argument. It then temporarily overrides the classpath to include the specified paths; the original classpath is restored prior to returning." [jars-and-dirs & body] `{:pre [(coll? ~jars-and-dirs) (every? (partial satisfies? Coercions) ~jars-and-dirs)]} `(let [orig-loader# (.. Thread currentThread getContextClassLoader) temp-loader# (URLClassLoader. (into-array URL (map jar-or-dir-to-url ~jars-and-dirs)) orig-loader#)] (try (.. Thread currentThread (setContextClassLoader temp-loader#)) ~@body (finally (.. Thread currentThread (setContextClassLoader orig-loader#)))))) OpenVoxProject-clj-kitchensink-6f5334f/src/puppetlabs/kitchensink/core.clj000066400000000000000000001213641523666416700270100ustar00rootroot00000000000000;; ## "The Kitchen Sink" ;; ;; Pretty much everything in here should _probably_ be organized into ;; proper namespaces, or perhaps even separate libraries ;; altogether. But who has time for that? (ns puppetlabs.kitchensink.core (:refer-clojure :exclude [boolean? uuid?]) (:require [clojure.java.io :as io] [clojure.pprint :as pprint] [clojure.set :as set] [clojure.string :as string] [clojure.tools.cli :as cli] [clojure.tools.logging :as log] [digest] [me.raynes.fs :as fs] [slingshot.slingshot :refer [throw+]]) (:import (java.io File Reader StringWriter) (java.time Duration ZoneId ZoneOffset ZonedDateTime) (java.time.format DateTimeFormatter) javax.naming.ldap.LdapName (javax.naming.ldap Rdn) (org.ini4j BasicProfileSection Config Ini))) (defn error-map [kind message] {:kind kind :msg message}) ;; ## Type checking (defn array? "Returns true if `x` is an array" [x] (some-> x (class) (.isArray))) (defn boolean? "Returns true if the value is a boolean" [value] (instance? Boolean value)) (defn regexp? "Returns true if the type is a regexp pattern" [regexp] {:post [(boolean? %)]} (instance? java.util.regex.Pattern regexp)) (defn zipper? "Checks to see if the object has zip/make-node metadata on it (confirming it to be a zipper." [obj] (contains? (meta obj) :zip/make-node)) ;; ## String utilities (defn strict-parse-bool "Parse a string and return its boolean value; throws an exception if the String does not match `\"true\"` or `\"false\"` (case-insensitive)." [^String s] {:pre [(string? s)] :post [(boolean? %)]} (condp = (.toLowerCase s) "true" true "false" false (throw+ (error-map ::parse-error (format "Unable to parse '%s' to a boolean" s))))) (defn parse-bool "Parse a string and return its boolean value." [s] {:pre [(or (nil? s) (string? s))] :post [(boolean? %)]} (Boolean/parseBoolean s)) (defn to-bool "Converts the argument to a boolean. The behavior is as follows: * If the argument is a Boolean, it is simply returned. * If the argument is a String, returns the Boolean `true` if the String matches `\"true\"` (case insensitive), or `false` if the String matches `\"false\"` (case insensitive). Throws an exception otherwise. * If the argument is `nil`, returns false." [val] {:pre [((some-fn boolean? string? nil?) val)] :post [(boolean? %)]} (cond (boolean? val) val (string? val) (strict-parse-bool val) (nil? val) false)) (defn string-contains? "Returns true if `s` has the `substring` in it" [^String substring ^String s] {:pre [(string? s) (string? substring)]} (>= (.indexOf s substring) 0)) (defn true-str? "Return true if the string contains true" [^String s] (.equalsIgnoreCase "true" s)) (defn pprint-to-string [x] (let [w (StringWriter.)] (pprint/pprint x w) (.toString w))) (defn to-sentence "Join the given strings as they would be listed in a sentence (using an Oxford comma if there are three or more strings). Examples: [\"apple\"] => \"apple\" [\"apple\" \"orange\"] => \"apple and orange\" [\"apple\" \"orange\" \"banana\"] => \"apple, orange, and banana\"" [strings] (let [num-strings (count strings)] (cond (empty? strings) "" (= num-strings 1) (first strings) (= num-strings 2) (str (first strings) " and " (second strings)) (= num-strings 3) (let [[s0 s1 s2] strings] (str s0 ", " s1 ", and " s2)) (> num-strings 3) (str (first strings) ", " (to-sentence (rest strings)))))) (defn key->str "Convert a keyword to a string, stripping the leading : character. This is distinct from the `name` function as it will preserve the 'namespace' portion before a slash. If the key is already a string, it is returned unmodified." [kw] (if (keyword? kw) (subs (str kw) 1) kw)) ;; ## I/O (defn lines "Returns a sequence of lines from the given filename" [filename] (with-open [file-reader (io/reader (fs/file filename))] ;; line seq is lazy and file-reader gets closed (doall (line-seq file-reader)))) (defn mkdirs! "Given a path (may be a File or a string), creates a directory (including any missing parent directories). Throws a slingshot exception with a meaningful error message if the directory cannot be created. (The reason for the existence of this function is that the Java File.mkdirs method only returns a boolean indicating whether the directory was created; if you get back a `false`, you have no idea whether it failed due to permission errors, or the path being invalid in some way, or the directory already exists.) The slingshot exception will look like this: `{:kind :puppetlabs.kitchensink.core/io-error :msg \"Parent directory '/foo/bar' is not writable\"}`" [path] {:pre [((some-fn #(instance? File %) string?) path)] :post [(fs/directory? path)]} (let [path-as-file (fs/file path)] (if (fs/file? path-as-file) (throw+ (error-map ::io-error (format "Path '%s' is a file" path))) (doseq [^File dir (reverse (cons path-as-file (fs/parents path-as-file)))] (when-not (fs/exists? dir) (let [parent (.getParentFile dir)] (when (fs/file? parent) (throw+ (error-map ::io-error (format "Parent directory '%s' is a file" parent)))) (when-not (.canWrite parent) (throw+ (error-map ::io-error (format "Parent directory '%s' is not writable" parent)))) (let [success (.mkdir dir)] (when-not success (throw+ (error-map ::io-error (format "Unable to create directory '%s'" parent))))))))))) ;; ## Math (defn quotient "Performs division on the supplied arguments, substituting `default` when the divisor is 0" ([dividend divisor] (quotient dividend divisor 0)) ([dividend divisor default] (if (zero? divisor) default (/ dividend divisor)))) ;; ## Numerics (defn parse-int "Parse a string `s` as an integer, returning nil if the string doesn't contain an integer." [s] {:pre [(string? s)] :post [(or (integer? %) (nil? %))]} (try (Integer/parseInt s) (catch NumberFormatException _e nil))) (defn safe-parse-int "Accept strings or integers. If string, parse to an integer, otherwise return the integer" [s] (if (integer? s) s (parse-int s))) (defn parse-float "Parse a string `s` as a float, returning nil if the string doesn't contain a float" [s] {:pre [(string? s)] :post [(or (float? %) (nil? %))]} (try (Float/parseFloat s) (catch NumberFormatException _e nil))) (defn parse-number "Converts a string `s` to a number, by attempting to parse it as an integer and then as a float. Returns nil if the string isn't numeric." [s] {:pre [(string? s)] :post [(or (number? %) (nil? %))]} ((some-fn parse-int parse-float) s)) ;; ## Randomness (defn rand-weighted-selection "Given alternating numeric weights and values, produces a randomly-selected value according to the weights, which should sum to one. If the weights sum to less than one, then the last value will have its weight adjusted upwards accordingly. If the weights sum to more than one, then values after the cumulative sum of the weights exceeds one will never be selected." [& weights-and-values] {:pre [(even? (count weights-and-values)) (every? number? (take-nth 2 weights-and-values))]} (let [weights (take-nth 2 weights-and-values) values (-> (take-nth 2 (drop 1 weights-and-values)) butlast) cutoffs (-> (reductions + weights) butlast) selected? (let [selected-cutoff (rand)] #(>= % selected-cutoff)) selected (filter (comp selected? first) (map vector cutoffs values))] (if (empty? selected) (last weights-and-values) (-> (first selected) second)))) (def ascii-character-sets (let [concatv (comp vec concat) ALPHA (mapv char (range 65 91)) alpha (mapv char (range 97 123)) digits (mapv char (range 48 58)) symbols (concatv (map char (range 33 48)) (map char (range 91 97)) (map char (range 123 127)))] {:alpha (concatv alpha ALPHA) :alpha-lower alpha :alpha-upper ALPHA :alpha-digits (concatv alpha ALPHA digits) :alpha-digits-symbols (concatv alpha ALPHA digits symbols) :symbols symbols :digits digits})) (defn rand-str "Produces a random string of length n, drawn from the given collection of characters. The following keywords may be used in place of a character collection: :alpha - [a-zA-Z] :alpha-lower - [a-z] :alpha-upper - [A-Z] :alpha-digits - [a-zA-Z0-9] :alpha-digits-symbols - all printable ASCII characters besides space :symbols - all visible, non-alpha-numeric ASCII characters (no space) :digits - [0-9] If no character collection or keyword is provided, :alpha-digits-symbols is used by default." ([n] (rand-str :alpha-digits-symbols n)) ([characters n] (let [char-coll (cond (and (keyword? characters) (contains? ascii-character-sets characters)) (get ascii-character-sets characters) (keyword? characters) (throw (IllegalArgumentException. (str characters " is not a recognized character collection keyword"))) :else (vec characters))] (apply str (repeatedly n #(rand-nth char-coll)))))) ;; ## Collection operations (defn symmetric-difference "Computes the symmetric set/difference between 2 sets" [s1 s2] (set/union (set/difference s1 s2) (set/difference s2 s1))) (defn as-collection "Returns the item wrapped in a collection, if it's not one already. Returns a list by default, or you can use a constructor func as the second arg." ([item] (as-collection item list)) ([item constructor] {:post [(coll? %)]} (if (coll? item) item (constructor item)))) (defn seq-contains? "True if seq contains elm" [seq elm] (some #(= elm %) seq)) (defn enumerate "Returns a lazy sequence consisting of 0 and the first item of coll, followed by 1 and the second item in coll, etc, until coll is exhausted." [coll] (map-indexed vector coll)) (def excludes? "Inverse of `contains?`. Returns false if key is present in the given collectoin, otherwise returns true." (complement contains?)) (defn contains-some "If coll `contains?` any of the keys in ks, returns the first such key. Otherwise returns nil." [coll ks] (some #(when (contains? coll %) %) ks)) (defn excludes-some "If coll `excludes?` any of the keys in ks, returns the first such key. Otherwise returns nil." [coll ks] (some #(when (excludes? coll %) %) ks)) (defn mapvals "Return map `m`, with each value transformed by function `f`. You may also provide an optional list of keys `ks`; if provided, only the specified keys will be modified." ([f m] (into {} (for [[k v] m] [k (f v)]))) ([f ks m] ;; would prefer to share code between the two implementations here, but ;; the `into` is much faster for the base case and the reduce is much ;; faster for any case where we're operating on a subset of the keys. ;; It seems like `select-keys` is fairly expensive. (reduce (fn [m k] (update-in m [k] f)) m ks))) (defn mapkeys "Return map `m`, with each key transformed by function `f`" [f m] (into {} (concat (for [[k v] m] [(f k) v])))) (defn maptrans "Return map `m`, with values transformed according to the key-to-function mappings specified in `keys-fns`. `keys-fns` should be a map whose keys are lists of keys from `m`, and whose values are functions to apply to those keys. Example: `(maptrans {[:a, :b] inc [:c] dec} {:a 1 :b 1 :c 1})` yields `{:a 2, :c 0, :b 2}`" [keys-fns m] {:pre [(map? keys-fns) (every? (fn [[ks fn]] (and (coll? ks) (ifn? fn))) keys-fns) (map? m)]} (let [ks (keys keys-fns)] (reduce (fn [m k] (mapvals (keys-fns k) k m)) m ks))) (defn dissoc-if-nil "Given a map and a key, checks to see if the value for the key is `nil`; if so, returns a modified map with the specified key removed. If the value is not `nil`, simply returns the original map." ([m k] {:pre [(map? m)] :post [(map? %)]} (if (nil? (m k)) (dissoc m k) m)) ([m k & ks] (let [ret (dissoc-if-nil m k)] (if ks (recur ret (first ks) (next ks)) ret)))) (defn dissoc-in "Dissociates an entry from a nested map. ks is a sequence of keys. Any empty maps that result will not be present in the new map." [m [k & ks]] (when m (if-let [res (and ks (dissoc-in (m k) ks))] (assoc m k res) (let [res (dissoc m k)] (when-not (empty? res) res))))) (defn walk-leaves "Walk a map applying a function to all leaf nodes" [m f] (mapvals #(if (map? %) (walk-leaves % f) (f %)) m)) (defn merge-with-key "Returns a map that consists of the rest of the maps conj-ed onto the first. If a key `k` occurs in more than one map, the mapping(s) from the latter (left-to-right) will be combined with the mapping in the result by calling (f k val-in-result val-in-latter)." {:added "1.0" :static true} [f & maps] (when (some identity maps) (let [merge-entry (fn [m e] (let [k (key e) v (val e)] (if (contains? m k) (assoc m k (f k (get m k) v)) (assoc m k v)))) merge2 (fn [m1 m2] (reduce merge-entry (or m1 {}) (seq m2)))] (reduce merge2 maps)))) (defn deep-merge "Deeply merges maps so that nested maps are combined rather than replaced. For example: (deep-merge {:foo {:bar :baz}} {:foo {:fuzz :buzz}}) ;;=> {:foo {:bar :baz, :fuzz :buzz}} ;; contrast with clojure.core/merge (merge {:foo {:bar :baz}} {:foo {:fuzz :buzz}}) ;;=> {:foo {:fuzz :quzz}} ; note how last value for :foo wins" [& vs] (if (every? map? vs) (apply merge-with deep-merge vs) (last vs))) (defn deep-merge-with "Deeply merges like `deep-merge`, but uses `f` to produce a value from the conflicting values for a key in multiple maps." [f & vs] (if (every? map? vs) (apply merge-with (partial deep-merge-with f) vs) (apply f vs))) (defn deep-merge-with-keys* "Helper function for deep-merge-with-keys" [f ks & vs] (if (every? map? vs) (apply merge-with-key (fn [k & vs] (apply deep-merge-with-keys* f (conj ks k) vs)) vs) (apply f ks vs))) (defn deep-merge-with-keys "Deeply merges like `deep-merge`, but uses `f` to produce a value from the conflicting values for a key path `ks` that appears in multiple maps, by calling `(f ks val-in-result val-in-latter)`." [f & vs] (apply deep-merge-with-keys* f [] vs)) (defn keyset "Returns the set of keys from the supplied map" [m] {:pre [(map? m)] :post [(set? %)]} (set (keys m))) (defn valset "Returns the set of values from the supplied map" [m] {:pre [(map? m)] :post [(set? %)]} (set (vals m))) (def select-values "Returns the sequence of values from the map for the entries with the specified keys" (comp vals select-keys)) (defn filter-map "Like 'filter', but works on maps. Returns a map containing the key-value pairs in 'm' for which 'pred' returns a truth-y value. 'pred' must be a function which takes two arguments." [pred m] (reduce (fn [result [key value]] (if (pred key value) (assoc result key value) result)) {} m)) (defn missing? "Inverse of contains? that supports multiple keys. Will return true if all items are missing from the collection, false otherwise. Example: ;; Returns true, as :z :f :h are all missing (missing? {:a 'a' :b 'b' :c 'c'} :z :f :h) ;; Returns false, as :a is in the collection (missing? {:a 'a' :b 'b' :c 'c'} :z :b)" [coll & keys] {:pre [(coll? coll)] :post [(boolean? %)]} (reduce (fn [_ key] (if (contains? coll key) (reduced false) true)) nil keys)) (defn ordered-comparator "Given a function and an order (:ascending or :descending), return a comparator function that takes two objects and compares them in ascending or descending order based on the value of applying the function to each." [f order] {:pre [(ifn? f) (contains? #{:ascending :descending} order)] :post [(fn? %)]} (fn [x y] (if (= order :ascending) (compare (f x) (f y)) (compare (f y) (f x))))) (defn compose-comparators "Composes two comparator functions into a single comparator function which will call the first comparator and return the result if it is non-zero; otherwise it will call the second comparator and return its result." [comp-fn1 comp-fn2] {:pre [(fn? comp-fn1) (fn? comp-fn2)] :post [(fn? %)]} (fn [x y] (let [val1 (comp-fn1 x y)] (if (= val1 0) (comp-fn2 x y) val1)))) (defn order-by-expr? "Predicate that returns true if the argument is a valid expression for use with the `order-by` function; in other words, returns true if the argument is a 2-item vector whose first element is an `ifn` and whose second element is either `:ascending` or `:descending`." [x] (and (vector? x) (ifn? (first x)) (contains? #{:ascending :descending} (second x)))) (defn order-by "Sorts a collection based on a sequence of 'order by' expressions. Each expression is a tuple containing a fn followed by either `:ascending` or `:descending`; returns a collection that is sorted based on the values of the 'order by' fns being applied to the elements in the original collection. If multiple 'order by' expressions are passed in, their precedence is determined by their order in the argument list." [order-bys coll] {:pre [(sequential? order-bys) (every? order-by-expr? order-bys) (coll? coll)]} (let [comp-fns (map (fn [[f order]] (ordered-comparator f order)) order-bys) final-comp (reduce compose-comparators comp-fns)] (sort final-comp coll))) (defn sort-nested-maps "For a data structure, recursively sort any nested maps and sets descending into map values, lists, vectors and set members as well. The result should be that all maps in the data structure become explicitly sorted with natural ordering. This can be used before serialization to ensure predictable serialization. The returned data structure is not a transient so it is still able to be modified, therefore caution should be taken to avoid modification else the data will lose its sorted status." [data] (cond (map? data) (into (sorted-map) (for [[k v] data] [k (sort-nested-maps v)])) (sequential? data) (map sort-nested-maps data) :else data)) ;; ## Exception handling (defn without-ns "Given a clojure keyword that is optionally namespaced, returns a keyword with the same name but with no namespace." [kw] {:pre [(keyword? kw)] :post [(keyword? %) (nil? (namespace %))]} (keyword (name kw))) (defn keep-going* "Executes the supplied fn repeatedly. Execution may be stopped with an InterruptedException." [f on-error] (when (try (f) true (catch InterruptedException _e false) (catch Throwable e (on-error e) true)) (recur f on-error))) (defmacro keep-going "Executes body, repeating the execution of body even if an exception is thrown" [on-error & body] `(keep-going* (fn [] ~@body) ~on-error)) (defmacro with-error-delivery "Executes body, and delivers an exception to the provided promise if one is thrown." [error & body] `(try ~@body (catch Throwable e# (deliver ~error e#)))) (defmacro with-timeout [timeout-s default & body] `(let [f# (future (do ~@body)) result# (deref f# (* 1000 ~timeout-s) ~default)] (future-cancel f#) result#)) ;; ## File paths (defn absolute-path "Replacement for raynes.fs/absolute-path, which was removed in raynes.fs 1.4.6. Returns string representation of absolute path, as opposed to fs/absolute, which returns a File object." [path] (.getPath ^File (fs/absolute path))) (defn normalized-path "Replacement for raynes.fs/normalized-path, which was removed in raynes.fs 1.4.6. Returns string representation of absolute path, as opposed to fs/normalized, which returns a File object." [path] (.getPath ^File (fs/normalized path))) ;; ## Temp files (defn temp-file-name "Returns a unique name to a temporary file, but does not actually create the file." [file-name-prefix] (io/file (fs/tmpdir) (fs/temp-name file-name-prefix))) (defn delete-on-exit "Will delete `f` on shutdown of the JVM" [f] (.deleteOnExit (fs/file f)) f) (defn temp-file "Creates a temporary file that will be deleted on JVM shutdown. Supported arguments are the same as for me.raynes.fs/temp-file: [prefix] [prefix suffix] [prefix suffix tries] You may also call with no arguments, in which case the prefix string will be empty." [& args] (if (empty? args) (delete-on-exit (fs/temp-file nil)) (delete-on-exit (apply fs/temp-file args)))) (defn temp-dir "Creates a temporary directory that will be deleted on JVM shutdown. Supported arguments are the same as for me.raynes.fs/temp-dir: [prefix] [prefix suffix] [prefix suffix tries] You may also call with no arguments, in which case the prefix string will be empty." [& args] (if (empty? args) (delete-on-exit (fs/temp-dir nil)) (delete-on-exit (apply fs/temp-dir args)))) ;; ## Configuration files (def keywordize "Normalize INI keys by ensuring they're lower-case and keywords" (comp keyword string/lower-case)) (defn fetch-int "Fetch a key from the INI section and convert it to an integer if it parses, otherwise return the string" [^BasicProfileSection section key] (let [val (.fetch section key)] (or (parse-int val) val))) (defn create-section-map "Given an INI section, create a clojure map of it's key/values" [^BasicProfileSection section] (reduce (fn [acc [key _]] (if (> (.length section key) 1) (throw (IllegalArgumentException. (str "Duplicate configuration entry: " (mapv keyword [(.getName section) key])))) (assoc acc (keywordize key) (fetch-int section key)))) {} section)) (defn parse-ini "Takes a io/reader that contains an ini file, and returns an Ini object containing the parsed results" [^Reader ini-reader] {:pre [(instance? Reader ini-reader)] :post [(instance? Ini %)]} (let [config (Config.) ini (Ini.)] (.setMultiOption config true) (.setConfig ini config) (.load ini ini-reader) ini)) (defn ini-to-map "Takes a .ini filename and returns a nested map of fully-interpolated values. Strings that look like integers are returned as integers, and all section names and keys are returned as symbols." [filename] {:pre [(or (string? filename) (instance? java.io.File filename))] :post [(map? %) (every? keyword? (keys %)) (every? map? (vals %))]} (with-open [ini (io/reader filename)] (reduce (fn [acc [name section]] (assoc acc (keywordize name) (create-section-map section))) {} (parse-ini ini)))) (defn inis-to-map "Takes a path and converts the pointed-at .ini files into a nested map (see `ini-to-map` for details). If `path` is a file, the behavior is exactly the same as `ini-to-map`. If `path` is a directory, we return a merged version of parsing all the .ini files in the directory (we do not do a recursive find of .ini files)." ([path] (inis-to-map path "*.ini")) ([path glob-pattern] {:pre [(or (string? path) (instance? java.io.File path))] :post [(map? %)]} (let [files (if-not (fs/directory? path) [path] (fs/glob (fs/file path glob-pattern)))] (->> files (map fs/absolute) (map ini-to-map) (apply deep-merge-with-keys (fn [ks & _] (throw (IllegalArgumentException. (str "Duplicate configuration entry: " ks))))) (merge {}))))) (defn spit-ini "Writes the `ini-map` to the Ini file at `file`. `ini-map` should a map similar to the ones created by ini-to-map. The keys are keywords for the sections and their values are maps of config keypairs." [^File file ini-map] (let [ini (org.ini4j.Ini. file)] (doseq [[section-key section] ini-map [k v] section] (.put ini (name section-key) (name k) v)) (.store ini))) (defn add-shutdown-hook! "Adds a shutdown hook to the JVM runtime. `f` is a function that takes 0 arguments; the return value is ignored. This function will be called if the JVM receiveds an interrupt signal (e.g. from `kill` or CTRL-C); you can use it to log shutdown messages, handle state cleanup, etc." [^Runnable f] {:pre [(fn? f)]} (.addShutdownHook (Runtime/getRuntime) (Thread. f))) (defmacro demarcate "Executes `body`, but logs `msg` to info before and after `body` is executed. `body` is executed in an implicit do, and the last expression's return value is returned by `demarcate`. user> (demarcate \"reticulating splines\" (+ 1 2 3)) \"Starting reticulating splines\" \"Finished reticulating splines\" 6 " [msg & body] `(do (log/info (str "Starting " ~msg)) (let [result# (do ~@body)] (log/info (str "Finished " ~msg)) result#))) ;; ## Command-line parsing (defn cli! "Validates that required command-line arguments are present. If they are not, throws a map** with an error message that is intended to be displayed to the user. Also checks to see whether the user has passed the `--help` flag. Input: - args : the command line arguments passed in by the user - specs : an array of supported argument specifications, as accepted by `clojure.tools.cli` - required : an array of keywords (using the long form of the argument spec) specifying which of the `specs` are required. If any of the `required` options are not present, the function will cause the program to exit and display the help message. ** The map is thrown using 'slingshot' (https://github.com/scgilardi/slingshot). It contains a `:kind` and `:msg`, where type is either `:error` or `:help`, and the message is either the error message or a help banner. Returns a three-item vector, containing: * a map of the parsed options * a vector containing the remaining cli arguments that were not parsed * a string containing a summary of all of the options that are available; for use in printing help messages if the user detects that the arguments are still invalid in some way." ([args specs] (cli! args specs nil)) ([args specs required-args] (let [specs (conj specs ["-h" "--help" "Show help" :default false]) {:keys [options arguments summary errors]} (cli/parse-opts args specs)] (when errors (let [msg (str "\n\n" "Error(s) occurred while parsing command-line arguments: " (apply str errors) "\n\n" summary)] (throw+ (error-map ::cli-error msg)))) (when (:help options) (throw+ (error-map ::cli-help summary))) (when-let [missing-field (some #(when-not (contains? options %) %) required-args)] (let [msg (str "\n\n" (format "Missing required argument '--%s'!" (name missing-field)) "\n\n" summary)] (throw+ (error-map ::cli-error msg)))) [options arguments summary]))) ;; ## SSL Certificate handling ;; ;; NOTE: Prefer functions provided by the jvm-certificate-authority library over these. ;; ;; These functions are only used by PuppetDB and they should likely move back into that ;; project until they can be refactored away over functions from the jvm-ca library. (defn- safe-value-request "Used to make eastwood happy about the type" [^Rdn v] (.getValue v)) (defn ^:deprecated cn-for-dn "Deprecated. Use functions from https://github.com/puppetlabs/jvm-ssl-utils instead. Extracts the CN (common name) from an LDAP DN (distinguished name). If more than one CN entry exists in the given DN, we return the most-specific one (the one that comes last, textually). If no CN is present in the DN, we return nil. Example: (cn-for-dn \"CN=foo.bar.com,OU=meh,C=us\") \"foo.bar.com\" (cn-for-dn \"CN=foo.bar.com,CN=baz.goo.com,OU=meh,C=us\") \"baz.goo.com\" (cn-for-dn \"OU=meh,C=us\") nil" [^String dn] {:pre [(string? dn)]} (some->> dn (LdapName.) (.getRdns) (filter #(= "CN" (.getType ^Rdn %))) (first) (safe-value-request) (str))) (defn ^:deprecated cn-for-cert "Deprecated. Use functions from https://github.com/puppetlabs/jvm-ssl-utils instead. Extract the CN from the DN of an x509 certificate. See `cn-for-dn` for details on how extraction is performed. If no CN exists in the certificate DN, nil is returned." [^java.security.cert.X509Certificate cert] (-> cert (.getSubjectX500Principal) (.getName) (cn-for-dn))) ;; ## Ring helpers (defn cn-whitelist->authorizer "Given a 'whitelist' file containing allowed CNs (one per line), build a function that takes a Ring request and returns true if the CN contained in the client certificate appears in the whitelist. `whitelist` can be either a local filename or a File object. This makes use of the `:ssl-client-cn` request parameter. See `com.puppetlabs.middleware/wrap-with-certificate-cn`." [whitelist] {:pre [(or (string? whitelist) (instance? java.io.File whitelist))] :post [(fn? %)]} (let [allowed? (set (lines whitelist))] (fn [{:keys [ssl-client-cn scheme]}] (or (= scheme :http) (allowed? ssl-client-cn))))) ;; ## Hashing (defn utf8-string->sha1 "Compute a SHA-1 hash for the UTF-8 encoded version of the supplied string" [^String s] {:pre [(string? s)] :post [(string? %)]} (let [bytes (.getBytes s "UTF-8")] (digest/sha-1 [bytes]))) (defn utf8-string->sha256 "Compute a SHA-256 hash for the UTF-8 encoded version of the supplied string" [^String s] {:pre [(string? s)] :post [(string? %)]} (let [bytes (.getBytes s "UTF-8")] (digest/sha-256 [bytes]))) (defn stream->sha256 "Compute a SHA-256 hash for the given java.io.InputStream object." [^java.io.InputStream stream] {:pre [(instance? java.io.InputStream stream)] :post [(string? %)]} (digest/sha-256 stream)) (defn file->sha256 "Compute a SHA-256 hash for the given java.io.File object. Uses an InputStream to read the file, so it doesn't load all the contents into memory at once." [^java.io.File file] {:pre [(instance? java.io.File file)] :post [(string? %)]} (digest/sha-256 file)) (defn bounded-memoize "Similar to memoize, but the cache will be reset if the number of entries exceeds the specified `bound`." [f bound] {:pre [(integer? bound) (pos? bound)]} (let [cache (atom {})] (fn [& args] (if-let [e (find @cache args)] (val e) (let [v (apply f args)] (when (> (count @cache) bound) (reset! cache {})) (swap! cache assoc args v) v))))) ;; ## UUID handling (defn uuid "Generate a random UUID and return its string representation" [] (str (java.util.UUID/randomUUID))) (defn uuid? "Verifies whether a string is a valid UUID" [uuid] (try (java.util.UUID/fromString uuid) true (catch IllegalArgumentException _e false))) ;; ## System interface (defn num-cpus "Grabs the number of available CPUs for the local host" [] {:post [(pos? %)]} (.availableProcessors (Runtime/getRuntime))) ;; Comparison of JVM versions (defn compare-jvm-versions "Same behavior as `compare`, but specifically for JVM version strings. Because Java versions don't follow semver or anything, we need to do some massaging of the input first: http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html" [a b] {:pre [(string? a) (string? b)] :post [(number? %)]} (let [parse #(mapv parse-int (-> % (string/split #"-") (first) (string/split #"[\\._]")))] (compare (parse a) (parse b)))) (def java-version "Returns a string of the currently running java version" (System/getProperty "java.version")) (defn get-lein-project-version "Get the version of the specified leinigen project from the runtime." [project] (System/getProperty (format "%s.version" project))) ;; control flow (defmacro cond-let "Takes a binding-form and a set of test/expr pairs. Evaluates each test one at a time. If a test returns logical true, cond-let evaluates and returns expr with binding-form bound to the value of test and doesn't evaluate any of the other tests or exprs. To provide a default value either provide a literal that evaluates to logical true and is binding-compatible with binding-form, or use :else as the test and don't refer to any parts of binding-form in the expr. (cond-let binding-form) returns nil." [bindings & clauses] (let [binding (first bindings)] (when-let [[test expr & more] clauses] (if (= test :else) expr `(if-let [~binding ~test] ~expr (cond-let ~bindings ~@more)))))) (defmacro while-let "Repeatedly executes body while test expression is true, evaluating the body with binding-form bound to the value of test." [bindings & body] (let [form (first bindings) test (second bindings)] `(loop [~form ~test] (when ~form ~@body (recur ~test))))) (defmacro some-pred->> "When expr does not satisfy pred, threads it into the first form (via ->>), and when that result does not satisfy pred, through the next etc" [pred expr & forms] (let [g (gensym) pstep (fn [step] `(if (~pred ~g) ~g (->> ~g ~step)))] `(let [~g ~expr ~@(interleave (repeat g) (map pstep forms))] ~g))) (defn- port-open? [port] (try (with-open [_ (java.net.ServerSocket. port)] port) (catch java.net.BindException e (when-not (re-find #"already in use" (.getMessage e)) (throw e))))) (defn open-port-num "Returns a currently open port number in the port range 16384 through 32767. Note that on Linux, anything above 32767 lies in the ephemeral port range, which is the range that the system uses to allocate ports upon user request. Thus, we choose from a different range to prevent possible port conflicts caused by various services (e.g. database connection pools)." [] (let [lo 16384 hi 32768] ; one higher because the upper limit is exclusive (if-let [open-port (some port-open? (shuffle (range lo hi)))] open-port (throw (java.net.BindException. (format "All ports in the range %d through %d are already in use (Bind failed)" lo (dec hi))))))) (defmacro assoc-if-new "Assocs the provided values with the corresponding keys if and only if the key is not already present in map." [map key val & kvs] {:pre [(even? (count kvs))]} (let [deferred-kvs (vec (for [[k v] (cons [key val] (partition 2 kvs))] [k `(fn [] ~v)]))] `(let [updates# (for [[k# v#] ~deferred-kvs :when (= ::not-found (get ~map k# ::not-found))] [k# (v#)])] (merge ~map (into {} updates#))))) (defn deref-swap! "Like swap! but returns the old value. Adapted from http://stackoverflow.com/a/15442107." [atom f & args] (loop [] (let [old @atom new (apply f old args)] (if (compare-and-set! atom old new) old (recur))))) (defn parse-interval "Given a time string of the form \"\", or \"\", this function parses this time amount and returns a java.time.Duration instance. If the unit is left off, the units are assumed to be time/seconds Example: \"12h\" -> (Duration/ofHours 12) Example: \"12\" -> (Duration/ofSeconds 12) Possible units: s(econds), m(inutes), h(ours), d(ays), y(ears) Returns nil if the time string cannot be parsed." [time-str] (when-not (nil? time-str) (when-let [[_ num unit] (re-matches #"^(\d+)([smhdy]?)$" time-str)] (let [num (parse-int num) time-fn (case unit "s" #(Duration/ofSeconds %) "m" #(Duration/ofMinutes %) "h" #(Duration/ofHours %) "d" #(Duration/ofDays %) "y" #(Duration/ofDays (* 365 %)) "" #(Duration/ofSeconds %))] (time-fn num))))) ;; ## HTTP Headers (defn base-type "Parse a base type out of a Content-Type, returns nil if there's no match. Does not validate any parameters." ;; Content-Type header grammar https://tools.ietf.org/html/rfc7231#section-3.1.1.1 ;; token definition https://tools.ietf.org/html/rfc7230#section-3.2.6 ;; white space definition https://tools.ietf.org/html/rfc7230#section-3.2.3 [content-type] (let [token #"[a-zA-Z0-9!#$%&'*+.^_`|~-]+" matcher (format "^(%s/%s)(?:[ \t;]|$)" token token)] (second (re-find (re-pattern matcher) content-type)))) (defn ZonedDateTime->timestamp-string ^String [^ZonedDateTime zdt] (.format DateTimeFormatter/ISO_OFFSET_DATE_TIME zdt)) (defn now->timestamp-string "Using the java native libraries, using the system clock, create a UTC based iso8601 timestamp" ^String [] (ZonedDateTime->timestamp-string (ZonedDateTime/now ZoneOffset/UTC))) (defn timestamp-string->ZonedDateTime "Given an iso8601 timestamp with offset, convert it into a java.time.ZonedDateTime. Throws DateTimeParseException if string can't be parsed" ^ZonedDateTime [^String timestamp] (ZonedDateTime/from (.parse DateTimeFormatter/ISO_OFFSET_DATE_TIME timestamp))) (defn ZonedDateTime->utc-ZonedDateTime "given a zoned date time, convert it to the utc timezone" ^ZonedDateTime [^ZonedDateTime zdt] (.withZoneSameInstant zdt (ZoneId/of "UTC"))) (defn is-only-number? "Given a string, does the string only consist of numeric characters (0 through 9)" [a] (some? (re-find #"^\d+$" a))) (defn starts-with-zero? "Given a string, does it start with zero?" [a] (some? (re-find #"^0" a))) (defn compare-versions "Similar in concept to clojure's `compare` but for version strings. Based on puppet's versioncmp function https://github.com/puppetlabs/puppet/blob/9ab8526d0bc5f0fe4e29e7dac9832307d8af2e48/lib/puppet/util/package.rb#L3 but does not ignore trailing zeros. Given two version strings, do a logical comparison of the two. If the first is an earlier version that the second, return a positive number. If they are equivalent versions, return 0, if the first is newer than the second, return a negative number." [a b] (let [version-regular-expression #"[\-.]|\d+|[^\-.\d]+" a-matches (re-seq version-regular-expression a) b-matches (re-seq version-regular-expression b)] (if (and (pos? (count a-matches)) (pos? (count b-matches))) (loop [current-a-matches a-matches current-b-matches b-matches] (let [current-a (first current-a-matches) current-b (first current-b-matches)] (cond (and (nil? current-a) (nil? current-b)) 0 (nil? current-a) (compare a b) (nil? current-b) (compare a b) (= current-a current-b) (recur (rest current-a-matches) (rest current-b-matches)) (= "-" current-a) -1 (= "-" current-b) 1 (= "." current-a) -1 (= "." current-b) 1 (and (is-only-number? current-a) (is-only-number? current-b)) (if (or (starts-with-zero? current-a) (starts-with-zero? current-b)) (compare (.toUpperCase ^String current-a) (.toUpperCase ^String current-b)) (compare (parse-int current-a) (parse-int current-b))) :else (compare (.toUpperCase ^String current-a) (.toUpperCase ^String current-b))))) (compare a b)))) OpenVoxProject-clj-kitchensink-6f5334f/src/puppetlabs/kitchensink/file.clj000066400000000000000000000170711523666416700267760ustar00rootroot00000000000000(ns puppetlabs.kitchensink.file (:require [clojure.java.io :as io] [clojure.string :as str] [clojure.tools.logging :as log]) (:import (java.io BufferedWriter File FileOutputStream OutputStreamWriter) (java.nio ByteBuffer) (java.nio.channels Channels) (java.nio.file CopyOption Files LinkOption Path Paths StandardCopyOption) (java.nio.file.attribute FileAttribute PosixFilePermissions) (java.util.zip GZIPInputStream) (org.apache.commons.compress.archivers.tar TarArchiveInputStream))) (defn str->path ^Path [path] (Paths/get path (into-array String []))) (defn dir+file->path ^Path [directory file] (Paths/get directory (into-array String [file]))) (defn get-perms "Returns the currently set permissions of the given file path." [path] (-> (str->path path) (Files/getPosixFilePermissions (into-array LinkOption [])) PosixFilePermissions/toString)) (defn set-perms "Set the provided permissions on the given path. The permissions string is in the form of the standard 9 character posix format, e.g. \"rwxr-xr-x\"." [path permissions] (-> (str->path path) (Files/setPosixFilePermissions (PosixFilePermissions/fromString permissions)) (.toFile))) (defn perms->attribute [permissions] (PosixFilePermissions/asFileAttribute (PosixFilePermissions/fromString permissions))) (def nofollow-links (into-array LinkOption [LinkOption/NOFOLLOW_LINKS])) (def ^:private default-permissions (PosixFilePermissions/fromString "rw-r-----")) (defn atomic-write "Write to a file atomically. This takes a function that should operate on a Writer as its first argument. If permissions are specified (as a string of the form \"rwxrwxrwx\") then the file will be created with those permissions. If not specified and the file already exists then existing permissions will be preserved. If no file exist a default is set." ([path write-function] (atomic-write path write-function nil)) ([path write-function permissions] (let [target (str->path path) dir (.getParent target) file-exists? (Files/exists target nofollow-links) owner (when file-exists? (Files/getOwner target nofollow-links)) group (when file-exists? (Files/getAttribute target "posix:group" nofollow-links)) permissions (if permissions (PosixFilePermissions/fromString permissions) (if file-exists? (Files/getPosixFilePermissions target nofollow-links) default-permissions)) temp-attributes (into-array FileAttribute [(perms->attribute "rw-------")]) temp-file (Files/createTempFile dir (.toString (.getFileName target)) "tmp" temp-attributes) stream (proxy [FileOutputStream] [(.toString temp-file)] (close [] (.sync (.getFD ^FileOutputStream this)) ;; this looks weird, but makes the proxy-super avoid reflection by masking `this` with a version that has the meta tag (let [^FileOutputStream this this] (proxy-super close))))] (with-open [writer (BufferedWriter. (OutputStreamWriter. stream))] (write-function writer)) (when owner (Files/setOwner temp-file owner)) (when group (Files/setAttribute temp-file "posix:group" group nofollow-links)) ;; We set these here instead of at file creation to avoid problems with ;; read-only files and umask (Files/setPosixFilePermissions temp-file permissions) (Files/move temp-file target (into-array CopyOption [StandardCopyOption/ATOMIC_MOVE]))))) (defn atomic-write-string "Write a string to a file atomically. See atomic-write for more details." ([path string] (atomic-write-string path string nil)) ([path string permissions] (atomic-write path #(.write ^BufferedWriter % ^String string) permissions))) (defn unzip-file "Given a path to an input file, and a path to an output location, attempt to unzip the file. Will not overwrite the same path with the output" [input-file output-file] (when (not= input-file output-file) (io/make-parents output-file) (with-open [file-input-stream (io/input-stream input-file) gzip-input-stream (GZIPInputStream. file-input-stream) file-output-stream (io/output-stream output-file)] (io/copy gzip-input-stream file-output-stream)))) (def empty-file-attributes (into-array FileAttribute [])) (defn write-tar-stream-to-file [^TarArchiveInputStream input-stream ^Path output-path ^ByteBuffer buffer] (with-open [out-channel (.getChannel (FileOutputStream. (.toFile output-path)))] ;; move the byte buffer out for efficiency (let [;; specifically don't close this as it will close the whole tarfile stream in-channel (Channels/newChannel input-stream)] (loop [in-buffer-size (.read in-channel buffer)] (when (or (pos? in-buffer-size) (pos? (.position buffer))) (.flip buffer) (.write out-channel buffer) (.compact buffer) (recur (.read in-channel buffer))))))) (defn untar-file "Given a path to a tar file, and a path to the parent directory to untar the repo in, recreate the contents of the tar file Note: does not recreate original permissions, or support symlinks." [path-to-tar-file output-directory] (let [trimmed-output (if (str/ends-with? output-directory "/") (subs output-directory 0 (dec (count output-directory))) output-directory) ;; allocate the copy buffer once and reuse for efficiency. buffer (ByteBuffer/allocateDirect (* 64 1024))] (with-open [tar-input-stream (TarArchiveInputStream. (io/input-stream path-to-tar-file))] (loop [entry (.getNextEntry tar-input-stream)] (when (some? entry) (if (.isDirectory entry) (Files/createDirectories (dir+file->path trimmed-output (.getName entry)) empty-file-attributes) (let [output-file (dir+file->path trimmed-output (.getName entry))] (io/make-parents output-file) (write-tar-stream-to-file tar-input-stream output-file buffer))) (recur (.getNextEntry tar-input-stream))))))) (defn delete-recursively "Given a path to a directory, delete everything in the directory recursively. Given a path to a file, it will delete that file. Returns true if successful, false if not. Failures are logged at the info level. Uses a `stack` based tail recursion method to avoid using a recursive stack" [^String path-to-delete] (let [path (str->path path-to-delete) first (.toFile path)] (loop [stack [first]] (if (seq stack) (let [^File last-element (last stack)] (if (.isDirectory last-element) (let [files (.listFiles ^File last-element)] ;; if it is empty, we can delete it (if (empty? files) (if (.delete last-element) (recur (butlast stack)) (do (log/infof "Failed to delete (%s), aborting delete of (%s)" (.getCanonicalPath last-element) (.getCanonicalPath first)) false)) ;; it isn't empty, so push all the contents to the stack and recurse (recur (concat stack files)))) (if (.delete last-element) (recur (butlast stack)) (do (log/infof "Failed to delete (%s), aborting delete of (%s)" (.getCanonicalPath last-element) (.getCanonicalPath first)) false)))) true))))OpenVoxProject-clj-kitchensink-6f5334f/src/puppetlabs/kitchensink/json.clj000066400000000000000000000070271523666416700270300ustar00rootroot00000000000000(ns puppetlabs.kitchensink.json "Cheshire related functions This front-ends the common set of core cheshire functions: * generate-string * generate-stream * parse-string * parse-stream This namespace when 'required' will also setup some common JSON encoders globally, so you can avoid doing this for each call." (:require [cheshire.core :as core] [cheshire.generate :as generate] [clojure.java.io :as io] [clojure.tools.logging :as log]) (:import com.fasterxml.jackson.core.JsonGenerator (java.time Instant LocalDate LocalDateTime))) (defn- java-instant-encoder [^Instant data jsonGenerator] (.writeString ^JsonGenerator jsonGenerator ^String (.toString data))) (def ^:dynamic *instant-encoder* java-instant-encoder) (defn- java-localdate-encoder [^LocalDate data jsonGenerator] (.writeString ^JsonGenerator jsonGenerator ^String (.toString data))) (def ^:dynamic *localdate-encoder* java-localdate-encoder) (defn- java-localdatetime-encoder [^LocalDateTime data jsonGenerator] (.writeString ^JsonGenerator jsonGenerator ^String (.toString data))) (def ^:dynamic *localdatetime-encoder* java-localdatetime-encoder) (defn add-common-json-encoders!* "Non-memoize version of add-common-json-encoders!" [] (when (satisfies? generate/JSONable (Instant/now)) (log/warn "Overriding existing JSONable protocol implementation for java.time.Instant")) (when (satisfies? generate/JSONable (LocalDateTime/now)) (log/warn "Overriding existing JSONable protocol implementation for java.time.LocalDateTime")) (when (satisfies? generate/JSONable (LocalDate/now)) (log/warn "Overriding existing JSONable protocol implementation for java.time.LocalDate")) (generate/add-encoder java.time.Instant (fn [data jsonGenerator] (*instant-encoder* data jsonGenerator))) (generate/add-encoder java.time.LocalDate (fn [data jsonGenerator] (*localdate-encoder* data jsonGenerator))) (generate/add-encoder java.time.LocalDateTime (fn [data jsonGenerator] (*localdatetime-encoder* data jsonGenerator)))) (def ^{:doc "Registers some common encoders for cheshire JSON encoding. This is a memoize function, to avoid unnecessary calls to add-encoder. Ideally this function should be called once in your app, for example your main class. Encoders currently include: * java.time.Instant * java.time.LocalDate * java.time.LocalDateTime"} add-common-json-encoders! (memoize add-common-json-encoders!*)) (def default-pretty-opts {:date-format "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" :pretty true}) (def ^String generate-string core/generate-string) (def ^String generate-stream core/generate-stream) (defn generate-pretty-string "Thinly wraps cheshire.core/generate-string, adding pretty printing from `default-pretty-opts`" ([obj] (generate-pretty-string obj default-pretty-opts)) ([obj opts] (generate-string obj (merge default-pretty-opts opts)))) (defn generate-pretty-stream "Thinly wraps cheshire.core/generate-stream, adding pretty printing from `default-pretty-opts`" ([obj writer] (generate-pretty-stream obj writer default-pretty-opts)) ([obj writer opts] (generate-stream obj writer (merge default-pretty-opts opts)))) (def parse-string core/parse-string) (def parse-stream core/parse-stream) (defn spit-json "Similar to clojure.core/spit, but writes the Clojure datastructure as JSON to `f`" [f obj & options] (with-open [writer ^java.io.BufferedWriter (apply io/writer f options)] (generate-pretty-stream obj writer)) nil) OpenVoxProject-clj-kitchensink-6f5334f/src/puppetlabs/kitchensink/time.clj000066400000000000000000000055411523666416700270140ustar00rootroot00000000000000(ns puppetlabs.kitchensink.time (:require [puppetlabs.kitchensink.core :as core]) (:import (java.time Duration))) (def matching-pattern #"^(\d+)(y|d|h|m|s)?$") (def unit-map {"y" (* 365 24 60 60), ; 365 days isn't technically a year, but is sufficient for most purposes "d" (* 24 60 60), "h" (* 60 60), "m" 60, "s" 1}) (defn duration-str->seconds "Given a puppet duration string, see https://github.com/puppetlabs/puppet/blob/fb44fd90c64ee11f0a29fb8924adbab5b0695555/lib/puppet/settings/duration_setting.rb for a reference implementation, return the duration in seconds" [duration] ;; handle both integer input and strings that parse as integers (if-let [parsed-seconds (core/safe-parse-int duration)] parsed-seconds ;; must be a format string (let [matches (re-seq matching-pattern duration)] (if-let [last-matches (last matches)] ;; in the form [ ] (* (core/parse-int (second last-matches)) (get unit-map (last last-matches))) (throw (IllegalArgumentException. (format "Invalid duration format %s" duration))))))) (defn duration-str->Duration "Given a puppet duration string, return a java.time.Duration equivalent" ^Duration [duration] (Duration/ofSeconds (duration-str->seconds duration))) (defn days->hours "Convert a number of days into equivalent hours" [days] (* 24 days)) (defn hours->days "Convert a number of hours into days and fractional days" [hours] (/ hours 24)) (defn hours->min "Convert a number of hours into minutes" [hours] (* 60 hours)) (defn min->hours "Convert a number of minutes into hours and fractional hours" [min] (/ min 60)) (defn min->sec "Convert a number of minutes into seconds" [min] (* 60 min)) (defn sec->min "Convert a number of seconds into minutes and fractional minutes" [sec] (/ sec 60)) (defn sec->ms "Convert a number of seconds into milliseconds" [sec] (* 1000 sec)) (defn ms->sec "Convert a number of milliseconds into seconds and fractional seconds" [ms] (/ ms 1000)) (defn min->ms "Convert a number of minutes into milliseconds" [min] (-> min min->sec sec->ms)) (defn ms->min "Convert a number of milliseconds into minutes and fractional minutes" [ms] (-> ms ms->sec sec->min)) (defn hours->ms "Convert a number of hours into milliseconds" [hours] (-> hours hours->min min->ms)) (defn ms->hours "Convert a number of milliseconds into hours and fractional hours" [ms] (-> ms ms->min min->hours)) (defn days->ms "Convert a number of days into milliseconds" [days] (-> days days->hours hours->ms)) (defn ms->days "Convert a number of milliseconds into days and fractional days" [ms] (-> ms ms->hours hours->days))OpenVoxProject-clj-kitchensink-6f5334f/test/000077500000000000000000000000001523666416700210565ustar00rootroot00000000000000OpenVoxProject-clj-kitchensink-6f5334f/test/puppetlabs/000077500000000000000000000000001523666416700232355ustar00rootroot00000000000000OpenVoxProject-clj-kitchensink-6f5334f/test/puppetlabs/kitchensink/000077500000000000000000000000001523666416700255475ustar00rootroot00000000000000OpenVoxProject-clj-kitchensink-6f5334f/test/puppetlabs/kitchensink/classpath_test.clj000066400000000000000000000012221523666416700312570ustar00rootroot00000000000000(ns puppetlabs.kitchensink.classpath-test (:require [clojure.test :refer :all] [puppetlabs.kitchensink.classpath :refer [with-additional-classpath-entries]])) (deftest with-additional-classpath-entries-test (let [paths ["classpath-test"] get-resource #(-> (Thread/currentThread) .getContextClassLoader (.getResource "does-not-exist-anywhere-else"))] (with-additional-classpath-entries paths (testing "classloader now includes the new path" (is (get-resource)))) (testing "classloader no longer includes the new path" (is (not (get-resource)))))) OpenVoxProject-clj-kitchensink-6f5334f/test/puppetlabs/kitchensink/core_test.clj000066400000000000000000001227721523666416700302430ustar00rootroot00000000000000(ns puppetlabs.kitchensink.core-test (:require [clojure.string :as string] [clojure.test :refer :all] [clojure.zip :as zip] [me.raynes.fs :as fs] [puppetlabs.kitchensink.core :as core] [puppetlabs.kitchensink.testutils :as testutils] [slingshot.slingshot :refer [try+]]) (:import (java.io ByteArrayInputStream File) (java.time Duration Month ZonedDateTime) (java.time.format DateTimeParseException) (java.util ArrayList))) (deftest array?-test (testing "array?" (testing "should work for nil input" (is (nil? (core/array? nil)))) (testing "should detect primitive arrays" (doseq [f #{object-array boolean-array byte-array short-array char-array int-array long-array float-array double-array}] (is (true? (core/array? (f 1)))))) (testing "should return nil for non-array objects" (doseq [x ['() [] {} "foo" 123 456.789 1/3]] (is (false? (core/array? x))))))) (deftest boolean?-test (testing "should return true if true" (is (boolean? true))) (testing "should return true if false" (is (boolean? false))) (testing "should return false if string" (is (not (boolean? "test")))) (testing "should return false if nil" (is (not (boolean? nil))))) (deftest regexp?-test (testing "should return true if pattern" (is (core/regexp? (re-pattern "test")))) (is (core/regexp? #"test")) (testing "should return false if string" (is (not (core/regexp? "test"))))) (deftest zipper?-test (testing "should return true for zippers" (is (true? (core/zipper? (zip/vector-zip [:foo :bar]))))) (testing "should return false for non-zippers" (is (false? (core/zipper? "hi"))) (is (false? (core/zipper? 42))) (is (false? (core/zipper? :foo))) (is (false? (core/zipper? [:foo :bar]))) (is (false? (core/zipper? {:foo :bar}))))) (deftest to-bool-test (testing "should return the same value when passed a Boolean" (is (true? (core/to-bool true))) (is (false? (core/to-bool false)))) (testing "should return true or false when passed a string representation of same" (is (true? (core/to-bool "true"))) (is (true? (core/to-bool "TRUE"))) (is (true? (core/to-bool "tRuE"))) (is (false? (core/to-bool "false"))) (is (false? (core/to-bool "FALSE"))) (is (false? (core/to-bool "fAlSe")))) (testing "should return false when passed nil" (is (false? (core/to-bool nil)))) (testing "should throw an exception when passed a string other than true or false" (try+ (core/to-bool "hi") (is (not true) "Expected exception to be thrown by core/to-bool when an invalid string is passed") #_{:clj-kondo/ignore [:unresolved-symbol]} (catch map? m (is (contains? m :kind)) (is (= :puppetlabs.kitchensink.core/parse-error (:kind m))) (is (= :parse-error (core/without-ns (:kind m)))) (is (contains? m :msg)) (is (re-find #"Unable to parse 'hi' to a boolean" (:msg m))))))) (deftest true-str?-test (are [t-or-f? str-val] (t-or-f? (core/true-str? str-val)) true? "true" true? "TRUE" true? "TrUe" false? "false" false? nil false? "FALSE")) (deftest to-sentence-test (are [coll string] (= string (core/to-sentence coll)) [] "" ["foo"] "foo" ["foo" "bar"] "foo and bar" ["foo" "bar" "baz"] "foo, bar, and baz" ["foo" "bar" "baz" "qux"] "foo, bar, baz, and qux")) (deftest key->str-test (testing "returns strings unmodified" (is (= "foo" (core/key->str "foo"))) (is (= ":foo" (core/key->str ":foo"))) (is (= "foo/bar" (core/key->str "foo/bar")))) (testing "returns the entire stringified keyword" (is (= "foo" (core/key->str :foo))) (is (= ":foo" (core/key->str (keyword ":foo")))) (is (= "foo/bar" (core/key->str :foo/bar))))) (deftest mkdirs-test (testing "creates all specified directories that don't exist for File arg" (let [tmpdir (core/temp-dir)] (fs/mkdirs (fs/file tmpdir "foo")) (core/mkdirs! (fs/file tmpdir "foo" "bar" "baz")) (is (fs/directory? (fs/file tmpdir "foo" "bar" "baz"))))) (testing "creates all specified directories that don't exist for String arg" (let [tmpdir (core/temp-dir)] (fs/mkdirs (fs/file tmpdir "foo")) (core/mkdirs! (.getPath (fs/file tmpdir "foo" "bar" "baz"))) (is (fs/directory? (fs/file tmpdir "foo" "bar" "baz"))))) (testing "throws exception if one of the elements of the path exists and is a file" (let [tmpdir (core/temp-dir)] (fs/mkdirs (fs/file tmpdir "foo")) (fs/touch (fs/file tmpdir "foo" "bar")) (try+ (core/mkdirs! (fs/file tmpdir "foo" "bar" "baz")) (is (not true) "Expected exception to be thrown by core/mkdirs! when one of the elements of the path already exists and is a file") (catch map? m (is (contains? m :kind)) (is (= :puppetlabs.kitchensink.core/io-error (:kind m))) (is (= :io-error (core/without-ns (:kind m)))) (is (contains? m :msg)) (is (re-find #"foo/bar' is a file" (:msg m))))))) (testing "throws exception if the path exists and is a file" (let [tmpdir (core/temp-dir)] (fs/mkdirs (fs/file tmpdir "foo")) (fs/touch (fs/file tmpdir "foo" "bar")) (try+ (core/mkdirs! (fs/file tmpdir "foo" "bar")) (is (not true) (str "Expected exception to be thrown by core/mkdirs! when " "the path already exists and is a file")) (catch map? m (is (contains? m :kind)) (is (= :puppetlabs.kitchensink.core/io-error (:kind m))) (is (= :io-error (core/without-ns (:kind m)))) (is (contains? m :msg)) (is (re-find #"foo/bar' is a file" (:msg m))))))) (testing "Permission denied on some directory in the hierarchy" (let [tmpdir (core/temp-dir)] (fs/mkdirs (fs/file tmpdir "foo")) (fs/chmod "-w" (fs/file tmpdir "foo")) (try+ (core/mkdirs! (fs/file tmpdir "foo" "bar" "baz")) (is (not true) "Expected exception to be thrown by core/mkdirs! when a permissions error occurs") (catch map? m (is (contains? m :kind)) (is (= :puppetlabs.kitchensink.core/io-error (:kind m))) (is (= :io-error (core/without-ns (:kind m)))) (is (contains? m :msg)) (is (re-find #"foo' is not writable" (:msg m)))))))) (deftest quotient-test (testing "core/quotient" (testing "should behave like '/' when divisor is non-zero" (is (= 22/7 (core/quotient 22 7)))) (testing "should return default when divisor is zero" (is (= 0 (core/quotient 1 0))) (is (= 10 (core/quotient 1 0 10)))))) (deftest rand-weighted-selection-test (testing "core/rand-weighted-selection" (testing "should make selections within 1% of expected values when n=100k" (let [make-selection #(core/rand-weighted-selection 0.1 :foo 0.3 :bar 0.4 :baz 0.19 :quux 0.01 :waffle) n (int 1e5) freqs (frequencies (repeatedly n make-selection)) expected-freqs {:foo 10000 :bar 30000 :baz 40000 :quux 19000 :waffle 1000}] (doseq [[value actual] freqs :let [expected (get expected-freqs value)]] (is (< (Math/abs ^int (- expected actual)) (/ n 100)))))) (testing "adjusts the weight of the last value when the weights sum to less than 1" (is (= :foo (core/rand-weighted-selection 0.0 :foo)))) (testing "doesn't select values whose weights come after prior weights have sum to 1" (dotimes [_ 1e3] (is (not= :bar (core/rand-weighted-selection 1.0 :foo 10.0 :bar))))) (testing "throws an error when" (testing "a weight does not have a value" (is (thrown? AssertionError (core/rand-weighted-selection 0.0)))) (testing "a weight is not numeric" (is (thrown? AssertionError (core/rand-weighted-selection :foo :bar))))))) (deftest rand-str-test (testing "core/rand-str" (testing "throws an IllegalArgumentException when given an unknown characters keyword" (is (thrown-with-msg? IllegalArgumentException #":CJK" (core/rand-str :CJK 42)))) (doseq [[kw cs] core/ascii-character-sets :let [cs (set cs)]] (testing (str "recognizes the " kw " character set keyword") (dotimes [_ 10] (is (every? cs (core/rand-str kw 1000)))))) (testing "uses collections of strings & characters as character sets" (let [as ["a" \a]] (dotimes [_ 100] (is (every? #(= % \a) (core/rand-str as 100)))))))) (deftest excludes?-test (testing "should return true if coll does not contain key" (is (core/excludes? {:foo 1} :bar))) (testing "should return false if coll does contain key" (is (not (core/excludes? {:foo 1} :foo))))) (deftest contains-some-test (testing "should return nil if coll doesn't contain any of the keys" (is (= nil (core/contains-some {:foo 1} [:bar :baz :bam])))) (testing "should return the first key that coll does contain" (is (= :baz (core/contains-some {:foo 1 :baz 2 :bam 3} [:bar :baz :bam]))))) (deftest excludes-some-test (testing "should return nil if coll does `contain?` all of the keys" (is (= nil (core/excludes-some {:bar 1 :baz 2} [:bar :baz])))) (testing "should return the first key that coll does *not* `contain?`" (is (= :baz (core/excludes-some {:bar 1 :foo 2} [:foo :baz :bam]))))) (deftest mapvals-test (testing "should default to applying a function to all of the keys" (is (= {:a 2 :b 3} (core/mapvals inc {:a 1 :b 2})))) (testing "should support applying a function to a subset of the keys" (is (= {:a 2 :b 2} (core/mapvals inc [:a] {:a 1 :b 2})))) (testing "should support keywords as the function to apply to all of the keys" (is (= {:a 1 :b 2} (core/mapvals :foo {:a {:foo 1} :b {:foo 2}})))) (testing "should support keywords as the function to apply to a subset of the keys" (is (= {:a 1 :b {:foo 2}} (core/mapvals :foo [:a] {:a {:foo 1} :b {:foo 2}}))))) (deftest maptrans-test (testing "should fail if the keys-fns param isn't valid" (is (thrown? AssertionError (core/maptrans "blah" {:a 1 :b 1})))) (testing "should transform a map based on the given functions" (is (= {:a 3 :b 3 :c 3 :d 3} (core/maptrans {[:a :b] inc [:d] dec} {:a 2 :b 2 :c 3 :d 4})))) (testing "should accept keywords as functions in the keys-fns param" (is (= {:a 3 :b 3} (core/maptrans {[:a :b] :foo} {:a {:foo 3} :b {:foo 3}}))))) (deftest dissoc-if-nil-test (let [testmap {:a 1 :b nil}] (testing "should remove the key if the value is nil" (is (= (dissoc testmap :b) (core/dissoc-if-nil testmap :b)))) (testing "should not remove the key if the value is not nil" (is (= testmap (core/dissoc-if-nil testmap :a)))))) (deftest dissoc-in-test (let [testmap {:a {:b 1 :c {:d 2}}}] (testing "should remove the key" (is (= {:a {:c {:d 2}}} (core/dissoc-in testmap [:a :b])))) (testing "should remove the empty map" (is (= {:a {:b 1}} (core/dissoc-in testmap [:a :c :d])))))) (deftest walk-leaves-test (testing "should apply a function to all of the leaves" (is (= {:a 2 :b {:c 5}} (core/walk-leaves {:a 1 :b {:c 4}} inc))))) (deftest merge-with-key-test (let [m1 {:a 1 :b 2} m2 {:a 3 :b 4} merge-fn (fn [k v1 v2] (if (= k :a) (+ v1 v2) v2))] (is (= {:a 4 :b 4} (core/merge-with-key merge-fn m1 m2))))) (deftest deep-merge-test (testing "should deeply nest duplicate keys that both have map values" (let [testmap-1 {:foo {:bar :baz}, :pancake :flapjack} testmap-2 {:foo {:fuzz {:buzz :quux}}}] (is (= {:foo {:bar :baz, :fuzz {:buzz :quux}}, :pancake :flapjack} (core/deep-merge testmap-1 testmap-2))))) (testing "should combine duplicate keys' values that aren't all maps by calling the provided function" (let [testmap-1 {:foo {:bars 2}} testmap-2 {:foo {:bars 3, :bazzes 4}}] (is (= {:foo {:bars 5, :bazzes 4}} (core/deep-merge-with + testmap-1 testmap-2))))) (testing "core/deep-merge-with-keys should pass keys to specified fn" (let [m1 {:a {:b 1 :c 2}} m2 {:a {:b 3 :c 4}} merge-fn (fn [ks v1 v2] (if (= ks [:a :b]) (+ v1 v2) v2))] (is (= {:a {:b 4 :c 4}} (core/deep-merge-with-keys merge-fn m1 m2)))))) (deftest filter-map-test (testing "should filter based on a given predicate" (let [test-map {:dog 5 :cat 4 :mouse 7 :cow 6}] (is (= (core/filter-map (fn [_k v] (even? v)) test-map) {:cat 4, :cow 6})) (is (= (core/filter-map (fn [k _v] (= 3 (count (name k)))) test-map) {:dog 5, :cat 4, :cow 6})) (is (= (core/filter-map (fn [k v] (and (= 3 (count (name k))) (> v 5))) test-map) {:cow 6})) (is (= (core/filter-map (fn [_k _v] true) test-map) test-map)) (is (= (core/filter-map (fn [_k _v] false) test-map) {})))) (testing "should return empty map if given nil" (is (= {} (core/filter-map nil nil))))) (deftest missing?-test (let [sample {:a "asdf" :b "asdf" :c "asdf"}] (testing "should return true for single key items if they don't exist in the coll" (is (true? (core/missing? sample :n)))) (testing "should return false for single key items if they exist in the coll" (is (false? (core/missing? sample :c)))) (testing "should return true for multiple key items if they all don't exist in the coll" (is (true? (core/missing? sample :n :f :g :z :h)))) (testing "should return false for multiple key items if one item exists in the coll" (is (false? (core/missing? sample :n :b :f))) (is (false? (core/missing? sample :a :h :f)))) (testing "should return false for multiple key items if all items exist in the coll" (is (false? (core/missing? sample :a :b :c)))))) (deftest order-by-test (let [test-data [{:id 1 :k1 "ALPHA" :k2 "BETA" :k3 "GAMMA"} {:id 2 :k1 "ALPHA" :k2 "BETA" :k3 "EPSILON"} {:id 3 :k1 "ALPHA" :k2 "CHARLIE" :k3 "DELTA"} {:id 4 :k1 "ALPHA" :k2 "CHARLIE" :k3 "FOXTROT"} {:id 5 :k1 "alpha" :k2 "beta" :k3 "alpha"}]] (testing "single field, ascending core/order-by" (is (= [3 2 4 1 5] (map :id (core/order-by [[:k3 :ascending]] test-data))))) (testing "single field, descending core/order-by" (is (= [5 1 4 2 3] (map :id (core/order-by [[:k3 :descending]] test-data))))) (testing "single function, descending core/order-by" (is (= [1 4 2 3 5] (map :id (core/order-by [[#(string/lower-case (:k3 %)) :descending]] test-data))))) (testing "multiple core/order-bys" (is (= [5 3 4 2 1] (map :id (core/order-by [[#(string/upper-case (:k1 %)) :ascending] [:k2 :descending] [:k3 :ascending]] test-data))))))) (deftest sort-nested-maps-test (testing "with nested structure" (let [input {:b "asdf" :a {:z "asdf" :k [:z {:z 26 :a 1} :c] :a {:m 12 :a 1} :b "asdf"}} output (core/sort-nested-maps input)] (testing "after sorting, maps should still match" (is (= input output))) (testing "all maps levels of output should be sorted" (is (sorted? output)) (is (sorted? (:a output))) (is (sorted? (get (vec (get-in output [:a :k])) 1))) (is (sorted? (get-in output [:a :a])))))) (testing "with a string" (let [input "string here" output (core/sort-nested-maps input)] (testing "should match" (is (= input output))))) (testing "with a list" (let [input '(:a :b :c) output (core/sort-nested-maps input)] (testing "should still match" (is (= input output)))))) (deftest without-ns-test (testing "removes namespace from a namespaced keyword" (is (= :foo (core/without-ns :foo/foo))) (is (= :foo (core/without-ns ::foo)))) (testing "doesn't alter non-namespaced keyword" (let [kw :foo] (is (= kw (core/without-ns kw)))))) (deftest string-hashing-test (testing "Computing a SHA-1 for a UTF-8 string" (testing "should fail if not passed a string" #_{:clj-kondo/ignore [:type-mismatch]} (is (thrown? AssertionError (core/utf8-string->sha1 1234)))) (testing "should produce a stable hash" (is (= (core/utf8-string->sha1 "foobar") (core/utf8-string->sha1 "foobar")))) (testing "should produce the correct hash" (is (= "8843d7f92416211de9ebb963ff4ce28125932878" (core/utf8-string->sha1 "foobar"))))) (testing "Computing a SHA-256 for a UTF-8 string" (testing "should fail if not passed a string" #_{:clj-kondo/ignore [:type-mismatch]} (is (thrown? AssertionError (core/utf8-string->sha256 1234)))) (testing "should produce a stable hash" (is (= (core/utf8-string->sha256 "foobar") (core/utf8-string->sha256 "foobar")))) (testing "should produce the correct hash" (is (= "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2" (core/utf8-string->sha256 "foobar")))))) (deftest stream-hashing (testing "Computing a SHA-256 hash for an input stream" (testing "should fail if not passed an input stream" #_ {:clj-kondo/ignore [:type-mismatch]} (is (thrown? AssertionError (core/stream->sha256 "what")))) (let [stream-fn #(ByteArrayInputStream. (.getBytes "foobar" "UTF-8"))] (testing "should produce a stable hash" (is (= (core/stream->sha256 (stream-fn)) (core/stream->sha256 (stream-fn))))) (testing "should produce the correct hash" (is (= "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2" (core/stream->sha256 (stream-fn)))))))) (deftest file-hashing (testing "Computing a SHA-256 hash for a file" (testing "should fail if not passed a file" (is (thrown? AssertionError (core/file->sha256 "what")))) (let [f (core/temp-file "sha256" ".txt")] (spit f "foobar") (testing "should produce a stable hash" (is (= (core/file->sha256 f) (core/file->sha256 f)))) (testing "should produce the correct hash" (is (= "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2" (core/file->sha256 f))))))) (deftest temp-file-name-test (testing "The file should not exist." (is (not (fs/exists? (core/temp-file-name "foo"))))) (testing "It should be possible to create a file at the given path." (is (fs/create (core/temp-file-name "foo"))))) (deftest temp-file-test (testing "should create a temp file when not given a prefix" (let [f (core/temp-file)] (is (fs/file? f)))) (testing "should create a temp file when given a prefix and suffix" (let [^File f (core/temp-file "foo" ".bar")] (is (fs/file? f)) (is (.startsWith (.getName f) "foo")) (is (.endsWith (.getName f) ".bar")))) (testing "should create a temp dir when not given a prefix" (let [d (core/temp-dir)] (is (fs/directory? d)))) (testing "should create a temp dir when given a prefix and suffix" (let [^File d (core/temp-dir "foo" ".bar")] (is (fs/directory? d)) (is (.startsWith (.getName d) "foo")) (is (.endsWith (.getName d) ".bar"))))) (deftest ini-parsing (testing "Parsing ini files" (testing "should work for a single file" (let [tf (core/temp-file)] (spit tf "[foo]\nbar=baz") (testing "when specified as a file object" (is (= (core/inis-to-map tf) {:foo {:bar "baz"}}))) (testing "when specified as a string" (is (= (core/inis-to-map (core/absolute-path tf)) {:foo {:bar "baz"}}))))) (testing "should work for a directory" (let [td (core/temp-dir)] (testing "when no matching files exist" (is (= (core/inis-to-map td) {}))) (let [tf (fs/file td "a-test.ini")] (spit tf "[foo]\nbar=baz")) (testing "when only a single matching file exists" (is (= (core/inis-to-map td) {:foo {:bar "baz"}}))) (let [tf (fs/file td "b-test.ini")] ;; Now add a second file (spit tf "[bar]\nbar=baz")) (testing "when multiple matching files exist" (is (= (core/inis-to-map td) {:foo {:bar "baz"} :bar {:bar "baz"}}))))))) (deftest cli-parsing (testing "Should throw an error if a required option is missing" (let [got-expected-error (atom false)] (try+ (core/cli! [] [["-r" "--required" "A required field"]] [:required]) (catch map? m (is (contains? m :kind)) (is (= :puppetlabs.kitchensink.core/cli-error (:kind m))) (is (= :cli-error (core/without-ns (:kind m)))) (is (contains? m :msg)) (reset! got-expected-error true))) (is (true? @got-expected-error)))) (testing "Should throw a help message if --help is provided" (let [got-expected-help (atom false)] (try+ (core/cli! ["--help"] [] []) (catch map? m (is (contains? m :kind)) (is (= :puppetlabs.kitchensink.core/cli-help (:kind m))) (is (= :cli-help (core/without-ns (:kind m)))) (is (contains? m :msg)) (reset! got-expected-help true))) (is (true? @got-expected-help)))) (testing "Should return options map, remaining args, and summary after parsing CLI args" (let [[cli-data remaining-args summary] (core/cli! ["-a" "1234 Sunny ave." "--greeting" "Hey, what's up?" "--toggle" "extra-arg"] [["-g" "--greeting GREETING" "A string to greet somebody"] ["-a" "--address ADDRESS" "Somebody's address"] ["-t" "--toggle" "A flag/boolean option"]] [])] (is (map? cli-data)) (is (= "1234 Sunny ave." (cli-data :address))) (is (= "Hey, what's up?" (cli-data :greeting))) (is (= true (cli-data :toggle))) (is (vector? remaining-args)) (is (= ["extra-arg"] remaining-args)) ;; Check summary contains expected options without being brittle about formatting (is (string? summary)) (is (string/includes? summary "-g, --greeting GREETING")) (is (string/includes? summary "A string to greet somebody")) (is (string/includes? summary "-a, --address ADDRESS")) (is (string/includes? summary "Somebody's address")) (is (string/includes? summary "-t, --toggle")) (is (string/includes? summary "A flag/boolean option")) (is (string/includes? summary "-h, --help")) (is (string/includes? summary "Show help")))) (testing "Errors reported by tools.cli should be thrown out of cli! as slingshot exceptions" (let [got-expected-exception (atom false)] (try+ (let [specs [["-f" "--foo FOO" "Something that is foo"]] args ["--bar"]] (core/cli! args specs)) (catch map? m (is (= :puppetlabs.kitchensink.core/cli-error (:kind m))) (is (contains? m :msg)) (is (re-find #"Unknown option.*--bar" (m :msg))) (reset! got-expected-exception true))) (is (true? @got-expected-exception))))) (deftest cert-utils (testing "extracting cn from a dn" #_{:clj-kondo/ignore [:type-mismatch]} (is (thrown? AssertionError (core/cn-for-dn 123)) "should throw error when arg is a number") (is (thrown? AssertionError (core/cn-for-dn nil)) "should throw error when arg is nil") (is (= (core/cn-for-dn "") nil) "should return nil when passed an empty string") (is (= (core/cn-for-dn "MEH=bar") nil) "should return nil when no CN is present") (is (= (core/cn-for-dn "cn=foo.bar.com") nil) "should return nil when CN present but lower case") (is (= (core/cn-for-dn "cN=foo.bar.com") nil) "should return nil when CN present but with mixed case") (is (= (core/cn-for-dn "CN=foo.bar.com") "foo.bar.com") "should work when only CN is present") (is (= (core/cn-for-dn "CN=foo.bar.com,OU=something") "foo.bar.com") "should work when more than just the CN is present") (is (= (core/cn-for-dn "CN=foo.bar.com,OU=something") "foo.bar.com") "should work when more than just the CN is present") (is (= (core/cn-for-dn "OU=something,CN=foo.bar.com") "foo.bar.com") "should work when more than just the CN is present and CN is last") (is (= (core/cn-for-dn "OU=something,CN=foo.bar.com,D=foobar") "foo.bar.com") "should work when more than just the CN is present and CN is in the middle") (is (= (core/cn-for-dn "CN=foo.bar.com,CN=goo.bar.com,OU=something") "goo.bar.com") "should use the most specific CN if multiple CN's are present"))) (deftest cert-whitelist-auth (testing "cert whitelist authorizer" (testing "should fail when whitelist is not given" (is (thrown? AssertionError (core/cn-whitelist->authorizer nil)))) (testing "should fail when whitelist is given, but not readable" (is (thrown? java.io.FileNotFoundException (core/cn-whitelist->authorizer "/this/does/not/exist")))) (testing "when whitelist is present" (let [whitelist (core/temp-file)] (spit whitelist "foo\nbar\n") (let [authorized? (core/cn-whitelist->authorizer whitelist)] (testing "should allow plain-text, HTTP requests" (is (authorized? {:scheme :http :ssl-client-cn "foobar"}))) (testing "should fail HTTPS requests without a client cert" (is (not (authorized? {:scheme :https})))) (testing "should reject certs that don't appear in the whitelist" (is (not (authorized? {:scheme :https :ssl-client-cn "goo"})))) (testing "should accept certs that appear in the whitelist" (is (authorized? {:scheme :https :ssl-client-cn "foo"})))))))) (deftest memoization (testing "with an illegal bound" (is (thrown? AssertionError (core/bounded-memoize identity -1))) (is (thrown? AssertionError (core/bounded-memoize identity 0))) (is (thrown? AssertionError (core/bounded-memoize identity 1.5))) (is (thrown? AssertionError (core/bounded-memoize identity "five")))) (testing "with a legal bound" (let [f (testutils/call-counter) memoized (core/bounded-memoize f 2)] (testing "should only call the function once per argument" (is (= (testutils/times-called f) 0)) (memoized 0) (is (= (testutils/times-called f) 1)) (memoized 0) (is (= (testutils/times-called f) 1)) (memoized 1) (is (= (testutils/times-called f) 2))) ;; We call it here for a hit, which we expect not to clear the cache, ;; then call it again to verify the cache wasn't cleared and therefore f ;; wasn't called (testing "should not clear the cache at max size on a hit" (memoized 1) (is (= (testutils/times-called f) 2)) (memoized 1) (is (= (testutils/times-called f) 2))) ;; Now call it with a new argument to clear the cache, then with an old ;; one to show f is called (testing "should clear the cache at max size on a miss" (memoized 2) (is (= (testutils/times-called f) 3)) (memoized 3) (is (= (testutils/times-called f) 4)))))) (deftest uuid-handling (testing "a generated core/uuid is a valid core/uuid" (is (core/uuid? (core/uuid)))) (testing "a phrase is not a core/uuid" (is (not (core/uuid? "Hello World"))))) (deftest jvm-versions (testing "comparing same versions should return 0" (is (= 0 (core/compare-jvm-versions "1.7.0_3" "1.7.0_3")))) (testing "comparing same versions should return 0, even with trailing fields" (is (= 0 (core/compare-jvm-versions "1.7.0_3" "1.7.0_3-beta3")))) (testing "should detect older versions" (is (neg? (core/compare-jvm-versions "1.7.0_0" "1.7.0_3"))) (is (neg? (core/compare-jvm-versions "1.7.0_0" "1.7.0_3-beta3"))) (is (neg? (core/compare-jvm-versions "1.7.0_2" "1.7.0_03"))) (is (neg? (core/compare-jvm-versions "1.6.0_3" "1.7.0_3"))) (is (neg? (core/compare-jvm-versions "1.6.0_2" "1.7.0_3"))) (is (neg? (core/compare-jvm-versions "0.6.0_2" "1.7.0_3")))) (testing "should detect newer versions" (is (pos? (core/compare-jvm-versions "1.7.0_13" "1.7.0_3"))) (is (pos? (core/compare-jvm-versions "1.8.0_3" "1.7.0_3"))) (is (pos? (core/compare-jvm-versions "1.8.0_3" "1.7.0_3-beta3"))) (is (pos? (core/compare-jvm-versions "2.7.0_3" "1.7.0_3"))) (is (pos? (core/compare-jvm-versions "1.7.0_10" "1.7.0_3"))))) (deftest some-pred->>-macro (testing "should thread all the way through if the pred never matches" (is (= 10 (core/some-pred->> nil? 1 (* 2) (+ 9) #_ {:clj-kondo/ignore [:invalid-arity]} (dec))))) (testing "should break and return the value if the pred matches" (is (= {:a 1} (core/some-pred->> map? 5 (/ 5) #_ {:clj-kondo/ignore [:invalid-arity]} (assoc {} :a) #_ {:clj-kondo/ignore [:invalid-arity]} (keys)))))) (deftest while-let-macro (let [counter (atom 0) list (ArrayList.)] (dotimes [_ 5] (.add list "foo")) (let [iter (.iterator list)] #_ {:clj-kondo/ignore [:unresolved-symbol]} (core/while-let [item (and (.hasNext iter) (.next iter))] (swap! counter inc))) (is (= 5 @counter)))) (deftest spit-ini-test (let [tf (core/temp-file)] (spit tf "[foo]\nbar=baz\n[bar]\nfoo=baz") (let [ini-map (core/ini-to-map tf)] (is (= ini-map {:foo {:bar "baz"} :bar {:foo "baz"}})) (testing "changing existing keys" (let [result-file (core/temp-file)] (core/spit-ini result-file (-> ini-map (assoc-in [:foo :bar] "baz changed") (assoc-in [:bar :foo] "baz also changed"))) (is (= {:foo {:bar "baz changed"} :bar {:foo "baz also changed"}} (core/ini-to-map result-file))))) (testing "adding a new section to an existing ini" (let [result-file (core/temp-file)] (core/spit-ini result-file (assoc-in ini-map [:baz :foo] "bar")) (is (= {:foo {:bar "baz"} :bar {:foo "baz"} :baz {:foo "bar"}} (core/ini-to-map result-file)))))))) (deftest duplicate-ini-entries (testing "duplicate settings" (let [tempfile (core/temp-file)] (spit tempfile "[foo]\nbar=baz\nbar=bizzle\n") (is (thrown-with-msg? IllegalArgumentException #"Duplicate configuration entry: \[:foo :bar\]" (core/ini-to-map tempfile)))) (let [tempdir (core/temp-dir) tempfile1 (fs/file tempdir "initest1.ini") tempfile2 (fs/file tempdir "initest2.ini")] (spit tempfile1 "[foo]\nsetting1=hi\nbar=baz\n") (spit tempfile2 "[foo]\nsetting2=hi\nbar=bizzle\n") (is (thrown-with-msg? IllegalArgumentException #"Duplicate configuration entry: \[:foo :bar\]" (core/inis-to-map tempdir))))) (testing "duplicate sections but no duplicate settings" (let [tempdir (core/temp-dir) tempfile1 (fs/file tempdir "initest1.ini") tempfile2 (fs/file tempdir "initest.ini")] (spit tempfile1 "[foo]\nsetting1=hi\nbar=baz\n") (spit tempfile2 "[foo]\nsetting2=hi\nbunk=bizzle\n") (is (= {:foo {:setting1 "hi" :bar "baz" :setting2 "hi" :bunk "bizzle"}} (core/inis-to-map tempdir)))))) (deftest timeout-test (let [wait-return (fn [^long time val] (Thread/sleep time) val)] (testing "core/with-timeout" (testing "does nothing if the body returns within the limit" (is (= true (core/with-timeout 1 false (wait-return 500 true))))) (testing "returns the default value if the body times out" (is (= false (core/with-timeout 1 false (wait-return 1005 true)))))))) (deftest ^:slow open-port-num-test (let [port-in-use (core/open-port-num)] (with-open [_s (java.net.ServerSocket. port-in-use)] (let [open-ports (set (take 60000 (repeatedly core/open-port-num)))] (is (every? pos? open-ports)) (is (not (contains? open-ports port-in-use))))))) (deftest assoc-if-new-test (testing "assoc-if-new assocs appropriately" (is (= {:a "foo"} (core/assoc-if-new {:a "foo"} :a "bar"))) (is (= {:a "bar" :b "foo"} (core/assoc-if-new {:b "foo"} :a "bar"))) (is (= {:a "foo" :b "bar"} (core/assoc-if-new {} :a "foo" :b "bar"))) (is (= {:a "foo" :b nil} (core/assoc-if-new {:b nil} :a "foo" :b "bar"))) (is (= {:a "foo" :b "baz"} (core/assoc-if-new {:b "baz"} :a "foo" :b "bar"))))) (deftest deref-swap-test (testing "deref-swap behaves as advertised" (let [a (atom 10) b (core/deref-swap! a inc)] (is (= 11 @a)) (is (= 10 b))))) (deftest parse-interval-test (are [x y] (= x (core/parse-interval y)) (Duration/ofSeconds 11) "11s" (Duration/ofMinutes 12) "12m" (Duration/ofHours 13) "13h" (Duration/ofDays 14) "14d" (Duration/ofDays (* 15 365)) "15y" (Duration/ofSeconds 0) "0" (Duration/ofSeconds 10) "10" nil "15a" nil "h" nil "12hhh" nil "12H" nil "1,300y" nil "" nil nil)) (deftest base-type-test (is (= "application/json" (core/base-type "application/json"))) (is (= "application/json" (core/base-type "application/json;charset=UTF-8"))) (is (= "application/json" (core/base-type "application/json ; charset=UTF-8"))) (is (= "foo/bar" (core/base-type "foo/bar;someparam=baz"))) (is (nil? (core/base-type "application/json:charset=UTF-8"))) (is (nil? (core/base-type "appl=ication/json ; charset=UTF-8")))) (deftest now->timestamp-string-test (let [result (core/now->timestamp-string)] (is (re-matcher #"202\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d" result)))) (deftest timestamp-string->ZonedDateTime-test (testing "throws exceptions for invalid timestamps" (is (thrown? DateTimeParseException (core/timestamp-string->ZonedDateTime "not a timestamp")))) (testing "correctly converts timestamps to local date times" (let [result (core/timestamp-string->ZonedDateTime "2023-06-14T17:27:51.732102Z")] (is (instance? ZonedDateTime result)) (is (= 2023 (.getYear result))) (is (= Month/JUNE (.getMonth result))) (is (= 14 (.getDayOfMonth result))) (is (= 17 (.getHour result))) (is (= 27 (.getMinute result))) (is (= 51 (.getSecond result))) (is (= "Z" (.toString (.getZone result)))))) (testing "respects time zone" (let [result (core/timestamp-string->ZonedDateTime "2023-06-14T17:27:51.732102+01:00")] (is (instance? ZonedDateTime result)) (is (= 2023 (.getYear result))) (is (= Month/JUNE (.getMonth result))) (is (= 14 (.getDayOfMonth result))) (is (= 17 (.getHour result))) (is (= 27 (.getMinute result))) (is (= 51 (.getSecond result))) (is (= "+01:00" (.toString (.getZone result))))))) (deftest ZonedDateTime->utc-ZonedDateTime-test (testing "does not impact timezone in utc" (let [result (core/ZonedDateTime->utc-ZonedDateTime (core/timestamp-string->ZonedDateTime "2023-06-14T17:27:51.732102Z"))] (is (instance? ZonedDateTime result)) (is (= 2023 (.getYear result))) (is (= Month/JUNE (.getMonth result))) (is (= 14 (.getDayOfMonth result))) (is (= 17 (.getHour result))) (is (= 27 (.getMinute result))) (is (= 51 (.getSecond result))) (is (= "UTC" (.toString (.getZone result)))))) (testing "converts non UTC to UtC" (let [result (core/ZonedDateTime->utc-ZonedDateTime (core/timestamp-string->ZonedDateTime "2023-06-14T17:27:51.732102+01:00"))] (is (instance? ZonedDateTime result)) (is (= 2023 (.getYear result))) (is (= Month/JUNE (.getMonth result))) (is (= 14 (.getDayOfMonth result))) (is (= 16 (.getHour result))) (is (= 27 (.getMinute result))) (is (= 51 (.getSecond result))) (is (= "UTC" (.toString (.getZone result))))))) (deftest is-only-number?-test (testing "returns false for things that aren't just numbers" (is (false? (core/is-only-number? "one"))) (is (false? (core/is-only-number? "1one"))) (is (false? (core/is-only-number? "one1"))) (is (false? (core/is-only-number? "1 1"))) (is (false? (core/is-only-number? "-1"))) (is (false? (core/is-only-number? "1.1")))) (testing "returns true for things that are just numbers" (is (true? (core/is-only-number? "1"))) (is (true? (core/is-only-number? "100000"))) (is (true? (core/is-only-number? "0"))))) (deftest starts-with-zero?-test (testing "returns false for strings that don't start with zero" (is (false? (core/starts-with-zero? "one"))) (is (false? (core/starts-with-zero? "1one"))) (is (false? (core/starts-with-zero? "1one0"))) (is (false? (core/starts-with-zero? "1 0")))) (testing "returns true for strings that start with zero" (is (true? (core/starts-with-zero? "0one"))) (is (true? (core/starts-with-zero? "01one"))) (is (true? (core/starts-with-zero? "01one0"))) (is (true? (core/starts-with-zero? "0 1 0"))))) (deftest compare-versions-test (testing "expected input produces expected output" (is (zero? (core/compare-versions "1.2" "1.2"))) (is (neg? (core/compare-versions "1.2" "1.3"))) (is (pos? (core/compare-versions "1.3" "1.2"))) (is (zero? (core/compare-versions "0002" "0002"))) (is (neg? (core/compare-versions "0002" "1"))) (is (neg? (core/compare-versions "0002" "1.06"))) (is (neg? (core/compare-versions "0002" "1.1-3"))) (is (neg? (core/compare-versions "0002" "1.1-6"))) (is (neg? (core/compare-versions "0002" "1.1.a"))) (is (pos? (core/compare-versions "1" "0002"))) (is (pos? (core/compare-versions "1.06" "0002"))) (is (pos? (core/compare-versions "1.1-3" "0002"))) (is (pos? (core/compare-versions "1.1-6" "0002"))) (is (pos? (core/compare-versions "1.1.a" "0002"))) (is (zero? (core/compare-versions "1" "1"))) (is (neg? (core/compare-versions "1" "1.06"))) (is (neg? (core/compare-versions "1" "1.1-3"))) (is (neg? (core/compare-versions "1" "1.1-6"))) (is (neg? (core/compare-versions "1" "1.1.a"))) (is (neg? (core/compare-versions "1" "2"))) (is (neg? (core/compare-versions "1" "2.0"))) (is (pos? (core/compare-versions "1.06" "1"))) (is (pos? (core/compare-versions "1.1-3" "1"))) (is (pos? (core/compare-versions "1.1-6" "1"))) (is (pos? (core/compare-versions "1.1.a" "1"))) (is (pos? (core/compare-versions "2" "1"))) (is (pos? (core/compare-versions "2.0" "1"))) (is (zero? (core/compare-versions "1.1-3" "1.1-3"))) (is (neg? (core/compare-versions "1.1-3" "1.1-6"))) (is (neg? (core/compare-versions "1.1-3" "1.1.a"))) (is (neg? (core/compare-versions "1.1-3" "2"))) (is (neg? (core/compare-versions "1.1-3" "2.0"))) (is (pos? (core/compare-versions "1.1-6" "1.1-3"))) (is (pos? (core/compare-versions "1.1.a" "1.1-3"))) (is (pos? (core/compare-versions "2" "1.1-3"))) (is (pos? (core/compare-versions "2.0" "1.1-3"))) (is (zero? (core/compare-versions "1.1.6" "1.1.6"))) (is (neg? (core/compare-versions "1.1.6" "1.1.a"))) (is (neg? (core/compare-versions "1.1.6" "2"))) (is (neg? (core/compare-versions "1.1.6" "2.0"))) (is (pos? (core/compare-versions "1.1.a" "1.1.6"))) (is (pos? (core/compare-versions "2" "1.1.6"))) (is (pos? (core/compare-versions "2.0" "1.1.6"))) (is (zero? (core/compare-versions "1.1.a" "1.1.a"))) (is (neg? (core/compare-versions "1.1.a" "2"))) (is (neg? (core/compare-versions "1.1.a" "2.0"))) (is (pos? (core/compare-versions "2" "1.1.a"))) (is (zero? (core/compare-versions "2" "2"))) (is (zero? (core/compare-versions "2.0" "2.0"))) ;; this might not be expected, but it is the behavior. (is (pos? (core/compare-versions "2.0" "2"))) (is (pos? (core/compare-versions "2.0" "1.1.a"))) (is (neg? (core/compare-versions "2.4" "2.4b"))) (is (pos? (core/compare-versions "2.4b" "2.4a"))))) (deftest get-lein-project-version-test (testing "unknown project returns nil" (is (nil? (core/get-lein-project-version "unknown")))) (testing "known project returns something" (is (string? (core/get-lein-project-version "kitchensink"))) (is (not (string/blank? (core/get-lein-project-version "kitchensink")))))) (deftest safe-parse-int-test (testing "Converts appropriately" (are [x y] (= x (core/safe-parse-int y)) 0 0 1 1 0 "0" 1 "1" nil "notanumber"))) OpenVoxProject-clj-kitchensink-6f5334f/test/puppetlabs/kitchensink/file_test.clj000066400000000000000000000161331523666416700302230ustar00rootroot00000000000000(ns puppetlabs.kitchensink.file-test (:require [clojure.java.io :as io] [clojure.pprint] [clojure.test :refer :all] [me.raynes.fs :as fs] [puppetlabs.kitchensink.file :refer [atomic-write get-perms set-perms unzip-file untar-file delete-recursively]]) (:import (java.io BufferedWriter File FileNotFoundException) (java.nio.file Files Path) (java.nio.file.attribute FileAttribute))) (deftest atomic-write-test (testing "when the file doesn't exist" (let [tmp-file (.toString ^File (fs/temp-file "atomic-writes-test")) content "Something said, not good"] (fs/delete tmp-file) (atomic-write tmp-file #(.write ^BufferedWriter % content)) (testing "it writes the data" (is (= content (slurp tmp-file)))) (testing "it applies a default mode of 0640" (is (= "rw-r-----" (get-perms tmp-file)))) (testing "it applies the specified mode" (fs/delete tmp-file) (atomic-write tmp-file #(.write ^BufferedWriter % content) "rwxrwxrwx") (is (= "rwxrwxrwx" (get-perms tmp-file)))) (testing "it can create a read-only file" (fs/delete tmp-file) (atomic-write tmp-file #(.write ^BufferedWriter % content) "r--------") (is (= "r--------" (get-perms tmp-file)))) (fs/delete tmp-file))) (testing "when overwriting" (let [tmp-file (.toString ^File (fs/temp-file "atomic-writes-test")) content "Something said, not good"] (set-perms tmp-file "rwxr-x--x") (testing "it preserves existing permissions" (atomic-write tmp-file #(.write ^BufferedWriter % content)) (is (= "rwxr-x--x" (get-perms tmp-file)))) (testing "it overwrites existing permissions if specified" (atomic-write tmp-file #(.write ^BufferedWriter % content) "rw-rw----") (is (= "rw-rw----" (get-perms tmp-file)))) (testing "it updates a read-only file" (set-perms tmp-file "r--------") (atomic-write tmp-file #(.write ^BufferedWriter % content)) (is (= content (slurp tmp-file)))) (fs/delete tmp-file))) (testing "the supplied function is allowed to close the writer" (let [tmp-file (.toString ^File (fs/temp-file "atomic-writes-test")) write-fn (fn [^BufferedWriter writer] (.write writer "They called me slow") (.close writer))] (atomic-write tmp-file write-fn) (fs/delete tmp-file)))) (deftest unzip-file-test (testing "can unzip known .gz file" (let [tmp-file (.toString ^File (fs/temp-file "unzipped-file"))] (unzip-file "dev-resources/fixtures/plain-text.txt.gz" tmp-file) (is (= (slurp tmp-file) (slurp "dev-resources/fixtures/plain-text.txt"))))) (testing "creates missing directories" (let [tmp-file (str (.toString ^File (fs/temp-dir "test-directory")) "/foo/bar/baz/thing.txt")] (unzip-file "dev-resources/fixtures/plain-text.txt.gz" tmp-file) (is (= (slurp tmp-file) (slurp "dev-resources/fixtures/plain-text.txt"))))) (testing "throws if file does not exist" (let [tmp-file (.toString ^File (fs/temp-file "not-unzipped-file"))] (is (thrown? FileNotFoundException (unzip-file "something" tmp-file))))) (testing "refuses to overwrite input file" (let [tmp-file (.toString ^File (fs/temp-file "copy-of-file"))] (io/copy (io/file "dev-resources/fixtures/plain-text.txt.gz") (io/file tmp-file)) (unzip-file tmp-file tmp-file) (is (= (slurp "dev-resources/fixtures/plain-text.txt.gz") (slurp tmp-file)))))) (def large-file-line-content "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789") (deftest untar-file-test (testing "can untar known tar file" (let [temp-dir (.toString ^File (fs/temp-dir "test-tar-directory")) temp-dir-length (count temp-dir)] (untar-file "dev-resources/fixtures/tar-contents.tar" temp-dir) ;; extract the paths, remove the temp-dir front, and sort the result so the results are predictable (let [names (sort (map #(subs (str %) temp-dir-length) (file-seq (io/file temp-dir))))] (is (= ["" "/tar-contents" "/tar-contents/bar" "/tar-contents/bar/1" "/tar-contents/bar/1/largetextfile.txt" "/tar-contents/bar/2" "/tar-contents/bar/3" "/tar-contents/bar/4" "/tar-contents/bar/4/hello.txt" "/tar-contents/foo" "/tar-contents/foo/foo" "/tar-contents/foo/hi.txt"] names)) (is (= "this is a greeting.\n" (slurp (str temp-dir "/tar-contents/bar/4/hello.txt")))) (is (= "This is a warm greeting.\n" (slurp (str temp-dir "/tar-contents/foo/hi.txt")))) (testing "large files are correctly processed" (let [counter (atom 0)] (with-open [rdr (io/reader (str temp-dir "/tar-contents/bar/1/largetextfile.txt"))] (doseq [line (line-seq rdr)] (swap! counter inc) ;; each line should be identical (is (= large-file-line-content line)))) ;; there should be 90 lines in the file. (is (= 90 @counter)))))))) (defn recursively-fill-directory [^Path directory depth max-depth] (when (< depth max-depth) (let [num-files (rand-int 5) num-dirs (rand-int 5)] (dotimes [n num-files] (Files/createTempFile directory (str "file-" depth "-" n "-") ".tmp" (into-array FileAttribute []))) (dotimes [n num-dirs] (let [new-dir (Files/createTempDirectory directory (str "directory-" depth "-" n "-") (into-array FileAttribute []))] (recursively-fill-directory new-dir (inc depth) max-depth)))))) (deftest delete-recursively-test (testing "can delete a single file" (let [tmp-file (Files/createTempFile "prefix-" ".tmp" (into-array FileAttribute []))] (is (.exists (.toFile tmp-file))) (is (delete-recursively (.getAbsolutePath (.toFile tmp-file)))) (is (not (.exists (.toFile tmp-file)))))) (testing "can delete a tree with files and directories" (let [tmp-dir (Files/createTempDirectory "testing" (into-array FileAttribute []))] (recursively-fill-directory tmp-dir 0 10) (is (.exists (.toFile tmp-dir))) (is (delete-recursively (.getAbsolutePath (.toFile tmp-dir)))) (is (not (.exists (.toFile tmp-dir))))))) OpenVoxProject-clj-kitchensink-6f5334f/test/puppetlabs/kitchensink/json_test.clj000066400000000000000000000066501523666416700302600ustar00rootroot00000000000000(ns puppetlabs.kitchensink.json-test (:require [puppetlabs.kitchensink.core :refer [temp-file]] [puppetlabs.kitchensink.json :as ks-json]) (:use [clojure.test]) (:import (java.io StringReader StringWriter) (java.time Instant LocalDate LocalDateTime))) (defn add-common-encoders-fixture [f] (ks-json/add-common-json-encoders!*) (f)) (use-fixtures :once add-common-encoders-fixture) (deftest test-generate-string (testing "should generate a json string" (is (= (ks-json/generate-string {:a 1 :b 2}) "{\"a\":1,\"b\":2}"))) (testing "should generate a json string that has a java.time.Instant in it and not explode" (is (= (ks-json/generate-string {:a 1 :b (Instant/parse "1994-09-19T21:00:30Z")}) "{\"a\":1,\"b\":\"1994-09-19T21:00:30Z\"}"))) (testing "should generate a json string that has a java.time.LocalDate in it and not explode" (is (= (ks-json/generate-string {:a 1 :b (LocalDate/parse "1953-05-29")}) "{\"a\":1,\"b\":\"1953-05-29\"}"))) (testing "should generate a json string that has a java.time.LocalDateTime in it and not explode" (is (= (ks-json/generate-string {:a 1 :b (LocalDateTime/parse "1954-07-31T21:00:30")}) "{\"a\":1,\"b\":\"1954-07-31T21:00:30\"}")))) (deftest test-generate-pretty-string (testing "should generate a json string" (is (= (ks-json/generate-pretty-string {:a 1 :b 2}) "{\n \"a\" : 1,\n \"b\" : 2\n}"))) (testing "should generate a json string that has a java.time.Instant in it and not explode" (is (= (ks-json/generate-pretty-string {:a 1 :b (Instant/parse "1994-09-19T21:00:30Z")}) "{\n \"a\" : 1,\n \"b\" : \"1994-09-19T21:00:30Z\"\n}"))) (testing "should generate a json string that has a java.time.LocalDate in it and not explode" (is (= (ks-json/generate-pretty-string {:a 1 :b (LocalDate/parse "1953-05-29")}) "{\n \"a\" : 1,\n \"b\" : \"1953-05-29\"\n}"))) (testing "should generate a json string that has a java.time.LocalDateTime in it and not explode" (is (= (ks-json/generate-pretty-string {:a 1 :b (LocalDateTime/parse "1954-07-31T21:00:30")}) "{\n \"a\" : 1,\n \"b\" : \"1954-07-31T21:00:30\"\n}")))) (deftest test-generate-stream (testing "should generate a json string from a stream" (let [sw (StringWriter.)] (ks-json/generate-stream {:a 1 :b 2} sw) (is (= (.toString sw) "{\"a\":1,\"b\":2}"))))) (deftest test-generate-pretty-stream (testing "should generate a pretty printed json string from a stream" (let [sw (StringWriter.)] (ks-json/generate-pretty-stream {:a 1 :b 2} sw) (is (= (.toString sw) "{\n \"a\" : 1,\n \"b\" : 2\n}"))))) (deftest test-parse-string (testing "should return a map from parsing a json string" (is (= (ks-json/parse-string "{\"a\":1,\"b\":2}") {"a" 1 "b" 2})))) (deftest test-parse-stream (testing "should return map from parsing a json stream" (is (= (ks-json/parse-stream (StringReader. "{\"a\":1,\"b\":2}")) {"a" 1 "b" 2})))) (deftest test-spit-json (let [json-out (temp-file "spit-json")] (testing "json output with keywords" (ks-json/spit-json json-out {:a 1 :b 2}) (is (= "{\n \"a\" : 1,\n \"b\" : 2\n}" (slurp json-out)))) (testing "json output with strings" (ks-json/spit-json json-out {"a" 1 "b" 2}) (is (= "{\n \"a\" : 1,\n \"b\" : 2\n}" (slurp json-out)))))) OpenVoxProject-clj-kitchensink-6f5334f/test/puppetlabs/kitchensink/testutils.clj000066400000000000000000000013411523666416700303000ustar00rootroot00000000000000(ns puppetlabs.kitchensink.testutils (:require [puppetlabs.kitchensink.core :as ks])) (defn call-counter "Returns a method that just tracks how many times it's called, and with what arguments. That information is stored in metadata for the method." [] (let [ncalls (ref 0) arguments (ref [])] (with-meta (fn [& args] (dosync (alter ncalls inc) (alter arguments conj args))) {:ncalls ncalls :args arguments}))) (defn times-called "Returns the number of times a `call-counter` function has been invoked." [f] (deref (:ncalls (meta f)))) (defmacro with-no-jvm-shutdown-hooks [& body] `(with-redefs [ks/add-shutdown-hook! (fn [_#] nil)] ~@body)) OpenVoxProject-clj-kitchensink-6f5334f/test/puppetlabs/kitchensink/testutils/000077500000000000000000000000001523666416700276075ustar00rootroot00000000000000OpenVoxProject-clj-kitchensink-6f5334f/test/puppetlabs/kitchensink/testutils/fixtures.clj000066400000000000000000000006411523666416700321530ustar00rootroot00000000000000(ns puppetlabs.kitchensink.testutils.fixtures (:require [puppetlabs.kitchensink.core :as kitchensink])) (defn with-no-jvm-shutdown-hooks "Test fixture to prevent JVM shutdown hooks from being added. Only works if the shutdown hook is being added by a call to the utility function `puppetlabs.kitchensink.core/add-shutdown-hook!`." [f] (with-redefs [kitchensink/add-shutdown-hook! (fn [_] nil)] (f))) OpenVoxProject-clj-kitchensink-6f5334f/test/puppetlabs/kitchensink/time_test.clj000066400000000000000000000130321523666416700302350ustar00rootroot00000000000000(ns puppetlabs.kitchensink.time-test (:require [clojure.test :refer :all]) (:require [puppetlabs.kitchensink.time :as time]) (:import (java.time Duration))) (deftest duration-str->seconds-test (testing "returns an integer when given an integer" (are [x] (= x (time/duration-str->seconds x)) -1 0 1 2 3 4 5 6 7 8 9 10)) (testing "returns expected values for simple input" (are [x y] (= x (time/duration-str->seconds y)) 94608000 "3y" 259200 "3d" 10800 "3h" 180 "3m" 3 "3s")) (testing "Throws exceptions on invalid input" (is (thrown? IllegalArgumentException (time/duration-str->seconds "6y3m3y"))) (is (thrown? IllegalArgumentException (time/duration-str->seconds "3y6d7d8d3d"))) (is (thrown? IllegalArgumentException (time/duration-str->seconds "3y3y6d7d3h"))) (is (thrown? IllegalArgumentException (time/duration-str->seconds "3y6y5h6d3m"))) (is (thrown? IllegalArgumentException (time/duration-str->seconds "3y6y5h6d3m3s"))) (is (thrown? IllegalArgumentException (time/duration-str->seconds "not-a-duration"))))) (deftest duration-str->Duration-test (testing "returns a duration with the expected number of seconds when given an integer" (testing "returns an integer when given an integer" (are [x] (= (Duration/ofSeconds x) (time/duration-str->Duration x)) -1 0 1 2 3 4 5 6 7 8 9 10)) (testing "returns expected values for simple input" (are [x y] (= (Duration/ofSeconds x) (time/duration-str->Duration y)) 94608000 "3y" 259200 "3d" 10800 "3h" 180 "3m" 3 "3s")) (testing "Throws exceptions on invalid input" (is (thrown? IllegalArgumentException (time/duration-str->Duration "6y3m3y"))) (is (thrown? IllegalArgumentException (time/duration-str->Duration "3y6d7d8d3d"))) (is (thrown? IllegalArgumentException (time/duration-str->Duration "3y3y6d7d3h"))) (is (thrown? IllegalArgumentException (time/duration-str->Duration "3y6y5h6d3m"))) (is (thrown? IllegalArgumentException (time/duration-str->Duration "3y6y5h6d3m3s"))) (is (thrown? IllegalArgumentException (time/duration-str->Duration "not-a-duration")))))) (deftest days->hours-test (testing "expected input provides expected output" (are [x y] (= x (time/days->hours y)) 0 0 -24 -1 24 1 48 2 (* 24 1.1) 1.1))) (deftest hours->days-test (testing "expected input provides expected output" (are [x y] (= x (time/hours->days y)) 0 0 -1 -24 1 24 2 48 1.1 (* 24 1.1)))) (deftest hours->min-test (testing "expected input provides expected output" (are [x y] (= x (time/hours->min y)) 0 0 -60 -1 60 1 120 2 (* 60 1.1) 1.1))) (deftest min->hours-test (testing "expected input provides expected output" (are [x y] (= x (time/min->hours y)) 0 0 -1 -60 1 60 2 120 1.1 (* 60 1.1)))) (deftest min->sec-test (testing "expected input provides expected output" (are [x y] (= x (time/min->sec y)) 0 0 -60 -1 60 1 120 2 (* 60 1.1) 1.1))) (deftest sec->min-test (testing "expected input provides expected output" (are [x y] (= x (time/sec->min y)) 0 0 -1 -60 1 60 2 120 1.1 (* 60 1.1)))) (deftest sec->ms-test (testing "expected input provides expected output" (are [x y] (= x (time/sec->ms y)) 0 0 -1000 -1 1000 1 2000 2 1100.0 1.1))) (deftest ms->sec-test (testing "expected input provides expected output" (are [x y] (= x (time/ms->sec y)) 0 0 -1 -1000 1 1000 2 2000 11/10 1100))) (deftest min->ms-test (testing "expected input provides expected output" (are [x y] (= x (time/min->ms y)) 0 0 -60000 -1 60000 1 120000 2 66000.0 1.1))) (deftest ms->min-test (testing "expected input provides expected output" (are [x y] (= x (time/ms->min y)) 0 0 -1 -60000 1 60000 2 120000 11/10 66000))) (deftest hours->ms-test (testing "expected input provides expected output" (are [x y] (= x (time/hours->ms y)) 0 0 -3600000 -1 3600000 1 7200000 2 3960000.0 1.1))) (deftest ms->hours-test (testing "expected input provides expected output" (are [x y] (= x (time/ms->hours y)) 0 0 -1 -3600000 1 3600000 2 7200000 11/10 3960000))) (deftest days->ms-test (testing "expected input provides expected output" (are [x y] (= x (time/days->ms y)) 0 0 -86400000 -1 86400000 1 172800000 2 (* 1.1 24 60 60 1000) 1.1))) (deftest ms->days-test (testing "expected input provides expected output" (are [x y] (= x (time/ms->days y)) 0 0 -1 -86400000 1 86400000 2 172800000 1.1 (* 1.1 24 60 60 1000))))