diff --git a/etc/style.css b/etc/style.css index 2dc15c2..175e27b 100644 --- a/etc/style.css +++ b/etc/style.css @@ -118,3 +118,8 @@ dl > dd { margin: -1em 0 1em 1em; } font-size: 0.8em; color: lightgrey; } + +.timestamp { + background-color: #eee; + padding: 0.05em 0.2em; + border: 1px solid #ccc; } diff --git a/org/document.go b/org/document.go index 8bddcfb..8fdfc1c 100644 --- a/org/document.go +++ b/org/document.go @@ -81,7 +81,7 @@ func New() *Configuration { DefaultSettings: map[string]string{ "TODO": "TODO | DONE", "EXCLUDE_TAGS": "noexport", - "OPTIONS": "toc:t e:t f:t pri:t todo:t tags:t", + "OPTIONS": "toc:t <:t e:t f:t pri:t todo:t tags:t", }, Log: log.New(os.Stderr, "go-org: ", 0), } @@ -166,6 +166,7 @@ func (d *Document) Get(key string) string { // GetOption returns the value associated to the export option key // Currently supported options: +// - < (export timestamps) // - e (export org entities) // - f (export footnotes) // - toc (export table of content) diff --git a/org/html_writer.go b/org/html_writer.go index bbf1125..60fb58b 100644 --- a/org/html_writer.go +++ b/org/html_writer.go @@ -246,6 +246,22 @@ func (w *HTMLWriter) WriteFootnoteLink(l FootnoteLink) { w.WriteString(fmt.Sprintf(`%s`, n, n, n)) } +func (w *HTMLWriter) WriteTimestamp(t Timestamp) { + if !w.document.GetOption("<") { + return + } + w.WriteString(`<`) + if t.IsDate { + w.WriteString(t.Time.Format(datestampFormat)) + } else { + w.WriteString(t.Time.Format(timestampFormat)) + } + if t.Interval != "" { + w.WriteString(" " + t.Interval) + } + w.WriteString(`>`) +} + func (w *HTMLWriter) WriteRegularLink(l RegularLink) { url := html.EscapeString(l.URL) if l.Protocol == "file" { diff --git a/org/inline.go b/org/inline.go index 1eacfdd..d4c8c43 100644 --- a/org/inline.go +++ b/org/inline.go @@ -1,9 +1,11 @@ package org import ( + "fmt" "path" "regexp" "strings" + "time" "unicode" ) @@ -17,6 +19,12 @@ type ExplicitLineBreak struct{} type StatisticToken struct{ Content string } +type Timestamp struct { + Time time.Time + IsDate bool + Interval string +} + type Emphasis struct { Kind string Content []Node @@ -40,9 +48,13 @@ var imageExtensionRegexp = regexp.MustCompile(`^[.](png|gif|jpe?g|svg|tiff?)$`) var videoExtensionRegexp = regexp.MustCompile(`^[.](webm|mp4)$`) var subScriptSuperScriptRegexp = regexp.MustCompile(`^([_^]){([^{}]+?)}`) +var timestampRegexp = regexp.MustCompile(`^<(\d{4}-\d{2}-\d{2})( [A-Za-z]+)?( \d{2}:\d{2})?( \+\d+[dwmy])?>`) var footnoteRegexp = regexp.MustCompile(`^\[fn:([\w-]+?)(:(.*?))?\]`) var statisticsTokenRegexp = regexp.MustCompile(`^\[(\d+/\d+|\d+%)\]`) +var timestampFormat = "2006-01-02 Mon 15:04" +var datestampFormat = "2006-01-02 Mon" + func (d *Document) parseInline(input string) (nodes []Node) { previous, current := 0, 0 for current < len(input) { @@ -58,6 +70,8 @@ func (d *Document) parseInline(input string) (nodes []Node) { consumed, node = d.parseEmphasis(input, current, true) case '[': consumed, node = d.parseOpeningBracket(input, current) + case '<': + consumed, node = d.parseTimestamp(input, current) case '\\': consumed, node = d.parseExplicitLineBreak(input, current) case '\n': @@ -222,6 +236,22 @@ func (d *Document) parseRegularLink(input string, start int) (int, Node) { return consumed, RegularLink{protocol, description, link, false} } +func (d *Document) parseTimestamp(input string, start int) (int, Node) { + if m := timestampRegexp.FindStringSubmatch(input[start:]); m != nil { + ddmmyy, hhmm, interval, isDate := m[1], m[3], strings.TrimSpace(m[4]), false + if hhmm == "" { + hhmm, isDate = "00:00", true + } + t, err := time.Parse(timestampFormat, fmt.Sprintf("%s Mon %s", ddmmyy, hhmm)) + if err != nil { + return 0, nil + } + timestamp := Timestamp{t, isDate, interval} + return len(m[0]), timestamp + } + return 0, nil +} + func (d *Document) parseEmphasis(input string, start int, isRaw bool) (int, Node) { marker, i := input[start], start if !hasValidPreAndBorderChars(input, i) { @@ -282,3 +312,4 @@ func (n StatisticToken) String() string { return orgWriter.nodesAsString(n) } func (n Emphasis) String() string { return orgWriter.nodesAsString(n) } func (n FootnoteLink) String() string { return orgWriter.nodesAsString(n) } func (n RegularLink) String() string { return orgWriter.nodesAsString(n) } +func (n Timestamp) String() string { return orgWriter.nodesAsString(n) } diff --git a/org/org_writer.go b/org/org_writer.go index 832f9db..a894482 100644 --- a/org/org_writer.go +++ b/org/org_writer.go @@ -278,6 +278,19 @@ func (w *OrgWriter) WriteExplicitLineBreak(l ExplicitLineBreak) { w.WriteString(`\\` + "\n" + w.indent) } +func (w *OrgWriter) WriteTimestamp(t Timestamp) { + w.WriteString("<") + if t.IsDate { + w.WriteString(t.Time.Format(datestampFormat)) + } else { + w.WriteString(t.Time.Format(timestampFormat)) + } + if t.Interval != "" { + w.WriteString(" " + t.Interval) + } + w.WriteString(">") +} + func (w *OrgWriter) WriteFootnoteLink(l FootnoteLink) { w.WriteString("[fn:" + l.Name) if l.Definition != nil { diff --git a/org/testdata/inline.html b/org/testdata/inline.html index eedcbd5..3cbb95b 100644 --- a/org/testdata/inline.html +++ b/org/testdata/inline.html @@ -111,4 +111,41 @@ auto link, i.e. not inside \[[square brackets]\] < +
  • +

    +timestamps +

    + +
  • diff --git a/org/testdata/inline.org b/org/testdata/inline.org index 6466578..7097358 100644 --- a/org/testdata/inline.org +++ b/org/testdata/inline.org @@ -27,3 +27,10 @@ 6. regular link to https (image) [[https://placekitten.com/200/200#.png]] 7. regular link enclosed in [] [[[https://www.example.com]]] [[[https://www.example.com][example.com]]] 8. auto link, i.e. not inside =\[[square brackets]\]= https://www.example.com +- timestamps + - <2019-01-06> + - <2019-01-06 Sun> + - <2019-01-06 Sun 18:00> + - <2019-01-06 Sun 18:00 +1w> + - <2019-01-06 18:00> + - <2019-01-06 18:00 +1w> diff --git a/org/testdata/inline.pretty_org b/org/testdata/inline.pretty_org index 6466578..f4429e9 100644 --- a/org/testdata/inline.pretty_org +++ b/org/testdata/inline.pretty_org @@ -27,3 +27,10 @@ 6. regular link to https (image) [[https://placekitten.com/200/200#.png]] 7. regular link enclosed in [] [[[https://www.example.com]]] [[[https://www.example.com][example.com]]] 8. auto link, i.e. not inside =\[[square brackets]\]= https://www.example.com +- timestamps + - <2019-01-06 Sun> + - <2019-01-06 Sun> + - <2019-01-06 Sun 18:00> + - <2019-01-06 Sun 18:00 +1w> + - <2019-01-06 Sun 18:00> + - <2019-01-06 Sun 18:00 +1w> diff --git a/org/writer.go b/org/writer.go index 855d61a..03d901e 100644 --- a/org/writer.go +++ b/org/writer.go @@ -29,6 +29,7 @@ type Writer interface { WriteExplicitLineBreak(ExplicitLineBreak) WriteLineBreak(LineBreak) WriteRegularLink(RegularLink) + WriteTimestamp(Timestamp) WriteFootnoteLink(FootnoteLink) WriteFootnoteDefinition(FootnoteDefinition) } @@ -78,6 +79,8 @@ func WriteNodes(w Writer, nodes ...Node) { w.WriteLineBreak(n) case RegularLink: w.WriteRegularLink(n) + case Timestamp: + w.WriteTimestamp(n) case FootnoteLink: w.WriteFootnoteLink(n) case FootnoteDefinition: