(.module: [lux (#- Name Type) [abstract [equivalence (#+ Equivalence)] [monad (#+ do)]] [control ["." exception (#+ exception:)] ["<>" parser ["<.>" xml (#+ Parser)] ["<.>" text]]] [data ["." product] ["." text ["%" format]] [format ["." xml (#+ XML)]] [collection ["." list ("#\." functor)]]] [math [number ["n" nat]]] ["." time (#+ Time) ["." instant (#+ Instant)] ["." date (#+ Date)] ["." year] ["." month]]] ["." // (#+ Version) [type (#+ Type)] ["#." value (#+ Build Value)] ["#." time_stamp (#+ Time_Stamp) ["#/." date] ["#/." time]]]) (type: #export Versioning {#time_stamp Time_Stamp #build Build #snapshot (List Type)}) (def: #export init {#time_stamp (instant.from_millis +0) #build 0 #snapshot (list)}) (def: #export equivalence (Equivalence Versioning) ($_ product.equivalence instant.equivalence n.equivalence (list.equivalence text.equivalence) )) (template [ ] [(def: xml.Tag ["" ])] [ "extension"] [ "value"] [ "updated"] [ "timestamp"] [ "buildNumber"] [ "lastUpdated"] [ "snapshotVersions"] [ "snapshotVersion"] [ "snapshot"] [ "versioning"] ) (def: (instant_format value) (%.Format Instant) (%.format (//time_stamp/date.format (instant.date value)) (//time_stamp/time.format (instant.time value)))) (template [
]
  [(def: 
     (->  XML)
     (|>> 
 #xml.Text list (#xml.Node  xml.attributes)))]

  [format_extension Type .. (|>)]
  [format_value Value .. //value.format]
  [format_updated Instant .. ..instant_format]

  [format_time_stamp Instant .. //time_stamp.format]
  [format_build_number Nat .. %.nat]
  [format_last_updated Instant .. ..instant_format]
  )

(def: (format_snapshot value type)
  (-> Value Type XML)
  (<| (#xml.Node .. xml.attributes)
      (list (..format_extension type)
            (..format_value value)
            (let [[version time_stamp build] value]
              (..format_updated time_stamp)))))

(def: #export (format version (^slots [#time_stamp #build #snapshot]))
  (-> Version Versioning XML)
  (<| (#xml.Node .. xml.attributes)
      (list (<| (#xml.Node .. xml.attributes)
                (list (..format_time_stamp time_stamp)
                      (..format_build_number build)))
            (..format_last_updated time_stamp)
            (<| (#xml.Node .. xml.attributes)
                (list\map (..format_snapshot [version time_stamp build])
                          snapshot)))))

(exception: #export (time_stamp_mismatch {expected Time_Stamp} {actual Text})
  (exception.report
   ["Expected time-stamp" (instant_format expected)]
   ["Actual time-stamp" actual]))

(exception: #export (value_mismatch {expected Value} {actual Text})
  (exception.report
   ["Expected" (//value.format expected)]
   ["Actual" actual]))

(def: (sub tag parser)
  (All [a] (-> xml.Tag (Parser a) (Parser a)))
  (do <>.monad
    [_ (.node tag)]
    (.children parser)))

(def: (text tag)
  (-> xml.Tag (Parser Text))
  (..sub tag .text))

(def: last_updated_parser
  (Parser Instant)
  (.embed (do <>.monad
                  [date //time_stamp/date.parser
                   time //time_stamp/time.parser]
                  (wrap (instant.from_date_time date time)))
                (..text ..)))

(def: time_stamp_parser
  (Parser Time_Stamp)
  (.embed //time_stamp.parser
                (..text ..)))

(def: build_parser
  (Parser Build)
  (.embed (<>.codec n.decimal
                          (.many .decimal))
                (..text ..)))

(def: (snapshot_parser expected)
  (-> Value (Parser Type))
  (<| (..sub ..)
      (do <>.monad
        [#let [[version time_stamp build] expected]
         updated (.somewhere (..text ..))
         _ (<>.assert (exception.construct ..time_stamp_mismatch [time_stamp updated])
                      (\ text.equivalence = (instant_format time_stamp) updated))
         actual (.somewhere (..text ..))
         _ (<>.assert (exception.construct ..value_mismatch [expected actual])
                      (\ text.equivalence = (//value.format expected) actual))]
        (.somewhere (..text ..)))))

(def: #export (parser version)
  (-> Version (Parser Versioning))
  (<| (..sub ..)
      (do <>.monad
        [[time_stamp build] (<| .somewhere
                                (..sub ..)
                                (<>.and (.somewhere ..time_stamp_parser)
                                        (.somewhere ..build_parser)))
         last_updated (.somewhere ..last_updated_parser)
         _ (<>.assert (exception.construct ..time_stamp_mismatch [time_stamp (instant_format last_updated)])
                      (\ instant.equivalence = time_stamp last_updated))
         snapshot (<| .somewhere
                      (..sub ..)
                      (<>.some (..snapshot_parser [version time_stamp build])))]
        (wrap {#time_stamp time_stamp
               #build build
               #snapshot snapshot}))))