Extract is the top-level function. It attempts to break the input string into a collection of date/time fields in order to populate a fieldExtract.
(s string)
| 106 | // string into a collection of date/time fields in order to populate a |
| 107 | // fieldExtract. |
| 108 | func (fe *fieldExtract) Extract(s string) error { |
| 109 | // Break the string into alphanumeric chunks. |
| 110 | textChunks := make([]stringChunk, fieldMaximum) |
| 111 | count, _ := chunk(s, textChunks) |
| 112 | |
| 113 | if count < 0 { |
| 114 | return inputErrorf("too many fields in input") |
| 115 | } else if count == 0 { |
| 116 | return inputErrorf("empty or blank input") |
| 117 | } |
| 118 | |
| 119 | // Create a place to store extracted numeric info. |
| 120 | numbers := make([]numberChunk, 0, fieldMaximum) |
| 121 | |
| 122 | appendNumber := func(prefix, number string) error { |
| 123 | v, err := strconv.Atoi(number) |
| 124 | if err != nil { |
| 125 | return err |
| 126 | } |
| 127 | |
| 128 | // Allow exactly one non-whitespace separator. |
| 129 | s := ' ' |
| 130 | for _, r := range prefix { |
| 131 | switch { |
| 132 | case s == ' ': |
| 133 | s = r |
| 134 | case unicode.IsSpace(r): |
| 135 | // Ignore whitespace characters. |
| 136 | default: |
| 137 | return inputErrorf(`detected multiple separators in "%s""`, prefix) |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | numbers = append(numbers, numberChunk{separator: s, v: v, magnitude: len(number)}) |
| 142 | return nil |
| 143 | } |
| 144 | |
| 145 | var leftoverText string |
| 146 | |
| 147 | // First, we'll try to pluck out any keywords that exist in the input. |
| 148 | // If a chunk is not a keyword or other special-case pattern, it |
| 149 | // must be a numeric value, which we'll pluck out for a second |
| 150 | // pass. If we see certain sentinel values, we'll pick them out, |
| 151 | // but keep going to ensure that the user hasn't written something |
| 152 | // like "epoch infinity". |
| 153 | for idx, chunk := range textChunks[:count] { |
| 154 | match := strings.ToLower(chunk.Match) |
| 155 | |
| 156 | switch match { |
| 157 | case keywordEpoch: |
| 158 | if err := fe.matchedSentinel(TimeEpoch, match); err != nil { |
| 159 | return err |
| 160 | } |
| 161 | |
| 162 | case keywordInfinity: |
| 163 | if strings.HasSuffix(chunk.NotMatch, "-") { |
| 164 | if err := fe.matchedSentinel(TimeNegativeInfinity, match); err != nil { |
| 165 | return err |
no test coverage detected