├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── src └── Data │ └── Printf.purs └── test └── Main.purs /.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc* 7 | /.purs* 8 | /.psa* 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | dist: trusty 3 | sudo: required 4 | node_js: 10 5 | env: 6 | - PATH=$HOME/purescript:$PATH 7 | install: 8 | - TAG=$(wget -q -O - https://github.com/purescript/purescript/releases/latest --server-response --max-redirect 0 2>&1 | sed -n -e 's/.*Location:.*tag\///p') 9 | - wget -O $HOME/purescript.tar.gz https://github.com/purescript/purescript/releases/download/$TAG/linux64.tar.gz 10 | - tar -xvf $HOME/purescript.tar.gz -C $HOME/ 11 | - chmod a+x $HOME/purescript 12 | - npm install -g bower pulp 13 | script: 14 | - bower install --production 15 | - bower install 16 | - pulp test 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Csongor Kiss 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # purescript-safe-printf 2 | 3 | [![Build Status](https://travis-ci.org/kcsongor/purescript-safe-printf.svg?branch=master)](https://travis-ci.org/kcsongor/purescript-safe-printf) 4 | 5 | A bare-bones proof of concept implementation of a typesafe printf-like interface 6 | where the format string is provided as a type-level string. 7 | 8 | This library uses the 0.12 version of the compiler. 9 | 10 | ## Example 11 | 12 | ```purescript 13 | format (SProxy :: SProxy "Hi %s! Your favourite number is %d") "Bill" 16 14 | ``` 15 | 16 | produces the string 17 | 18 | ``` 19 | "Hi Bill! Your favourite number is 16" 20 | ``` 21 | 22 | A function of the "right type" is generated from the format string, so that 23 | 24 | ``` 25 | :t format (SProxy :: SProxy "Hi %s! Your favourite number is %d") 26 | ``` 27 | 28 | gives 29 | ``` 30 | String -> Int -> String 31 | ``` 32 | 33 | You can also choose to use wildcards if you don't want to repeat yourself: 34 | 35 | ```purs 36 | let formatted = format (SProxy :: _ "Hi %s! You are %d") "Bill" 12 37 | ``` 38 | 39 | 40 | ## TODO 41 | Currently only "%d" and "%s" are supported, without any other fancy formatting. 42 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-safe-printf", 3 | "homepage": "https://github.com/kcsongor/purescript-safe-printf", 4 | "description": "Safe printf-like formatting in purescript", 5 | "license": "BSD-3-Clause", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/kcsongor/purescript-safe-printf.git" 9 | }, 10 | "ignore": [ 11 | "**/.*", 12 | "node_modules", 13 | "bower_components", 14 | "output" 15 | ], 16 | "dependencies": { 17 | "purescript-prelude": "^4.1.0", 18 | "purescript-console": "^4.2.0", 19 | "purescript-typelevel-prelude": "^4.0.0" 20 | }, 21 | "devDependencies": { 22 | "purescript-assert": "^4.0.0", 23 | "purescript-psci-support": "^4.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Data/Printf.purs: -------------------------------------------------------------------------------- 1 | module Data.Printf where 2 | 3 | import Prelude (identity, (<>), show) 4 | import Type.Prelude (class IsSymbol, SProxy(..), reflectSymbol) 5 | import Prim.Symbol as Symbol 6 | 7 | class Format (string :: Symbol) fun | string -> fun where 8 | format :: SProxy string -> fun 9 | 10 | instance formatFFormat :: 11 | ( Parse string format 12 | , FormatF format fun 13 | ) => Format string fun where 14 | format _ = formatF (FProxy :: FProxy format) "" 15 | 16 | class FormatF (format :: FList) fun | format -> fun where 17 | formatF :: FProxy format -> String -> fun 18 | 19 | instance formatFNil :: FormatF FNil String where 20 | formatF _ = identity 21 | 22 | instance formatFConsD :: 23 | FormatF rest fun 24 | => FormatF (FCons D rest) (Int -> fun) where 25 | formatF _ str 26 | = \i -> formatF (FProxy :: FProxy rest) (str <> show i) 27 | 28 | instance formatFConsS :: 29 | FormatF rest fun 30 | => FormatF (FCons S rest) (String -> fun) where 31 | formatF _ str 32 | = \s -> formatF (FProxy :: FProxy rest) (str <> s) 33 | 34 | instance formatFConsLit :: 35 | ( IsSymbol lit 36 | , FormatF rest fun 37 | ) => FormatF (FCons (Lit lit) rest) fun where 38 | formatF _ str 39 | = formatF (FProxy :: FProxy rest) (str <> reflectSymbol (SProxy :: SProxy lit)) 40 | 41 | -------------------------------------------------------------------------------- 42 | class Parse (string :: Symbol) (format :: FList) | string -> format 43 | 44 | foreign import kind FList 45 | foreign import data FNil :: FList 46 | foreign import data FCons :: Specifier -> FList -> FList 47 | 48 | data FProxy (f :: FList) = FProxy 49 | 50 | instance aNilParse :: Parse "" (FCons (Lit "") FNil) 51 | else instance bConsParse :: (Symbol.Cons h t string, Match h t fl) => Parse string fl 52 | 53 | class Match (head :: Symbol) (tail :: Symbol) (out :: FList) | head tail -> out 54 | 55 | instance aMatchFmt :: Match a "" (FCons (Lit a) FNil) 56 | else instance bMatchFmt :: 57 | ( Symbol.Cons h t s 58 | , MatchFmt h spec 59 | , Parse t rest 60 | ) => Match "%" s (FCons (Lit "") (FCons spec rest)) 61 | else instance cMatchFmt :: 62 | ( Parse s (FCons (Lit acc) r) 63 | , Symbol.Cons o acc rest 64 | ) => Match o s (FCons (Lit rest) r) 65 | 66 | class MatchFmt (head :: Symbol) (out :: Specifier) | head -> out 67 | instance matchFmtD :: MatchFmt "d" D 68 | instance matchFmtS :: MatchFmt "s" S 69 | 70 | -- TODO: add more of these... 71 | foreign import kind Specifier 72 | foreign import data D :: Specifier 73 | foreign import data S :: Specifier 74 | foreign import data Lit :: Symbol -> Specifier 75 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Data.Printf (format) 6 | import Effect (Effect) 7 | import Test.Assert (assert) 8 | import Type.Prelude (SProxy(..)) 9 | 10 | main :: Effect Unit 11 | main = do 12 | let formatted = format (SProxy :: SProxy "Hi %s! You are %d") "Bill" 12 13 | assert $ formatted == "Hi Bill! You are 12" 14 | --------------------------------------------------------------------------------