(.module: [lux (#- Name Type) [abstract [monad (#+ do)] [equivalence (#+ Equivalence)]] [control [pipe (#+ do>)] ["." try (#+ Try)] ["." exception (#+ exception:)] ["<>" parser ["<.>" xml (#+ Parser)] ["<.>" text]] [concurrency ["." promise (#+ Promise)]]] [data ["." product] ["." text ["%" format] ["." encoding]] [format ["." xml (#+ XML)]] [collection ["." list ("#\." functor)]]] [math [number ["n" nat]]] ["." time (#+ Time) ["." instant (#+ Instant)] ["." date (#+ Date)] ["." year] ["." month]] [world [net ["." uri (#+ URI)]]]] ["." // ["." artifact] ["/#" // #_ [repository (#+ Repository)] ["#." artifact (#+ Group Name Version Artifact) ["#/." type (#+ Type)]]]]) (def: snapshot "SNAPSHOT") (type: #export Time_Stamp Instant) (type: #export Build Nat) (type: #export Versioning {#time_stamp Time_Stamp #build Build #snapshot (List Type)}) (type: #export Value [Version Time_Stamp Build]) (type: #export Metadata {#artifact Artifact #versioning Versioning}) (def: (pad value) (-> Nat Text) (if (n.< 10 value) (%.format "0" (%.nat value)) (%.nat value))) (def: (date_format value) (%.Format Date) (%.format (|> value date.year year.value .nat %.nat) (|> value date.month month.number ..pad) (|> value date.day_of_month ..pad))) (def: (time_format value) (%.Format Time) (let [(^slots [#time.hour #time.minute #time.second]) (time.clock value)] (%.format (..pad hour) (..pad minute) (..pad second)))) (def: (instant_format value) (%.Format Instant) (%.format (..date_format (instant.date value)) (..time_format (instant.time value)))) (template [ ] [(def: )] ["." time_stamp_separator] ["-" value_separator] ) (def: (time_stamp_format value) (%.Format Time_Stamp) (%.format (..date_format (instant.date value)) ..time_stamp_separator (..time_format (instant.time value)))) (def: (value_format [version time_stamp build]) (%.Format Value) (%.format (text.replace_all ..snapshot (..time_stamp_format time_stamp) version) ..value_separator (%.nat build))) (template [ ] [(def: xml.Tag ["" ])] [ "groupId"] [ "artifactId"] [ "version"] [ "lastUpdated"] [ "metadata"] [ "versioning"] [ "snapshot"] [ "timestamp"] [ "buildNumber"] [ "snapshotVersions"] [ "snapshotVersion"] [ "extension"] [ "value"] [ "updated"] ) (template [
]
  [(def: 
     (->  XML)
     (|>> 
 #xml.Text list (#xml.Node  xml.attributes)))]

  [format_group Group .. (|>)]
  [format_name Name .. (|>)]
  [format_version Version .. (|>)]
  [format_last_updated Instant .. ..instant_format]
  [format_time_stamp Instant .. ..time_stamp_format]
  [format_build_number Nat .. %.nat]
  [format_extension Type .. (|>)]
  [format_value Value .. ..value_format]
  [format_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: (format_versioning 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)))))

(def: #export (format (^slots [#artifact #versioning]))
  (-> Metadata XML)
  (let [(^slots [#///artifact.group #///artifact.name #///artifact.version]) artifact]
    (#xml.Node ..
               xml.attributes
               (list (..format_group group)
                     (..format_name name)
                     (..format_version version)
                     (..format_versioning version versioning)))))

(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: date_parser
  (.Parser Date)
  (do <>.monad
    [year (<>.codec n.decimal (.exactly 4 .decimal))
     year (<>.lift (year.year (.int year)))
     month (<>.codec n.decimal (.exactly 2 .decimal))
     month (<>.lift (month.by_number month))
     day_of_month (<>.codec n.decimal (.exactly 2 .decimal))]
    (<>.lift (date.date year month day_of_month))))

(def: time_parser
  (.Parser Time)
  (do <>.monad
    [hour (<>.codec n.decimal (.exactly 2 .decimal))
     minute (<>.codec n.decimal (.exactly 2 .decimal))
     second (<>.codec n.decimal (.exactly 2 .decimal))]
    (<>.lift (time.time
              {#time.hour hour
               #time.minute minute
               #time.second second
               #time.milli_second 0}))))

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

(def: time_stamp_parser
  (Parser Time_Stamp)
  (.embed (do <>.monad
                  [date ..date_parser
                   _ (.this ..time_stamp_separator)
                   time ..time_parser]
                  (wrap (instant.from_date_time date time)))
                (..text ..)))

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

(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: (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: (versioning_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}))))

(def: #export parser
  (Parser Metadata)
  (<| (..sub ..)
      (do <>.monad
        [group (.somewhere (..text ..))
         name (.somewhere (..text ..))
         version (.somewhere (..text ..))
         versioning (.somewhere (..versioning_parser version))]
        (wrap {#artifact {#///artifact.group group
                          #///artifact.name name
                          #///artifact.version version}
               #versioning versioning}))))

(def: versioning_equivalence
  (Equivalence Versioning)
  ($_ product.equivalence
      instant.equivalence
      n.equivalence
      (list.equivalence text.equivalence)
      ))

(def: #export equivalence
  (Equivalence Metadata)
  ($_ product.equivalence
      ///artifact.equivalence
      ..versioning_equivalence
      ))

(def: #export (uri artifact)
  (-> Artifact URI)
  (let [/ uri.separator
        version (get@ #///artifact.version artifact)
        artifact (///artifact.uri artifact)]
    (%.format artifact / version / //.file)))

(def: epoch
  Instant
  (instant.from_millis +0))

(def: init_versioning
  {#time_stamp ..epoch
   #build 0
   #snapshot (list)})

(def: #export (read repository artifact)
  (-> (Repository Promise) Artifact (Promise (Try Metadata)))
  (do promise.monad
    [project (\ repository download (..uri artifact))]
    (case project
      (#try.Success project)
      (wrap (|> project
                (do> try.monad
                     [(\ encoding.utf8 decode)]
                     [(\ xml.codec decode)]
                     [(.run ..parser)])))
      
      (#try.Failure error)
      (wrap (#try.Success
             {#artifact artifact
              #versioning ..init_versioning})))))

(def: #export (write repository artifact metadata)
  (-> (Repository Promise) Artifact Metadata (Promise (Try Any)))
  (|> metadata
      ..format
      (\ xml.codec encode)
      (\ encoding.utf8 encode)
      (\ repository upload (..uri artifact))))