├── .all-contributorsrc ├── .github └── workflows │ ├── README.md │ ├── build.yml │ └── release.yml ├── .gitignore ├── .nvmrc ├── .releaserc.json ├── .yarn └── releases │ └── yarn-4.1.0.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── Setup.hs ├── atomic-write.cabal ├── docs ├── CODE_OF_CONDUCT.md └── CONTRIBUTING.md ├── examples └── logger │ ├── .gitignore │ ├── README.md │ ├── Setup.hs │ ├── app.log │ ├── app │ └── Main.hs │ ├── logger-example.cabal │ ├── package.yaml │ ├── stack.yaml │ └── stack.yaml.lock ├── package.json ├── spec ├── Spec.hs └── System │ └── AtomicWrite │ └── Writer │ ├── ByteString │ └── BinarySpec.hs │ ├── ByteStringBuilderSpec.hs │ ├── ByteStringSpec.hs │ ├── LazyByteString │ └── BinarySpec.hs │ ├── LazyByteStringSpec.hs │ ├── LazyText │ └── BinarySpec.hs │ ├── LazyTextSpec.hs │ ├── String │ └── BinarySpec.hs │ ├── StringSpec.hs │ ├── Text │ └── BinarySpec.hs │ └── TextSpec.hs ├── src └── System │ └── AtomicWrite │ ├── Internal.hs │ └── Writer │ ├── ByteString.hs │ ├── ByteString │ └── Binary.hs │ ├── ByteStringBuilder.hs │ ├── LazyByteString.hs │ ├── LazyByteString │ └── Binary.hs │ ├── LazyText.hs │ ├── LazyText │ └── Binary.hs │ ├── String.hs │ ├── String │ └── Binary.hs │ ├── Text.hs │ └── Text │ └── Binary.hs ├── stack.yaml ├── stack.yaml.lock └── yarn.lock /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "atomic-write", 3 | "projectOwner": "stackbuilders", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": true, 11 | "commitConvention": "gitmoji", 12 | "contributors": [ 13 | { 14 | "login": "jsl", 15 | "name": "Justin S. Leitgeb", 16 | "avatar_url": "https://avatars.githubusercontent.com/u/9977?v=4", 17 | "profile": "https://www.stackbuilders.com/news/author/justin-leitgeb", 18 | "contributions": [ 19 | "code" 20 | ] 21 | }, 22 | { 23 | "login": "cptrodolfox", 24 | "name": "William R. Arellano", 25 | "avatar_url": "https://avatars.githubusercontent.com/u/20303685?v=4", 26 | "profile": "https://github.com/cptrodolfox", 27 | "contributions": [ 28 | "code" 29 | ] 30 | }, 31 | { 32 | "login": "Alex0jk", 33 | "name": "Alexander Mejía", 34 | "avatar_url": "https://avatars.githubusercontent.com/u/22301755?v=4", 35 | "profile": "https://github.com/Alex0jk", 36 | "contributions": [ 37 | "code" 38 | ] 39 | }, 40 | { 41 | "login": "acamino", 42 | "name": "Agustin Camino", 43 | "avatar_url": "https://avatars.githubusercontent.com/u/957202?v=4", 44 | "profile": "https://github.com/acamino", 45 | "contributions": [ 46 | "code" 47 | ] 48 | }, 49 | { 50 | "login": "juanpaucar", 51 | "name": "Juan Paucar", 52 | "avatar_url": "https://avatars.githubusercontent.com/u/2164411?v=4", 53 | "profile": "https://juancarlos.io/", 54 | "contributions": [ 55 | "code" 56 | ] 57 | }, 58 | { 59 | "login": "BarbDMC", 60 | "name": "Barbara Morantes", 61 | "avatar_url": "https://avatars.githubusercontent.com/u/67979158?v=4", 62 | "profile": "https://www.barbaramorantes.com/", 63 | "contributions": [ 64 | "example" 65 | ] 66 | } 67 | ], 68 | "contributorsPerLine": 7, 69 | "linkToUsage": true 70 | } 71 | -------------------------------------------------------------------------------- /.github/workflows/README.md: -------------------------------------------------------------------------------- 1 | # CI Workflows 2 | 3 | ## Overview 4 | 5 | - [Build](build.yml) 6 | - Build and test Haskell code 7 | - [Draft](draft.yml) 8 | - Create a GH draft release with a static binary 9 | - [Release](release.yml) 10 | - Upload the package and docs to Hackage (release candidate) 11 | 12 | ## Events 13 | 14 | ```mermaid 15 | graph LR; 16 | event[GH Event]-->|on push|Build; 17 | event-->|tag created|Draft; 18 | Draft-->|create draft release|End; 19 | event-->|release published|Release; 20 | Release-->|upload artifacts to Hackage - release candidate|End; 21 | Build-->End; 22 | ``` 23 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | workflow_call: 8 | 9 | concurrency: 10 | group: build-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | generate-matrix: 15 | name: "Generate matrix from cabal" 16 | outputs: 17 | matrix: ${{ steps.set-matrix.outputs.matrix }} 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Extract the tested GHC versions 21 | id: set-matrix 22 | uses: kleidukos/get-tested@v0.1.7.0 23 | with: 24 | cabal-file: atomic-write.cabal 25 | ubuntu-version: latest 26 | version: 0.1.7.0 27 | 28 | build-and-test: 29 | name: GHC ${{ matrix.ghc }} on ${{ matrix.os }} 30 | needs: generate-matrix 31 | strategy: 32 | matrix: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }} 33 | runs-on: ${{ matrix.os }} 34 | steps: 35 | - name: Checkout 36 | uses: actions/checkout@v3 37 | - name: Install Haskell tooling 38 | uses: haskell-actions/setup@v2 39 | with: 40 | ghc-version: ${{ matrix.ghc }} 41 | cabal-version: "3.6" 42 | - name: Configure project 43 | run: cabal configure --enable-tests 44 | - name: Build project 45 | run: cabal build 46 | - name: Run tests 47 | run: cabal test --test-show-details=direct 48 | - name: Check documentation 49 | run: cabal haddock 50 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | concurrency: 7 | group: release 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | build: 12 | uses: ./.github/workflows/build.yml 13 | 14 | upload: 15 | runs-on: ubuntu-latest 16 | needs: [build] 17 | permissions: 18 | contents: write 19 | issues: write 20 | pull-requests: write 21 | id-token: write 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v3 25 | - name: Setup tooling 26 | uses: haskell/actions/setup@v2 27 | with: 28 | ghc-version: "8.10" 29 | cabal-version: "3.6" 30 | - name: Freeze dependencies 31 | run: cabal freeze 32 | - name: Cache dependencies 33 | uses: actions/cache@v3 34 | with: 35 | key: ${{ runner.os }}-${{ hashFiles('*.cabal', 'cabal.project.freeze') }} 36 | restore-keys: ${{ runner.os }}- 37 | path: | 38 | ~/.cabal/packages 39 | ~/.cabal/store 40 | dist-newstyle 41 | - name: Setup NodeJS 42 | uses: actions/setup-node@v4 43 | with: 44 | node-version-file: .nvmrc 45 | cache: yarn 46 | - name: Install packages 47 | run: yarn install --immutable 48 | - name: Publish package to Hackage 49 | run: yarn release 50 | env: 51 | HACKAGE_TOKEN: ${{ secrets.HACKAGE_API_KEY }} 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-* 3 | cabal-dev 4 | *.o 5 | *.hi 6 | *.hie 7 | *.chi 8 | *.chs.h 9 | *.dyn_o 10 | *.dyn_hi 11 | .hpc 12 | .hsenv 13 | .cabal-sandbox/ 14 | cabal.sandbox.config 15 | *.prof 16 | *.aux 17 | *.hp 18 | *.eventlog 19 | .stack-work/ 20 | cabal.project.local 21 | cabal.project.local~ 22 | .HTF/ 23 | .ghc.environment.* 24 | 25 | # VSCode 26 | .vscode 27 | 28 | # NodeJS 29 | node_modules/ 30 | .yarn/* 31 | !.yarn/patches 32 | !.yarn/plugins 33 | !.yarn/releases 34 | !.yarn/sdks 35 | !.yarn/versions 36 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 21.6.2 2 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/semantic-release", 3 | "branches": ["main"], 4 | "plugins": [ 5 | "@semantic-release/commit-analyzer", 6 | "@semantic-release/release-notes-generator", 7 | [ 8 | "semantic-release-hackage", 9 | { 10 | "packageName": "atomic-write", 11 | "versionPrefix": "0.", 12 | "publishDocumentation": true 13 | } 14 | ], 15 | "@semantic-release/github" 16 | ], 17 | "tagFormat": "v0.${version}" 18 | } 19 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.1.0.cjs 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.0.7 2 | ### Added 3 | * Binary mode versions of the `atomicWriteFile` and `atomicWriteFileWithMode` functions for ByteString, Text, 4 | String, LazyText and LazyByteString. 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Stack Builders Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build](https://github.com/stackbuilders/atomic-write/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/stackbuilders/atomic-write/actions/workflows/build.yml) 2 | [![Hackage version](https://img.shields.io/hackage/v/atomic-write.svg)](http://hackage.haskell.org/package/atomic-write) 3 | 4 | [![All Contributors](https://img.shields.io/badge/all_contributors-6-orange.svg?style=flat-square)](#contributors-) 5 | 6 | 7 | # Atomic Write 8 | 9 | Atomic Write assists with atomic modification of files using 10 | Haskell. It is a wrapper for using the atomic mv(1) operation which 11 | correctly sets permissions based on the original file, or on system 12 | defaults if no file previously exists. 13 | 14 | ## How it works 15 | On most Unix systems, mv is an atomic operation. This makes it simple to write to a file atomically just by using the mv operation. However, this will destroy the permissions on the original file. This library does the following to preserve permissions while atomically writing to a file: 16 | 17 | If an original file exists, take those permissions and apply them to the temp file before mving the file into place. 18 | 19 | If the original file does not exist, create a following with default permissions (based on the currently-active umask). 20 | 21 | This way, when the file is mv'ed into place, the permissions will be the ones held by the original file. 22 | 23 | This library is based on similar implementations found in common libraries in Ruby and Python: 24 | 25 | - [Ruby on Rails includes a similar method called atomic_write](https://apidock.com/rails/File/atomic_write/class) 26 | 27 | - [Chef includes atomic update functionality](https://github.com/chef/chef/blob/c4631816132fcfefaba3d123a1d0dfe8bc2866bb/lib/chef/file_content_management/deploy/mv_unix.rb#L23:L71) 28 | 29 | - [There is a python library for atomically updating a file](https://github.com/sashka/atomicfile) 30 | 31 | ## Usage 32 | 33 | To use `atomic-write`, import the module corresponding to the type you wish to write atomically, e.g., to write a (strict) ByteString atomically: 34 | 35 | ```import System.AtomicWrite.Writer.ByteString``` 36 | 37 | Then you can use the atomicWriteFile function that accepts a FilePath and a ByteString, e.g.: 38 | 39 | ```atomicWriteFile myFilePath myByteString``` 40 | 41 | See the 42 | [Haddock documentation](http://hackage.haskell.org/package/atomic-write). 43 | 44 | ## Contributors ✨ 45 | 46 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 69 | 70 | 71 |
Justin S. Leitgeb
Justin S. Leitgeb

💻
William R. Arellano
William R. Arellano

💻
Alexander Mejía
Alexander Mejía

💻
Agustin Camino
Agustin Camino

💻
Juan Paucar
Juan Paucar

💻
Barbara Morantes
Barbara Morantes

💡
65 | 66 | Add your contributions 67 | 68 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 79 | 80 | ## License 81 | 82 | MIT, see [the LICENSE file](LICENSE). 83 | 84 | ## Contributing 85 | 86 | Do you want to contribute to this project? Please take a look at our [contributing guideline](/docs/CONTRIBUTING.md) to know how you can help us build it. 87 | 88 | --- 89 | Stack Builders 90 | [Check out our libraries](https://github.com/stackbuilders/) | [Join our team](https://www.stackbuilders.com/join-us/) 91 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /atomic-write.cabal: -------------------------------------------------------------------------------- 1 | name: atomic-write 2 | version: 0.2.0.7 3 | synopsis: Atomically write to a file 4 | homepage: https://github.com/stackbuilders/atomic-write 5 | description: 6 | . 7 | Atomically write to a file on POSIX-compliant systems while preserving 8 | permissions. 9 | . 10 | On most Unix systems, `mv` is an atomic operation. This makes it simple to write 11 | to a file atomically just by using the mv operation. However, this will 12 | destroy the permissions on the original file. This library does the following 13 | to preserve permissions while atomically writing to a file: 14 | . 15 | * If an original file exists, take those permissions and apply them to the 16 | temp file before `mv`ing the file into place. 17 | . 18 | * If the original file does not exist, create a following with default 19 | permissions (based on the currently-active umask). 20 | . 21 | This way, when the file is `mv`'ed into place, the permissions will be the ones 22 | held by the original file. 23 | . 24 | This library is based on similar implementations found in common libraries in 25 | Ruby and Python: 26 | . 27 | * 28 | . 29 | * 30 | . 31 | * 32 | . 33 | To use `atomic-write`, import the module corresponding to the type you wish to 34 | write atomically, e.g., to write a (strict) ByteString atomically: 35 | . 36 | > import System.AtomicWrite.Writer.ByteString 37 | . 38 | Then you can use the atomicWriteFile function that accepts a `FilePath` and a 39 | `ByteString`, e.g.: 40 | . 41 | > atomicWriteFile myFilePath myByteString 42 | license: MIT 43 | license-file: LICENSE 44 | author: Justin Leitgeb 45 | maintainer: support@stackbuilders.com 46 | copyright: 2015-2019 Stack Builders Inc. 47 | category: System 48 | build-type: Simple 49 | cabal-version: >=1.10 50 | tested-with: 51 | GHC ==8.4.3, GHC ==8.10.7, GHC ==9.4.7 52 | 53 | bug-reports: https://github.com/stackbuilders/atomic-write/issues 54 | 55 | library 56 | exposed-modules: System.AtomicWrite.Writer.ByteString 57 | , System.AtomicWrite.Writer.ByteString.Binary 58 | , System.AtomicWrite.Writer.ByteStringBuilder 59 | , System.AtomicWrite.Writer.LazyByteString 60 | , System.AtomicWrite.Writer.LazyByteString.Binary 61 | , System.AtomicWrite.Writer.String 62 | , System.AtomicWrite.Writer.String.Binary 63 | , System.AtomicWrite.Writer.Text 64 | , System.AtomicWrite.Writer.Text.Binary 65 | , System.AtomicWrite.Writer.LazyText 66 | , System.AtomicWrite.Writer.LazyText.Binary 67 | 68 | other-modules: System.AtomicWrite.Internal 69 | 70 | build-depends: base >= 4.5 && < 5.0 71 | , temporary >= 1.3 && < 1.4 72 | , unix-compat >= 0.5 && < 1.0 73 | , directory >= 1.3 && < 1.4 74 | , filepath >= 1.4 && < 1.6 75 | , text >= 1.2 && < 3.0 76 | , bytestring >= 0.10.4 && < 0.13.0 77 | 78 | hs-source-dirs: src 79 | default-language: Haskell2010 80 | ghc-options: -Wall 81 | 82 | 83 | test-suite atomic-write-test 84 | type: exitcode-stdio-1.0 85 | hs-source-dirs: spec 86 | main-is: Spec.hs 87 | 88 | other-modules: System.AtomicWrite.Writer.ByteStringSpec 89 | , System.AtomicWrite.Writer.ByteString.BinarySpec 90 | , System.AtomicWrite.Writer.ByteStringBuilderSpec 91 | , System.AtomicWrite.Writer.LazyByteStringSpec 92 | , System.AtomicWrite.Writer.LazyByteString.BinarySpec 93 | , System.AtomicWrite.Writer.StringSpec 94 | , System.AtomicWrite.Writer.String.BinarySpec 95 | , System.AtomicWrite.Writer.TextSpec 96 | , System.AtomicWrite.Writer.Text.BinarySpec 97 | , System.AtomicWrite.Writer.LazyTextSpec 98 | , System.AtomicWrite.Writer.LazyText.BinarySpec 99 | 100 | build-depends: base >= 4.5 && < 5.0 101 | , atomic-write 102 | , temporary 103 | , unix-compat 104 | , filepath 105 | , text 106 | , bytestring 107 | , hspec >= 2.5 && < 2.12 108 | 109 | build-tools: hspec-discover >= 2.0 && < 3.0 110 | 111 | default-language: Haskell2010 112 | ghc-options: -Wall 113 | 114 | 115 | source-repository head 116 | type: git 117 | location: git@github.com:stackbuilders/atomic-write.git 118 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of conduct 2 | 3 | ## Purpose 4 | The primary goal of this Code of Conduct is to enable an open and welcoming environment. We pledge to making participation in our project a harassment-free experience for everyone, regardless of gender, sexual 5 | orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). 6 | 7 | ## General recommendations 8 | Examples of behavior that contributes to creating a positive environment include: 9 | 10 | - Using welcoming and inclusive language 11 | - Being respectful of differing viewpoints and experiences 12 | - Gracefully accepting constructive criticism 13 | - Focusing on what is best for the community 14 | - Showing empathy towards other community members 15 | 16 | Examples of unacceptable behavior by participants include: 17 | 18 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 19 | - Trolling, insulting/derogatory comments, and personal or political attacks 20 | - Public or private harassment 21 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 22 | - Other conduct which could reasonably be considered inappropriate in a professional setting 23 | 24 | ## Maintainer responsibilities 25 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 26 | 27 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 28 | 29 | ## Scope 30 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 31 | 32 | ## Enforcement 33 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [community@stackbuilders.com](mailto:community@stackbuilders.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 34 | 35 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | Thank you for your interest in contributing to this Stack Builders' library. To contribute, please take our [Code of Conduct](CODE_OF_CONDUCT.md) into account, along with the following recommendations: 3 | 4 | - When submitting contributions to this repository, please make sure to discuss with the maintainer(s) the change you want to make. You can do this through an issue, or by sending an email to [community@stackbuilders.com](mailto:community@stackbuilders.com) 5 | 6 | - Once the change has been discussed with the maintainer(s), feel free to open a Pull Request. Please include a link to the issue you're trying to solve, or a quick summary of the discussed changes. 7 | 8 | - This repo makes use of [semantic-release](https://github.com/semantic-release/semantic-release) with the [semantic-release-hackage](https://github.com/stackbuilders/semantic-release-hackage) puglin. PRs and commits should be following conventional commits formating to properly capture expected changes and determine the version to release. 9 | 10 | - If adding any new features that you think should be considered in the README file, please add that information in your Pull Request. 11 | 12 | - Once you get an approval from any of the maintainers, please merge your Pull Request. Keep in mind that some of our Stack Builders repositories use CI/CD pipelines, so you will need to pass all of the required checks before merging. 13 | 14 | ## Getting help 15 | Contact any of our current maintainers, or send us an email at [community@stackbuilders.com](mailto:community@stackbuilders.com) for more information. Thank you for contributing! -------------------------------------------------------------------------------- /examples/logger/.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | *~ 3 | -------------------------------------------------------------------------------- /examples/logger/README.md: -------------------------------------------------------------------------------- 1 | # logger-example 2 | 3 | This Haskell code example demonstrates a basic logging simulation. 4 | 5 | It emulates a logging operation that sequentially generates a set of log messages. Within a loop, it prints messages to the console, ranging from "Log message 1" to "Log message 5". Each message is printed alongside a confirmation that it has been logged, with a 1-second delay between messages. 6 | 7 | Upon reaching "Log message 5", the process shifts from console output to writing this message into a log file. 8 | 9 | 10 | ## How to use this example 11 | 12 | ### Required dependencies 13 | 14 | - atomic-write 15 | - text 16 | - filepath 17 | 18 | 19 | ### Run Example 20 | 21 | - Use the command: `stack run` 22 | -------------------------------------------------------------------------------- /examples/logger/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /examples/logger/app.log: -------------------------------------------------------------------------------- 1 | Log message 5 -------------------------------------------------------------------------------- /examples/logger/app/Main.hs: -------------------------------------------------------------------------------- 1 | import System.AtomicWrite.Writer.Text 2 | import qualified Data.Text as T 3 | import qualified Data.Text.IO as TIO 4 | import Control.Monad (forM_) 5 | import Control.Concurrent (forkIO, threadDelay) 6 | 7 | logFilePath :: FilePath 8 | logFilePath = "app.log" 9 | 10 | logMessage :: FilePath -> T.Text -> IO () 11 | logMessage logFile message = atomicWriteFile logFile message 12 | 13 | simulateLoggingProcess :: FilePath -> IO () 14 | simulateLoggingProcess logFile = do 15 | forM_ [1..10 :: Int] $ \i -> do 16 | let message = "Log message " <> show i 17 | logMessage logFile (T.pack message) 18 | putStrLn $ "Logged: " <> message 19 | threadDelay 1000000 20 | 21 | main :: IO () 22 | main = do 23 | TIO.writeFile logFilePath mempty 24 | 25 | _ <- forkIO (simulateLoggingProcess logFilePath) 26 | 27 | threadDelay 5000000 28 | 29 | putStrLn "Log file contents: " 30 | TIO.readFile logFilePath >>= TIO.putStrLn 31 | -------------------------------------------------------------------------------- /examples/logger/logger-example.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.2 2 | 3 | -- This file has been generated from package.yaml by hpack version 0.36.0. 4 | -- 5 | -- see: https://github.com/sol/hpack 6 | 7 | name: logger-example 8 | version: 0.1.0.0 9 | description: Please see the README on GitHub at 10 | homepage: https://github.com/githubuser/logger-example#readme 11 | bug-reports: https://github.com/githubuser/logger-example/issues 12 | author: Author name here 13 | maintainer: example@example.com 14 | copyright: 2023 Author name here 15 | license: BSD-3-Clause 16 | license-file: LICENSE 17 | build-type: Simple 18 | extra-source-files: 19 | README.md 20 | CHANGELOG.md 21 | 22 | source-repository head 23 | type: git 24 | location: https://github.com/githubuser/logger-example 25 | 26 | library 27 | exposed-modules: 28 | Lib 29 | other-modules: 30 | Paths_logger_example 31 | autogen-modules: 32 | Paths_logger_example 33 | hs-source-dirs: 34 | src 35 | ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints 36 | build-depends: 37 | atomic-write >=0.2 38 | , base >=4.7 && <5 39 | , filepath >=1.4 && <2 40 | , text >=2.0 41 | default-language: Haskell2010 42 | 43 | executable logger-example-exe 44 | main-is: Main.hs 45 | other-modules: 46 | Paths_logger_example 47 | autogen-modules: 48 | Paths_logger_example 49 | hs-source-dirs: 50 | app 51 | ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N 52 | build-depends: 53 | atomic-write >=0.2 54 | , base >=4.7 && <5 55 | , filepath >=1.4 && <2 56 | , logger-example 57 | , text >=2.0 58 | default-language: Haskell2010 59 | 60 | test-suite logger-example-test 61 | type: exitcode-stdio-1.0 62 | main-is: Spec.hs 63 | other-modules: 64 | Paths_logger_example 65 | autogen-modules: 66 | Paths_logger_example 67 | hs-source-dirs: 68 | test 69 | ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N 70 | build-depends: 71 | atomic-write >=0.2 72 | , base >=4.7 && <5 73 | , filepath >=1.4 && <2 74 | , logger-example 75 | , text >=2.0 76 | default-language: Haskell2010 77 | -------------------------------------------------------------------------------- /examples/logger/package.yaml: -------------------------------------------------------------------------------- 1 | name: logger-example 2 | version: 0.1.0.0 3 | github: "stackbuilders/atomic-write" 4 | license: BSD-3-Clause 5 | author: "@BarbDMC" 6 | maintainer: "amejia@stackbuilders.com" 7 | copyright: "2023 @BarbDMC" 8 | 9 | extra-source-files: 10 | - README.md 11 | - CHANGELOG.md 12 | 13 | # Metadata used when publishing your package 14 | # synopsis: Short description of your package 15 | # category: Web 16 | 17 | # To avoid duplicated efforts in documentation and dealing with the 18 | # complications of embedding Haddock markup inside cabal files, it is 19 | # common to point users to the README.md file. 20 | description: Please see the README on GitHub at 21 | 22 | dependencies: 23 | - base >= 4.7 && < 5 24 | - atomic-write >= 0.2 25 | - text >= 2.0 26 | - filepath >= 1.4 && < 2 27 | 28 | ghc-options: 29 | - -Wall 30 | - -Wcompat 31 | - -Widentities 32 | - -Wincomplete-record-updates 33 | - -Wincomplete-uni-patterns 34 | - -Wmissing-export-lists 35 | - -Wmissing-home-modules 36 | - -Wpartial-fields 37 | - -Wredundant-constraints 38 | 39 | library: 40 | source-dirs: src 41 | 42 | executables: 43 | logger-example-exe: 44 | main: Main.hs 45 | source-dirs: app 46 | ghc-options: 47 | - -threaded 48 | - -rtsopts 49 | - -with-rtsopts=-N 50 | dependencies: 51 | - logger-example 52 | 53 | -------------------------------------------------------------------------------- /examples/logger/stack.yaml: -------------------------------------------------------------------------------- 1 | 2 | resolver: 3 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/21/19.yaml 4 | 5 | 6 | packages: 7 | - . 8 | -------------------------------------------------------------------------------- /examples/logger/stack.yaml.lock: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by Stack. 2 | # You should not edit this file by hand. 3 | # For more information, please see the documentation at: 4 | # https://docs.haskellstack.org/en/stable/lock_files 5 | 6 | packages: [] 7 | snapshots: 8 | - completed: 9 | sha256: fb482b8e2d5d061cdda4ba1da2957c012740c893a5ee1c1b99001adae7b1fbe7 10 | size: 640046 11 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/21/19.yaml 12 | original: 13 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/21/19.yaml 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atomic-write", 3 | "version": "0.0.0", 4 | "description": "Writes files atomically in Haskell while preserving permissions", 5 | "repository": "git@github.com:stackbuilders/atomic-write.git", 6 | "author": "Stack Builders ", 7 | "license": "MIT", 8 | "engines": { 9 | "node": "21.6.2" 10 | }, 11 | "scripts": { 12 | "release": "semantic-release" 13 | }, 14 | "devDependencies": { 15 | "semantic-release": "^24.0.0", 16 | "semantic-release-hackage": "^1.1.2" 17 | }, 18 | "packageManager": "yarn@4.1.0" 19 | } 20 | -------------------------------------------------------------------------------- /spec/Spec.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -F -pgmF hspec-discover #-} 2 | -------------------------------------------------------------------------------- /spec/System/AtomicWrite/Writer/ByteString/BinarySpec.hs: -------------------------------------------------------------------------------- 1 | module System.AtomicWrite.Writer.ByteString.BinarySpec (spec) where 2 | 3 | 4 | import Test.Hspec (Spec, describe, 5 | it, shouldBe) 6 | 7 | import System.AtomicWrite.Writer.ByteString.Binary (atomicWriteFile, atomicWriteFileWithMode) 8 | 9 | import System.FilePath.Posix (joinPath) 10 | import System.IO.Temp (withSystemTempDirectory) 11 | import System.PosixCompat.Files (fileMode, 12 | getFileStatus, 13 | setFileCreationMask, 14 | setFileMode) 15 | 16 | import Data.ByteString.Char8 (pack) 17 | 18 | 19 | spec :: Spec 20 | spec = do 21 | describe "atomicWriteFile" $ do 22 | it "writes the contents to a file" $ 23 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 24 | 25 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 26 | 27 | atomicWriteFile path $ pack "just testing" 28 | contents <- readFile path 29 | 30 | contents `shouldBe` "just testing" 31 | it "preserves the permissions of original file, regardless of umask" $ 32 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 33 | let filePath = joinPath [tmpDir, "testFile"] 34 | 35 | writeFile filePath "initial contents" 36 | setFileMode filePath 0o100644 37 | 38 | newStat <- getFileStatus filePath 39 | fileMode newStat `shouldBe` 0o100644 40 | 41 | -- New files are created with 100600 perms. 42 | _ <- setFileCreationMask 0o100066 43 | 44 | -- Create a new file once different mask is set and make sure that mask 45 | -- is applied. 46 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 47 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 48 | fileMode sanityCheckStat `shouldBe` 0o100600 49 | 50 | -- Since we move, this makes the new file assume the filemask of 0600 51 | atomicWriteFile filePath $ pack "new contents" 52 | 53 | resultStat <- getFileStatus filePath 54 | 55 | -- reset mask to not break subsequent specs 56 | _ <- setFileCreationMask 0o100022 57 | 58 | -- Fails when using atomic mv command unless apply perms on initial file 59 | fileMode resultStat `shouldBe` 0o100644 60 | 61 | 62 | it "creates a new file with permissions based on active umask" $ 63 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 64 | let 65 | filePath = joinPath [tmpDir, "testFile"] 66 | sampleFilePath = joinPath [tmpDir, "sampleFile"] 67 | 68 | -- Set somewhat distinctive defaults for test 69 | _ <- setFileCreationMask 0o100171 70 | 71 | -- We don't know what the default file permissions are, so create a 72 | -- file to sample them. 73 | writeFile sampleFilePath "I'm being written to sample permissions" 74 | 75 | newStat <- getFileStatus sampleFilePath 76 | fileMode newStat `shouldBe` 0o100606 77 | 78 | atomicWriteFile filePath $ pack "new contents" 79 | 80 | resultStat <- getFileStatus filePath 81 | 82 | -- reset mask to not break subsequent specs 83 | _ <- setFileCreationMask 0o100022 84 | 85 | -- The default tempfile permissions are 0600, so this fails unless we 86 | -- make sure that the default umask is relied on for creation of the 87 | -- tempfile. 88 | fileMode resultStat `shouldBe` 0o100606 89 | describe "atomicWriteFileWithMode" $ do 90 | it "writes contents to a file" $ 91 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 92 | 93 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 94 | 95 | atomicWriteFileWithMode 0o100777 path $ pack "just testing" 96 | 97 | contents <- readFile path 98 | 99 | contents `shouldBe` "just testing" 100 | 101 | 102 | it "changes the permissions of a previously created file, regardless of umask" $ 103 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 104 | let filePath = joinPath [tmpDir, "testFile"] 105 | 106 | writeFile filePath "initial contents" 107 | setFileMode filePath 0o100644 108 | 109 | newStat <- getFileStatus filePath 110 | fileMode newStat `shouldBe` 0o100644 111 | 112 | -- New files are created with 100600 perms. 113 | _ <- setFileCreationMask 0o100066 114 | 115 | -- Create a new file once different mask is set and make sure that mask 116 | -- is applied. 117 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 118 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 119 | fileMode sanityCheckStat `shouldBe` 0o100600 120 | 121 | -- Since we move, this makes the new file assume the filemask of 0600 122 | atomicWriteFileWithMode 0o100655 filePath $ pack "new contents" 123 | 124 | resultStat <- getFileStatus filePath 125 | 126 | -- reset mask to not break subsequent specs 127 | _ <- setFileCreationMask 0o100022 128 | 129 | -- Fails when using atomic mv command unless apply perms on initial file 130 | fileMode resultStat `shouldBe` 0o100655 131 | 132 | 133 | it "creates a new file with specified permissions" $ 134 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 135 | let 136 | filePath = joinPath [tmpDir, "testFile"] 137 | atomicWriteFileWithMode 0o100606 filePath $ pack "new contents" 138 | 139 | resultStat <- getFileStatus filePath 140 | 141 | fileMode resultStat `shouldBe` 0o100606 142 | 143 | -------------------------------------------------------------------------------- /spec/System/AtomicWrite/Writer/ByteStringBuilderSpec.hs: -------------------------------------------------------------------------------- 1 | module System.AtomicWrite.Writer.ByteStringBuilderSpec (spec) where 2 | 3 | import Test.Hspec (it, describe, shouldBe, Spec) 4 | 5 | import System.AtomicWrite.Writer.ByteStringBuilder (atomicWriteFile, atomicWriteFileWithMode) 6 | 7 | import System.IO.Temp (withSystemTempDirectory) 8 | import System.FilePath.Posix (joinPath) 9 | import System.PosixCompat.Files 10 | (setFileMode, setFileCreationMask, getFileStatus, fileMode) 11 | 12 | import Data.ByteString.Builder (lazyByteString) 13 | 14 | import Data.ByteString.Lazy.Char8 (pack) 15 | 16 | spec :: Spec 17 | spec = do 18 | describe "atomicWriteFile" $ do 19 | it "writes contents to a file" $ 20 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 21 | 22 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 23 | 24 | atomicWriteFile path $ lazyByteString $ pack "just testing" 25 | contents <- readFile path 26 | 27 | contents `shouldBe` "just testing" 28 | it "preserves the permissions of original file, regardless of umask" $ 29 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 30 | let filePath = joinPath [tmpDir, "testFile"] 31 | 32 | writeFile filePath "initial contents" 33 | setFileMode filePath 0o100644 34 | 35 | newStat <- getFileStatus filePath 36 | fileMode newStat `shouldBe` 0o100644 37 | 38 | -- New files are created with 100600 perms. 39 | _ <- setFileCreationMask 0o100066 40 | 41 | -- Create a new file once different mask is set and make sure that mask 42 | -- is applied. 43 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 44 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 45 | fileMode sanityCheckStat `shouldBe` 0o100600 46 | 47 | -- Since we move, this makes the new file assume the filemask of 0600 48 | atomicWriteFile filePath $ lazyByteString $ pack "new contents" 49 | 50 | resultStat <- getFileStatus filePath 51 | 52 | -- reset mask to not break subsequent specs 53 | _ <- setFileCreationMask 0o100022 54 | 55 | -- Fails when using atomic mv command unless apply perms on initial file 56 | fileMode resultStat `shouldBe` 0o100644 57 | 58 | 59 | it "creates a new file with permissions based on active umask" $ 60 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 61 | let 62 | filePath = joinPath [tmpDir, "testFile"] 63 | sampleFilePath = joinPath [tmpDir, "sampleFile"] 64 | 65 | -- Set somewhat distinctive defaults for test 66 | _ <- setFileCreationMask 0o100171 67 | 68 | -- We don't know what the default file permissions are, so create a 69 | -- file to sample them. 70 | writeFile sampleFilePath "I'm being written to sample permissions" 71 | 72 | newStat <- getFileStatus sampleFilePath 73 | fileMode newStat `shouldBe` 0o100606 74 | 75 | atomicWriteFile filePath $ lazyByteString $ pack "new contents" 76 | 77 | resultStat <- getFileStatus filePath 78 | 79 | -- reset mask to not break subsequent specs 80 | _ <- setFileCreationMask 0o100022 81 | 82 | -- The default tempfile permissions are 0600, so this fails unless we 83 | -- make sure that the default umask is relied on for creation of the 84 | -- tempfile. 85 | fileMode resultStat `shouldBe` 0o100606 86 | describe "atomicWriteFileWithMode" $ do 87 | it "writes contents to a file" $ 88 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 89 | 90 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 91 | 92 | atomicWriteFileWithMode 0o100777 path $ lazyByteString $ pack "just testing" 93 | 94 | contents <- readFile path 95 | 96 | contents `shouldBe` "just testing" 97 | 98 | 99 | it "changes the permissions of a previously created file, regardless of umask" $ 100 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 101 | let filePath = joinPath [tmpDir, "testFile"] 102 | 103 | writeFile filePath "initial contents" 104 | setFileMode filePath 0o100644 105 | 106 | newStat <- getFileStatus filePath 107 | fileMode newStat `shouldBe` 0o100644 108 | 109 | -- New files are created with 100600 perms. 110 | _ <- setFileCreationMask 0o100066 111 | 112 | -- Create a new file once different mask is set and make sure that mask 113 | -- is applied. 114 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 115 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 116 | fileMode sanityCheckStat `shouldBe` 0o100600 117 | 118 | -- Since we move, this makes the new file assume the filemask of 0600 119 | atomicWriteFileWithMode 0o100655 filePath $ lazyByteString $ pack "new contents" 120 | 121 | resultStat <- getFileStatus filePath 122 | 123 | -- reset mask to not break subsequent specs 124 | _ <- setFileCreationMask 0o100022 125 | 126 | -- Fails when using atomic mv command unless apply perms on initial file 127 | fileMode resultStat `shouldBe` 0o100655 128 | 129 | 130 | it "creates a new file with specified permissions" $ 131 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 132 | let 133 | filePath = joinPath [tmpDir, "testFile"] 134 | atomicWriteFileWithMode 0o100606 filePath $ lazyByteString $ pack "new contents" 135 | 136 | resultStat <- getFileStatus filePath 137 | 138 | fileMode resultStat `shouldBe` 0o100606 139 | -------------------------------------------------------------------------------- /spec/System/AtomicWrite/Writer/ByteStringSpec.hs: -------------------------------------------------------------------------------- 1 | module System.AtomicWrite.Writer.ByteStringSpec (spec) where 2 | 3 | import Test.Hspec (Spec, describe, it, 4 | shouldBe) 5 | 6 | import System.AtomicWrite.Writer.ByteString (atomicWriteFile, 7 | atomicWriteFileWithMode) 8 | 9 | import System.FilePath.Posix (joinPath) 10 | import System.IO.Temp (withSystemTempDirectory) 11 | import System.PosixCompat.Files (fileMode, getFileStatus, 12 | setFileCreationMask, 13 | setFileMode) 14 | 15 | import Data.ByteString.Char8 (pack) 16 | 17 | spec :: Spec 18 | spec = do 19 | describe "atomicWriteFile" $ do 20 | it "writes contents to a file" $ 21 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 22 | 23 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 24 | 25 | atomicWriteFile path $ pack "just testing" 26 | contents <- readFile path 27 | 28 | contents `shouldBe` "just testing" 29 | it "preserves the permissions of original file, regardless of umask" $ 30 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 31 | let filePath = joinPath [tmpDir, "testFile"] 32 | 33 | writeFile filePath "initial contents" 34 | setFileMode filePath 0o100644 35 | 36 | newStat <- getFileStatus filePath 37 | fileMode newStat `shouldBe` 0o100644 38 | 39 | -- New files are created with 100600 perms. 40 | _ <- setFileCreationMask 0o100066 41 | 42 | -- Create a new file once different mask is set and make sure that mask 43 | -- is applied. 44 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 45 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 46 | fileMode sanityCheckStat `shouldBe` 0o100600 47 | 48 | -- Since we move, this makes the new file assume the filemask of 0600 49 | atomicWriteFile filePath $ pack "new contents" 50 | 51 | resultStat <- getFileStatus filePath 52 | 53 | -- reset mask to not break subsequent specs 54 | _ <- setFileCreationMask 0o100022 55 | 56 | -- Fails when using atomic mv command unless apply perms on initial file 57 | fileMode resultStat `shouldBe` 0o100644 58 | 59 | 60 | it "creates a new file with permissions based on active umask" $ 61 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 62 | let 63 | filePath = joinPath [tmpDir, "testFile"] 64 | sampleFilePath = joinPath [tmpDir, "sampleFile"] 65 | 66 | -- Set somewhat distinctive defaults for test 67 | _ <- setFileCreationMask 0o100171 68 | 69 | -- We don't know what the default file permissions are, so create a 70 | -- file to sample them. 71 | writeFile sampleFilePath "I'm being written to sample permissions" 72 | 73 | newStat <- getFileStatus sampleFilePath 74 | fileMode newStat `shouldBe` 0o100606 75 | 76 | atomicWriteFile filePath $ pack "new contents" 77 | 78 | resultStat <- getFileStatus filePath 79 | 80 | -- reset mask to not break subsequent specs 81 | _ <- setFileCreationMask 0o100022 82 | 83 | -- The default tempfile permissions are 0600, so this fails unless we 84 | -- make sure that the default umask is relied on for creation of the 85 | -- tempfile. 86 | fileMode resultStat `shouldBe` 0o100606 87 | describe "atomicWriteFileWithMode" $ do 88 | it "writes contents to a file" $ 89 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 90 | 91 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 92 | 93 | atomicWriteFileWithMode 0o100777 path $ pack "just testing" 94 | 95 | contents <- readFile path 96 | 97 | contents `shouldBe` "just testing" 98 | 99 | 100 | it "changes the permissions of a previously created file, regardless of umask" $ 101 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 102 | let filePath = joinPath [tmpDir, "testFile"] 103 | 104 | writeFile filePath "initial contents" 105 | setFileMode filePath 0o100644 106 | 107 | newStat <- getFileStatus filePath 108 | fileMode newStat `shouldBe` 0o100644 109 | 110 | -- New files are created with 100600 perms. 111 | _ <- setFileCreationMask 0o100066 112 | 113 | -- Create a new file once different mask is set and make sure that mask 114 | -- is applied. 115 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 116 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 117 | fileMode sanityCheckStat `shouldBe` 0o100600 118 | 119 | -- Since we move, this makes the new file assume the filemask of 0600 120 | atomicWriteFileWithMode 0o100655 filePath $ pack "new contents" 121 | 122 | resultStat <- getFileStatus filePath 123 | 124 | -- reset mask to not break subsequent specs 125 | _ <- setFileCreationMask 0o100022 126 | 127 | -- Fails when using atomic mv command unless apply perms on initial file 128 | fileMode resultStat `shouldBe` 0o100655 129 | 130 | 131 | it "creates a new file with specified permissions" $ 132 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 133 | let 134 | filePath = joinPath [tmpDir, "testFile"] 135 | atomicWriteFileWithMode 0o100606 filePath $ pack "new contents" 136 | 137 | resultStat <- getFileStatus filePath 138 | 139 | fileMode resultStat `shouldBe` 0o100606 140 | 141 | -------------------------------------------------------------------------------- /spec/System/AtomicWrite/Writer/LazyByteString/BinarySpec.hs: -------------------------------------------------------------------------------- 1 | module System.AtomicWrite.Writer.LazyByteString.BinarySpec (spec) where 2 | 3 | import Test.Hspec (Spec, 4 | describe, it, 5 | shouldBe) 6 | 7 | import System.AtomicWrite.Writer.LazyByteString.Binary (atomicWriteFile, 8 | atomicWriteFileWithMode) 9 | 10 | import System.FilePath.Posix (joinPath) 11 | import System.IO.Temp (withSystemTempDirectory) 12 | import System.PosixCompat.Files (fileMode, 13 | getFileStatus, 14 | setFileCreationMask, 15 | setFileMode) 16 | 17 | import Data.ByteString.Lazy.Char8 (pack) 18 | 19 | spec :: Spec 20 | spec = do 21 | describe "atomicWriteFile" $ do 22 | it "writes contents to a file" $ 23 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 24 | 25 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 26 | 27 | atomicWriteFile path $ pack "just testing" 28 | contents <- readFile path 29 | 30 | contents `shouldBe` "just testing" 31 | it "preserves the permissions of original file, regardless of umask" $ 32 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 33 | let filePath = joinPath [tmpDir, "testFile"] 34 | 35 | writeFile filePath "initial contents" 36 | setFileMode filePath 0o100644 37 | 38 | newStat <- getFileStatus filePath 39 | fileMode newStat `shouldBe` 0o100644 40 | 41 | -- New files are created with 100600 perms. 42 | _ <- setFileCreationMask 0o100066 43 | 44 | -- Create a new file once different mask is set and make sure that mask 45 | -- is applied. 46 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 47 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 48 | fileMode sanityCheckStat `shouldBe` 0o100600 49 | 50 | -- Since we move, this makes the new file assume the filemask of 0600 51 | atomicWriteFile filePath $ pack "new contents" 52 | 53 | resultStat <- getFileStatus filePath 54 | 55 | -- reset mask to not break subsequent specs 56 | _ <- setFileCreationMask 0o100022 57 | 58 | -- Fails when using atomic mv command unless apply perms on initial file 59 | fileMode resultStat `shouldBe` 0o100644 60 | 61 | 62 | it "creates a new file with permissions based on active umask" $ 63 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 64 | let 65 | filePath = joinPath [tmpDir, "testFile"] 66 | sampleFilePath = joinPath [tmpDir, "sampleFile"] 67 | 68 | -- Set somewhat distinctive defaults for test 69 | _ <- setFileCreationMask 0o100171 70 | 71 | -- We don't know what the default file permissions are, so create a 72 | -- file to sample them. 73 | writeFile sampleFilePath "I'm being written to sample permissions" 74 | 75 | newStat <- getFileStatus sampleFilePath 76 | fileMode newStat `shouldBe` 0o100606 77 | 78 | atomicWriteFile filePath $ pack "new contents" 79 | 80 | resultStat <- getFileStatus filePath 81 | 82 | -- reset mask to not break subsequent specs 83 | _ <- setFileCreationMask 0o100022 84 | 85 | -- The default tempfile permissions are 0600, so this fails unless we 86 | -- make sure that the default umask is relied on for creation of the 87 | -- tempfile. 88 | fileMode resultStat `shouldBe` 0o100606 89 | describe "atomicWriteFileWithMode" $ do 90 | it "writes contents to a file" $ 91 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 92 | 93 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 94 | 95 | atomicWriteFileWithMode 0o100777 path $ pack "just testing" 96 | 97 | contents <- readFile path 98 | 99 | contents `shouldBe` "just testing" 100 | 101 | 102 | it "changes the permissions of a previously created file, regardless of umask" $ 103 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 104 | let filePath = joinPath [tmpDir, "testFile"] 105 | 106 | writeFile filePath "initial contents" 107 | setFileMode filePath 0o100644 108 | 109 | newStat <- getFileStatus filePath 110 | fileMode newStat `shouldBe` 0o100644 111 | 112 | -- New files are created with 100600 perms. 113 | _ <- setFileCreationMask 0o100066 114 | 115 | -- Create a new file once different mask is set and make sure that mask 116 | -- is applied. 117 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 118 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 119 | fileMode sanityCheckStat `shouldBe` 0o100600 120 | 121 | -- Since we move, this makes the new file assume the filemask of 0600 122 | atomicWriteFileWithMode 0o100655 filePath $ pack "new contents" 123 | 124 | resultStat <- getFileStatus filePath 125 | 126 | -- reset mask to not break subsequent specs 127 | _ <- setFileCreationMask 0o100022 128 | 129 | -- Fails when using atomic mv command unless apply perms on initial file 130 | fileMode resultStat `shouldBe` 0o100655 131 | 132 | 133 | it "creates a new file with specified permissions" $ 134 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 135 | let 136 | filePath = joinPath [tmpDir, "testFile"] 137 | atomicWriteFileWithMode 0o100606 filePath $ pack "new contents" 138 | 139 | resultStat <- getFileStatus filePath 140 | 141 | fileMode resultStat `shouldBe` 0o100606 142 | -------------------------------------------------------------------------------- /spec/System/AtomicWrite/Writer/LazyByteStringSpec.hs: -------------------------------------------------------------------------------- 1 | module System.AtomicWrite.Writer.LazyByteStringSpec (spec) where 2 | 3 | import Test.Hspec (it, describe, shouldBe, Spec) 4 | 5 | import System.AtomicWrite.Writer.LazyByteString (atomicWriteFile, atomicWriteFileWithMode) 6 | 7 | import System.IO.Temp (withSystemTempDirectory) 8 | import System.FilePath.Posix (joinPath) 9 | import System.PosixCompat.Files 10 | (setFileMode, setFileCreationMask, getFileStatus, fileMode) 11 | 12 | import Data.ByteString.Lazy.Char8 (pack) 13 | 14 | spec :: Spec 15 | spec = do 16 | describe "atomicWriteFile" $ do 17 | it "writes contents to a file" $ 18 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 19 | 20 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 21 | 22 | atomicWriteFile path $ pack "just testing" 23 | contents <- readFile path 24 | 25 | contents `shouldBe` "just testing" 26 | it "preserves the permissions of original file, regardless of umask" $ 27 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 28 | let filePath = joinPath [tmpDir, "testFile"] 29 | 30 | writeFile filePath "initial contents" 31 | setFileMode filePath 0o100644 32 | 33 | newStat <- getFileStatus filePath 34 | fileMode newStat `shouldBe` 0o100644 35 | 36 | -- New files are created with 100600 perms. 37 | _ <- setFileCreationMask 0o100066 38 | 39 | -- Create a new file once different mask is set and make sure that mask 40 | -- is applied. 41 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 42 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 43 | fileMode sanityCheckStat `shouldBe` 0o100600 44 | 45 | -- Since we move, this makes the new file assume the filemask of 0600 46 | atomicWriteFile filePath $ pack "new contents" 47 | 48 | resultStat <- getFileStatus filePath 49 | 50 | -- reset mask to not break subsequent specs 51 | _ <- setFileCreationMask 0o100022 52 | 53 | -- Fails when using atomic mv command unless apply perms on initial file 54 | fileMode resultStat `shouldBe` 0o100644 55 | 56 | 57 | it "creates a new file with permissions based on active umask" $ 58 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 59 | let 60 | filePath = joinPath [tmpDir, "testFile"] 61 | sampleFilePath = joinPath [tmpDir, "sampleFile"] 62 | 63 | -- Set somewhat distinctive defaults for test 64 | _ <- setFileCreationMask 0o100171 65 | 66 | -- We don't know what the default file permissions are, so create a 67 | -- file to sample them. 68 | writeFile sampleFilePath "I'm being written to sample permissions" 69 | 70 | newStat <- getFileStatus sampleFilePath 71 | fileMode newStat `shouldBe` 0o100606 72 | 73 | atomicWriteFile filePath $ pack "new contents" 74 | 75 | resultStat <- getFileStatus filePath 76 | 77 | -- reset mask to not break subsequent specs 78 | _ <- setFileCreationMask 0o100022 79 | 80 | -- The default tempfile permissions are 0600, so this fails unless we 81 | -- make sure that the default umask is relied on for creation of the 82 | -- tempfile. 83 | fileMode resultStat `shouldBe` 0o100606 84 | describe "atomicWriteFileWithMode" $ do 85 | it "writes contents to a file" $ 86 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 87 | 88 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 89 | 90 | atomicWriteFileWithMode 0o100777 path $ pack "just testing" 91 | 92 | contents <- readFile path 93 | 94 | contents `shouldBe` "just testing" 95 | 96 | 97 | it "changes the permissions of a previously created file, regardless of umask" $ 98 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 99 | let filePath = joinPath [tmpDir, "testFile"] 100 | 101 | writeFile filePath "initial contents" 102 | setFileMode filePath 0o100644 103 | 104 | newStat <- getFileStatus filePath 105 | fileMode newStat `shouldBe` 0o100644 106 | 107 | -- New files are created with 100600 perms. 108 | _ <- setFileCreationMask 0o100066 109 | 110 | -- Create a new file once different mask is set and make sure that mask 111 | -- is applied. 112 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 113 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 114 | fileMode sanityCheckStat `shouldBe` 0o100600 115 | 116 | -- Since we move, this makes the new file assume the filemask of 0600 117 | atomicWriteFileWithMode 0o100655 filePath $ pack "new contents" 118 | 119 | resultStat <- getFileStatus filePath 120 | 121 | -- reset mask to not break subsequent specs 122 | _ <- setFileCreationMask 0o100022 123 | 124 | -- Fails when using atomic mv command unless apply perms on initial file 125 | fileMode resultStat `shouldBe` 0o100655 126 | 127 | 128 | it "creates a new file with specified permissions" $ 129 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 130 | let 131 | filePath = joinPath [tmpDir, "testFile"] 132 | atomicWriteFileWithMode 0o100606 filePath $ pack "new contents" 133 | 134 | resultStat <- getFileStatus filePath 135 | 136 | fileMode resultStat `shouldBe` 0o100606 137 | -------------------------------------------------------------------------------- /spec/System/AtomicWrite/Writer/LazyText/BinarySpec.hs: -------------------------------------------------------------------------------- 1 | module System.AtomicWrite.Writer.LazyText.BinarySpec (spec) where 2 | 3 | import Test.Hspec (Spec, describe, it, 4 | shouldBe) 5 | 6 | import System.AtomicWrite.Writer.LazyText.Binary (atomicWriteFile, atomicWriteFileWithMode) 7 | 8 | import System.FilePath.Posix (joinPath) 9 | import System.IO.Temp (withSystemTempDirectory) 10 | import System.PosixCompat.Files (fileMode, 11 | getFileStatus, 12 | setFileCreationMask, 13 | setFileMode) 14 | 15 | import Data.Text.Lazy (pack) 16 | 17 | spec :: Spec 18 | spec = do 19 | describe "atomicWriteFile" $ do 20 | it "writes contents to a file" $ 21 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 22 | 23 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 24 | 25 | atomicWriteFile path $ pack "just testing" 26 | contents <- readFile path 27 | 28 | contents `shouldBe` "just testing" 29 | it "preserves the permissions of original file, regardless of umask" $ 30 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 31 | let filePath = joinPath [tmpDir, "testFile"] 32 | 33 | writeFile filePath "initial contents" 34 | setFileMode filePath 0o100644 35 | 36 | newStat <- getFileStatus filePath 37 | fileMode newStat `shouldBe` 0o100644 38 | 39 | -- New files are created with 100600 perms. 40 | _ <- setFileCreationMask 0o100066 41 | 42 | -- Create a new file once different mask is set and make sure that mask 43 | -- is applied. 44 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 45 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 46 | fileMode sanityCheckStat `shouldBe` 0o100600 47 | 48 | -- Since we move, this makes the new file assume the filemask of 0600 49 | atomicWriteFile filePath $ pack "new contents" 50 | 51 | resultStat <- getFileStatus filePath 52 | 53 | -- reset mask to not break subsequent specs 54 | _ <- setFileCreationMask 0o100022 55 | 56 | -- Fails when using atomic mv command unless apply perms on initial file 57 | fileMode resultStat `shouldBe` 0o100644 58 | 59 | 60 | it "creates a new file with permissions based on active umask" $ 61 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 62 | let 63 | filePath = joinPath [tmpDir, "testFile"] 64 | sampleFilePath = joinPath [tmpDir, "sampleFile"] 65 | 66 | -- Set somewhat distinctive defaults for test 67 | _ <- setFileCreationMask 0o100171 68 | 69 | -- We don't know what the default file permissions are, so create a 70 | -- file to sample them. 71 | writeFile sampleFilePath "I'm being written to sample permissions" 72 | 73 | newStat <- getFileStatus sampleFilePath 74 | fileMode newStat `shouldBe` 0o100606 75 | 76 | atomicWriteFile filePath $ pack "new contents" 77 | 78 | resultStat <- getFileStatus filePath 79 | 80 | -- reset mask to not break subsequent specs 81 | _ <- setFileCreationMask 0o100022 82 | 83 | -- The default tempfile permissions are 0600, so this fails unless we 84 | -- make sure that the default umask is relied on for creation of the 85 | -- tempfile. 86 | fileMode resultStat `shouldBe` 0o100606 87 | describe "atomicWriteFileWithMode" $ do 88 | it "writes contents to a file" $ 89 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 90 | 91 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 92 | 93 | atomicWriteFileWithMode 0o100777 path $ pack "just testing" 94 | 95 | contents <- readFile path 96 | 97 | contents `shouldBe` "just testing" 98 | 99 | 100 | it "changes the permissions of a previously created file, regardless of umask" $ 101 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 102 | let filePath = joinPath [tmpDir, "testFile"] 103 | 104 | writeFile filePath "initial contents" 105 | setFileMode filePath 0o100644 106 | 107 | newStat <- getFileStatus filePath 108 | fileMode newStat `shouldBe` 0o100644 109 | 110 | -- New files are created with 100600 perms. 111 | _ <- setFileCreationMask 0o100066 112 | 113 | -- Create a new file once different mask is set and make sure that mask 114 | -- is applied. 115 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 116 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 117 | fileMode sanityCheckStat `shouldBe` 0o100600 118 | 119 | -- Since we move, this makes the new file assume the filemask of 0600 120 | atomicWriteFileWithMode 0o100655 filePath $ pack "new contents" 121 | 122 | resultStat <- getFileStatus filePath 123 | 124 | -- reset mask to not break subsequent specs 125 | _ <- setFileCreationMask 0o100022 126 | 127 | -- Fails when using atomic mv command unless apply perms on initial file 128 | fileMode resultStat `shouldBe` 0o100655 129 | 130 | 131 | it "creates a new file with specified permissions" $ 132 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 133 | let 134 | filePath = joinPath [tmpDir, "testFile"] 135 | atomicWriteFileWithMode 0o100606 filePath $ pack "new contents" 136 | 137 | resultStat <- getFileStatus filePath 138 | 139 | fileMode resultStat `shouldBe` 0o100606 140 | -------------------------------------------------------------------------------- /spec/System/AtomicWrite/Writer/LazyTextSpec.hs: -------------------------------------------------------------------------------- 1 | module System.AtomicWrite.Writer.LazyTextSpec (spec) where 2 | 3 | import Test.Hspec (it, describe, shouldBe, Spec) 4 | 5 | import System.AtomicWrite.Writer.LazyText (atomicWriteFile, atomicWriteFileWithMode) 6 | 7 | import System.IO.Temp (withSystemTempDirectory) 8 | import System.FilePath.Posix (joinPath) 9 | import System.PosixCompat.Files 10 | (setFileMode, setFileCreationMask, getFileStatus, fileMode) 11 | 12 | import Data.Text.Lazy (pack) 13 | 14 | spec :: Spec 15 | spec = do 16 | describe "atomicWriteFile" $ do 17 | it "writes contents to a file" $ 18 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 19 | 20 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 21 | 22 | atomicWriteFile path $ pack "just testing" 23 | contents <- readFile path 24 | 25 | contents `shouldBe` "just testing" 26 | it "preserves the permissions of original file, regardless of umask" $ 27 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 28 | let filePath = joinPath [tmpDir, "testFile"] 29 | 30 | writeFile filePath "initial contents" 31 | setFileMode filePath 0o100644 32 | 33 | newStat <- getFileStatus filePath 34 | fileMode newStat `shouldBe` 0o100644 35 | 36 | -- New files are created with 100600 perms. 37 | _ <- setFileCreationMask 0o100066 38 | 39 | -- Create a new file once different mask is set and make sure that mask 40 | -- is applied. 41 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 42 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 43 | fileMode sanityCheckStat `shouldBe` 0o100600 44 | 45 | -- Since we move, this makes the new file assume the filemask of 0600 46 | atomicWriteFile filePath $ pack "new contents" 47 | 48 | resultStat <- getFileStatus filePath 49 | 50 | -- reset mask to not break subsequent specs 51 | _ <- setFileCreationMask 0o100022 52 | 53 | -- Fails when using atomic mv command unless apply perms on initial file 54 | fileMode resultStat `shouldBe` 0o100644 55 | 56 | 57 | it "creates a new file with permissions based on active umask" $ 58 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 59 | let 60 | filePath = joinPath [tmpDir, "testFile"] 61 | sampleFilePath = joinPath [tmpDir, "sampleFile"] 62 | 63 | -- Set somewhat distinctive defaults for test 64 | _ <- setFileCreationMask 0o100171 65 | 66 | -- We don't know what the default file permissions are, so create a 67 | -- file to sample them. 68 | writeFile sampleFilePath "I'm being written to sample permissions" 69 | 70 | newStat <- getFileStatus sampleFilePath 71 | fileMode newStat `shouldBe` 0o100606 72 | 73 | atomicWriteFile filePath $ pack "new contents" 74 | 75 | resultStat <- getFileStatus filePath 76 | 77 | -- reset mask to not break subsequent specs 78 | _ <- setFileCreationMask 0o100022 79 | 80 | -- The default tempfile permissions are 0600, so this fails unless we 81 | -- make sure that the default umask is relied on for creation of the 82 | -- tempfile. 83 | fileMode resultStat `shouldBe` 0o100606 84 | describe "atomicWriteFileWithMode" $ do 85 | it "writes contents to a file" $ 86 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 87 | 88 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 89 | 90 | atomicWriteFileWithMode 0o100777 path $ pack "just testing" 91 | 92 | contents <- readFile path 93 | 94 | contents `shouldBe` "just testing" 95 | 96 | 97 | it "changes the permissions of a previously created file, regardless of umask" $ 98 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 99 | let filePath = joinPath [tmpDir, "testFile"] 100 | 101 | writeFile filePath "initial contents" 102 | setFileMode filePath 0o100644 103 | 104 | newStat <- getFileStatus filePath 105 | fileMode newStat `shouldBe` 0o100644 106 | 107 | -- New files are created with 100600 perms. 108 | _ <- setFileCreationMask 0o100066 109 | 110 | -- Create a new file once different mask is set and make sure that mask 111 | -- is applied. 112 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 113 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 114 | fileMode sanityCheckStat `shouldBe` 0o100600 115 | 116 | -- Since we move, this makes the new file assume the filemask of 0600 117 | atomicWriteFileWithMode 0o100655 filePath $ pack "new contents" 118 | 119 | resultStat <- getFileStatus filePath 120 | 121 | -- reset mask to not break subsequent specs 122 | _ <- setFileCreationMask 0o100022 123 | 124 | -- Fails when using atomic mv command unless apply perms on initial file 125 | fileMode resultStat `shouldBe` 0o100655 126 | 127 | 128 | it "creates a new file with specified permissions" $ 129 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 130 | let 131 | filePath = joinPath [tmpDir, "testFile"] 132 | atomicWriteFileWithMode 0o100606 filePath $ pack "new contents" 133 | 134 | resultStat <- getFileStatus filePath 135 | 136 | fileMode resultStat `shouldBe` 0o100606 137 | -------------------------------------------------------------------------------- /spec/System/AtomicWrite/Writer/String/BinarySpec.hs: -------------------------------------------------------------------------------- 1 | module System.AtomicWrite.Writer.String.BinarySpec (spec) where 2 | 3 | import Test.Hspec (Spec, describe, it, 4 | shouldBe) 5 | 6 | import System.AtomicWrite.Writer.String.Binary (atomicWriteFile, atomicWriteFileWithMode) 7 | 8 | import System.FilePath (joinPath) 9 | import System.IO.Temp (withSystemTempDirectory) 10 | import System.PosixCompat.Files (fileMode, 11 | getFileStatus, 12 | setFileCreationMask, 13 | setFileMode) 14 | 15 | 16 | {-# ANN module "HLint: ignore Reduce duplication" #-} 17 | 18 | spec :: Spec 19 | spec = do 20 | describe "atomicWriteFile" $ do 21 | it "writes contents to a file" $ 22 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 23 | 24 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 25 | 26 | atomicWriteFile path "just testing" 27 | 28 | contents <- readFile path 29 | 30 | contents `shouldBe` "just testing" 31 | 32 | 33 | it "preserves the permissions of original file, regardless of umask" $ 34 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 35 | let filePath = joinPath [tmpDir, "testFile"] 36 | 37 | writeFile filePath "initial contents" 38 | setFileMode filePath 0o100644 39 | 40 | newStat <- getFileStatus filePath 41 | fileMode newStat `shouldBe` 0o100644 42 | 43 | -- New files are created with 100600 perms. 44 | _ <- setFileCreationMask 0o100066 45 | 46 | -- Create a new file once different mask is set and make sure that mask 47 | -- is applied. 48 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 49 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 50 | fileMode sanityCheckStat `shouldBe` 0o100600 51 | 52 | -- Since we move, this makes the new file assume the filemask of 0600 53 | atomicWriteFile filePath "new contents" 54 | 55 | resultStat <- getFileStatus filePath 56 | 57 | -- reset mask to not break subsequent specs 58 | _ <- setFileCreationMask 0o100022 59 | 60 | -- Fails when using atomic mv command unless apply perms on initial file 61 | fileMode resultStat `shouldBe` 0o100644 62 | 63 | 64 | it "creates a new file with permissions based on active umask" $ 65 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 66 | let 67 | filePath = joinPath [tmpDir, "testFile"] 68 | sampleFilePath = joinPath [tmpDir, "sampleFile"] 69 | 70 | -- Set somewhat distinctive defaults for test 71 | _ <- setFileCreationMask 0o100171 72 | 73 | -- We don't know what the default file permissions are, so create a 74 | -- file to sample them. 75 | writeFile sampleFilePath "I'm being written to sample permissions" 76 | 77 | newStat <- getFileStatus sampleFilePath 78 | fileMode newStat `shouldBe` 0o100606 79 | 80 | atomicWriteFile filePath "new contents" 81 | 82 | resultStat <- getFileStatus filePath 83 | 84 | -- reset mask to not break subsequent specs 85 | _ <- setFileCreationMask 0o100022 86 | 87 | -- The default tempfile permissions are 0600, so this fails unless we 88 | -- make sure that the default umask is relied on for creation of the 89 | -- tempfile. 90 | fileMode resultStat `shouldBe` 0o100606 91 | describe "atomicWriteFileWithMode" $ do 92 | it "writes contents to a file" $ 93 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 94 | 95 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 96 | 97 | atomicWriteFileWithMode 0o100777 path "just testing" 98 | 99 | contents <- readFile path 100 | 101 | contents `shouldBe` "just testing" 102 | 103 | 104 | it "changes the permissions of a previously created file, regardless of umask" $ 105 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 106 | let filePath = joinPath [tmpDir, "testFile"] 107 | 108 | writeFile filePath "initial contents" 109 | setFileMode filePath 0o100644 110 | 111 | newStat <- getFileStatus filePath 112 | fileMode newStat `shouldBe` 0o100644 113 | 114 | -- New files are created with 100600 perms. 115 | _ <- setFileCreationMask 0o100066 116 | 117 | -- Create a new file once different mask is set and make sure that mask 118 | -- is applied. 119 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 120 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 121 | fileMode sanityCheckStat `shouldBe` 0o100600 122 | 123 | -- Since we move, this makes the new file assume the filemask of 0600 124 | atomicWriteFileWithMode 0o100655 filePath "new contents" 125 | 126 | resultStat <- getFileStatus filePath 127 | 128 | -- reset mask to not break subsequent specs 129 | _ <- setFileCreationMask 0o100022 130 | 131 | -- Fails when using atomic mv command unless apply perms on initial file 132 | fileMode resultStat `shouldBe` 0o100655 133 | 134 | 135 | it "creates a new file with specified permissions" $ 136 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 137 | let 138 | filePath = joinPath [tmpDir, "testFile"] 139 | atomicWriteFileWithMode 0o100606 filePath "new contents" 140 | 141 | resultStat <- getFileStatus filePath 142 | 143 | fileMode resultStat `shouldBe` 0o100606 144 | -------------------------------------------------------------------------------- /spec/System/AtomicWrite/Writer/StringSpec.hs: -------------------------------------------------------------------------------- 1 | module System.AtomicWrite.Writer.StringSpec (spec) where 2 | 3 | import Test.Hspec (Spec, describe, it, shouldBe) 4 | 5 | import System.AtomicWrite.Writer.String (atomicWriteFile, 6 | atomicWriteFileWithMode) 7 | 8 | import System.FilePath (joinPath) 9 | import System.IO.Temp (withSystemTempDirectory) 10 | import System.PosixCompat.Files (fileMode, getFileStatus, 11 | setFileCreationMask, 12 | setFileMode) 13 | 14 | 15 | {-# ANN module "HLint: ignore Reduce duplication" #-} 16 | 17 | spec :: Spec 18 | spec = do 19 | describe "atomicWriteFile" $ do 20 | it "writes contents to a file" $ 21 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 22 | 23 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 24 | 25 | atomicWriteFile path "just testing" 26 | 27 | contents <- readFile path 28 | 29 | contents `shouldBe` "just testing" 30 | 31 | 32 | it "preserves the permissions of original file, regardless of umask" $ 33 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 34 | let filePath = joinPath [tmpDir, "testFile"] 35 | 36 | writeFile filePath "initial contents" 37 | setFileMode filePath 0o100644 38 | 39 | newStat <- getFileStatus filePath 40 | fileMode newStat `shouldBe` 0o100644 41 | 42 | -- New files are created with 100600 perms. 43 | _ <- setFileCreationMask 0o100066 44 | 45 | -- Create a new file once different mask is set and make sure that mask 46 | -- is applied. 47 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 48 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 49 | fileMode sanityCheckStat `shouldBe` 0o100600 50 | 51 | -- Since we move, this makes the new file assume the filemask of 0600 52 | atomicWriteFile filePath "new contents" 53 | 54 | resultStat <- getFileStatus filePath 55 | 56 | -- reset mask to not break subsequent specs 57 | _ <- setFileCreationMask 0o100022 58 | 59 | -- Fails when using atomic mv command unless apply perms on initial file 60 | fileMode resultStat `shouldBe` 0o100644 61 | 62 | 63 | it "creates a new file with permissions based on active umask" $ 64 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 65 | let 66 | filePath = joinPath [tmpDir, "testFile"] 67 | sampleFilePath = joinPath [tmpDir, "sampleFile"] 68 | 69 | -- Set somewhat distinctive defaults for test 70 | _ <- setFileCreationMask 0o100171 71 | 72 | -- We don't know what the default file permissions are, so create a 73 | -- file to sample them. 74 | writeFile sampleFilePath "I'm being written to sample permissions" 75 | 76 | newStat <- getFileStatus sampleFilePath 77 | fileMode newStat `shouldBe` 0o100606 78 | 79 | atomicWriteFile filePath "new contents" 80 | 81 | resultStat <- getFileStatus filePath 82 | 83 | -- reset mask to not break subsequent specs 84 | _ <- setFileCreationMask 0o100022 85 | 86 | -- The default tempfile permissions are 0600, so this fails unless we 87 | -- make sure that the default umask is relied on for creation of the 88 | -- tempfile. 89 | fileMode resultStat `shouldBe` 0o100606 90 | describe "atomicWriteFileWithMode" $ do 91 | it "writes contents to a file" $ 92 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 93 | 94 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 95 | 96 | atomicWriteFileWithMode 0o100777 path "just testing" 97 | 98 | contents <- readFile path 99 | 100 | contents `shouldBe` "just testing" 101 | 102 | 103 | it "changes the permissions of a previously created file, regardless of umask" $ 104 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 105 | let filePath = joinPath [tmpDir, "testFile"] 106 | 107 | writeFile filePath "initial contents" 108 | setFileMode filePath 0o100644 109 | 110 | newStat <- getFileStatus filePath 111 | fileMode newStat `shouldBe` 0o100644 112 | 113 | -- New files are created with 100600 perms. 114 | _ <- setFileCreationMask 0o100066 115 | 116 | -- Create a new file once different mask is set and make sure that mask 117 | -- is applied. 118 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 119 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 120 | fileMode sanityCheckStat `shouldBe` 0o100600 121 | 122 | -- Since we move, this makes the new file assume the filemask of 0600 123 | atomicWriteFileWithMode 0o100655 filePath "new contents" 124 | 125 | resultStat <- getFileStatus filePath 126 | 127 | -- reset mask to not break subsequent specs 128 | _ <- setFileCreationMask 0o100022 129 | 130 | -- Fails when using atomic mv command unless apply perms on initial file 131 | fileMode resultStat `shouldBe` 0o100655 132 | 133 | 134 | it "creates a new file with specified permissions" $ 135 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 136 | let 137 | filePath = joinPath [tmpDir, "testFile"] 138 | atomicWriteFileWithMode 0o100606 filePath "new contents" 139 | 140 | resultStat <- getFileStatus filePath 141 | 142 | fileMode resultStat `shouldBe` 0o100606 143 | -------------------------------------------------------------------------------- /spec/System/AtomicWrite/Writer/Text/BinarySpec.hs: -------------------------------------------------------------------------------- 1 | module System.AtomicWrite.Writer.Text.BinarySpec (spec) where 2 | 3 | import Test.Hspec (Spec, describe, it, 4 | shouldBe) 5 | 6 | import System.AtomicWrite.Writer.Text.Binary (atomicWriteFile, 7 | atomicWriteFileWithMode) 8 | 9 | import System.FilePath.Posix (joinPath) 10 | import System.IO.Temp (withSystemTempDirectory) 11 | import System.PosixCompat.Files (fileMode, getFileStatus, 12 | setFileCreationMask, 13 | setFileMode) 14 | 15 | import Data.Text (pack) 16 | 17 | spec :: Spec 18 | spec = do 19 | describe "atomicWriteFile" $ do 20 | it "writes contents to a file" $ 21 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 22 | 23 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 24 | 25 | atomicWriteFile path $ pack "just testing" 26 | contents <- readFile path 27 | 28 | contents `shouldBe` "just testing" 29 | it "preserves the permissions of original file, regardless of umask" $ 30 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 31 | let filePath = joinPath [tmpDir, "testFile"] 32 | 33 | writeFile filePath "initial contents" 34 | setFileMode filePath 0o100644 35 | 36 | newStat <- getFileStatus filePath 37 | fileMode newStat `shouldBe` 0o100644 38 | 39 | -- New files are created with 100600 perms. 40 | _ <- setFileCreationMask 0o100066 41 | 42 | -- Create a new file once different mask is set and make sure that mask 43 | -- is applied. 44 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 45 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 46 | fileMode sanityCheckStat `shouldBe` 0o100600 47 | 48 | -- Since we move, this makes the new file assume the filemask of 0600 49 | atomicWriteFile filePath $ pack "new contents" 50 | 51 | resultStat <- getFileStatus filePath 52 | 53 | -- reset mask to not break subsequent specs 54 | _ <- setFileCreationMask 0o100022 55 | 56 | -- Fails when using atomic mv command unless apply perms on initial file 57 | fileMode resultStat `shouldBe` 0o100644 58 | 59 | 60 | it "creates a new file with permissions based on active umask" $ 61 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 62 | let 63 | filePath = joinPath [tmpDir, "testFile"] 64 | sampleFilePath = joinPath [tmpDir, "sampleFile"] 65 | 66 | -- Set somewhat distinctive defaults for test 67 | _ <- setFileCreationMask 0o100171 68 | 69 | -- We don't know what the default file permissions are, so create a 70 | -- file to sample them. 71 | writeFile sampleFilePath "I'm being written to sample permissions" 72 | 73 | newStat <- getFileStatus sampleFilePath 74 | fileMode newStat `shouldBe` 0o100606 75 | 76 | atomicWriteFile filePath $ pack "new contents" 77 | 78 | resultStat <- getFileStatus filePath 79 | 80 | -- reset mask to not break subsequent specs 81 | _ <- setFileCreationMask 0o100022 82 | 83 | -- The default tempfile permissions are 0600, so this fails unless we 84 | -- make sure that the default umask is relied on for creation of the 85 | -- tempfile. 86 | fileMode resultStat `shouldBe` 0o100606 87 | describe "atomicWriteFileWithMode" $ do 88 | it "writes contents to a file" $ 89 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 90 | 91 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 92 | 93 | atomicWriteFileWithMode 0o100777 path $ pack "just testing" 94 | 95 | contents <- readFile path 96 | 97 | contents `shouldBe` "just testing" 98 | 99 | 100 | it "changes the permissions of a previously created file, regardless of umask" $ 101 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 102 | let filePath = joinPath [tmpDir, "testFile"] 103 | 104 | writeFile filePath "initial contents" 105 | setFileMode filePath 0o100644 106 | 107 | newStat <- getFileStatus filePath 108 | fileMode newStat `shouldBe` 0o100644 109 | 110 | -- New files are created with 100600 perms. 111 | _ <- setFileCreationMask 0o100066 112 | 113 | -- Create a new file once different mask is set and make sure that mask 114 | -- is applied. 115 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 116 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 117 | fileMode sanityCheckStat `shouldBe` 0o100600 118 | 119 | -- Since we move, this makes the new file assume the filemask of 0600 120 | atomicWriteFileWithMode 0o100655 filePath $ pack "new contents" 121 | 122 | resultStat <- getFileStatus filePath 123 | 124 | -- reset mask to not break subsequent specs 125 | _ <- setFileCreationMask 0o100022 126 | 127 | -- Fails when using atomic mv command unless apply perms on initial file 128 | fileMode resultStat `shouldBe` 0o100655 129 | 130 | 131 | it "creates a new file with specified permissions" $ 132 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 133 | let 134 | filePath = joinPath [tmpDir, "testFile"] 135 | atomicWriteFileWithMode 0o100606 filePath $ pack "new contents" 136 | 137 | resultStat <- getFileStatus filePath 138 | 139 | fileMode resultStat `shouldBe` 0o100606 140 | -------------------------------------------------------------------------------- /spec/System/AtomicWrite/Writer/TextSpec.hs: -------------------------------------------------------------------------------- 1 | module System.AtomicWrite.Writer.TextSpec (spec) where 2 | 3 | import Test.Hspec (it, describe, shouldBe, Spec) 4 | 5 | import System.AtomicWrite.Writer.Text (atomicWriteFile, atomicWriteFileWithMode) 6 | 7 | import System.IO.Temp (withSystemTempDirectory) 8 | import System.FilePath.Posix (joinPath) 9 | import System.PosixCompat.Files 10 | (setFileMode, setFileCreationMask, getFileStatus, fileMode) 11 | 12 | import Data.Text (pack) 13 | 14 | spec :: Spec 15 | spec = do 16 | describe "atomicWriteFile" $ do 17 | it "writes contents to a file" $ 18 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 19 | 20 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 21 | 22 | atomicWriteFile path $ pack "just testing" 23 | contents <- readFile path 24 | 25 | contents `shouldBe` "just testing" 26 | it "preserves the permissions of original file, regardless of umask" $ 27 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 28 | let filePath = joinPath [tmpDir, "testFile"] 29 | 30 | writeFile filePath "initial contents" 31 | setFileMode filePath 0o100644 32 | 33 | newStat <- getFileStatus filePath 34 | fileMode newStat `shouldBe` 0o100644 35 | 36 | -- New files are created with 100600 perms. 37 | _ <- setFileCreationMask 0o100066 38 | 39 | -- Create a new file once different mask is set and make sure that mask 40 | -- is applied. 41 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 42 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 43 | fileMode sanityCheckStat `shouldBe` 0o100600 44 | 45 | -- Since we move, this makes the new file assume the filemask of 0600 46 | atomicWriteFile filePath $ pack "new contents" 47 | 48 | resultStat <- getFileStatus filePath 49 | 50 | -- reset mask to not break subsequent specs 51 | _ <- setFileCreationMask 0o100022 52 | 53 | -- Fails when using atomic mv command unless apply perms on initial file 54 | fileMode resultStat `shouldBe` 0o100644 55 | 56 | 57 | it "creates a new file with permissions based on active umask" $ 58 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 59 | let 60 | filePath = joinPath [tmpDir, "testFile"] 61 | sampleFilePath = joinPath [tmpDir, "sampleFile"] 62 | 63 | -- Set somewhat distinctive defaults for test 64 | _ <- setFileCreationMask 0o100171 65 | 66 | -- We don't know what the default file permissions are, so create a 67 | -- file to sample them. 68 | writeFile sampleFilePath "I'm being written to sample permissions" 69 | 70 | newStat <- getFileStatus sampleFilePath 71 | fileMode newStat `shouldBe` 0o100606 72 | 73 | atomicWriteFile filePath $ pack "new contents" 74 | 75 | resultStat <- getFileStatus filePath 76 | 77 | -- reset mask to not break subsequent specs 78 | _ <- setFileCreationMask 0o100022 79 | 80 | -- The default tempfile permissions are 0600, so this fails unless we 81 | -- make sure that the default umask is relied on for creation of the 82 | -- tempfile. 83 | fileMode resultStat `shouldBe` 0o100606 84 | describe "atomicWriteFileWithMode" $ do 85 | it "writes contents to a file" $ 86 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 87 | 88 | let path = joinPath [ tmpDir, "writeTest.tmp" ] 89 | 90 | atomicWriteFileWithMode 0o100777 path $ pack "just testing" 91 | 92 | contents <- readFile path 93 | 94 | contents `shouldBe` "just testing" 95 | 96 | 97 | it "changes the permissions of a previously created file, regardless of umask" $ 98 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 99 | let filePath = joinPath [tmpDir, "testFile"] 100 | 101 | writeFile filePath "initial contents" 102 | setFileMode filePath 0o100644 103 | 104 | newStat <- getFileStatus filePath 105 | fileMode newStat `shouldBe` 0o100644 106 | 107 | -- New files are created with 100600 perms. 108 | _ <- setFileCreationMask 0o100066 109 | 110 | -- Create a new file once different mask is set and make sure that mask 111 | -- is applied. 112 | writeFile (joinPath [tmpDir, "sanityCheck"]) "with sanity check mask" 113 | sanityCheckStat <- getFileStatus $ joinPath [tmpDir, "sanityCheck"] 114 | fileMode sanityCheckStat `shouldBe` 0o100600 115 | 116 | -- Since we move, this makes the new file assume the filemask of 0600 117 | atomicWriteFileWithMode 0o100655 filePath $ pack "new contents" 118 | 119 | resultStat <- getFileStatus filePath 120 | 121 | -- reset mask to not break subsequent specs 122 | _ <- setFileCreationMask 0o100022 123 | 124 | -- Fails when using atomic mv command unless apply perms on initial file 125 | fileMode resultStat `shouldBe` 0o100655 126 | 127 | 128 | it "creates a new file with specified permissions" $ 129 | withSystemTempDirectory "atomicFileTest" $ \tmpDir -> do 130 | let 131 | filePath = joinPath [tmpDir, "testFile"] 132 | atomicWriteFileWithMode 0o100606 filePath $ pack "new contents" 133 | 134 | resultStat <- getFileStatus filePath 135 | 136 | fileMode resultStat `shouldBe` 0o100606 137 | -------------------------------------------------------------------------------- /src/System/AtomicWrite/Internal.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : System.AtomicWrite.Internal 3 | -- Copyright : © 2015-2019 Stack Builders Inc. 4 | -- License : MIT 5 | -- 6 | -- Maintainer : Stack Builders 7 | -- Stability : experimental 8 | -- Portability : portable 9 | -- 10 | -- Provides functionality to create a temporary file with correct permissions 11 | -- atomically. 12 | 13 | module System.AtomicWrite.Internal where 14 | 15 | import System.Directory (doesFileExist, renameFile) 16 | import System.FilePath (takeDirectory) 17 | import System.IO (Handle, hClose, hSetBinaryMode, 18 | openTempFile, 19 | openTempFileWithDefaultPermissions) 20 | import System.Posix.Types (FileMode) 21 | import System.PosixCompat.Files (fileMode, getFileStatus, setFileMode) 22 | 23 | -- | Returns a temporary file with permissions correctly set. Chooses 24 | -- either previously-set permissions if the file that we're writing 25 | -- to existed, or permissions following the current umask. 26 | tempFileFor :: 27 | FilePath -- ^ The target filepath that we will replace atomically. 28 | -> IO (FilePath, Handle) 29 | tempFileFor targetFilePath = 30 | 31 | doesFileExist targetFilePath >>= 32 | tmpFile targetFilePath (takeDirectory targetFilePath) "atomic.write" 33 | 34 | where 35 | 36 | tmpFile :: FilePath -> FilePath -> String -> Bool -> IO (FilePath, Handle) 37 | tmpFile targetPath workingDirectory template previousExisted = 38 | 39 | if previousExisted then 40 | openTempFile workingDirectory template >>= 41 | 42 | \(tmpPath, handle) -> 43 | 44 | getFileStatus targetPath >>= setFileMode tmpPath . fileMode >> 45 | 46 | return (tmpPath, handle) 47 | 48 | else 49 | openTempFileWithDefaultPermissions workingDirectory template 50 | 51 | 52 | closeAndRename :: Handle -> FilePath -> FilePath -> IO () 53 | closeAndRename tmpHandle tempFile destFile = 54 | hClose tmpHandle >> renameFile tempFile destFile 55 | 56 | maybeSetFileMode :: FilePath -> Maybe FileMode -> IO () 57 | maybeSetFileMode path = 58 | maybe 59 | ( return () ) 60 | ( \mode -> setFileMode path mode ) 61 | 62 | 63 | -- Helper Function 64 | atomicWriteFileMaybeModeText :: 65 | Maybe FileMode -- ^ The mode to set the file to 66 | -> FilePath -- ^ The path where the file will be updated or created 67 | -> (Handle -> a -> IO ()) -- ^ The function to use to write on the file 68 | -> a -- ^ The content to write to the file 69 | -> IO () 70 | atomicWriteFileMaybeModeText mmode path hF text = 71 | tempFileFor path >>= \(tmpPath, h) -> hSetBinaryMode h False 72 | >> hF h text 73 | >> closeAndRename h tmpPath path 74 | >> maybeSetFileMode path mmode 75 | -- Helper Function 76 | atomicWriteFileMaybeModeBinary :: 77 | Maybe FileMode -- ^ The mode to set the file to 78 | -> FilePath -- ^ The path where the file will be updated or created 79 | -> (Handle -> a -> IO ()) -- ^ The function to use to write on the file 80 | -> a -- ^ The content to write to the file 81 | -> IO () 82 | atomicWriteFileMaybeModeBinary mmode path hF text = 83 | tempFileFor path >>= \(tmpPath, h) -> hSetBinaryMode h True 84 | >> hF h text 85 | >> closeAndRename h tmpPath path 86 | >> maybeSetFileMode path mmode 87 | -------------------------------------------------------------------------------- /src/System/AtomicWrite/Writer/ByteString.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : System.AtomicWrite.Writer.ByteString 3 | -- Copyright : © 2015-2019 Stack Builders Inc. 4 | -- License : MIT 5 | -- 6 | -- Maintainer : Stack Builders 7 | -- Stability : experimental 8 | -- Portability : portable 9 | -- 10 | -- Provides functionality to dump the contents of a ByteString 11 | -- to a file in text mode. 12 | 13 | module System.AtomicWrite.Writer.ByteString (atomicWriteFile, atomicWriteFileWithMode) where 14 | 15 | import System.AtomicWrite.Internal (atomicWriteFileMaybeModeText) 16 | 17 | import System.Posix.Types (FileMode) 18 | 19 | import Data.ByteString (ByteString, hPutStr) 20 | 21 | 22 | -- | Creates or modifies a file atomically on POSIX-compliant 23 | -- systems while preserving permissions. 24 | atomicWriteFile :: 25 | FilePath -- ^ The path where the file will be updated or created 26 | -> ByteString -- ^ The content to write to the file 27 | -> IO () 28 | atomicWriteFile = atomicWriteFileMaybeMode Nothing 29 | 30 | -- | Creates or modifies a file atomically on POSIX-compliant 31 | -- systems and updates permissions. 32 | atomicWriteFileWithMode :: 33 | FileMode 34 | -> FilePath -- ^ The path where the file will be updated or created 35 | -> ByteString -- ^ The content to write to the file 36 | -> IO () 37 | atomicWriteFileWithMode = atomicWriteFileMaybeMode . Just 38 | 39 | -- | Helper function 40 | atomicWriteFileMaybeMode :: 41 | Maybe FileMode 42 | -> FilePath -- ^ The path where the file will be updated or created 43 | -> ByteString -- ^ The content to write to the file 44 | -> IO () 45 | atomicWriteFileMaybeMode mmode path = atomicWriteFileMaybeModeText mmode path hPutStr 46 | 47 | -------------------------------------------------------------------------------- /src/System/AtomicWrite/Writer/ByteString/Binary.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : System.AtomicWrite.Writer.ByteString.Binary 3 | -- Copyright : © 2015-2019 Stack Builders Inc. 4 | -- License : MIT 5 | -- 6 | -- Maintainer : Stack Builders 7 | -- Stability : experimental 8 | -- Portability : portable 9 | -- 10 | -- Provides functionality to dump the contents of a ByteString 11 | -- to a file open in binary mode 12 | 13 | module System.AtomicWrite.Writer.ByteString.Binary (atomicWriteFile, atomicWriteFileWithMode) where 14 | 15 | import System.AtomicWrite.Internal (atomicWriteFileMaybeModeBinary) 16 | 17 | import System.Posix.Types (FileMode) 18 | 19 | import Data.ByteString (ByteString, hPutStr) 20 | 21 | 22 | -- | Creates or modifies a file atomically on POSIX-compliant 23 | -- systems while preserving permissions. The file is opened in 24 | -- binary mode. 25 | atomicWriteFile :: 26 | FilePath -- ^ The path where the file will be updated or created 27 | -> ByteString -- ^ The content to write to the file 28 | -> IO () 29 | atomicWriteFile = atomicWriteFileMaybeMode Nothing 30 | 31 | -- | Creates or modifies a file atomically on POSIX-compliant 32 | -- systems and updates permissions. The file is opened in binary 33 | -- mode. 34 | atomicWriteFileWithMode :: 35 | FileMode 36 | -> FilePath -- ^ The path where the file will be updated or created 37 | -> ByteString -- ^ The content to write to the file 38 | -> IO () 39 | atomicWriteFileWithMode mode = 40 | atomicWriteFileMaybeMode $ Just mode 41 | 42 | -- | Helper function for opening the file in binary mode. 43 | atomicWriteFileMaybeMode :: 44 | Maybe FileMode 45 | -> FilePath -- ^ The path where the file will be updated or created 46 | -> ByteString -- ^ The content to write to the file 47 | -> IO () 48 | atomicWriteFileMaybeMode mmode path = atomicWriteFileMaybeModeBinary mmode path hPutStr 49 | -------------------------------------------------------------------------------- /src/System/AtomicWrite/Writer/ByteStringBuilder.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : System.AtomicWrite.Writer.ByteStringBuilder 3 | -- Copyright : © 2015-2019 Stack Builders Inc. 4 | -- License : MIT 5 | -- 6 | -- Maintainer : Stack Builders 7 | -- Stability : experimental 8 | -- Portability : portable 9 | -- 10 | -- Provides functionality to dump the contents of a ByteStringBuilder 11 | -- to a file. 12 | 13 | module System.AtomicWrite.Writer.ByteStringBuilder (atomicWriteFile, atomicWriteFileWithMode) where 14 | 15 | import System.AtomicWrite.Internal (closeAndRename, maybeSetFileMode, 16 | tempFileFor) 17 | 18 | import Data.ByteString.Builder (Builder, hPutBuilder) 19 | 20 | import GHC.IO.Handle (BufferMode (BlockBuffering), 21 | hSetBinaryMode, hSetBuffering) 22 | 23 | import System.Posix.Types (FileMode) 24 | 25 | -- | Creates or modifies a file atomically on POSIX-compliant 26 | -- systems while preserving permissions. 27 | atomicWriteFile :: 28 | FilePath -- ^ The path where the file will be updated or created 29 | -> Builder -- ^ The content to write to the file 30 | -> IO () 31 | atomicWriteFile = 32 | atomicWriteFileMaybeMode Nothing 33 | 34 | -- | Creates or modifies a file atomically on POSIX-compliant 35 | -- systems and updates permissions. 36 | atomicWriteFileWithMode :: 37 | FileMode 38 | -> FilePath -- ^ The path where the file will be updated or created 39 | -> Builder -- ^ The content to write to the file 40 | -> IO () 41 | atomicWriteFileWithMode mode = 42 | atomicWriteFileMaybeMode $ Just mode 43 | 44 | -- Helper function 45 | atomicWriteFileMaybeMode :: 46 | Maybe FileMode 47 | -> FilePath -- ^ The path where the file will be updated or created 48 | -> Builder -- ^ The content to write to the file 49 | -> IO () 50 | atomicWriteFileMaybeMode mmode path builder = do 51 | (temppath, h) <- tempFileFor path 52 | 53 | -- Recommendations for binary and buffering are from the 54 | -- Data.ByteString.Builder docs: 55 | -- http://hackage.haskell.org/package/bytestring-0.10.2.0/docs/Data-ByteString-Builder.html#v:hPutBuilder 56 | hSetBinaryMode h True 57 | hSetBuffering h (BlockBuffering Nothing) 58 | 59 | hPutBuilder h builder 60 | 61 | closeAndRename h temppath path 62 | 63 | -- set new permissions if a FileMode was provided 64 | maybeSetFileMode path mmode 65 | -------------------------------------------------------------------------------- /src/System/AtomicWrite/Writer/LazyByteString.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : System.AtomicWrite.Writer.LazyByteString 3 | -- Copyright : © 2015-2019 Stack Builders Inc. 4 | -- License : MIT 5 | -- 6 | -- Maintainer : Stack Builders 7 | -- Stability : experimental 8 | -- Portability : portable 9 | -- 10 | -- Provides functionality to dump the contents of a Lazy ByteString 11 | -- to a file. 12 | 13 | module System.AtomicWrite.Writer.LazyByteString (atomicWriteFile, atomicWriteFileWithMode) where 14 | 15 | import System.AtomicWrite.Internal (atomicWriteFileMaybeModeText) 16 | 17 | import Data.ByteString.Lazy (ByteString, hPutStr) 18 | 19 | import System.Posix.Types (FileMode) 20 | 21 | -- | Creates or modifies a file atomically on POSIX-compliant 22 | -- systems while preserving permissions. 23 | atomicWriteFile :: 24 | FilePath -- ^ The path where the file will be updated or created 25 | -> ByteString -- ^ The content to write to the file 26 | -> IO () 27 | atomicWriteFile = 28 | atomicWriteFileMaybeMode Nothing 29 | 30 | -- | Creates or modifies a file atomically on 31 | -- POSIX-compliant systems and updates permissions 32 | atomicWriteFileWithMode :: 33 | FileMode -- ^ The mode to set the file to 34 | -> FilePath -- ^ The path where the file will be updated or created 35 | -> ByteString -- ^ The content to write to the file 36 | -> IO () 37 | atomicWriteFileWithMode = 38 | atomicWriteFileMaybeMode . Just 39 | 40 | -- Helper Function 41 | atomicWriteFileMaybeMode :: 42 | Maybe FileMode -- ^ The mode to set the file to 43 | -> FilePath -- ^ The path where the file will be updated or created 44 | -> ByteString -- ^ The content to write to the file 45 | -> IO () 46 | atomicWriteFileMaybeMode mmode path = atomicWriteFileMaybeModeText mmode path hPutStr 47 | -------------------------------------------------------------------------------- /src/System/AtomicWrite/Writer/LazyByteString/Binary.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : System.AtomicWrite.Writer.LazyByteString.Binary 3 | -- Copyright : © 2015-2019 Stack Builders Inc. 4 | -- License : MIT 5 | -- 6 | -- Maintainer : Stack Builders 7 | -- Stability : experimental 8 | -- Portability : portable 9 | -- 10 | -- Provides functionality to dump the contents of a Lazy ByteString 11 | -- to a file in binary mode. 12 | 13 | module System.AtomicWrite.Writer.LazyByteString.Binary (atomicWriteFile, atomicWriteFileWithMode) where 14 | 15 | import System.AtomicWrite.Internal (atomicWriteFileMaybeModeBinary) 16 | 17 | import Data.ByteString.Lazy (ByteString, hPutStr) 18 | 19 | import System.Posix.Types (FileMode) 20 | 21 | 22 | -- | Creates or modifies a file atomically on POSIX-compliant 23 | -- systems while preserving permissions. 24 | atomicWriteFile :: 25 | FilePath -- ^ The path where the file will be updated or created 26 | -> ByteString -- ^ The content to write to the file 27 | -> IO () 28 | atomicWriteFile = 29 | atomicWriteFileMaybeMode Nothing 30 | 31 | -- | Creates or modifies a file atomically on 32 | -- POSIX-compliant systems and updates permissions 33 | atomicWriteFileWithMode :: 34 | FileMode -- ^ The mode to set the file to 35 | -> FilePath -- ^ The path where the file will be updated or created 36 | -> ByteString -- ^ The content to write to the file 37 | -> IO () 38 | atomicWriteFileWithMode = atomicWriteFileMaybeMode . Just 39 | 40 | -- Helper Function 41 | atomicWriteFileMaybeMode :: 42 | Maybe FileMode -- ^ The mode to set the file to 43 | -> FilePath -- ^ The path where the file will be updated or created 44 | -> ByteString -- ^ The content to write to the file 45 | -> IO () 46 | atomicWriteFileMaybeMode mmode path = atomicWriteFileMaybeModeBinary mmode path hPutStr 47 | -------------------------------------------------------------------------------- /src/System/AtomicWrite/Writer/LazyText.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : System.AtomicWrite.Writer.LazyText 3 | -- Copyright : © 2015-2019 Stack Builders Inc. 4 | -- License : MIT 5 | -- 6 | -- Maintainer : Stack Builders 7 | -- Stability : experimental 8 | -- Portability : portable 9 | -- 10 | -- Provides functionality to dump the contents of a Text 11 | -- to a file. 12 | 13 | module System.AtomicWrite.Writer.LazyText (atomicWriteFile, atomicWriteFileWithMode) where 14 | 15 | import System.AtomicWrite.Internal (atomicWriteFileMaybeModeText) 16 | 17 | import Data.Text.Lazy (Text) 18 | 19 | import Data.Text.Lazy.IO (hPutStr) 20 | 21 | import System.Posix.Types (FileMode) 22 | 23 | -- | Creates a file atomically on POSIX-compliant 24 | -- systems while preserving permissions. 25 | atomicWriteFile :: 26 | FilePath -- ^ The path where the file will be updated or created 27 | -> Text -- ^ The content to write to the file 28 | -> IO () 29 | atomicWriteFile = 30 | atomicWriteFileMaybeMode Nothing 31 | 32 | -- | Creates or modifies a file atomically on 33 | -- POSIX-compliant systems and updates permissions 34 | atomicWriteFileWithMode :: 35 | FileMode -- ^ The mode to set the file to 36 | -> FilePath -- ^ The path where the file will be updated or created 37 | -> Text -- ^ The content to write to the file 38 | -> IO () 39 | atomicWriteFileWithMode = 40 | atomicWriteFileMaybeMode . Just 41 | 42 | -- Helper Function 43 | atomicWriteFileMaybeMode :: 44 | Maybe FileMode -- ^ The mode to set the file to 45 | -> FilePath -- ^ The path where the file will be updated or created 46 | -> Text -- ^ The content to write to the file 47 | -> IO () 48 | atomicWriteFileMaybeMode mmode path = atomicWriteFileMaybeModeText mmode path hPutStr 49 | -------------------------------------------------------------------------------- /src/System/AtomicWrite/Writer/LazyText/Binary.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : System.AtomicWrite.Writer.LazyText.Binary 3 | -- Copyright : © 2015-2019 Stack Builders Inc. 4 | -- License : MIT 5 | -- 6 | -- Maintainer : Stack Builders 7 | -- Stability : experimental 8 | -- Portability : portable 9 | -- 10 | -- Provides functionality to dump the contents of a Text 11 | -- to a file in binary mode. 12 | 13 | module System.AtomicWrite.Writer.LazyText.Binary (atomicWriteFile, atomicWriteFileWithMode) where 14 | 15 | import System.AtomicWrite.Internal (atomicWriteFileMaybeModeBinary) 16 | 17 | import Data.Text.Lazy (Text) 18 | 19 | import Data.Text.Lazy.IO (hPutStr) 20 | 21 | import System.Posix.Types (FileMode) 22 | 23 | 24 | -- | Creates a file atomically on POSIX-compliant 25 | -- systems while preserving permissions. 26 | atomicWriteFile :: 27 | FilePath -- ^ The path where the file will be updated or created 28 | -> Text -- ^ The content to write to the file 29 | -> IO () 30 | atomicWriteFile = 31 | atomicWriteFileMaybeMode Nothing 32 | 33 | -- | Creates or modifies a file atomically on 34 | -- POSIX-compliant systems and updates permissions 35 | atomicWriteFileWithMode :: 36 | FileMode -- ^ The mode to set the file to 37 | -> FilePath -- ^ The path where the file will be updated or created 38 | -> Text -- ^ The content to write to the file 39 | -> IO () 40 | atomicWriteFileWithMode = atomicWriteFileMaybeMode . Just 41 | 42 | -- Helper Function 43 | atomicWriteFileMaybeMode :: 44 | Maybe FileMode -- ^ The mode to set the file to 45 | -> FilePath -- ^ The path where the file will be updated or created 46 | -> Text -- ^ The content to write to the file 47 | -> IO () 48 | atomicWriteFileMaybeMode mmode path = atomicWriteFileMaybeModeBinary mmode path hPutStr 49 | -------------------------------------------------------------------------------- /src/System/AtomicWrite/Writer/String.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : System.AtomicWrite.Writer.String 3 | -- Copyright : © 2015-2019 Stack Builders Inc. 4 | -- License : MIT 5 | -- 6 | -- Maintainer : Stack Builders 7 | -- Stability : experimental 8 | -- Portability : portable 9 | -- 10 | -- Provides functionality to dump the contents of a String 11 | -- to a file. 12 | 13 | module System.AtomicWrite.Writer.String (atomicWriteFile, atomicWithFile, atomicWriteFileWithMode, atomicWithFileAndMode) where 14 | 15 | import System.AtomicWrite.Internal (closeAndRename, maybeSetFileMode, 16 | tempFileFor) 17 | 18 | import System.IO (Handle, hPutStr) 19 | 20 | import System.Posix.Types (FileMode) 21 | 22 | -- | Creates or modifies a file atomically on POSIX-compliant 23 | -- systems while preserving permissions. 24 | atomicWriteFile :: 25 | FilePath -- ^ The path where the file will be updated or created 26 | -> String -- ^ The content to write to the file 27 | -> IO () 28 | atomicWriteFile = (. flip hPutStr) . atomicWithFile 29 | 30 | 31 | -- | Creates or modifies a file atomically on 32 | -- POSIX-compliant systems and updates permissions 33 | atomicWriteFileWithMode :: 34 | FileMode -- ^ The mode to set the file to 35 | -> FilePath -- ^ The path where the file will be updated or created 36 | -> String -- ^ The content to write to the file 37 | -> IO () 38 | atomicWriteFileWithMode mode = ( . flip hPutStr) 39 | . atomicWithFileAndMode mode 40 | 41 | -- | A general version of 'atomicWriteFile' 42 | atomicWithFile :: FilePath -> (Handle -> IO ()) -> IO () 43 | atomicWithFile = atomicWithFileAndMaybeMode Nothing 44 | 45 | -- | A general version of 'atomicWriteFileWithMode' 46 | atomicWithFileAndMode :: FileMode 47 | -> FilePath 48 | -> (Handle -> IO ()) 49 | -> IO () 50 | atomicWithFileAndMode = atomicWithFileAndMaybeMode . Just 51 | 52 | -- | Helper function 53 | atomicWithFileAndMaybeMode :: Maybe FileMode 54 | -> FilePath 55 | -> (Handle -> IO ()) 56 | -> IO () 57 | atomicWithFileAndMaybeMode mmode path action = 58 | tempFileFor path >>= \(tmpPath, h) -> action h 59 | >> closeAndRename h tmpPath path 60 | >> maybeSetFileMode path mmode 61 | -------------------------------------------------------------------------------- /src/System/AtomicWrite/Writer/String/Binary.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : System.AtomicWrite.Writer.String.Binary 3 | -- Copyright : © 2015-2019 Stack Builders Inc. 4 | -- License : MIT 5 | -- 6 | -- Maintainer : Stack Builders 7 | -- Stability : experimental 8 | -- Portability : portable 9 | -- 10 | -- Provides functionality to dump the contents of a String 11 | -- to a file in binary mode. 12 | 13 | module System.AtomicWrite.Writer.String.Binary (atomicWriteFile, atomicWithFile, atomicWriteFileWithMode, atomicWithFileAndMode) where 14 | 15 | import System.AtomicWrite.Internal (closeAndRename, maybeSetFileMode, 16 | tempFileFor) 17 | 18 | import System.IO (Handle, hPutStr, hSetBinaryMode) 19 | 20 | import System.Posix.Types (FileMode) 21 | 22 | -- | Creates or modifies a file atomically on POSIX-compliant 23 | -- systems while preserving permissions. 24 | atomicWriteFile :: 25 | FilePath -- ^ The path where the file will be updated or created 26 | -> String -- ^ The content to write to the file 27 | -> IO () 28 | atomicWriteFile = (. flip hPutStr) . atomicWithFile 29 | 30 | 31 | -- | Creates or modifies a file atomically on 32 | -- POSIX-compliant systems and updates permissions 33 | atomicWriteFileWithMode :: 34 | FileMode -- ^ The mode to set the file to 35 | -> FilePath -- ^ The path where the file will be updated or created 36 | -> String -- ^ The content to write to the file 37 | -> IO () 38 | atomicWriteFileWithMode mode = ( . flip hPutStr) 39 | . atomicWithFileAndMode mode 40 | 41 | -- | A general version of 'atomicWriteFile' 42 | atomicWithFile :: FilePath -> (Handle -> IO ()) -> IO () 43 | atomicWithFile = atomicWithFileAndMaybeMode Nothing 44 | 45 | -- | A general version of 'atomicWriteFileWithMode' 46 | atomicWithFileAndMode :: FileMode 47 | -> FilePath 48 | -> (Handle -> IO ()) 49 | -> IO () 50 | atomicWithFileAndMode = atomicWithFileAndMaybeMode . Just 51 | 52 | -- | Helper function 53 | atomicWithFileAndMaybeMode :: Maybe FileMode 54 | -> FilePath 55 | -> (Handle -> IO ()) 56 | -> IO () 57 | atomicWithFileAndMaybeMode mmode path action = 58 | tempFileFor path >>= \(tmpPath, h) -> hSetBinaryMode h True 59 | >> action h 60 | >> closeAndRename h tmpPath path 61 | >> maybeSetFileMode path mmode 62 | -------------------------------------------------------------------------------- /src/System/AtomicWrite/Writer/Text.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : System.AtomicWrite.Writer.Text 3 | -- Copyright : © 2015-2019 Stack Builders Inc. 4 | -- License : MIT 5 | -- 6 | -- Maintainer : Stack Builders 7 | -- Stability : experimental 8 | -- Portability : portable 9 | -- 10 | -- Provides functionality to dump the contents of a Text 11 | -- to a file. 12 | 13 | module System.AtomicWrite.Writer.Text (atomicWriteFile, atomicWriteFileWithMode) where 14 | 15 | import System.AtomicWrite.Internal (atomicWriteFileMaybeModeText) 16 | 17 | import Data.Text (Text) 18 | 19 | import Data.Text.IO (hPutStr) 20 | 21 | import System.Posix.Types (FileMode) 22 | 23 | -- | Creates a file atomically on POSIX-compliant 24 | -- systems while preserving permissions. 25 | atomicWriteFile :: 26 | FilePath -- ^ The path where the file will be updated or created 27 | -> Text -- ^ The content to write to the file 28 | -> IO () 29 | atomicWriteFile = 30 | atomicWriteFileMaybeMode Nothing 31 | 32 | -- | Creates or modifies a file atomically on 33 | -- POSIX-compliant systems and updates permissions 34 | atomicWriteFileWithMode :: 35 | FileMode -- ^ The mode to set the file to 36 | -> FilePath -- ^ The path where the file will be updated or created 37 | -> Text -- ^ The content to write to the file 38 | -> IO () 39 | atomicWriteFileWithMode = 40 | atomicWriteFileMaybeMode . Just 41 | 42 | -- Helper Function 43 | atomicWriteFileMaybeMode :: 44 | Maybe FileMode -- ^ The mode to set the file to 45 | -> FilePath -- ^ The path where the file will be updated or created 46 | -> Text -- ^ The content to write to the file 47 | -> IO () 48 | atomicWriteFileMaybeMode mmode path = atomicWriteFileMaybeModeText mmode path hPutStr 49 | -------------------------------------------------------------------------------- /src/System/AtomicWrite/Writer/Text/Binary.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : System.AtomicWrite.Writer.Text.Binary 3 | -- Copyright : © 2015-2019 Stack Builders Inc. 4 | -- License : MIT 5 | -- 6 | -- Maintainer : Stack Builders 7 | -- Stability : experimental 8 | -- Portability : portable 9 | -- 10 | -- Provides functionality to dump the contents of a Text 11 | -- to a file in binary mode. 12 | 13 | module System.AtomicWrite.Writer.Text.Binary (atomicWriteFile, atomicWriteFileWithMode) where 14 | 15 | import System.AtomicWrite.Internal (atomicWriteFileMaybeModeBinary) 16 | 17 | import Data.Text (Text) 18 | 19 | import Data.Text.IO (hPutStr) 20 | 21 | import System.Posix.Types (FileMode) 22 | 23 | -- | Creates a file atomically on POSIX-compliant 24 | -- systems while preserving permissions. 25 | atomicWriteFile :: 26 | FilePath -- ^ The path where the file will be updated or created 27 | -> Text -- ^ The content to write to the file 28 | -> IO () 29 | atomicWriteFile = 30 | atomicWriteFileMaybeMode Nothing 31 | 32 | -- | Creates or modifies a file atomically on 33 | -- POSIX-compliant systems and updates permissions 34 | atomicWriteFileWithMode :: 35 | FileMode -- ^ The mode to set the file to 36 | -> FilePath -- ^ The path where the file will be updated or created 37 | -> Text -- ^ The content to write to the file 38 | -> IO () 39 | atomicWriteFileWithMode = 40 | atomicWriteFileMaybeMode . Just 41 | 42 | -- Helper Function 43 | atomicWriteFileMaybeMode :: 44 | Maybe FileMode -- ^ The mode to set the file to 45 | -> FilePath -- ^ The path where the file will be updated or created 46 | -> Text -- ^ The content to write to the file 47 | -> IO () 48 | atomicWriteFileMaybeMode mmode path = atomicWriteFileMaybeModeBinary mmode path hPutStr 49 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-21.13 2 | -------------------------------------------------------------------------------- /stack.yaml.lock: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by Stack. 2 | # You should not edit this file by hand. 3 | # For more information, please see the documentation at: 4 | # https://docs.haskellstack.org/en/stable/lock_files 5 | 6 | packages: [] 7 | snapshots: 8 | - completed: 9 | sha256: 8017c7970c2a8a9510c60cc70ac245d59e0c34eb932b91d37af09fe59855d854 10 | size: 640038 11 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/21/13.yaml 12 | original: lts-21.13 13 | --------------------------------------------------------------------------------