From 64f68ae1afc499db40fe7c8f5b69a1c3b8251496 Mon Sep 17 00:00:00 2001 From: Felix Krause Date: Tue, 8 Nov 2016 21:13:01 +0100 Subject: [PATCH] Implemented !!timestamp --- test/tserialization.nim | 10 +++- yaml/hints.nim | 101 +++++++++++++++++++++++++++++++++++----- yaml/serialization.nim | 70 +++++++++++++++++++++++++++- 3 files changed, 167 insertions(+), 14 deletions(-) diff --git a/test/tserialization.nim b/test/tserialization.nim index c089926..1261132 100644 --- a/test/tserialization.nim +++ b/test/tserialization.nim @@ -5,7 +5,7 @@ # distribution, for details about the copyright. import "../yaml" -import unittest, strutils, streams, tables +import unittest, strutils, streams, tables, times type MyTuple = tuple @@ -208,6 +208,14 @@ suite "Serialization": var output = dump(input, tsNone, asTidy, blockOnly) assertStringEqual yamlDirs & "\n!n!nil:string \"\"", output + test "Load timestamps": + let input = "[2001-12-15T02:59:43.1Z, 2001-12-14t21:59:43.10-05:00, 2001-12-14 21:59:43.10-5]" + var result: seq[Time] + load(input, result) + assert result.len() == 3 + # currently, there is no good way of checking the result content, because + # the parsed Time may have any timezone offset. + test "Load string sequence": let input = newStringStream(" - a\n - b") var result: seq[string] diff --git a/yaml/hints.nim b/yaml/hints.nim index 0892787..b3b6e4a 100644 --- a/yaml/hints.nim +++ b/yaml/hints.nim @@ -33,10 +33,11 @@ type ## ``yTypeBoolTrue`` ``y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON`` ## ``yTypeBoolFalse`` ``n|N|no|No|NO|false|False|FALSE|off|Off|OFF`` ## ``yTypeNull`` ``~ | null | Null | NULL`` + ## ``yTypeTimestamp`` see `here `_. ## ``yTypeUnknown`` ``*`` ## ================== ========================= yTypeInteger, yTypeFloat, yTypeFloatInf, yTypeFloatNaN, yTypeBoolTrue, - yTypeBoolFalse, yTypeNull, yTypeUnknown + yTypeBoolFalse, yTypeNull, yTypeUnknown, yTypeTimestamp YamlTypeHintState = enum ythInitial, @@ -59,7 +60,16 @@ type ythPointLowerIN, ythPointLowerN, ythPointLowerNA, - ythMinus, yth0, ythInt, ythDecimal, ythNumE, ythNumEPlusMinus, ythExponent + ythMinus, yth0, ythInt1, ythInt1Zero, ythInt2, ythInt2Zero, ythInt3, + ythInt3Zero, ythInt4, ythInt4Zero, ythInt, + ythDecimal, ythNumE, ythNumEPlusMinus, ythExponent, + + ythYearMinus, ythMonth1, ythMonth2, ythMonthMinus, ythMonthMinusNoYmd, + ythDay1, ythDay1NoYmd, ythDay2, ythDay2NoYmd, + ythAfterDayT, ythAfterDaySpace, ythHour1, ythHour2, ythHourColon, + ythMinute1, ythMinute2, ythMinuteColon, ythSecond1, ythSecond2, ythFraction, + ythAfterTimeSpace, ythAfterTimeZ, ythAfterTimePlusMinus, ythTzHour1, + ythTzHour2, ythTzHourColon, ythTzMinute1, ythTzMinute2 macro typeHintStateMachine(c: untyped, content: untyped): typed = yAssert content.kind == nnkStmtList @@ -101,20 +111,78 @@ template advanceTypeHint(ch: char) {.dirty.} = typeHintStateMachine ch: of '~': ythInitial => ythNULL of '.': - [yth0, ythInt] => ythDecimal + [yth0, ythInt1, ythInt2, ythInt3, ythInt4, ythInt] => ythDecimal [ythInitial, ythMinus] => ythPoint - of '+': ythNumE => ythNumEPlusMinus + ythSecond2 => ythFraction + of '+': + ythNumE => ythNumEPlusMinus + [ythFraction, ythSecond2] => ythAfterTimePlusMinus of '-': - ythInitial => ythMinus - ythNumE => ythNumEPlusMinus + ythInitial => ythMinus + ythNumE => ythNumEPlusMinus + [ythInt4, ythInt4Zero] => ythYearMinus + ythMonth1 => ythMonthMinusNoYmd + ythMonth2 => ythMonthMinus + [ythFraction, ythSecond2] => ythAfterTimePlusMinus + of ':': + [ythHour1, ythHour2] => ythHourColon + ythMinute2 => ythMinuteColon + [ythTzHour1, ythTzHour2] => ythTzHourColon of '0': - [ythInitial, ythMinus] => yth0 + ythInitial => ythInt1Zero + ythMinus => yth0 [ythNumE, ythNumEPlusMinus] => ythExponent - [ythInt, ythDecimal, ythExponent] => nil + ythInt1 => ythInt2 + ythInt1Zero => ythInt2Zero + ythInt2 => ythInt3 + ythInt2Zero => ythInt3Zero + ythInt3 => ythInt4 + ythInt3Zero => ythInt4Zero + ythInt4 => ythInt + ythYearMinus => ythMonth1 + ythMonth1 => ythMonth2 + ythMonthMinus => ythDay1 + ythMonthMinusNoYmd => ythDay1NoYmd + ythDay1 => ythDay2 + ythDay1NoYmd => ythDay2NoYmd + [ythAfterDaySpace, ythAfterDayT] => ythHour1 + ythHour1 => ythHour2 + ythHourColon => ythMinute1 + ythMinute1 => ythMinute2 + ythMinuteColon => ythSecond1 + ythSecond1 => ythSecond2 + ythAfterTimePlusMinus => ythTzHour1 + ythTzHour1 => ythTzHour2 + ythTzHourColon => ythTzMinute1 + ythTzMinute1 => ythTzMinute2 + [ythInt, ythDecimal, ythExponent, ythFraction] => nil of '1'..'9': - [ythInitial, ythMinus] => ythInt + ythInitial => ythInt1 + ythInt1 => ythInt2 + ythInt1Zero => ythInt2Zero + ythInt2 => ythInt3 + ythInt2Zero => ythInt3Zero + ythInt3 => ythInt4 + ythInt3Zero => ythInt4Zero + [ythInt4, ythMinus] => ythInt [ythNumE, ythNumEPlusMinus] => ythExponent - [ythInt, ythDecimal, ythExponent] => nil + ythYearMinus => ythMonth1 + ythMonth1 => ythMonth2 + ythMonthMinus => ythDay1 + ythMonthMinusNoYmd => ythDay1NoYmd + ythDay1 => ythDay2 + ythDay1NoYmd => ythDay2NoYmd + [ythAfterDaySpace, ythAfterDayT] => ythHour1 + ythHour1 => ythHour2 + ythHourColon => ythMinute1 + ythMinute1 => ythMinute2 + ythMinuteColon => ythSecond1 + ythSecond1 => ythSecond2 + ythAfterTimePlusMinus => ythTzHour1 + ythTzHour1 => ythTzHour2 + ythTzHourColon => ythTzMinute1 + ythTzMinute1 => ythTzMinute2 + [ythInt, ythDecimal, ythExponent, ythFraction] => nil of 'a': ythF => ythLowerFA ythPointN => ythPointNA @@ -174,7 +242,9 @@ template advanceTypeHint(ch: char) {.dirty.} = of 'S': ythFAL => ythFALS ythYE => ythYES - of 't', 'T': ythInitial => ythT + of 't', 'T': + ythInitial => ythT + [ythDay1, ythDay2, ythDay1NoYmd, ythDay2NoYmd] => ythAfterDayT of 'u': ythN => ythLowerNU ythLowerTR => ythLowerTRU @@ -182,6 +252,11 @@ template advanceTypeHint(ch: char) {.dirty.} = ythN => ythNU ythTR => ythTRU of 'y', 'Y': ythInitial => ythY + of 'Z': [ythSecond2, ythFraction, ythAfterTimeSpace] => ythAfterTimeZ + of ' ', '\t': + [ythSecond2, ythFraction] => ythAfterTimeSpace + [ythDay1, ythDay2, ythDay1NoYmd, ythDay2NoYmd] => ythAfterDaySpace + [ythAfterTimeSpace, ythAfterDaySpace] => nil proc guessType*(scalar: string): TypeHint {.raises: [].} = ## Parse scalar string according to the RegEx table documented at @@ -192,8 +267,10 @@ proc guessType*(scalar: string): TypeHint {.raises: [].} = of ythNULL: result = yTypeNull of ythTRUE, ythON, ythYES, ythY: result = yTypeBoolTrue of ythFALSE, ythOFF, ythNO, ythN: result = yTypeBoolFalse - of ythInt, yth0: result = yTypeInteger + of ythInt1, ythInt2, ythInt3, ythInt4, ythInt, yth0: result = yTypeInteger of ythDecimal, ythExponent: result = yTypeFloat of ythPointINF: result = yTypeFloatInf of ythPointNAN: result = yTypeFloatNaN + of ythDay2, ythSecond2, ythFraction, ythAfterTimeZ, ythTzHour1, ythTzHour2, + ythTzMinute1, ythTzMinute2: result = yTypeTimestamp else: result = yTypeUnknown diff --git a/yaml/serialization.nim b/yaml/serialization.nim index 31a5820..d1365d9 100644 --- a/yaml/serialization.nim +++ b/yaml/serialization.nim @@ -16,7 +16,7 @@ ## type. Please consult the serialization guide on the NimYAML website for more ## information. -import tables, typetraits, strutils, macros, streams +import tables, typetraits, strutils, macros, streams, times import parser, taglib, presenter, stream, ../private/internal, hints export stream # *something* in here needs externally visible `==`(x,y: AnchorId), @@ -321,6 +321,72 @@ proc representObject*(value: char, ts: TagStyle, c: SerializationContext, ## represents a char value as YAML scalar c.put(scalarEvent("" & value, tag, yAnchorNone)) +proc yamlTag*(T: typedesc[Time]): TagId {.inline, raises: [].} = yTagTimestamp + +proc constructObject*(s: var YamlStream, c: ConstructionContext, + result: var Time) + {.raises: [YamlConstructionError, YamlStreamError].} = + constructScalarItem(s, item, Time): + if guessType(item.scalarContent) == yTypeTimestamp: + var + tmp = newStringOfCap(60) + pos = 8 + c: char + while pos < item.scalarContent.len(): + c = item.scalarContent[pos] + if c in {' ', '\t', 'T', 't'}: break + inc(pos) + if pos == item.scalarContent.len(): + tmp.add(item.scalarContent) + tmp.add("T00:00:00+00:00") + else: + tmp.add(item.scalarContent[0 .. pos - 1]) + if c in {' ', '\t'}: + while true: + inc(pos) + c = item.scalarContent[pos] + if c notin {' ', '\t'}: break + else: inc(pos) + tmp.add("T") + let timeStart = pos + inc(pos, 7) + var fractionStart = -1 + while pos < item.scalarContent.len(): + c = item.scalarContent[pos] + if c in {'+', '-', 'Z', ' ', '\t'}: break + elif c == '.': fractionStart = pos + inc(pos) + if fractionStart == -1: + tmp.add(item.scalarContent[timeStart .. pos - 1]) + else: + tmp.add(item.scalarContent[timeStart .. fractionStart - 1]) + if c in {'Z', ' ', '\t'}: tmp.add("+00:00") + else: + tmp.add(c) + inc(pos) + let tzStart = pos + inc(pos) + if pos < item.scalarContent.len() and item.scalarContent[pos] != ':': + inc(pos) + if pos - tzStart == 1: tmp.add('0') + tmp.add(item.scalarContent[tzStart .. pos - 1]) + if pos == item.scalarContent.len(): tmp.add(":00") + elif pos + 2 == item.scalarContent.len(): + tmp.add(":0") + tmp.add(item.scalarContent[pos + 1]) + else: + tmp.add(item.scalarContent[pos .. pos + 2]) + let info = tmp.parse("yyyy-M-d'T'H-mm-sszzz") + result = info.toTime() + else: + raise s.constructionError("Not a parsable timestamp: " & + escape(item.scalarContent)) + +proc representObject*(value: Time, ts: TagStyle, c: SerializationContext, + tag: TagId) {.raises: [ValueError].} = + let tmp = value.getGMTime() + c.put(scalarEvent(tmp.format("yyyy-MM-dd'T'HH:mm:ss'Z'"))) + proc yamlTag*[I](T: typedesc[seq[I]]): TagId {.inline, raises: [].} = let uri = nimTag("system:seq(" & safeTagUri(yamlTag(I)) & ')') result = lazyLoadTag(uri) @@ -984,6 +1050,8 @@ proc constructChild*[T](s: var YamlStream, c: ConstructionContext, raise s.constructionError("not implemented!") of yTypeUnknown: possibleTagIds.add(yamlTag(string)) + of yTypeTimestamp: + possibleTagIds.add(yamlTag(Time)) of yTagExclamationMark: possibleTagIds.add(yamlTag(string)) else: