Created
April 24, 2015 08:58
-
-
Save notogawa/ab2d552e4ec489f3794d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# LANGUAGE FlexibleContexts #-} | |
module Data.JQ where | |
import Control.Applicative ( Applicative, (<$>), (<*>) ) | |
import Control.Monad ( (>=>) ) | |
import Control.Monad.Reader.Class | |
import qualified Data.Aeson as JSON | |
import qualified Data.Aeson.Types as JSON | |
import qualified Data.HashMap.Strict as HM | |
import Data.List ( foldl1' ) | |
import Data.Text ( Text ) | |
import qualified Data.Text as T | |
import qualified Data.Vector as V | |
-- $setup | |
-- >>> :set -XOverloadedStrings | |
-- >>> import Data.Maybe | |
data JQArrayFilterExpr = JQArrayFilterKey Text | |
| JQArrayFilterAt Int | |
| JQArrayFilterFromTo (Maybe Int) (Maybe Int) | |
deriving Eq | |
instance Show JQArrayFilterExpr where | |
show expr = "[" ++ show' expr ++ "]" where | |
show' (JQArrayFilterKey key) = "\"" ++ T.unpack key ++ "\"" | |
show' (JQArrayFilterAt ix) = show ix | |
show' (JQArrayFilterFromTo from to) = maybe "" show from ++ ":" ++ maybe "" show to | |
data JQObjectElem = JQObjectElem (Either Text JQFilter) (Either Text JQFilter) | |
| JQObjectShortcutElem Text | |
deriving Eq | |
instance Show JQObjectElem where | |
show (JQObjectElem key value) = either T.unpack show key ++ ":" ++ either T.unpack show value | |
show (JQObjectShortcutElem object) = T.unpack object | |
data JQFilter = JQDotFilter | |
| JQFieldFilter JQFilter Text Bool | |
| JQArrayFilter JQFilter JQArrayFilterExpr Bool | |
| JQAllFilter JQFilter Bool | |
| JQCommaFilter JQFilter JQFilter | |
| JQBarFilter JQFilter JQFilter | |
| JQArray [JQFilter] | |
| JQObject [JQObjectElem] | |
deriving Eq | |
instance Show JQFilter where | |
show JQDotFilter = "." | |
show (JQFieldFilter jq key p) = show jq ++ T.unpack key ++ [ '?' | p ] | |
show (JQArrayFilter jq expr p) = shows jq "[" ++ show expr ++ "]" ++ [ '?' | p ] | |
show (JQAllFilter q p) = shows q "[]" ++ [ '?' | p ] | |
show (JQCommaFilter a b) = show a ++ "," ++ show b | |
show (JQBarFilter a b) = show a ++ show b | |
show (JQArray jqs) = "[" ++ foldl1' (\a b -> a ++ "," ++ b) (map show jqs) ++ "]" | |
show (JQObject elems) = "{" ++ foldl1' (\a b -> a ++ "," ++ b) (map show elems) ++ "}" | |
data JQError = JQE | |
instance Show JQError where | |
show _ = "JQE" | |
-- | jq filter | |
-- | |
-- http://stedolan.github.io/jq/manual/#example1 | |
-- >>> query JQDotFilter (JSON.toJSON "Hello, world!") | |
-- Right [String "Hello, world!"] | |
-- | |
-- http://stedolan.github.io/jq/manual/#example2 | |
-- >>> query (JQFieldFilter JQDotFilter "foo" False) (fromJust $ JSON.decode "{\"foo\": 42, \"bar\": \"less interesting data\"}") | |
-- Right [Number 42.0] | |
-- >>> query (JQFieldFilter JQDotFilter "foo" False) (fromJust $ JSON.decode "{\"notfoo\": true, \"alsonotfoo\": false}") | |
-- Right [Null] | |
-- >>> query (JQArrayFilter JQDotFilter (JQArrayFilterKey "foo") False) (fromJust $ JSON.decode "{\"foo\": 42}") | |
-- Right [Number 42.0] | |
-- | |
-- http://stedolan.github.io/jq/manual/#example3 | |
-- >>> query (JQFieldFilter JQDotFilter "foo" True ) (fromJust $ JSON.decode "{\"foo\": 42, \"bar\": \"less interesting data\"}") | |
-- Right [Number 42.0] | |
-- >>> query (JQFieldFilter JQDotFilter "foo" True) (fromJust $ JSON.decode "{\"notfoo\": true, \"alsonotfoo\": false}") | |
-- Right [Null] | |
-- >>> query (JQArrayFilter JQDotFilter (JQArrayFilterKey "foo") True) (fromJust $ JSON.decode "{\"foo\": 42}") | |
-- Right [Number 42.0] | |
-- >>> query (JQArrayFilter JQDotFilter (JQArrayFilterKey "foo") True) (fromJust $ JSON.decode "[1,2]") | |
-- Right [] | |
-- | |
-- http://stedolan.github.io/jq/manual/#example4 | |
-- >>> query (JQArrayFilter JQDotFilter (JQArrayFilterAt 0) False) (fromJust $ JSON.decode "[{\"name\":\"JSON\", \"good\":true}, {\"name\":\"XML\", \"good\":false}]") | |
-- Right [Object (fromList [("name",String "JSON"),("good",Bool True)])] | |
-- >>> query (JQArrayFilter JQDotFilter (JQArrayFilterAt 2) False) (fromJust $ JSON.decode "[{\"name\":\"JSON\", \"good\":true}, {\"name\":\"XML\", \"good\":false}]") | |
-- Right [Null] | |
-- >>> query (JQArrayFilter JQDotFilter (JQArrayFilterFromTo (Just 2) (Just 4)) False) (fromJust $ JSON.decode "[\"a\",\"b\",\"c\",\"d\",\"e\"]") | |
-- Right [String "c",String "d"] | |
-- >>> query (JQArrayFilter JQDotFilter (JQArrayFilterFromTo (Just 2) (Just 4)) False) (JSON.toJSON "abcdefghi") | |
-- Right [String "cd"] | |
-- >>> query (JQArrayFilter JQDotFilter (JQArrayFilterFromTo Nothing (Just 3)) False) (fromJust $ JSON.decode "[\"a\",\"b\",\"c\",\"d\",\"e\"]") | |
-- Right [String "a",String "b",String "c"] | |
-- >>> query (JQArrayFilter JQDotFilter (JQArrayFilterFromTo (Just (-2)) Nothing) False) (fromJust $ JSON.decode "[\"a\",\"b\",\"c\",\"d\",\"e\"]") | |
-- Right [String "d",String "e"] | |
-- | |
-- http://stedolan.github.io/jq/manual/#example5 | |
-- >>> query (JQAllFilter JQDotFilter False) (fromJust $ JSON.decode "[{\"name\":\"JSON\", \"good\":true}, {\"name\":\"XML\", \"good\":false}]") | |
-- Right [Object (fromList [("name",String "JSON"),("good",Bool True)]),Object (fromList [("name",String "XML"),("good",Bool False)])] | |
-- >>> query (JQAllFilter JQDotFilter False) (fromJust $ JSON.decode "[]") | |
-- Right [] | |
-- >>> query (JQAllFilter JQDotFilter False) (fromJust $ JSON.decode "{\"a\": 1, \"b\": 1}") | |
-- Right [Number 1.0,Number 1.0] | |
-- | |
-- http://stedolan.github.io/jq/manual/#example6 | |
-- >>> query (JQCommaFilter (JQFieldFilter JQDotFilter "foo" False) (JQFieldFilter JQDotFilter "bar" False)) (fromJust $ JSON.decode "{\"foo\": 42, \"bar\": \"something else\", \"baz\": true}") | |
-- Right [Number 42.0,String "something else"] | |
-- >>> query (JQCommaFilter (JQFieldFilter JQDotFilter "user" False) (JQAllFilter (JQFieldFilter JQDotFilter "projects" False) False)) (fromJust $ JSON.decode "{\"user\":\"stedolan\", \"projects\": [\"jq\", \"wikiflow\"]}") | |
-- Right [String "stedolan",String "jq",String "wikiflow"] | |
-- >>> query (JQCommaFilter (JQFieldFilter JQDotFilter "user" False) (JQAllFilter (JQFieldFilter JQDotFilter "projects" False) False)) (fromJust $ JSON.decode "{\"user\":\"stedolan\", \"projects\": [\"jq\", \"wikiflow\"]}") | |
-- Right [String "stedolan",String "jq",String "wikiflow"] | |
-- | |
-- http://stedolan.github.io/jq/manual/#example7 | |
-- >>> query (JQBarFilter (JQAllFilter JQDotFilter False) (JQFieldFilter JQDotFilter "name" False)) (fromJust $ JSON.decode "[{\"name\":\"JSON\", \"good\":true}, {\"name\":\"XML\", \"good\":false}]") | |
-- Right [String "JSON",String "XML"] | |
-- | |
-- http://stedolan.github.io/jq/manual/#example8 | |
-- >>> query (JQArray [JQFieldFilter JQDotFilter "user" False, JQAllFilter (JQFieldFilter JQDotFilter "projects" False) False]) (fromJust $ JSON.decode "{\"user\":\"stedolan\", \"projects\": [\"jq\", \"wikiflow\"]}") | |
-- Right [Array (fromList [String "stedolan",String "jq",String "wikiflow"])] | |
-- | |
query :: (Applicative m, MonadReader JSON.Value m) => JQFilter -> m (Either JQError [JSON.Value]) | |
query JQDotFilter = Right . (:[]) <$> ask | |
query (JQFieldFilter jq key p)= (>>= \vs -> concat <$> mapM go vs) <$> query jq where | |
go :: JSON.Value -> Either JQError [JSON.Value] | |
go value@(JSON.Object _) = return [maybe JSON.Null id . JSON.parseMaybe (JSON.parseJSON >=> (JSON..: key)) $ value] | |
go _ = if p then return [] else Left JQE | |
query (JQArrayFilter jq expr p) = (>>= \vs -> concat <$> mapM (go expr) vs) <$> query jq where | |
go :: JQArrayFilterExpr -> JSON.Value -> Either JQError [JSON.Value] | |
go (JQArrayFilterKey key) value@(JSON.Object _) = return [maybe JSON.Null id . JSON.parseMaybe (JSON.parseJSON >=> (JSON..: key)) $ value] | |
go (JQArrayFilterAt ix) (JSON.Array array) = return [maybe JSON.Null id $ array V.!? ix] | |
go (JQArrayFilterFromTo Nothing Nothing) _ = Left JQE | |
go (JQArrayFilterFromTo mfrom mto) (JSON.Array array) = return . drop from . take to $ V.toList array where | |
len = V.length array | |
from = maybe 0 (\x -> x + if x < 0 then len else 0) mfrom | |
to = maybe len (\x -> x + if x < 0 then len else 0) mto | |
go (JQArrayFilterFromTo mfrom mto) (JSON.String str) = return [ JSON.toJSON $ T.drop from $ T.take to str ] where | |
len = T.length str | |
from = maybe 0 (\x -> x + if x < 0 then len else 0) mfrom | |
to = maybe len (\x -> x + if x < 0 then len else 0) mto | |
go _ _ = if p then return [] else Left JQE | |
query (JQAllFilter jq p) = (>>= \vs -> concat <$> mapM go vs) <$> query jq where | |
go :: JSON.Value -> Either JQError [JSON.Value] | |
go (JSON.Object object) = return $ HM.elems object | |
go (JSON.Array array) = return $ V.toList array | |
go _ = if p then return [] else Left JQE | |
query (JQCommaFilter a b) = (\x y -> (++) <$> x <*> y) <$> query a <*> query b | |
query (JQBarFilter a b) = do | |
evs <- query a | |
case evs of | |
Left e -> return $ Left e | |
Right values -> foldl1' (\x y -> (++) <$> x <*> y) <$> mapM (\value -> local (const value) $ query b) values | |
query (JQArray jqs) = either Left (Right . (:[]) . JSON.toJSON) . foldl1' (\x y -> (++) <$> x <*> y) <$> mapM query jqs | |
query (JQObject elems) = undefined -- do objs <- mapM_ query' elems | |
query' (JQObjectShortcutElem object) = query' (JQObjectElem (Left object) (Right $ JQFieldFilter JQDotFilter object False)) | |
query' (JQObjectElem ekeyjq evaluejq) = do | |
ekey <- either (return . Right . (:[]) . JSON.toJSON) query ekeyjq | |
case ekey of | |
Right [JSON.String key] -> do | |
evalue <- either (return . Right . (:[]) . JSON.toJSON) query evaluejq | |
case evalue of | |
Right value -> return $ Right [JSON.object [ key JSON..= value ] ] | |
_ -> return $ Left JQE | |
_ -> return $ Left JQE |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment