saner format for metadata --json
metadata --json output format has changed, adding a inner json object named "fields" which contains only the fields and their values. This should be easier to parse than the old format, which mixed up metadata fields with other keys in the json object. Any consumers of the old format will need to be updated. This adds a dependency on unordered-containers for parsing MetaData from JSON, but it's a free dependency; aeson pulls in that library.
This commit is contained in:
parent
5c92b8e034
commit
8bc8469c38
6 changed files with 80 additions and 6 deletions
|
@ -1,5 +1,10 @@
|
||||||
git-annex (6.20160620) UNRELEASED; urgency=medium
|
git-annex (6.20160726) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* metadata --json output format has changed, adding a inner json object
|
||||||
|
named "fields" which contains only the fields and their values.
|
||||||
|
This should be easier to parse than the old format, which mixed up
|
||||||
|
metadata fields with other keys in the json object.
|
||||||
|
Any consumers of the old format will need to be updated.
|
||||||
* Added --branch option to copy, drop, fsck, get, metadata, mirror, move,
|
* Added --branch option to copy, drop, fsck, get, metadata, mirror, move,
|
||||||
and whereis commands. This option makes git-annex operate on files that
|
and whereis commands. This option makes git-annex operate on files that
|
||||||
are included in a specified branch (or other treeish).
|
are included in a specified branch (or other treeish).
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- git-annex command
|
{- git-annex command
|
||||||
-
|
-
|
||||||
- Copyright 2014 Joey Hess <id@joeyh.name>
|
- Copyright 2014-2016 Joey Hess <id@joeyh.name>
|
||||||
-
|
-
|
||||||
- Licensed under the GNU GPL version 3 or higher.
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
@ -10,9 +10,13 @@ module Command.MetaData where
|
||||||
import Command
|
import Command
|
||||||
import Annex.MetaData
|
import Annex.MetaData
|
||||||
import Logs.MetaData
|
import Logs.MetaData
|
||||||
|
import Messages.JSON (ParsedJSON(..))
|
||||||
|
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.ByteString.Lazy.UTF8 as BU
|
||||||
import Data.Time.Clock.POSIX
|
import Data.Time.Clock.POSIX
|
||||||
|
import Data.Aeson
|
||||||
|
|
||||||
cmd :: Command
|
cmd :: Command
|
||||||
cmd = withGlobalOptions ([jsonOption] ++ annexedMatchingOptions) $
|
cmd = withGlobalOptions ([jsonOption] ++ annexedMatchingOptions) $
|
||||||
|
@ -95,10 +99,38 @@ perform now o k = case getSet o of
|
||||||
|
|
||||||
cleanup :: Key -> CommandCleanup
|
cleanup :: Key -> CommandCleanup
|
||||||
cleanup k = do
|
cleanup k = do
|
||||||
l <- map unwrapmeta . fromMetaData <$> getCurrentMetaData k
|
m <- getCurrentMetaData k
|
||||||
maybeShowJSON (JSONObject l)
|
let Object o = toJSON (MetaDataFields m)
|
||||||
showLongNote $ unlines $ concatMap showmeta l
|
maybeShowJSON $ AesonObject o
|
||||||
|
showLongNote $ unlines $ concatMap showmeta $
|
||||||
|
map unwrapmeta (fromMetaData m)
|
||||||
return True
|
return True
|
||||||
where
|
where
|
||||||
unwrapmeta (f, v) = (fromMetaField f, map fromMetaValue (S.toList v))
|
unwrapmeta (f, v) = (fromMetaField f, map fromMetaValue (S.toList v))
|
||||||
showmeta (f, vs) = map ((f ++ "=") ++) vs
|
showmeta (f, vs) = map ((f ++ "=") ++) vs
|
||||||
|
|
||||||
|
-- Metadata serialized to JSON in the field named "fields" of
|
||||||
|
-- a larger object.
|
||||||
|
newtype MetaDataFields = MetaDataFields MetaData
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
instance ToJSON MetaDataFields where
|
||||||
|
toJSON (MetaDataFields m) = object [ (fieldsField, toJSON m) ]
|
||||||
|
|
||||||
|
instance FromJSON MetaDataFields where
|
||||||
|
parseJSON (Object v) = do
|
||||||
|
f <- v .: fieldsField
|
||||||
|
case f of
|
||||||
|
Nothing -> return (MetaDataFields emptyMetaData)
|
||||||
|
Just v' -> MetaDataFields <$> parseJSON v'
|
||||||
|
parseJSON _ = fail "expected an object"
|
||||||
|
|
||||||
|
fieldsField :: T.Text
|
||||||
|
fieldsField = T.pack "fields"
|
||||||
|
|
||||||
|
parseJSONInput :: String -> Maybe (Either FilePath Key, MetaData)
|
||||||
|
parseJSONInput i = do
|
||||||
|
v <- decode (BU.fromString i)
|
||||||
|
case parsedAdded v of
|
||||||
|
Nothing -> return (parsedKeyfile v, emptyMetaData)
|
||||||
|
Just (MetaDataFields m) -> return (parsedKeyfile v, m)
|
||||||
|
|
|
@ -44,14 +44,32 @@ import Common
|
||||||
import Utility.Base64
|
import Utility.Base64
|
||||||
import Utility.QuickCheck
|
import Utility.QuickCheck
|
||||||
|
|
||||||
|
import qualified Data.Text as T
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
import qualified Data.HashMap.Strict as HM
|
||||||
import Data.Char
|
import Data.Char
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
import Data.Aeson
|
||||||
|
|
||||||
newtype MetaData = MetaData (M.Map MetaField (S.Set MetaValue))
|
newtype MetaData = MetaData (M.Map MetaField (S.Set MetaValue))
|
||||||
deriving (Show, Eq, Ord)
|
deriving (Show, Eq, Ord)
|
||||||
|
|
||||||
|
instance ToJSON MetaData where
|
||||||
|
toJSON (MetaData m) = object $ map go (M.toList m)
|
||||||
|
where
|
||||||
|
go (MetaField f, s) = (T.pack (CI.original f), toJSON s)
|
||||||
|
|
||||||
|
instance FromJSON MetaData where
|
||||||
|
parseJSON (Object o) = do
|
||||||
|
l <- HM.toList <$> parseJSON (Object o)
|
||||||
|
MetaData . M.fromList <$> mapM go l
|
||||||
|
where
|
||||||
|
go (t, l) = case mkMetaField (T.unpack t) of
|
||||||
|
Left e -> fail e
|
||||||
|
Right f -> (,) <$> pure f <*> parseJSON l
|
||||||
|
parseJSON _ = fail "expected an object"
|
||||||
|
|
||||||
{- A metadata value can be currently be set (True), or may have been
|
{- A metadata value can be currently be set (True), or may have been
|
||||||
- set before and we're remembering it no longer is (False). -}
|
- set before and we're remembering it no longer is (False). -}
|
||||||
newtype CurrentlySet = CurrentlySet Bool
|
newtype CurrentlySet = CurrentlySet Bool
|
||||||
|
@ -64,6 +82,13 @@ newtype MetaField = MetaField (CI.CI String)
|
||||||
data MetaValue = MetaValue CurrentlySet String
|
data MetaValue = MetaValue CurrentlySet String
|
||||||
deriving (Read, Show)
|
deriving (Read, Show)
|
||||||
|
|
||||||
|
instance ToJSON MetaValue where
|
||||||
|
toJSON (MetaValue _ v) = toJSON v
|
||||||
|
|
||||||
|
instance FromJSON MetaValue where
|
||||||
|
parseJSON (String v) = return $ MetaValue (CurrentlySet True) (T.unpack v)
|
||||||
|
parseJSON _ = fail "expected a string"
|
||||||
|
|
||||||
{- Metadata values compare and order the same whether currently set or not. -}
|
{- Metadata values compare and order the same whether currently set or not. -}
|
||||||
instance Eq MetaValue where
|
instance Eq MetaValue where
|
||||||
MetaValue _ a == MetaValue _ b = a == b
|
MetaValue _ a == MetaValue _ b = a == b
|
||||||
|
|
1
debian/control
vendored
1
debian/control
vendored
|
@ -25,6 +25,7 @@ Build-Depends:
|
||||||
libghc-uuid-dev,
|
libghc-uuid-dev,
|
||||||
libghc-json-dev,
|
libghc-json-dev,
|
||||||
libghc-aeson-dev,
|
libghc-aeson-dev,
|
||||||
|
libghc-unordered-containers-dev,
|
||||||
libghc-ifelse-dev,
|
libghc-ifelse-dev,
|
||||||
libghc-bloomfilter-dev,
|
libghc-bloomfilter-dev,
|
||||||
libghc-edit-distance-dev,
|
libghc-edit-distance-dev,
|
||||||
|
|
|
@ -71,6 +71,16 @@ When run without any -s or -t parameters, displays the current metadata.
|
||||||
Enable JSON output. This is intended to be parsed by programs that use
|
Enable JSON output. This is intended to be parsed by programs that use
|
||||||
git-annex. Each line of output is a JSON object.
|
git-annex. Each line of output is a JSON object.
|
||||||
|
|
||||||
|
The format of the JSON objects changed in git-annex version 6.20160726.
|
||||||
|
|
||||||
|
Example of the new format:
|
||||||
|
|
||||||
|
{"command":"metadata","file":"foo","key":"...","fields":{"author":["bar"],...},"note":"...","success":true}
|
||||||
|
|
||||||
|
Example of the old format, which lacks the inner fields object:
|
||||||
|
|
||||||
|
{"command":"metadata","file":"foo","key":"...","author":["bar"],...,"note":"...","success":true}
|
||||||
|
|
||||||
* `--all`
|
* `--all`
|
||||||
|
|
||||||
Specify instead of a file to get/set metadata on all known keys.
|
Specify instead of a file to get/set metadata on all known keys.
|
||||||
|
|
|
@ -323,7 +323,7 @@ Executable git-annex
|
||||||
Build-Depends:
|
Build-Depends:
|
||||||
base (>= 4.5 && < 5.0),
|
base (>= 4.5 && < 5.0),
|
||||||
optparse-applicative (>= 0.11.0),
|
optparse-applicative (>= 0.11.0),
|
||||||
containers (>= 0.5.0.0),
|
containers (>= 0.5.0.0),
|
||||||
exceptions (>= 0.6),
|
exceptions (>= 0.6),
|
||||||
QuickCheck (>= 2.1),
|
QuickCheck (>= 2.1),
|
||||||
stm (>= 2.3),
|
stm (>= 2.3),
|
||||||
|
@ -338,6 +338,7 @@ Executable git-annex
|
||||||
time, old-locale,
|
time, old-locale,
|
||||||
esqueleto, persistent-sqlite, persistent (<2.5), persistent-template,
|
esqueleto, persistent-sqlite, persistent (<2.5), persistent-template,
|
||||||
aeson,
|
aeson,
|
||||||
|
unordered-containers,
|
||||||
feed,
|
feed,
|
||||||
regex-tdfa
|
regex-tdfa
|
||||||
CC-Options: -Wall
|
CC-Options: -Wall
|
||||||
|
|
Loading…
Add table
Reference in a new issue