├── .flowconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── package-lock.json ├── package.json ├── src ├── Bismuth.purs └── Bismuth │ └── LibDef.purs └── test ├── Main.purs ├── generated.flow.js └── test.flow.js /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | ./test/ 5 | 6 | [libs] 7 | 8 | [lints] 9 | 10 | [options] 11 | 12 | [strict] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /generated-docs/ 6 | /.psc-package/ 7 | /.psc* 8 | /.purs* 9 | /.psa* 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: required 3 | dist: trusty 4 | node_js: 9 5 | install: 6 | - npm install -g purescript pulp bower 7 | script: 8 | - bower install 9 | - npm install 10 | - pulp test 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Justin Woo 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bismuth 2 | 3 | [![Build Status](https://travis-ci.org/justinwoo/purescript-bismuth.svg?branch=master)](https://travis-ci.org/justinwoo/purescript-bismuth) 4 | 5 | A library for generating flow type signatures for direct interop between PureScript and Flow. Similar to [OhYes](https://github.com/justinwoo/purescript-ohyes) 6 | 7 | ## Tl;dr 8 | 9 | ```hs 10 | type A = 11 | { a :: Number 12 | , b :: String 13 | , c :: { d :: String } 14 | , e :: Array String 15 | , f :: Nullable String 16 | , g :: Number -> Number -> Number 17 | , h :: Fn2 Number Number Number 18 | , i :: Fn2 Number (Fn2 Number Number Number) Number 19 | , l :: Foreign 20 | , k :: StrMap Number 21 | } 22 | 23 | type VariantTest = Variant 24 | ( a :: String 25 | , b :: Number 26 | , c :: Boolean 27 | ) 28 | ``` 29 | 30 | ```js 31 | //@flow 32 | export type A = { 33 | a: number, 34 | b: string, 35 | c: { d: string }, 36 | e: string[], 37 | f: string | null, 38 | g: (a: number) => (a: number) => number, 39 | h: (a: number, b: number) => number, 40 | i: (a: number, b: (a: number, b: number) => number) => number, 41 | k: { [key: string]: number }, 42 | l: any 43 | }; 44 | export type VariantTest = 45 | | { type: "a", value: string } 46 | | { type: "b", value: number } 47 | | { type: "c", value: boolean }; 48 | ``` 49 | 50 | ## Example 51 | 52 | See and CONTRIBUTE to the [example here](https://github.com/justinwoo/bismuth-example) 53 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-bismuth", 3 | "license": "MIT", 4 | "repository": { 5 | "type": "git", 6 | "url": "git://github.com/justinwoo/purescript-bismuth.git" 7 | }, 8 | "ignore": [ 9 | "**/.*", 10 | "node_modules", 11 | "bower_components", 12 | "output" 13 | ], 14 | "dependencies": { 15 | "purescript-typelevel-prelude": "^2.5.0", 16 | "purescript-nullable": "^3.0.0", 17 | "purescript-variant": "^4.0.0", 18 | "purescript-foreign": "^4.0.1", 19 | "purescript-generics-rep": "^5.3.0", 20 | "purescript-lists": "^4.12.0", 21 | "purescript-prelude": "^3.1.1" 22 | }, 23 | "devDependencies": { 24 | "purescript-spec": "^2.0.0", 25 | "purescript-node-fs-aff": "^5.0.0", 26 | "purescript-prettier": "^0.1.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gis", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "flow-bin": { 8 | "version": "0.60.1", 9 | "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.60.1.tgz", 10 | "integrity": "sha512-n31idUasUQVqOYl8Tvig7kfmdcAdRC+J1Gt88J435DZm0saNfwqeYOMoZrehRD6JgaAGQ0PaJfoxWuL9lKZXpA==", 11 | "dev": true 12 | }, 13 | "prettier": { 14 | "version": "1.8.2", 15 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.8.2.tgz", 16 | "integrity": "sha512-fHWjCwoRZgjP1rvLP7OGqOznq7xH1sHMQUFLX8qLRO79hI57+6xbc5vB904LxEkCfgFgyr3vv06JkafgCSzoZg==", 17 | "dev": true 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gis", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "pulp test && killall flow; flow", 11 | "format": "prettier test/**/*.js --write" 12 | }, 13 | "author": "", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "flow-bin": "^0.60.1", 17 | "prettier": "^1.8.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Bismuth.purs: -------------------------------------------------------------------------------- 1 | module Bismuth where 2 | 3 | -- | Identity function to apply the HasFlowRep constraint 4 | import Control.Monad.Eff (Eff) 5 | import Data.Foreign (Foreign) 6 | import Data.Function (id) 7 | import Data.Function.Uncurried (Fn2) 8 | import Data.List (List, intercalate, (:)) 9 | import Data.Monoid (mempty) 10 | import Data.Nullable (Nullable) 11 | import Data.Semigroup ((<>)) 12 | import Data.StrMap (StrMap) 13 | import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) 14 | import Data.Variant (Variant) 15 | import Prelude (Unit) 16 | import Type.Proxy (Proxy(..)) 17 | import Type.Row (class RowToList, Cons, Nil, RLProxy(..), kind RowList) 18 | 19 | -- | Identity function to apply the HasFlowRep constraint 20 | toFlow :: forall a 21 | . HasFlowRep a 22 | => a -> a 23 | toFlow = id 24 | 25 | -- | Generate a Flow type signature for a given type. Takes the name to be used as an arg. 26 | generateFlowType :: forall proxy a 27 | . HasFlowRep a 28 | => String -> proxy a -> String 29 | generateFlowType name _ = 30 | "export type " <> name <> "=" <> ty 31 | where 32 | ty = toFlowRep (Proxy :: Proxy a) 33 | 34 | -- | A convenience function for generating types taking a concrete value over a proxy. 35 | generateFlowType' :: forall a 36 | . HasFlowRep a 37 | => String 38 | -> a 39 | -> String 40 | generateFlowType' s a = generateFlowType s (Proxy :: Proxy a) 41 | 42 | -- | A convenience function for getting the flow rep of a concrete value 43 | toFlowRep' :: forall a 44 | . HasFlowRep a 45 | => a 46 | -> String 47 | toFlowRep' _ = toFlowRep (Proxy :: Proxy a) 48 | 49 | class HasFlowRep a where 50 | toFlowRep :: Proxy a -> String 51 | 52 | instance numberHasFlowRep :: HasFlowRep Number where 53 | toFlowRep _ = "number" 54 | 55 | instance stringHasFlowRep :: HasFlowRep String where 56 | toFlowRep _ = "string" 57 | 58 | instance booleanHasFlowRep :: HasFlowRep Boolean where 59 | toFlowRep _ = "boolean" 60 | 61 | instance foreignHasFlowRep :: HasFlowRep Foreign where 62 | toFlowRep _ = "any" 63 | 64 | instance unitHasFlowRep :: HasFlowRep Unit where 65 | toFlowRep _ = "any" 66 | 67 | instance strmapHasFlowRep :: 68 | ( HasFlowRep a 69 | ) => HasFlowRep (StrMap a) where 70 | toFlowRep _ = "{[key: string]:" <> ty <> "}" 71 | where 72 | ty = toFlowRep (Proxy :: Proxy a) 73 | 74 | instance nullableHasFlowRep :: 75 | ( HasFlowRep a 76 | ) => HasFlowRep (Nullable a) where 77 | toFlowRep _ = ty <> " | null" 78 | where 79 | ty = toFlowRep (Proxy :: Proxy a) 80 | 81 | instance arrayHasFlowRep :: 82 | ( HasFlowRep a 83 | ) => HasFlowRep (Array a) where 84 | toFlowRep _ = toFlowRep p <> "[]" 85 | where 86 | p = Proxy :: Proxy a 87 | 88 | instance effHasFlowRep :: 89 | ( HasFlowRep a 90 | ) => HasFlowRep (Eff e a) where 91 | toFlowRep _ = "() => " <> a 92 | where 93 | a = toFlowRep (Proxy :: Proxy a) 94 | 95 | instance functionHasFlowRep :: 96 | ( HasFlowRep a 97 | , HasFlowRep b 98 | ) => HasFlowRep (Function a b) where 99 | toFlowRep _ = "(a: " <> a <> ") => " <> b 100 | where 101 | a = toFlowRep (Proxy :: Proxy a) 102 | b = toFlowRep (Proxy :: Proxy b) 103 | 104 | instance fn2HasFlowRep :: 105 | ( HasFlowRep a 106 | , HasFlowRep b 107 | , HasFlowRep c 108 | ) => HasFlowRep (Fn2 a b c) where 109 | toFlowRep _ = 110 | "(a: " <> a <> 111 | ", b: " <> b <> 112 | ") => " <> c 113 | where 114 | a = toFlowRep (Proxy :: Proxy a) 115 | b = toFlowRep (Proxy :: Proxy b) 116 | c = toFlowRep (Proxy :: Proxy c) 117 | 118 | instance recordHasFlowRep :: 119 | ( RowToList row rl 120 | , HasFlowRepFields rl 121 | ) => HasFlowRep (Record row) where 122 | toFlowRep _ = "{" <> fields <> "}" 123 | where 124 | fields = intercalate "," (toFlowRepFields (RLProxy :: RLProxy rl)) 125 | 126 | class HasFlowRepFields (rl :: RowList) where 127 | toFlowRepFields :: RLProxy rl -> List String 128 | 129 | instance consHasFlowRepFields :: 130 | ( HasFlowRepFields tail 131 | , IsSymbol name 132 | , HasFlowRep ty 133 | ) => HasFlowRepFields (Cons name ty tail) where 134 | toFlowRepFields _ = head : tail 135 | where 136 | key = reflectSymbol (SProxy :: SProxy name) 137 | val = toFlowRep (Proxy :: Proxy ty) 138 | head = key <> ":" <> val 139 | tailp = RLProxy :: RLProxy tail 140 | tail = toFlowRepFields tailp 141 | 142 | instance nilHasFlowRepFields :: HasFlowRepFields Nil where 143 | toFlowRepFields _ = mempty 144 | 145 | -- | a Variant is represented by VariantRep, which is a newtype record of 146 | -- | `newtype VariantRep a = VariantRep { type ∷ String , value ∷ a }` 147 | -- | as seen here: 148 | -- | https://github.com/natefaubion/purescript-variant/blob/aef507e2972d294ecd735575371eccbc61ac1ac4/src/Data/Variant/Internal.purs#L31 149 | instance fakeSumRecordHasFlowRep :: 150 | ( RowToList row rl 151 | , FakeSumRecordMembers rl 152 | ) => HasFlowRep (Variant row) where 153 | toFlowRep _ = intercalate "|" members 154 | where 155 | rlp = RLProxy :: RLProxy rl 156 | members = toFakeSumRecordMembers rlp 157 | 158 | class FakeSumRecordMembers (rl :: RowList) where 159 | toFakeSumRecordMembers :: RLProxy rl -> List String 160 | 161 | instance consFakeSumRecordMembers :: 162 | ( FakeSumRecordMembers tail 163 | , IsSymbol name 164 | , HasFlowRep ty 165 | ) => FakeSumRecordMembers (Cons name ty tail) where 166 | toFakeSumRecordMembers _ = head : tail 167 | where 168 | namep = SProxy :: SProxy name 169 | key = reflectSymbol namep 170 | typ = Proxy :: Proxy ty 171 | val = toFlowRep typ 172 | head = "{type:\"" <> key <> "\", value:" <> val <> "}" 173 | tailp = RLProxy :: RLProxy tail 174 | tail = toFakeSumRecordMembers tailp 175 | 176 | instance nilFakeSumRecordMembers :: FakeSumRecordMembers Nil where 177 | toFakeSumRecordMembers _ = mempty 178 | -------------------------------------------------------------------------------- /src/Bismuth/LibDef.purs: -------------------------------------------------------------------------------- 1 | module Bismuth.LibDef where 2 | 3 | import Prelude 4 | 5 | import Bismuth (class HasFlowRep, toFlowRep) 6 | import Data.Foldable (foldMap, intercalate) 7 | import Data.StrMap (StrMap, toArrayWithKey) 8 | import Type.Proxy (Proxy(..)) 9 | 10 | -- | Extra declarations to be added to module definitions 11 | type Declarations = Array String 12 | 13 | -- | All exported items of our exports 14 | type Exports = StrMap String 15 | 16 | -- | A convenience function for creating module definitions 17 | -- | Takes a the module name, an array of declarations to be inserted, and exports 18 | createModuleDefinition :: String -> Declarations -> Exports -> String 19 | createModuleDefinition s declarations exports = 20 | "declare module '" <> s <> "' {\n" 21 | <> foldMap (flip (<>) ";\n") declarations <> "\n" 22 | <> "declare module.exports: {\n" 23 | <> intercalate ",\n" (toArrayWithKey renderItem exports) 24 | <> "\n}\n}" 25 | where 26 | renderItem k v = k <> ": " <> v 27 | 28 | -- | A convenience function for declaring a flow type in a lib definiton 29 | declareFlowType :: forall proxy a 30 | . HasFlowRep a 31 | => String -> proxy a -> String 32 | declareFlowType name _ = 33 | "declare type " <> name <> "=" <> ty 34 | where 35 | ty = toFlowRep (Proxy :: Proxy a) 36 | 37 | -- | A convenience function for declaring types taking a concrete value over a proxy. 38 | declareFlowType' :: forall a 39 | . HasFlowRep a 40 | => String 41 | -> a 42 | -> String 43 | declareFlowType' s a = declareFlowType s (Proxy :: Proxy a) 44 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Bismuth (generateFlowType) 6 | import Control.Monad.Eff (Eff) 7 | import Control.Monad.Eff.Class (liftEff) 8 | import Data.Foldable (intercalate) 9 | import Data.Foreign (Foreign) 10 | import Data.Function.Uncurried (Fn2) 11 | import Data.Identity (Identity(..)) 12 | import Data.Nullable (Nullable) 13 | import Data.StrMap (StrMap) 14 | import Data.Symbol (SProxy(..)) 15 | import Data.Variant (Variant, inj) 16 | import Node.Encoding (Encoding(..)) 17 | import Node.FS.Sync (writeTextFile) 18 | import Test.Spec (describe, it) 19 | import Test.Spec.Reporter (consoleReporter) 20 | import Test.Spec.Runner (run) 21 | import Text.Prettier (defaultOptions, format) 22 | import Type.Proxy (Proxy(..)) 23 | 24 | type A = 25 | { a :: Number 26 | , b :: String 27 | , c :: { d :: String } 28 | , e :: Array String 29 | , f :: Nullable String 30 | , g :: Number -> Number -> Number 31 | , h :: Fn2 Number Number Number 32 | , i :: Fn2 Number (Fn2 Number Number Number) Number 33 | , k :: StrMap Number 34 | , l :: Foreign 35 | , m :: Eff () String 36 | } 37 | 38 | type VariantTest = Variant 39 | ( a :: String 40 | , b :: Number 41 | , c :: Boolean 42 | ) 43 | 44 | variantTestValue :: VariantTest 45 | variantTestValue = inj (SProxy :: SProxy "a") "string" 46 | 47 | main :: _ 48 | main = run [consoleReporter] do 49 | describe "purescript-bismuth" do 50 | describe "codegen" do 51 | it "can generate types" do 52 | liftEff generateFlowFile 53 | 54 | generateFlowFile :: _ 55 | generateFlowFile = writeTextFile UTF8 "./test/generated.flow.js" values 56 | where 57 | values = format defaultOptions $ "//@flow\n" <> intercalate "\n" 58 | [ generateFlowType "A" (Identity variantTestValue) -- you can use Identity for values with a generic "proxy" 59 | , generateFlowType "VariantTest" (Proxy :: Proxy VariantTest) 60 | ] 61 | -------------------------------------------------------------------------------- /test/generated.flow.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | export type A = 3 | | { type: "a", value: string } 4 | | { type: "b", value: number } 5 | | { type: "c", value: boolean }; 6 | export type VariantTest = 7 | | { type: "a", value: string } 8 | | { type: "b", value: number } 9 | | { type: "c", value: boolean }; 10 | -------------------------------------------------------------------------------- /test/test.flow.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | import * as Gen from "./generated.flow"; 3 | 4 | const a1: Gen.A = { 5 | a: 1, 6 | b: "asdf", 7 | c: { 8 | d: "asdf" 9 | }, 10 | e: ["asdf"], 11 | f: "asdf", 12 | g: a => b => a + b, 13 | h: (a, b) => a, 14 | i: (a, b) => a, 15 | k: { a: 1212 }, 16 | l: "literally anything", 17 | m: () => "asdf" 18 | }; 19 | 20 | const a2: Gen.A = { 21 | a: 123, 22 | b: "asdf", 23 | c: { 24 | d: "asdf" 25 | }, 26 | e: ["asdf"], 27 | f: null, 28 | g: a => b => a - b, 29 | h: (a, b) => a, 30 | i: (a, b) => a, 31 | k: { j: 1, m: 2 }, 32 | l: false, 33 | m: () => "asdf" 34 | }; 35 | 36 | const fakeUnion1: Gen.VariantTest = { 37 | type: "a", 38 | value: "asdf" 39 | }; 40 | 41 | const fakeUnion2: Gen.VariantTest = { 42 | type: "b", 43 | value: 123 44 | }; 45 | 46 | const fakeUnion3: Gen.VariantTest = { 47 | type: "c", 48 | value: true 49 | }; 50 | --------------------------------------------------------------------------------