Added known issue to FT

Added known issue to FT

{-
Copyright 2015 SlamData, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-}

module Test.SlamData.Feature.Test.Search where

import Prelude
import Control.Apply ((*>))
import Test.Feature.Log (successMsg)
import Test.SlamData.Feature.Expectations as Expect
import Test.SlamData.Feature.Monad (SlamFeature)
import Test.SlamData.Feature.Interactions as Interact
import Test.Feature.Scenario (scenario)

searchScenario ∷ String → Array String → SlamFeature Unit → SlamFeature Unit
searchScenario =
  scenario
    "Search"
    (Interact.createWorkspaceInTestFolder "Search")
    (Interact.deleteFileInTestFolder "Search.slam"
       *> Interact.browseRootFolder)

test ∷ SlamFeature Unit
test = do
  searchScenario "Search for a city" [] do
    Interact.insertOpenCardInLastDeck
    Interact.selectFileForLastOpenCard "/test-mount/testDb/zips"
    Interact.accessNextCardInLastDeck
    Interact.insertSearchCardInLastDeck
    Interact.provideSearchStringInLastSearchCard "springfield"
    Interact.accessNextCardInLastDeck
    Interact.selectBuildChart
    Interact.insertPivotCard
    Interact.addColumn "city"
    Interact.addColumn "pop"
    Interact.accessNextCardInLastDeck
    Interact.insertChartCardInLastDeck
    Expect.cardsInTableColumnInLastCardToContain 25 "city" "SPRINGFIELD"
    successMsg "Successfully searched for a city"

  searchScenario "Search within results" [] do
    Interact.insertOpenCardInLastDeck
    Interact.selectFileForLastOpenCard "/test-mount/testDb/zips"
    Interact.accessNextCardInLastDeck
    Interact.insertSearchCardInLastDeck
    Interact.provideSearchStringInLastSearchCard "springfield"
    Interact.accessNextCardInLastDeck
    Interact.insertSearchCardInLastDeck
    Interact.provideSearchStringInLastSearchCard "OR"
    Interact.accessNextCardInLastDeck
    Interact.selectBuildChart
    Interact.insertPivotCard
    Interact.addColumn "city"
    Interact.addColumn "state"
    Interact.accessNextCardInLastDeck
    Interact.insertChartCardInLastDeck
    Expect.cardsInTableColumnInLastCardToContain 2 "city" "SPRINGFIELD"
    Expect.cardsInTableColumnInLastCardToContain 2 "state" "OR"
    successMsg "Successfully searched within results"

  searchScenario "Search with field names" ["https://github.com/slamdata/slamdata/issues/1190"] do
    Interact.insertOpenCardInLastDeck
    Interact.selectFileForLastOpenCard "/test-mount/testDb/zips"
    Interact.accessNextCardInLastDeck
    Interact.insertSearchCardInLastDeck
    Interact.provideSearchStringInLastSearchCard
      "city:springfield state:or pop:>20000"
    Interact.accessNextCardInLastDeck
    Interact.selectBuildChart
    Interact.insertPivotCard
    Interact.addColumn "city"
    Interact.addColumn "state"
    Interact.addColumn "pop"
    Interact.accessNextCardInLastDeck
    Interact.insertChartCardInLastDeck
    Expect.cardsInTableColumnInLastCardToContain 1 "city" "SPRINGFIELD"
    Expect.cardsInTableColumnInLastCardToContain 1 "state" "OR"
    Expect.cardsInTableColumnInLastCardToBeGT 1 "pop" "30000"
    successMsg "Successfully searched with field names"

  searchScenario "Suppress search results" [] do
    Interact.insertOpenCardInLastDeck
    Interact.selectFileForLastOpenCard "/test-mount/testDb/zips"
    Interact.accessNextCardInLastDeck
    Interact.insertSearchCardInLastDeck
    Interact.provideSearchStringInLastSearchCard "city:portland -state:OR"
    Interact.accessNextCardInLastDeck
    Interact.selectBuildChart
    Interact.insertPivotCard
    Interact.addColumn "city"
    Interact.addColumn "state"
    Interact.accessNextCardInLastDeck
    Interact.insertChartCardInLastDeck
    Expect.cardsInTableColumnInLastCardToContain 10 "city" "PORTLAND"
    Expect.cardsInTableColumnInLastCardToNotEq 10 "state" "OR"
    successMsg "Successfully suppressed search results"

{-
Copyright 2015 SlamData, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-}

module Test.SlamData.Feature.Test.Search where

import Prelude
import Control.Apply ((*>))
import Test.Feature.Log (successMsg)
import Test.SlamData.Feature.Expectations as Expect
import Test.SlamData.Feature.Monad (SlamFeature)
import Test.SlamData.Feature.Interactions as Interact
import Test.Feature.Scenario (scenario)

searchScenario ∷ String → Array String → SlamFeature Unit → SlamFeature Unit
searchScenario =
  scenario
    "Search"
    (Interact.createWorkspaceInTestFolder "Search")
    (Interact.deleteFileInTestFolder "Search.slam"
       *> Interact.browseRootFolder)

test ∷ SlamFeature Unit
test = do
  searchScenario "Search for a city" [] do
    Interact.insertOpenCardInLastDeck
    Interact.selectFileForLastOpenCard "/test-mount/testDb/zips"
    Interact.accessNextCardInLastDeck
    Interact.insertSearchCardInLastDeck
    Interact.provideSearchStringInLastSearchCard "springfield"
    Interact.accessNextCardInLastDeck
    Interact.selectBuildChart
    Interact.insertPivotCard
    Interact.addColumn "city"
    Interact.addColumn "pop"
    Interact.accessNextCardInLastDeck
    Interact.insertChartCardInLastDeck
    Expect.cardsInTableColumnInLastCardToContain 25 "city" "SPRINGFIELD"
    successMsg "Successfully searched for a city"

  searchScenario "Search within results" [] do
    Interact.insertOpenCardInLastDeck
    Interact.selectFileForLastOpenCard "/test-mount/testDb/zips"
    Interact.accessNextCardInLastDeck
    Interact.insertSearchCardInLastDeck
    Interact.provideSearchStringInLastSearchCard "springfield"
    Interact.accessNextCardInLastDeck
    Interact.insertSearchCardInLastDeck
    Interact.provideSearchStringInLastSearchCard "OR"
    Interact.accessNextCardInLastDeck
    Interact.selectBuildChart
    Interact.insertPivotCard
    Interact.addColumn "city"
    Interact.addColumn "state"
    Interact.accessNextCardInLastDeck
    Interact.insertChartCardInLastDeck
    Expect.cardsInTableColumnInLastCardToContain 2 "city" "SPRINGFIELD"
    Expect.cardsInTableColumnInLastCardToContain 2 "state" "OR"
    successMsg "Successfully searched within results"

  searchScenario "Search with field names" [] do
    Interact.insertOpenCardInLastDeck
    Interact.selectFileForLastOpenCard "/test-mount/testDb/zips"
    Interact.accessNextCardInLastDeck
    Interact.insertSearchCardInLastDeck
    Interact.provideSearchStringInLastSearchCard
      "city:springfield state:or pop:>20000"
    Interact.accessNextCardInLastDeck
    Interact.selectBuildChart
    Interact.insertPivotCard
    Interact.addColumn "city"
    Interact.addColumn "state"
    Interact.addColumn "pop"
    Interact.accessNextCardInLastDeck
    Interact.insertChartCardInLastDeck
    Expect.cardsInTableColumnInLastCardToContain 1 "city" "SPRINGFIELD"
    Expect.cardsInTableColumnInLastCardToContain 1 "state" "OR"
    Expect.cardsInTableColumnInLastCardToBeGT 1 "pop" "30000"
    successMsg "Successfully searched with field names"

  searchScenario "Suppress search results" [] do
    Interact.insertOpenCardInLastDeck
    Interact.selectFileForLastOpenCard "/test-mount/testDb/zips"
    Interact.accessNextCardInLastDeck
    Interact.insertSearchCardInLastDeck
    Interact.provideSearchStringInLastSearchCard "city:portland -state:OR"
    Interact.accessNextCardInLastDeck
    Interact.selectBuildChart
    Interact.insertPivotCard
    Interact.addColumn "city"
    Interact.addColumn "state"
    Interact.accessNextCardInLastDeck
    Interact.insertChartCardInLastDeck
    Expect.cardsInTableColumnInLastCardToContain 10 "city" "PORTLAND"
    Expect.cardsInTableColumnInLastCardToNotEq 10 "state" "OR"
    successMsg "Successfully suppressed search results"


fire() implemented

fire() implemented

module DemoApp.WithRactive where

