diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,11 @@
 # rhbzquery releases
 
+## 0.3 (2020-12-10)
+- add --list-fields option
+- field validation: error for unknown fields
+- for 'content' field, map '~'/'!~' to matches/notmatches
+- refactor operator handling to generate help
+
 ## 0.2 (2020-12-09)
 - support most search type operators (regexp, substr, isempty, and negatives)
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -12,20 +12,22 @@
 
 `rhbzquery closed rawhide xyz` : closed rawhide bugs for package xyz
 
-`rhbzquery --mine` : your open bugs (reads userid from `.bugzillarc`)
+`rhbzquery --mine` : your open bugs (gets userid from `.bugzillarc`)
 
 `rhbzquery rhel8.3 bash` : RHEL 8.3 bash bugs
 
 `rhbzquery "Package Review" reporter_realname="Your Name"` : open package reviews you reported
 
-`rhbzquery --mine all flag~fedora-review+` : all your open and closed approved reviews
+`rhbzquery --mine all flag~fedora-review+` : all open and closed approved reviews you reviewed
 
 `rhbzquery component~bugzilla summary~bugzilla` : open bugs with component and summary containing "bugzilla"
 
 `rhbzquery --file f33 xyz` : file a bug against the xyz package in F33
 
 ### Help
-`rhbzquery --help` : lists the many fields (note: not all well supported yet, eg timestamps).
+`rhbzquery --help` describes arguments
+
+`rhbzquery --list-fields` : lists the many fields (not all well supported yet, eg timestamps).
 
 ## Installation
 Run `stack install` or `cabal install`.
diff --git a/TODO b/TODO
--- a/TODO
+++ b/TODO
@@ -1,9 +1,7 @@
-- copy url to clipboard
+- --list-operators?
+- --url for server
 - tui bug browser
-- help: --fields-matching
-- use NOT Closed ?
+- --json output
 - cc=me, reporter=me
 - config file: for defining "my team" etc
-- --help: only show most important fields
-  --help-all for all
-- --count bugs
+- --count bugs (no such api though)
diff --git a/rhbzquery.cabal b/rhbzquery.cabal
--- a/rhbzquery.cabal
+++ b/rhbzquery.cabal
@@ -1,5 +1,5 @@
 name:                rhbzquery
-version:             0.2
+version:             0.3
 synopsis:            Bugzilla query tool
 description:
         A CLI tool for creating bugzilla queries for bugzilla.redhat.com.
@@ -25,6 +25,7 @@
 executable rhbzquery
   main-is:             Main.hs
   other-modules:       Bugzilla
+                       Common
                        Fields
                        Help
                        ParseArg
