Skip to content

Support loading files from GitHub repositories #218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 25, 2021
Merged
55 changes: 29 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,36 @@
- Run and print output or show resulting JavaScript
- Multiple view modes: code, output or both
- Persistent session
- Load PureScript code from Github Gists
- Load PureScript code from GitHub Gists or repository files

### Which Libraries are Available?
### Control Features via the Query String

Most of these features can be controlled not only from the toolbar, but also using the [query parameters](https://en.wikipedia.org/wiki/Query_string):

- **Load From GitHub Repo**: Load PureScript code from a GitHub repository using the `github` parameter
- Example: `github=/purescript/trypurescript/master/client/examples/Main.purs` will load the code from this file. Note: the file should be a single PureScript module with the module name `Main`.

- **Load From Gist**: Load PureScript code from Gist id using the `gist` parameter
- Example: `gist=37c3c97f47a43f20c548` will load the code from this Gist if the file was named `Main.purs`.

- **View Mode**: Control the view mode using the `view` parameter
- Options are: `code`, `output`, `both` (default)
- Example: `view=output` will only display the output

- **Auto Compile**: Automatic compilation can be turned off using the `compile` parameter
- Options are: `true` (default), `false`
- Example: `compile=false` will turn auto compilation off

- **JavaScript Code Generation**: Print the resulting JavaScript code in the output window instead of the output of the program using the `js` parameter
- Options are: `true`, `false` (default)
- Example: `js=true` will print JavaScript code instead of the program's output

- **Session**: Load code from a session which is stored with [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) using the `session` parameter
- Usually managed by Try PureScript
- Example: `session=9162f098-070f-4053-60ea-eba47021450d` (Note: will probably not work for you)
- When used with the `gist` or `github` query parameters the code will be loaded from the source file and not the session

### Which Libraries Are Available?

Try PureScript aims to provide a complete, recent package set from <https://github.com/purescript/package-sets>. The available libraries are those listed in `staging/spago.dhall`, at the versions in the package set mentioned in `staging/packages.dhall`.

Expand All @@ -41,30 +68,6 @@ $ spago ls packages | cut -f 1 -d ' ' | xargs spago install

Before deploying an updated package set, someone (your reviewer) should check that the memory required to hold the package set's externs files does not exceed that of the try.purescript.org server.

### Control Features via the Query String

Most of these features can be controlled not only from the toolbar, but also using the [query parameters](https://en.wikipedia.org/wiki/Query_string):

- **Load From Gist**: Load PureScript code from Gist id using the `gist` parameter
- Example: `gist=37c3c97f47a43f20c548` will load the code from this Gist if the file was named `Main.purs`

- **View Mode**: Control the view mode using the `view` parameter
- Options are: `code`, `output`, `both` (default)
- Example: `view=output` will only display the output

- **Auto Compile**: Automatic compilation can be turned off using the `compile` parameter
- Options are: `true` (default), `false`
- Example: `compile=false` will turn auto compilation off

- **JavaScript Code Generation**: Print the resulting JavaScript code in the output window instead of the output of the program using the `js` parameter
- Options are: `true`, `false` (default)
- Example: `js=true` will print JavaScript code instead of the program's output

- **Session**: Load code from a session which is stored with [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) using the `session` parameter
- Usually managed by Try PureScript
- Example: `session=9162f098-070f-4053-60ea-eba47021450d` (Note: will probably not work for you)
- When used with the `gist` query parameter the code will be loaded from the Gist and not the session

## Development

### 1. Client setup
Expand Down
9 changes: 7 additions & 2 deletions client/config/dev/Try.Config.purs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
module Try.Config where

import Prelude

loaderUrl :: String
loaderUrl = "js/output"

compileUrl :: String
compileUrl = "http://localhost:8081"

mainGist :: String
mainGist = "7ad2b2eef11ac7dcfd14aa1585dd8f69"
tag :: String
tag = "load-from-github"

mainGitHubExample :: String
mainGitHubExample = "/purescript/trypurescript/" <> tag <> "/client/examples/Main.purs"
9 changes: 7 additions & 2 deletions client/config/prod/Try.Config.purs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
module Try.Config where

import Prelude

loaderUrl :: String
loaderUrl = "https://compile.purescript.org/output"

compileUrl :: String
compileUrl = "https://compile.purescript.org"

mainGist :: String
mainGist = "7ad2b2eef11ac7dcfd14aa1585dd8f69"
tag :: String
tag = "load-from-github"

mainGitHubExample :: String
mainGitHubExample = "/purescript/trypurescript/" <> tag <> "/client/examples/Main.purs"
23 changes: 23 additions & 0 deletions client/examples/ADTs.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module Main where

import Prelude

import Effect.Console (logShow)
import Data.Map (Map, lookup, singleton)
import TryPureScript (render, withConsole)

-- | A Name consists of a first name and a last name
data Name = Name String String

-- | With compiler versions >= 0.8.2, we can derive
-- | instances for Eq and Ord, making names comparable.
derive instance eqName :: Eq Name
derive instance ordName :: Ord Name

-- | The Ord instance allows us to use Names as the
-- | keys in a Map.
phoneBook :: Map Name String
phoneBook = singleton (Name "John" "Smith") "555-555-1234"

main = render =<< withConsole do
logShow (lookup (Name "John" "Smith") phoneBook)
20 changes: 20 additions & 0 deletions client/examples/DoNotation.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Main where

import Prelude
import Control.MonadPlus (guard)
import Effect.Console (logShow)
import Data.Array ((..))
import Data.Foldable (for_)
import TryPureScript

-- Find Pythagorean triples using an array comprehension.
triples :: Int -> Array (Array Int)
triples n = do
z <- 1 .. n
y <- 1 .. z
x <- 1 .. y
guard $ x * x + y * y == z * z
pure [x, y, z]

main = render =<< withConsole do
for_ (triples 20) logShow
56 changes: 56 additions & 0 deletions client/examples/Generic.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module Main where

import Prelude
import Effect.Console (logShow)
import Data.Generic.Rep (class Generic)
import Data.Eq.Generic (genericEq)
import Data.Ord.Generic (genericCompare)
import Data.Show.Generic (genericShow)
import TryPureScript (render, withConsole)

data Address = Address
{ city :: String
, state :: String
}

data Person = Person
{ first :: String
, last :: String
, address :: Address
}

-- Generic instances can be derived by the compiler,
-- using the derive keyword:
derive instance genericAddress :: Generic Address _

derive instance genericPerson :: Generic Person _

-- Now we can write instances for standard type classes
-- (Show, Eq, Ord) by using standard definitions
instance showAddress :: Show Address where
show = genericShow

instance eqAddress :: Eq Address where
eq = genericEq

instance ordAddress :: Ord Address where
compare = genericCompare

instance showPerson :: Show Person where
show = genericShow

instance eqPerson :: Eq Person where
eq = genericEq

instance ordPerson :: Ord Person where
compare = genericCompare

main = render =<< withConsole do
logShow $ Person
{ first: "John"
, last: "Smith"
, address: Address
{ city: "Faketown"
, state: "CA"
}
}
12 changes: 12 additions & 0 deletions client/examples/Loops.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module Main where

import Prelude

import Effect.Console (log)
import Data.Array ((..))
import Data.Foldable (for_)
import TryPureScript (render, withConsole)

main = render =<< withConsole do
for_ (10 .. 1) \n -> log (show n <> "...")
log "Lift off!"
66 changes: 66 additions & 0 deletions client/examples/Main.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
module Main where

import Prelude

import Data.Foldable (fold)
import Effect (Effect)
import TryPureScript (h1, h2, p, text, list, indent, link, render, code)

main :: Effect Unit
main =
render $ fold
[ h1 (text "Try PureScript!")
, p (text "Try out the examples below, or create your own!")
, h2 (text "Examples")
, list (map fromExample examples)
, h2 (text "Share Your Code")
, p (text "A PureScript file can be loaded from GitHub from a gist or a repository. To share code using a gist, simply include the gist ID in the URL as follows:")
, indent (p (code (text " try.purescript.org?gist=gist-id")))
, p (fold
[ text "The Gist should contain PureScript modulenamed "
, code (text "Main")
, text "in a file named "
, code (text "Main.purs")
, text " containing your PureScript code."
])
, p (text "To share code from a repository, include the path to the source file the URL as follows:")
, indent (p (code (text " try.purescript.org?github=/owner/repo/Source.purs")))
, p (fold
[ text "The file should be a PureScript module named "
, code (text "Main")
, text " containing your PureScript code."
])
]
where
fromExample { title, source } =
link ("https://github.com/purescript/trypurescript/load-from-github/client/examples/" <> source) (text title)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this ought to use a tag from some kind of configuration rather than hardcoding the branch name, so that we can change these without breaking the currently released version. In practice we will want this to be the tag from the current version.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea — I could thread this in as an argument to main, so that it can be accessed in various places taken from outside the PureScript code.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good 👍

Copy link
Member Author

@thomashoneyman thomashoneyman May 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could also do that with the entire dev / prod configurations in that case — thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that makes the overall setup simpler, sure, I don't see why not.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, doing this would require some kind of text replacement processing pass to work. After all, this example is code loaded literally into the Try PureScript editor.

Otherwise, we'd have to do something like read the current tag from data in the index.html file or something like that, but this would be user-visible code and I'm not a huge fan of that.

Another option is to manually hard-code the tag as part of the release process and document where the tag would need to be updated -- we release so infrequently that this feels legitimately like an option.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True - I think manually hardcoding the tag and documenting that it needs updating will work.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should split the repository README so that the main README is for users and there's a separate development guide. It could include instructions for developing, like we already have, but could also have a release guide which includes:

  • Updating the hardcoded tag
  • Checking externs sizes
  • Running the deployment

What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now I've just added a note about the hardcoded tags to the README, but I think we probably do want a dedicated, separate development + release guide.


examples =
[ { title: "Algebraic Data Types"
, source: "ADTs.purs"
}
, { title: "Loops"
, source: "Loops.purs"
}
, { title: "Operators"
, source: "Operators.purs"
}
, { title: "Records"
, source: "Records.purs"
}
, { title: "Recursion"
, source: "Recursion.purs"
}
, { title: "Do Notation"
, source: "DoNotation.purs"
}
, { title: "Type Classes"
, source: "TypeClasses.purs"
}
, { title: "Generic Programming"
, source: "Generic.purs"
}
, { title: "QuickCheck"
, source: "QuickCheck.purs"
}
]
20 changes: 20 additions & 0 deletions client/examples/Operators.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Main where

import Prelude
import Effect.Console (log)
import TryPureScript (render, withConsole)

type FilePath = String

subdirectory :: FilePath -> FilePath -> FilePath
subdirectory p1 p2 = p1 <> "/" <> p2

-- Functions can be given an infix alias
-- The generated code will still use the original function name
infixl 5 subdirectory as </>

filepath :: FilePath
filepath = "usr" </> "local" </> "bin"

main = render =<< withConsole do
log filepath
21 changes: 21 additions & 0 deletions client/examples/QuickCheck.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module Main where

import Prelude
import Data.Array (sort)
import Test.QuickCheck (quickCheck, (===))
import TryPureScript (render, withConsole, h1, h2, p, text)

main = do
render $ h1 $ text "QuickCheck"
render $ p $ text """QuickCheck is a Haskell library which allows us to assert properties
hold for our functions. QuickCheck uses type classes to generate
random test cases to verify those properties.
purescript-quickcheck is a port of parts of the QuickCheck library to
PureScript."""
render $ h2 $ text "Sort function is idempotent"
render =<< withConsole do
quickCheck \(xs :: Array Int) -> sort (sort xs) === sort xs
render $ h2 $ text "Every array is sorted"
render $ p $ text "This test should fail on some array which is not sorted"
render =<< withConsole do
quickCheck \(xs :: Array Int) -> sort xs === xs
17 changes: 17 additions & 0 deletions client/examples/Records.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module Main where

import Prelude
import Effect.Console (log)
import TryPureScript (render, withConsole)

-- We can write functions which require certain record labels...
showPerson o = o.lastName <> ", " <> o.firstName

-- ... but we are free to call those functions with any
-- additional arguments, such as "age" here.
main = render =<< withConsole do
log $ showPerson
{ firstName: "John"
, lastName: "Smith"
, age: 30
}
17 changes: 17 additions & 0 deletions client/examples/Recursion.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module Main where

import Prelude
import Effect.Console (logShow)
import TryPureScript (render, withConsole)

isOdd :: Int -> Boolean
isOdd 0 = false
isOdd n = isEven (n - 1)

isEven :: Int -> Boolean
isEven 0 = true
isEven n = isOdd (n - 1)

main = render =<< withConsole do
logShow $ isEven 1000
logShow $ isEven 1001
Loading