import Prelude                   (Unit, bind, not, (++))
import Data.Maybe                (Maybe(Nothing, Just))
import Control.Monad.Eff         (Eff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Control.Monad.Eff.Ractive
import Control.Monad.Eff.Random  (RANDOM, random)

newtype ContT r m a = ContT ((a -> m r) -> m r)

-- | Change a property by using Ractive's set() method
-- | The `RactiveM` type constructor is used to represent _RactiveJS_ effects.
-- |
-- | See [set-property from RactiveJS docs](http://docs.ractivejs.org/latest/ractive-set) for more details.
change :: forall value. String -> value -> Ractive -> (forall e. Eff (ractiveM :: RactiveM | e) Unit)
change property value ractive = do
                                  set property value ractive
-- | Inverse a Boolean value
inverse :: Boolean -> Boolean
inverse canRand = not canRand

-- | Change the random numeric value in Ractive's property "message"
-- | The `RactiveM` type constructor is used to represent _RactiveJS_ effects.
-- | The `RANDOM` type constructor is used to represent _RANDOM_ values generator effects.
setRandom :: forall e. Ractive -> Eff (random :: RANDOM, ractiveM :: RactiveM | e) Unit
setRandom ractive = do
            n <- random
            (change "message" n ractive)

-- | Callback for `logo-clicked` proxy event
-- | Each time we click a new random number will be generated unless we disable it by clicking on the button below
onLogoClicked :: forall event e. Ractive -> event -> Eff (ractiveM :: RactiveM, random :: RANDOM | e) Unit
onLogoClicked = \r e -> do
                         canRandomize <- get "canRandomize" r
                         if canRandomize then (setRandom r) else (change "message" "Randomization disabled!" r)

-- | Callback for `control-button-clicked` proxy event
-- | Here we can enable/disable the randomization functionality of the app.
onControlButtonClicked :: forall event eff. Ractive -> event -> Eff (ractiveM :: RactiveM | eff) Unit
onControlButtonClicked = \r e -> do
                                  canRandomize <- (get "canRandomize" r)
                                  change "canRandomize" (inverse canRandomize) r

-- | Writes log messages to the Console Output Panel in Browser
writeLog :: forall e. Ractive -> String -> String -> Eff (ractiveM :: RactiveM | e) Unit
writeLog ractive logName message = do
                                    current <- get logName ractive
                                    let newLog = (current++ message)
                                    change logName newLog ractive

main :: forall eff.
                Eff (
                  ractiveM :: RactiveM,
                  console :: CONSOLE,
                  random :: RANDOM | eff
                  ) Unit
main = do
       -- | Here we define a single Ractive instance.
       -- | Every instance must be given a `template` and a DOM-element `el` to mount.
       -- | The template can be a simple string describing instance's HTML-structure or a separate
       -- | <script>-tag with a non-javascript type-attribute, for example "text/ractive".
       -- | In most cases there's also a `data`-field containing properties,
       -- | child-components, functions etc.

       -- | Define Component Properties
       let componentSettings = Data { template : "#component-template",
                                 "data" : {
                                   info : "I'm an embedded Ractive.JS component",
                                   logoUrl   : "./content/img/ractive-logo.png"
                                 }
                              }
       -- | Create a new Component based on Ractive as its `base class`
       let component = extend componentSettings
       -- | Define MainApp's Properties containing a Child Component
       let appSettings = Data {
                         template : "#template",
                         el       : "#app",
                         "data" : {
                                  uiLibrary : "RactiveJS",
                                  language  : "PureScript",
                                  logoUrl   : "./content/img/ps-logo.png",
                                  message   : "Click on the PureScript Logo!",
                                  consoleMessages: "no messages",
                                  canRandomize : true,
                                  counter: 0,
                                  numbers: []
                              },
                          components : {
                            "mycomponent" : component -- Child Component
                          }
                      }
       -- | Instantiate the MainApp Component
       -- | This component will load its template first and then all subordinated
       -- | components and their templates
       ract <- ractive appSettings
       -- | Here we 'move' our Ractive instance from DOMNode 'app' to 'dummy'
       domObj <- detach ract -- first, we `detach` the instance
       insert ract (Target "#dummy") Nothing -- and second, we `insert` it
       -- | ---------------------------------
       -- Register event-handlers for logo-clicks & button-clicks.
       -- Generate a random number each time we click the logo.
       on "logo-clicked" (onLogoClicked ract) ract
       on "control-button-clicked" (onControlButtonClicked ract) ract
       -- | Push a value into Ractive (console only)
       -- | The Callbacks are not mandatory. Just pass a `Nothing`.
       -- | If a Callback is Nothing a `logic-less` callback will be used on JS-side
       push "numbers" 12345 (Just (\p -> writeLog ract "consoleMessages" "\r\n\r\npush completed")) ract
       -- | Get a value from Ractive and execute callback
       (pop "numbers" (Just (\result -> writeLog ract "consoleMessages" ("\r\n\r\ngot value: " ++ result))) ract)

       -- We can also deregister event handlers like in the example below
       -- See also: http://docs.ractivejs.org/latest/ractive-off
       --> off (Just "logo-clicked") Nothing ract

       -- Ractive supports observers via observe & observeOnce APIs
       --> See also: http://docs.ractivejs.org/latest/ractive-observe
       --> and: http://docs.ractivejs.org/latest/ractive-observeonce
       --> the Callback receives:
       --------------------------> n = new value
       --------------------------> o = old value
       --------------------------> kp = keypath
       (observe "message" (\n o kp -> writeLog ract "consoleMessages" ("\r\n\r\nrandom: " ++ n)) Nothing ract)
       -- | Observe `counter` property changes.
       (observe "counter" (\n o kp -> writeLog ract "consoleMessages" ("\r\n\r\ncounter: " ++ n)) Nothing ract)
       -- | returns the first DOM-Node matching the given CSS selector
       logo <- (find ".app-logo" ract)
       -- | returns an Array of DOM-Nodes matching the CSS selector
       --> See also: http://docs.ractivejs.org/latest/ractive-findall
       elems <- (findAll "div" Nothing ract)
       -- | Increment & decrement the counter.
       -- | The registered observer will display changes in the Console Panel.
       (add "counter" (Just 5.0) (Just \r -> log "Counter incremented!") ract)
       (subtract "counter" (Just 1.0) (Just \r -> log "Counter decremented!") ract)
       -- Change the internal state of Ractive instance
       -- Here we manipulate the property `message`
       --> change "message" "HELLO WORLD!" ract

       -- | animate API
       (animate "message" "Click on the PureScript Logo! :)" Nothing ract)

       -- | We can fire events with `fire`
       fire "logo-clicked" Nothing ract
       -- | ----------------------------

       -- Return a value from Ractive
       -- See also: http://docs.ractivejs.org/latest/ractive-get
       m <- (get "message" ract)
       log m

module DemoApp.WithRactive where

import Prelude                   (Unit, bind, not, (++))
import Data.Maybe                (Maybe(Nothing, Just))
import Control.Monad.Eff         (Eff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Control.Monad.Eff.Ractive
import Control.Monad.Eff.Random  (RANDOM, random)

newtype ContT r m a = ContT ((a -> m r) -> m r)

-- | Change a property by using Ractive's set() method
-- | The `RactiveM` type constructor is used to represent _RactiveJS_ effects.
-- |
-- | See [set-property from RactiveJS docs](http://docs.ractivejs.org/latest/ractive-set) for more details.
change :: forall value. String -> value -> Ractive -> (forall e. Eff (ractiveM :: RactiveM | e) Unit)
change property value ractive = do
                                  set property value ractive
-- | Inverse a Boolean value
inverse :: Boolean -> Boolean
inverse canRand = not canRand

-- | Change the random numeric value in Ractive's property "message"
-- | The `RactiveM` type constructor is used to represent _RactiveJS_ effects.
-- | The `RANDOM` type constructor is used to represent _RANDOM_ values generator effects.
setRandom :: forall e. Ractive -> Eff (random :: RANDOM, ractiveM :: RactiveM | e) Unit
setRandom ractive = do
            n <- random
            (change "message" n ractive)

-- | Callback for `logo-clicked` proxy event
-- | Each time we click a new random number will be generated unless we disable it by clicking on the button below
onLogoClicked :: forall event e. Ractive -> event -> Eff (ractiveM :: RactiveM, random :: RANDOM | e) Unit
onLogoClicked = \r e -> do
                         canRandomize <- get "canRandomize" r
                         if canRandomize then (setRandom r) else (change "message" "Randomization disabled!" r)

-- | Callback for `control-button-clicked` proxy event
-- | Here we can enable/disable the randomization functionality of the app.
onControlButtonClicked :: forall event eff. Ractive -> event -> Eff (ractiveM :: RactiveM | eff) Unit
onControlButtonClicked = \r e -> do
                                  canRandomize <- (get "canRandomize" r)
                                  change "canRandomize" (inverse canRandomize) r

-- | Writes log messages to the Console Output Panel in Browser
writeLog :: forall e. Ractive -> String -> String -> Eff (ractiveM :: RactiveM | e) Unit
writeLog ractive logName message = do
                                    current <- get logName ractive
                                    let newLog = (current++ message)
                                    change logName newLog ractive

main :: forall eff.
                Eff (
                  ractiveM :: RactiveM,
                  console :: CONSOLE,
                  random :: RANDOM | eff
                  ) Unit
main = do
       -- | Here we define a single Ractive instance.
       -- | Every instance must be given a `template` and a DOM-element `el` to mount.
       -- | The template can be a simple string describing instance's HTML-structure or a separate
       -- | <script>-tag with a non-javascript type-attribute, for example "text/ractive".
       -- | In most cases there's also a `data`-field containing properties,
       -- | child-components, functions etc.

       -- | Define Component Properties
       let componentSettings = Data { template : "#component-template",
                                 "data" : {
                                   info : "I'm an embedded Ractive.JS component",
                                   logoUrl   : "./content/img/ractive-logo.png"
                                 }
                              }
       -- | Create a new Component based on Ractive as its `base class`
       let component = extend componentSettings
       -- | Define MainApp's Properties containing a Child Component
       let appSettings = Data {
                         template : "#template",
                         el       : "#app",
                         "data" : {
                                  uiLibrary : "RactiveJS",
                                  language  : "PureScript",
                                  logoUrl   : "./content/img/ps-logo.png",
                                  message   : "Click on the PureScript Logo!",
                                  consoleMessages: "no messages",
                                  canRandomize : true,
                                  counter: 0,
                                  numbers: []
                              },
                          components : {
                            "mycomponent" : component -- Child Component
                          }
                      }
       -- | Instantiate the MainApp Component
       -- | This component will load its template first and then all subordinated
       -- | components and their templates
       ract <- ractive appSettings
       -- | Here we 'move' our Ractive instance from DOMNode 'app' to 'dummy'
       domObj <- detach ract -- first, we `detach` the instance
       insert ract (Target "#dummy") Nothing -- and second, we `insert` it
       -- | ---------------------------------
       -- Register event-handlers for logo-clicks & button-clicks.
       -- Generate a random number each time we click the logo.
       on "logo-clicked" (onLogoClicked ract) ract
       on "control-button-clicked" (onControlButtonClicked ract) ract
       -- | Push a value into Ractive (console only)
       -- | The Callbacks are not mandatory. Just pass a `Nothing`.
       -- | If a Callback is Nothing a `logic-less` callback will be used on JS-side
       push "numbers" 12345 (Just (\p -> writeLog ract "consoleMessages" "\r\n\r\npush completed")) ract
       -- | Get a value from Ractive and execute callback
       (pop "numbers" (Just (\result -> writeLog ract "consoleMessages" ("\r\n\r\ngot value: " ++ result))) ract)

       -- We can also deregister event handlers like in the example below
       -- See also: http://docs.ractivejs.org/latest/ractive-off
       --> off (Just "logo-clicked") Nothing ract

       -- Ractive supports observers via observe & observeOnce APIs
       --> See also: http://docs.ractivejs.org/latest/ractive-observe
       --> and: http://docs.ractivejs.org/latest/ractive-observeonce
       --> the Callback receives:
       --------------------------> n = new value
       --------------------------> o = old value
       --------------------------> kp = keypath
       (observe "message" (\n o kp -> writeLog ract "consoleMessages" ("\r\n\r\nrandom: " ++ n)) Nothing ract)
       -- | Observe `counter` property changes.
       (observe "counter" (\n o kp -> writeLog ract "consoleMessages" ("\r\n\r\ncounter: " ++ n)) Nothing ract)
       -- | returns the first DOM-Node matching the given CSS selector
       logo <- (find ".app-logo" ract)
       -- | returns an Array of DOM-Nodes matching the CSS selector
       --> See also: http://docs.ractivejs.org/latest/ractive-findall
       elems <- (findAll "div" Nothing ract)
       -- | Increment & decrement the counter.
       -- | The registered observer will display changes in the Console Panel.
       (add "counter" (Just 5.0) (Just \r -> log "Counter incremented!") ract)
       (subtract "counter" (Just 1.0) (Just \r -> log "Counter decremented!") ract)
       -- Change the internal state of Ractive instance
       -- Here we manipulate the property `message`
       --> change "message" "HELLO WORLD!" ract

       -- | animate API
       (animate "message" "Click on the PureScript Logo! :)" Nothing ract)

       -- | We can fire events with `fire`
       -- | Here we generate a few `logo-clicked` proxy-events
       fire "logo-clicked" Nothing ract
       -- | ----------------------------

       -- Return a value from Ractive
       -- See also: http://docs.ractivejs.org/latest/ractive-get
       m <- (get "message" ract)
       log m


Simplify example of routing

Simplify example of routing

module Main where

import Prelude
import Control.Alternative ((<|>))
import Control.Monad.Aff.AVar (AVAR)
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (log, CONSOLE)
import Control.Monad.Eff.Exception (EXCEPTION)
import Data.MediaType.Common (textHTML)
import Hyper.Core (closeHeaders, writeStatus, StatusLineOpen, statusOK, class ResponseWriter, ResponseEnded, Conn, Middleware, Port(Port))
import Hyper.HTML (element_, h1, p, text)
import Hyper.Method (Method)
import Hyper.Node.Server (ResponseBody, defaultOptions, runServer)
import Hyper.Response (class Response, respond, contentType)
import Hyper.Routing.ResourceRouter (defaultRouterFallbacks, router, linkTo, resource, runRouter, handler)
import Node.Buffer (BUFFER)
import Node.HTTP (HTTP)

app :: forall m req res rw c.
       (Monad m, ResponseWriter rw m ResponseBody, Response m String ResponseBody) =>
       Middleware
       m
       (Conn { url :: String, method :: Method | req }
             { writer :: rw StatusLineOpen | res }
             c)
       (Conn { url :: String, method :: Method | req }
             { writer :: rw ResponseEnded | res }
             c)
app =
  runRouter
  defaultRouterFallbacks

  -- Resources:
  (router home <|> router about)
    where
      htmlWithStatus status doc =
        writeStatus status
        >=> contentType textHTML
        >=> closeHeaders
        >=> respond doc

      homeView =
        element_ "section" [ h1 [] [ text "Welcome!" ]
                           , p [] [ text "Read more at "
                                    -- Type-safe routing:
                                  , linkTo about [text "About"]
                                  , text "."
                                  ]
                           ]

      home =
        resource { path = []
                 , "GET" = handler (htmlWithStatus statusOK homeView)
                 }

      aboutView =
        element_ "section" [ h1 [] [ text "About" ]
                           , p [] [ text "OK, about this example..." ]
                           ]

      about =
        resource { path = ["about"]
                 , "GET" = handler (htmlWithStatus statusOK aboutView)
                 }

main :: forall e. Eff (http :: HTTP, console :: CONSOLE, err :: EXCEPTION, avar :: AVAR, buffer :: BUFFER | e) Unit
main =
  let
    -- Some nice console printing when the server starts, and if a request
    -- fails (in this case when the request body is unreadable for some reason).
    onListening (Port port) = log ("Listening on http://localhost:" <> show port)
    onRequestError err = log ("Request failed: " <> show err)

  -- Let's run it.
  in runServer defaultOptions onListening onRequestError {} app

module Main where

import Prelude
import Control.Alternative ((<|>))
import Control.Monad.Aff.AVar (AVAR)
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (log, CONSOLE)
import Control.Monad.Eff.Exception (EXCEPTION)
import Data.MediaType.Common (textHTML)
import Data.Tuple (Tuple(Tuple))
import Hyper.Core (closeHeaders, statusMethodNotAllowed, statusNotFound, writeStatus, StatusLineOpen, statusOK, class ResponseWriter, ResponseEnded, Conn, Middleware, Port(Port))
import Hyper.HTML (asString, element_, h1, p, text)
import Hyper.Method (Method)
import Hyper.Node.Server (ResponseBody, defaultOptions, runServer)
import Hyper.Response (class Response, respond, contentType)
import Hyper.Routing.ResourceRouter (router, linkTo, resource, runRouter, handler)
import Node.Buffer (BUFFER)
import Node.Encoding (Encoding(UTF8))
import Node.HTTP (HTTP)

app :: forall m req res rw c.
       (Monad m, ResponseWriter rw m ResponseBody, Response m (Tuple String Encoding) ResponseBody) =>
       Middleware
       m
       (Conn { url :: String, method :: Method | req }
             { writer :: rw StatusLineOpen | res }
             c)
       (Conn { url :: String, method :: Method | req }
             { writer :: rw ResponseEnded | res }
             c)
app =
  runRouter
  { onNotFound:
    writeStatus statusNotFound
    >=> closeHeaders
    >=> respond (Tuple "Not Found" UTF8)
  , onMethodNotAllowed:
    \method ->
    writeStatus statusMethodNotAllowed
    >=> closeHeaders
    >=> respond (Tuple ("Method " <> show method <> " not allowed.") UTF8)
  }

  -- Resources:
  (router home <|> router about)
    where
      htmlWithStatus status x =
        writeStatus status
        >=> contentType textHTML
        >=> closeHeaders
        >=> respond (Tuple (asString x) UTF8)

      homeView =
        element_ "section" [ h1 [] [ text "Welcome!" ]
                           , p [] [ text "Read more at "
                                    -- Type-safe routing:
                                  , linkTo about [text "About"]
                                  , text "."
                                  ]
                           ]

      home =
        resource { path = []
                 , "GET" = handler (htmlWithStatus statusOK homeView)
                 }

      aboutView =
        element_ "section" [ h1 [] [ text "About" ]
                           , p [] [ text "OK, about this example..." ]
                           ]

      about =
        resource { path = ["about"]
                 , "GET" = handler (htmlWithStatus statusOK aboutView)
                 }

main :: forall e. Eff (http :: HTTP, console :: CONSOLE, err :: EXCEPTION, avar :: AVAR, buffer :: BUFFER | e) Unit
main =
  let
    -- Some nice console printing when the server starts, and if a request
    -- fails (in this case when the request body is unreadable for some reason).
    onListening (Port port) = log ("Listening on http://localhost:" <> show port)
    onRequestError err = log ("Request failed: " <> show err)

  -- Let's run it.
  in runServer defaultOptions onListening onRequestError {} app


optionalMatch - matching optional path parts

module Routing.Match where

import Prelude

import Control.Alt (class Alt, (<|>))
import Control.Alternative (class Alternative)
import Control.Plus (class Plus)

import Data.Either (Either(..))
import Data.Foldable (foldl)
import Data.Int (fromString)
import Data.List (List(..), reverse)
import Data.Map as M
import Data.Maybe (Maybe(..))
import Data.Newtype (class Newtype, unwrap)
import Data.Semiring.Free (Free, free)
import Data.Tuple (Tuple(..), snd)
import Data.Validation.Semiring (V, invalid, unV)

import Global (readFloat, isNaN)

import Routing.Match.Class (class MatchClass)
import Routing.Match.Error (MatchError(..), showMatchError)
import Routing.Types (Route, RoutePart(..))

newtype Match a = Match (Route -> V (Free MatchError) (Tuple Route a))

-- Manual instance due to the `Route` synonym in the above
instance newtypeMatch :: Newtype (Match a) (List RoutePart -> V (Free MatchError) (Tuple (List RoutePart) a)) where
  wrap = Match
  unwrap (Match m) = m

instance matchMatchClass :: MatchClass Match where
  lit input = Match \route ->
    case route of
      Cons (Path i) rs | i == input ->
        pure $ Tuple rs unit
      Cons (Path _) rs ->
        invalid $ free $  UnexpectedPath input
      _ ->
        invalid $ free ExpectedPathPart

  num = Match \route ->
    case route of
      Cons (Path input) rs ->
        let res = readFloat input in
        if isNaN res then
          invalid $ free ExpectedNumber
        else
          pure $ Tuple rs res
      _ ->
        invalid $ free ExpectedNumber

  int = Match \route ->
    case route of
      Cons (Path input) rs -> case fromString input of
        Nothing -> invalid $ free ExpectedInt
        Just res -> pure $ Tuple rs res
      _ ->
        invalid $ free ExpectedInt

  bool = Match \route ->
    case route of
      Cons (Path input) rs | input == "true" ->
        pure $ Tuple rs true
      Cons (Path input) rs | input == "false" ->
        pure $ Tuple rs false
      _ ->
        invalid $ free ExpectedBoolean

  str = Match \route ->
    case route of
      Cons (Path input) rs ->
        pure $ Tuple rs input
      _ ->
        invalid $ free ExpectedString

  param key = Match \route ->
    case route of
      Cons (Query map) rs ->
        case M.lookup key map of
          Nothing ->
            invalid $ free $ KeyNotFound key
          Just el ->
            pure $ Tuple (Cons (Query <<< M.delete key $ map) rs) el
      _ ->
        invalid $ free ExpectedQuery

  params = Match \route ->
    case route of
      Cons (Query map) rs ->
        pure $ Tuple rs map
      _ ->
        invalid $ free ExpectedQuery

  end = Match \route ->
    case route of
      Nil -> pure $ Tuple Nil unit
      _ -> invalid $ free ExpectedEnd

  fail msg = Match \_ ->
    invalid $ free $ Fail msg

instance matchFunctor :: Functor Match where
  map fn (Match r2e) = Match $ \r ->
    unV invalid (\(Tuple rs a) -> pure $ Tuple rs (fn a)) $ r2e r

instance matchAlt :: Alt Match where
  alt (Match r2e1) (Match r2e2) = Match $ \r -> do
    (r2e1 r) <|> (r2e2 r)

instance matchPlus :: Plus Match where
  empty = Match $ const $ invalid one

instance matchAlternative :: Alternative Match

instance matchApply :: Apply Match where
  apply (Match r2a2b) (Match r2a) =
    Match $ (\r -> unV (processFnErr r) processFnRes (r2a2b r))
    where processFnErr r err =
            invalid $ err * unV id (const one) (r2a r)
          processFnRes (Tuple rs a2b) =
            unV invalid (\(Tuple rss a) -> pure $ Tuple rss (a2b a)) (r2a rs)

instance matchApplicative :: Applicative Match where
  pure a = Match \r -> pure $ Tuple r a

-- | Matches list of matchers. Useful when argument can easy fail (not `str`)
-- | returns `Match Nil` if no matches
list :: forall a. Match a -> Match (List a)
list (Match r2a) =
  Match $ go Nil
  where go :: List a -> Route -> V (Free MatchError) (Tuple Route (List a))
        go accum r =
          unV
          (const $ pure (Tuple r (reverse accum)))
          (\(Tuple rs a) -> go (Cons a accum) rs)
          (r2a r)

-- It groups `Free MatchError` -> [[MatchError]] -map with showMatchError ->
-- [[String]] -fold with semicolon-> [String] -fold with newline-> String
runMatch :: forall a. Match a -> Route -> Either String a
runMatch (Match fn) route =
  unV foldErrors (Right <<< snd) $ fn route
  where
  foldErrors errs =
    Left $ foldl (\b a -> a <> "\n" <> b) "" do
      es <- reverse <$> unwrap errs
      pure $ foldl (\b a -> a <> ";" <> b) "" $ showMatchError <$>  es

-- | if we match something that can fail then we have to
-- | match `Either a b`. This function converts matching on such
-- | sum to matching on right subpart. Matching on left branch fails.
-- | i.e.
-- | ```purescript
-- | data Sort = Asc | Desc
-- | sortOfString :: String -> Either String Sort
-- | sortOfString "asc" = Right Asc
-- | sortOfString "desc" = Right Desc
-- | sortOfString _ = Left "incorrect sort"
-- |
-- | newtype Routing = Routing Sort
-- | routes :: Match Routing
-- | routes = (pure Routing) <*> (eitherMatch (sortOfString <$> var))
-- |
-- | ```
eitherMatch :: forall a b. Match (Either a b) -> Match b
eitherMatch (Match r2eab) = Match $ \r ->
  unV invalid runEither $ (r2eab r)
  where
  runEither (Tuple rs eit) =
    case eit of
      Left _ -> invalid $ free $ Fail "Nested check failed"
      Right res -> pure $ Tuple rs res

-- | useful for matching optional params at the end of a path
-- | ```
-- | optParams = maybe M.empty id <$> optionalMatch params <* end
-- | runMatch (lit "path" *> optParams) (parse id "path/?a=1")
-- | -- (Right (fromFoldable [(Tuple "a" "1")]))
-- | ```
optionalMatch :: forall a. Match a -> Match (Maybe a)
optionalMatch (Match fn) = Match (\route -> unV (const $ pure (Tuple route Nothing)) (pure <<< map Just) $ fn route)