diff --git a/src/Common.hs b/src/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Common.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE CPP #-}
+
+module Common (error') where
+
+#if MIN_VERSION_simple_cmd(0,1,4)
+import SimpleCmd (error')
+#else
+error' :: String -> a
+#if MIN_VERSION_base(4,9,0)
+error' = errorWithoutStackTrace
+#else
+error' = error
+#endif
+#endif
diff --git a/src/Fields.hs b/src/Fields.hs
--- a/src/Fields.hs
+++ b/src/Fields.hs
@@ -1,4 +1,5 @@
 module Fields (
+  allBzFields,
   BzFields(..),
   argToFields,
   argToSimpleField
@@ -8,6 +9,7 @@
 import Data.Version
 import Numeric.Natural
 
+import Common
 import ParseArg (ArgType(..), Operator(..), ProductVersion(..), showOp)
 
 data BzFields = BzProduct
@@ -24,25 +26,14 @@
   show (BzParameter f) = f
   show (BzMeta c n) = c: show n
 
-mapField :: String -> String
-mapField "itm" = "cf_internal_target_milestone"
-mapField "itr" = "cf_internal_target_release"
-mapField "status" = "bug_status"
-mapField "verified" = "cf_verified"
-mapField "sst" = "agile_team.name"
-mapField "summary" = "short_desc"
-mapField "flag" = "flagtypes.name"
-mapField "flags" = "flagtypes.name"
-mapField p = p
-
 argToSimpleField :: ArgType -> [(BzFields,String)]
 argToSimpleField (ArgProdVer prodver) =
   productVersionQuery prodver
 argToSimpleField (ArgParameter p Equals v) =
   [(BzParameter (mapField p), v)]
 argToSimpleField (ArgParameter p _ v) =
-  error $ "Only '" ++ p ++ "=" ++ v ++ "'allowed here"
-argToSimpleField (ArgOther c) =
+  error' $ "Only '" ++ p ++ "=" ++ v ++ "'allowed here"
+argToSimpleField (ArgComponent c) =
   [(BzComponent,c)]
 -- FIXME or error for "all"?
 argToSimpleField _ = []
@@ -57,16 +48,23 @@
           val = if p == "sst"
                 then "sst_" ++ v
                 else v
+          oper = if p == "content"
+                 then case op of
+                        Equals -> "equals"
+                        Substring -> "matches"
+                        NotSubstring -> "notmatches"
+                        _ -> error' "'content' only supports matches and notmatches"
+                 else showOp op
       in if '.' `elem` param || op /= Equals
          then (i+1,[(BzMeta 'f' i, param)
-                   ,(BzMeta 'o' i, showOp op)
+                   ,(BzMeta 'o' i, oper)
                    ,(BzMeta 'v' i, val)])
          else (i,[(BzParameter param, val)])
     ArgParameterEmpty p e ->
       let param = mapField p
       in (i+1,[(BzMeta 'f' i, param)
               ,(BzMeta 'o' i, if e then "isempty" else "isnotempty")])
-    ArgOther c -> (i,[(BzComponent,c)])
+    ArgComponent c -> (i,[(BzComponent,c)])
 
 productVersionQuery :: ProductVersion -> [(BzFields,String)]
 productVersionQuery (Fedora Nothing) = [(BzProduct, "Fedora")]
@@ -79,7 +77,154 @@
                                       ,(BzVersion, show n)]
 productVersionQuery (RHEL ver) =
   case versionBranch ver of
-    [] -> error "Can't search RHEL without version"
+    [] -> error' "Can't search RHEL without version"
     [major] -> [(BzProduct, "Red Hat Enterprise Linux " ++ show major)]
     (major:_) ->  [(BzProduct, "Red Hat Enterprise Linux " ++ show major)
                   ,(BzVersion, showVersion ver)]
+
+-- FIXME move map to allBzFields
+mapField :: String -> String
+mapField f =
+  let longname =
+        case f of
+          "itm" -> "cf_internal_target_milestone"
+          "itr" -> "cf_internal_target_release"
+          "status" -> "bug_status"
+          "verified" -> "cf_verified"
+          "sst" -> "agile_team.name"
+          "summary" -> "short_desc"
+          "flag" -> "flagtypes.name"
+          "flags" -> "flagtypes.name"
+          p -> p
+  in if longname `elem` allBzFields
+     then longname
+     else error' $ "unknown field: " ++ f
+
+-- https://bugzilla.redhat.com/query.cgi?query_format=advanced
+-- https://bugzilla.redhat.com/rest/field/bug
+-- or: bugzilla query --json -b bug#
+allBzFields :: [String]
+allBzFields =
+  ["agile_pool.name",
+   "agile_team.name",
+   "alias",
+   "assigned_to",
+   "assigned_to_realname",
+   "attach_data.thedata",
+   "attachments.description",
+   "attachments.filename",
+   "attachments.isobsolete",
+   "attachments.ispatch",
+   "attachments.isprivate",
+   "attachments.mimetype",
+   "attachments.submitter",
+   "blocked",
+   "bug_agile_pool.pool_id",
+   "bug_agile_pool.pool_order",
+   "bug_file_loc",
+   "bug_group",
+   "bug_id",
+   "bug_severity",
+   "bug_status",
+   "cc",
+   "cclist_accessible",
+   "cf_approved_release",
+   "cf_atomic",
+   "cf_build_id",
+   "cf_business_market_problem",
+   "cf_category",
+   "cf_clone_of",
+   "cf_cloudforms_team",
+   "cf_compliance_control_group",
+   "cf_compliance_level",
+   "cf_conditional_nak",
+   "cf_crm",
+   "cf_cust_facing",
+   "cf_deadline",
+   "cf_deadline_type",
+   "cf_devdoctest",
+   "cf_devel_whiteboard",
+   "cf_doc_type",
+   "cf_docs_score",
+   "cf_documentation_action",
+   "cf_environment",
+   "cf_epm_cdp",
+   "cf_epm_phd",
+   "cf_epm_prf_state",
+   "cf_epm_pri",
+   "cf_epm_ptl",
+   "cf_epm_put",
+   "cf_final_deadline",
+   "cf_fixed_in",
+   "cf_internal_target_milestone",
+   "cf_internal_target_release",
+   "cf_internal_whiteboard",
+   "cf_last_closed",
+   "cf_mount_type",
+   "cf_ovirt_team",
+   "cf_pgm_internal",
+   "cf_pm_score",
+   "cf_qa_whiteboard",
+   "cf_qe_conditional_nak",
+   "cf_regression_status",
+   "cf_release_notes",
+   "cf_srtnotes",
+   "cf_story_points",
+   "cf_target_upstream_version",
+   "cf_type",
+   "cf_verified_branch",
+   "cf_zstream_target_release",
+   "classification",
+   "comment_tag",
+   "commenter",
+   "component",
+   "content",
+   "creation_ts",
+   "days_elapsed",
+   "deadline",
+   "delta_ts",
+   "dependent_products",
+   "dependson",
+   "docs_contact",
+   "docs_contact_realname",
+   "estimated_time",
+   "everconfirmed",
+   "ext_bz_bug_map.ext_bz_bug_id",
+   "ext_bz_bug_map.ext_status",
+   "external_bugzilla.description",
+   "external_bugzilla.url",
+   "extra_components",
+   "extra_versions",
+   "flagtypes.name",
+   "keywords",
+   "last_visit_ts",
+   "longdesc",
+   "longdescs.count",
+   "longdescs.isprivate",
+   "op_sys",
+   "owner_idle_time",
+   "percentage_complete",
+   "priority",
+   "product",
+   "qa_contact",
+   "qa_contact_realname",
+   "remaining_time",
+   "rep_platform",
+   "reporter",
+   "reporter_accessible",
+   "reporter_realname",
+   "requestees.login_name",
+   "resolution",
+   "rh_rule",
+   "rh_sub_components",
+   "see_also",
+   "setters.login_name",
+   "short_desc",
+   "status_whiteboard",
+   "tag",
+   "target_milestone",
+   "target_release",
+   "version",
+   "view",
+   "votes",
+   "work_time"]
diff --git a/src/Help.hs b/src/Help.hs
--- a/src/Help.hs
+++ b/src/Help.hs
@@ -8,149 +8,19 @@
 #endif
 import qualified Options.Applicative.Help.Pretty as P
 
-import ParseArg (statusList)
+import ParseArg (operators, showOpHelp, statusList)
 
 detailedHelp :: P.Doc
 detailedHelp =
   P.vcat
-  [ P.text "Tool for generating bugzilla queries"
-  , P.empty
-  , P.text "FIELDS = " <> P.lbrace <> P.align (P.hsep (P.punctuate P.comma (map P.text allBzFields)) <> P.rbrace)
+  [ P.empty
+  , P.text "Tool for generating bugzilla queries"
   , P.empty
   , P.text "STATUS = " <> P.lbrace <> P.align (P.fillCat (P.punctuate P.comma (map P.text (statusList ++ ["ALL"]))) <> P.rbrace)
   , P.empty
   , P.text "PRODUCTVERSION = " <> P.lbrace <> P.align (P.fillCat (P.punctuate P.comma (map P.text ["rawhide", "fedora", "fXY", "epel", "epelX", "rhel8", "rhel7", "rhelX.Z"])) <> P.rbrace)
   , P.empty
-  , P.text "'op' is " <> P.align (P.fillCat (P.punctuate P.comma (map P.text ["'='(equals)", "'!='(notequals)", "'~'(substring)", "'!~'(notsubstring)", "'=~'(regexp)", "'!=~'(notregexp)", "'~a~'(allwordssubstr)", "'!a~'(nowordssubstr)", "'~c~'(casesubstring)", "'~e~'(isempty)", "'!e~'(isnotempty)", "'~w~'(allwords)", "'!w~'(nowords)"])))
+  , P.text "'op' is " <> P.align (P.fillCat (P.punctuate P.comma (map (P.text . showOpHelp) operators)) P.<//> P.text "[content~ & content!~ map to matches/notmatches]")
   , P.empty
   , P.text "See https://github.com/juhp/rhbzquery#readme for examples"
   ]
-
--- FIXME: filter by @redhat.com
--- bugzilla query --json -b 1865911
-allBzFields :: [String]
-allBzFields =
--- from https://bugzilla.redhat.com/rest/field/bug
-  ["agile_pool.name",
-   "agile_team.name",
-   "alias",
-   "assigned_to",
-   "assigned_to_realname",
-   "attach_data.thedata",
-   "attachments.description",
-   "attachments.filename",
-   "attachments.isobsolete",
-   "attachments.ispatch",
-   "attachments.isprivate",
-   "attachments.mimetype",
-   "attachments.submitter",
-   "blocked",
-   "bug_agile_pool.pool_id",
-   "bug_agile_pool.pool_order",
-   "bug_file_loc",
-   "bug_group",
-   "bug_id",
-   "bug_severity",
-   "bug_status",
-   "cc",
-   "cclist_accessible",
-   "cf_approved_release",
-   "cf_atomic",
-   "cf_build_id",
-   "cf_business_market_problem",
-   "cf_category",
-   "cf_clone_of",
-   "cf_cloudforms_team",
-   "cf_compliance_control_group",
-   "cf_compliance_level",
-   "cf_conditional_nak",
-   "cf_crm",
-   "cf_cust_facing",
-   "cf_deadline",
-   "cf_deadline_type",
-   "cf_devdoctest",
-   "cf_devel_whiteboard",
-   "cf_doc_type",
-   "cf_docs_score",
-   "cf_documentation_action",
-   "cf_environment",
-   "cf_epm_cdp",
-   "cf_epm_phd",
-   "cf_epm_prf_state",
-   "cf_epm_pri",
-   "cf_epm_ptl",
-   "cf_epm_put",
-   "cf_final_deadline",
-   "cf_fixed_in",
-   "cf_internal_target_milestone",
-   "cf_internal_target_release",
-   "cf_internal_whiteboard",
-   "cf_last_closed",
-   "cf_mount_type",
-   "cf_ovirt_team",
-   "cf_pgm_internal",
-   "cf_pm_score",
-   "cf_qa_whiteboard",
-   "cf_qe_conditional_nak",
-   "cf_regression_status",
-   "cf_release_notes",
-   "cf_srtnotes",
-   "cf_story_points",
-   "cf_target_upstream_version",
-   "cf_type",
-   "cf_verified_branch",
-   "cf_zstream_target_release",
-   "classification",
-   "comment_tag",
-   "commenter",
-   "component",
-   "content",
-   "creation_ts",
-   "days_elapsed",
-   "deadline",
-   "delta_ts",
-   "dependent_products",
-   "dependson",
-   "docs_contact",
-   "docs_contact_realname",
-   "estimated_time",
-   "everconfirmed",
-   "ext_bz_bug_map.ext_bz_bug_id",
-   "ext_bz_bug_map.ext_status",
-   "external_bugzilla.description",
-   "external_bugzilla.url",
-   "extra_components",
-   "extra_versions",
-   "flagtypes.name",
-   "keywords",
-   "last_visit_ts",
-   "longdesc",
-   "longdescs.count",
-   "longdescs.isprivate",
-   "op_sys",
-   "owner_idle_time",
-   "percentage_complete",
-   "priority",
-   "product",
-   "qa_contact",
-   "qa_contact_realname",
-   "remaining_time",
-   "rep_platform",
-   "reporter",
-   "reporter_accessible",
-   "reporter_realname",
-   "requestees.login_name",
-   "resolution",
-   "rh_rule",
-   "rh_sub_components",
-   "see_also",
-   "setters.login_name",
-   "short_desc",
-   "status_whiteboard",
-   "tag",
-   "target_milestone",
-   "target_release",
-   "version",
-   "view",
-   "votes",
-   "work_time"]
diff --git a/src/Main.hs b/src/Main.hs
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -15,21 +15,22 @@
 import Network.HTTP.Types
 import Options.Applicative (fullDesc, header, progDescDoc,
 #if !MIN_VERSION_simple_cmd_args(0,1,4)
-                            some
+                            many
 #endif
   )
-import SimpleCmd
+import SimpleCmd (cmd_)
 import SimpleCmdArgs
 import System.Directory
 
 import Bugzilla
+import Common
 import Fields
 import Help
 import ParseArg
 import Paths_rhbzquery
 import User
 
-data OtherQuery = CreateBug | APIQuery
+data QueryMode = BugList | ListFields | CreateBug | APIQuery
 
 main :: IO ()
 main =
@@ -37,30 +38,43 @@
   run <$>
   switchWith 'n' "dryrun" "Do not open url" <*>
   switchWith 'm' "mine" "My bugs" <*>
-  optional (flagWith' CreateBug 'f' "file" "File a bug" <|>
-            flagWith' APIQuery 'w' "api" "Web API query") <*>
-  some (strArg argHelp)
+  (flagWith' ListFields 'l' "list-fields" "List the query FIELDs" <|>
+   flagWith' CreateBug 'f' "file" "File a bug" <|>
+   flagWith BugList APIQuery 'w' "api" "Web API query") <*>
+  many (strArg argHelp)
   where
-    run :: Bool -> Bool -> Maybe OtherQuery -> [String] -> IO ()
-    run dryrun mine mquery args = do
+    run :: Bool -> Bool -> QueryMode -> [String] -> IO ()
+    run _ _ ListFields _ = mapM_ putStrLn allBzFields
+    run dryrun mine mode args = do
+      when (null args) $ error' "Please specify at least one argument"
       user <- if mine
         then do
         mail <- getBzUser
         return [ArgParameter "assigned_to" Equals mail]
         else return []
       let argtypes = mapMaybe readBzQueryArg args
-          url = case mquery of
-            Just CreateBug ->
-              let query = L.nub $ concatMap argToSimpleField argtypes
-              in "https://" <> B.pack brc <> "/enter_bug.cgi" <> renderQuery True (bzQuery query)
-            _ ->
-              let status = [ArgParameter "bug_status" Equals "__open__" | not (hasStatusSet argtypes)]
-                  query = L.nub $ numberMetaFields $ status ++ user ++ argtypes
-              in "https://" <> B.pack brc <> "/" <> (if isNothing mquery then "buglist.cgi" else "rest/bug")<> renderQuery True (bzQuery query)
+          url =
+            let query =
+                  case mode of
+                    CreateBug -> L.nub $ concatMap argToSimpleField argtypes
+                    _ ->
+                      let status = [ArgParameter "bug_status" Equals "__open__" | not (hasStatusSet argtypes)]
+                      in L.nub $ numberMetaFields $ status ++ user ++ argtypes
+            in queryURL mode query
       B.putStrLn url
       unless dryrun $ do
         whenJustM (findExecutable "xdg-open") $ \xdgOpen ->
           cmd_ xdgOpen [B.unpack url]
+
+    queryURL :: QueryMode -> [(BzFields,String)] -> B.ByteString
+    queryURL mode query =
+      "https://" <> B.pack brc <> "/" <>
+      case mode of
+        APIQuery -> "rest/bug"
+        BugList -> "buglist.cgi"
+        CreateBug -> "enter_bug.cgi"
+        ListFields -> "query.cgi" -- unused
+      <> renderQuery True (bzQuery query)
 
     hasStatusSet :: [ArgType] -> Bool
     hasStatusSet [] = False
diff --git a/src/ParseArg.hs b/src/ParseArg.hs
--- a/src/ParseArg.hs
+++ b/src/ParseArg.hs
@@ -3,7 +3,9 @@
   readBzQueryArg,
   ArgType(..),
   Operator(..),
+  operators,
   showOp,
+  showOpHelp,
   ProductVersion(..),
   statusList
   )
@@ -15,45 +17,80 @@
 import Data.Version.Extra
 import Numeric.Natural
 
+import Common
+
 argHelp :: String
 argHelp = "[COMPONENT|STATUS|PRODUCTVERSION|FIELD=VALUE|FIELDopVALUE]..."
 
 data Operator = Equals | NotEqual
-              | Contains | NotContain
-              | ContainsCase
               | Regexp |NotRegexp
-              | ContainsAll | NotContainAll
-              | ContainsWords | NotContainWords
+              | Substring | NotSubstring
+              | CaseSubstring
+              | AllWordsSubstr | NoWordsSubstr
+              | AllWords | NoWords
+              | IsEmpty | IsNotEmpty
               -- | ContentMatches | ContentNotMatch
-  deriving Eq
+  deriving (Eq,Show,Enum,Bounded)
 
 showOp :: Operator -> String
-showOp Equals = "equals"
-showOp NotEqual = "notequals"
-showOp Contains = "substring"
-showOp NotContain = "notsubstring"
-showOp ContainsCase = "casesubstring"
-showOp Regexp = "regexp"
-showOp NotRegexp = "notregexp"
-showOp ContainsAll = "allwordssubstr"
-showOp NotContainAll = "nowordssubstr"
-showOp ContainsWords = "allwords"
-showOp NotContainWords = "nowords"
---showOp IsEmpty = "isempty"
---showOp IsNotEmpty = "isnotempty"
+showOp = lower . show
 
+operators :: [Operator]
+operators = enumFromTo minBound maxBound
+
+data OperatorData = OpUnary String | OpNull String
+  deriving Eq
+
+opSyntax :: OperatorData -> String
+opSyntax (OpUnary s) = s
+opSyntax (OpNull s) = s
+
+showOpHelp :: Operator -> String
+showOpHelp op =
+  "'" ++ opSyntax (opData op) ++ "'(" ++ showOp op ++ ")"
+
+instance Ord OperatorData where
+  compare op1 op2 =
+    let sop1 = opSyntax op1
+        sop2 = opSyntax op2
+    in
+      if sop1 `isInfixOf` sop2 then GT
+      else if sop2 `isInfixOf` sop1 then LT
+           else if sop1 == sop2
+                then error' $ sop1 ++ " listed twice in 'operators'"
+                else compare (reverse sop1) (reverse sop2)
+
+-- FIXME check all unique
+opData :: Operator -> OperatorData
+opData Equals = OpUnary "="
+opData NotEqual = OpUnary "!="
+opData Substring = OpUnary "~"
+opData NotSubstring = OpUnary "!~"
+opData CaseSubstring = OpUnary "~c~"
+opData Regexp = OpUnary "=~"
+opData NotRegexp = OpUnary "!=~"
+opData AllWordsSubstr = OpUnary "~a~"
+opData NoWordsSubstr = OpUnary "!a~"
+opData AllWords = OpUnary "~w~"
+opData NoWords = OpUnary "!w~"
+opData IsEmpty = OpNull "~e~"
+opData IsNotEmpty = OpNull "!e~"
+-- -- only for 'content':
+-- opData Matches = OpUnary "~m~"
+-- opData NotMatches = OpUnary "!m~"
+
 data ArgType = ArgProdVer ProductVersion
              | ArgStatusAll
              | ArgParameter String Operator String
              | ArgParameterEmpty String Bool
-             | ArgOther String
+             | ArgComponent String
 
 readBzQueryArg :: String -> Maybe ArgType
 readBzQueryArg s =
   ArgProdVer <$> readProductVersion s <|>
   parseStatus s <|>
-  parseParam s <|>
-  pure (ArgOther s)
+  parseField s <|>
+  parseComponent s
 
 data ProductVersion = Fedora (Maybe Natural)
                     | Rawhide
@@ -80,36 +117,38 @@
     else
       ArgParameter "bug_status" Equals <$> find (== upper s) statusList
 
-parseParam :: String -> Maybe ArgType
-parseParam ps =
-  parseParamWith "!=~" NotRegexp <|>
-  parseParamWith "=~" Regexp <|>
-  parseParamWith "!=" NotEqual <|>
-  parseParamWith "=" Equals <|>
-  parseParamWith "!~" NotContain <|>
-  parseParamWith "~a~" ContainsAll <|>
-  parseParamWith "!a~" NotContainAll <|>
-  parseParamWith "~c~" ContainsCase <|>
-  parseParamEmpty "~e~" True <|>
-  parseParamEmpty "!e~" False <|>
-  parseParamWith "~w~" ContainsWords <|>
-  parseParamWith "!w~" NotContainWords <|>
---  parseParamWith "~m~" ContentMatches <|>
---  parseParamWith "!m~" ContentNotMatch <|>
-  parseParamWith "~" Contains
+parseField :: String -> Maybe ArgType
+parseField ps =
+  foldr ((<|>) . parseParam) Nothing $ sortOn opData operators
   where
-    parseParamWith :: String -> Operator -> Maybe ArgType
-    parseParamWith op oper =
-      case splitOn op ps of
-        [a,b] -> if null a || null b
-                 then error $ "bad parameter: " ++ ps
-                 else Just (ArgParameter a oper b)
-        _ -> Nothing
+    parseParam :: Operator -> Maybe ArgType
+    parseParam oper =
+      case opData oper of
+        OpUnary op ->
+          if op `isInfixOf` ps then
+            case splitOn op ps of
+              (f:val) -> Just (ArgParameter f oper (intercalate op val))
+              _ -> Nothing
+          else Nothing
+        OpNull op ->
+          case stripSuffix op ps of
+            Just a ->
+              if null a
+              then error' $ "bad parameter: " ++ ps
+              else Just (ArgParameterEmpty a (oper == IsEmpty))
+            _ -> Nothing
 
-    parseParamEmpty :: String -> Bool -> Maybe ArgType
-    parseParamEmpty op empty =
-      case stripSuffix op ps of
-        Just a -> if null a
-                  then error $ "bad parameter: " ++ ps
-                  else Just (ArgParameterEmpty a empty)
-        _ -> Nothing
+-- https://fedoraproject.org/wiki/Packaging:Naming?rd=Packaging:NamingGuidelines#Common_Character_Set_for_Package_Naming
+-- abcdefghijklmnopqrstuvwxyz
+-- ABCDEFGHIJKLMNOPQRSTUVWXYZ
+-- 0123456789-._+
+parseComponent :: String -> Maybe ArgType
+parseComponent ps =
+  if all isPackageChar ps
+  then Just (ArgComponent ps)
+  else error' $ "Invalid component name: " ++ ps
+  where
+    isPackageChar :: Char -> Bool
+    isPackageChar c =
+      -- Bugzilla components can contain a space though
+      isAsciiUpper c || isAsciiLower c || isDigit c || c `elem` " -._+"
