1package org 2 3import ( 4 "fmt" 5 "regexp" 6 "strings" 7 "unicode" 8 "unicode/utf8" 9) 10 11// OrgWriter export an org document into pretty printed org document. 12type OrgWriter struct { 13 ExtendingWriter Writer 14 TagsColumn int 15 16 strings.Builder 17 indent string 18} 19 20var exampleBlockUnescapeRegexp = regexp.MustCompile(`(^|\n)([ \t]*)(\*|,\*|#\+|,#\+)`) 21 22var emphasisOrgBorders = map[string][]string{ 23 "_": []string{"_", "_"}, 24 "*": []string{"*", "*"}, 25 "/": []string{"/", "/"}, 26 "+": []string{"+", "+"}, 27 "~": []string{"~", "~"}, 28 "=": []string{"=", "="}, 29 "_{}": []string{"_{", "}"}, 30 "^{}": []string{"^{", "}"}, 31} 32 33func NewOrgWriter() *OrgWriter { 34 return &OrgWriter{ 35 TagsColumn: 77, 36 } 37} 38 39func (w *OrgWriter) WriterWithExtensions() Writer { 40 if w.ExtendingWriter != nil { 41 return w.ExtendingWriter 42 } 43 return w 44} 45 46func (w *OrgWriter) Before(d *Document) {} 47func (w *OrgWriter) After(d *Document) {} 48 49func (w *OrgWriter) WriteNodesAsString(nodes ...Node) string { 50 builder := w.Builder 51 w.Builder = strings.Builder{} 52 WriteNodes(w, nodes...) 53 out := w.String() 54 w.Builder = builder 55 return out 56} 57 58func (w *OrgWriter) WriteHeadline(h Headline) { 59 start := w.Len() 60 w.WriteString(strings.Repeat("*", h.Lvl)) 61 if h.Status != "" { 62 w.WriteString(" " + h.Status) 63 } 64 if h.Priority != "" { 65 w.WriteString(" [#" + h.Priority + "]") 66 } 67 w.WriteString(" ") 68 WriteNodes(w, h.Title...) 69 if len(h.Tags) != 0 { 70 tString := ":" + strings.Join(h.Tags, ":") + ":" 71 if n := w.TagsColumn - len(tString) - (w.Len() - start); n > 0 { 72 w.WriteString(strings.Repeat(" ", n) + tString) 73 } else { 74 w.WriteString(" " + tString) 75 } 76 } 77 w.WriteString("\n") 78 if len(h.Children) != 0 { 79 w.WriteString(w.indent) 80 } 81 if h.Properties != nil { 82 WriteNodes(w, *h.Properties) 83 } 84 WriteNodes(w, h.Children...) 85} 86 87func (w *OrgWriter) WriteBlock(b Block) { 88 w.WriteString(w.indent + "#+BEGIN_" + b.Name) 89 if len(b.Parameters) != 0 { 90 w.WriteString(" " + strings.Join(b.Parameters, " ")) 91 } 92 w.WriteString("\n") 93 if isRawTextBlock(b.Name) { 94 w.WriteString(w.indent) 95 } 96 content := w.WriteNodesAsString(b.Children...) 97 if b.Name == "EXAMPLE" || (b.Name == "SRC" && len(b.Parameters) >= 1 && b.Parameters[0] == "org") { 98 content = exampleBlockUnescapeRegexp.ReplaceAllString(content, "$1$2,$3") 99 } 100 w.WriteString(content) 101 if !isRawTextBlock(b.Name) { 102 w.WriteString(w.indent) 103 } 104 w.WriteString("#+END_" + b.Name + "\n") 105 106 if b.Result != nil { 107 w.WriteString("\n") 108 WriteNodes(w, b.Result) 109 } 110} 111 112func (w *OrgWriter) WriteResult(r Result) { 113 w.WriteString("#+RESULTS:\n") 114 WriteNodes(w, r.Node) 115} 116 117func (w *OrgWriter) WriteInlineBlock(b InlineBlock) { 118 switch b.Name { 119 case "src": 120 w.WriteString(b.Name + "_" + b.Parameters[0]) 121 if len(b.Parameters) > 1 { 122 w.WriteString("[" + strings.Join(b.Parameters[1:], " ") + "]") 123 } 124 w.WriteString("{") 125 WriteNodes(w, b.Children...) 126 w.WriteString("}") 127 case "export": 128 w.WriteString("@@" + b.Parameters[0] + ":") 129 WriteNodes(w, b.Children...) 130 w.WriteString("@@") 131 } 132} 133 134func (w *OrgWriter) WriteDrawer(d Drawer) { 135 w.WriteString(w.indent + ":" + d.Name + ":\n") 136 WriteNodes(w, d.Children...) 137 w.WriteString(w.indent + ":END:\n") 138} 139 140func (w *OrgWriter) WritePropertyDrawer(d PropertyDrawer) { 141 w.WriteString(":PROPERTIES:\n") 142 for _, kvPair := range d.Properties { 143 k, v := kvPair[0], kvPair[1] 144 if v != "" { 145 v = " " + v 146 } 147 w.WriteString(fmt.Sprintf(":%s:%s\n", k, v)) 148 } 149 w.WriteString(":END:\n") 150} 151 152func (w *OrgWriter) WriteFootnoteDefinition(f FootnoteDefinition) { 153 w.WriteString(fmt.Sprintf("[fn:%s]", f.Name)) 154 content := w.WriteNodesAsString(f.Children...) 155 if content != "" && !unicode.IsSpace(rune(content[0])) { 156 w.WriteString(" ") 157 } 158 w.WriteString(content) 159} 160 161func (w *OrgWriter) WriteParagraph(p Paragraph) { 162 content := w.WriteNodesAsString(p.Children...) 163 if len(content) > 0 && content[0] != '\n' { 164 w.WriteString(w.indent) 165 } 166 w.WriteString(content + "\n") 167} 168 169func (w *OrgWriter) WriteExample(e Example) { 170 for _, n := range e.Children { 171 w.WriteString(w.indent + ":") 172 if content := w.WriteNodesAsString(n); content != "" { 173 w.WriteString(" " + content) 174 } 175 w.WriteString("\n") 176 } 177} 178 179func (w *OrgWriter) WriteKeyword(k Keyword) { 180 w.WriteString(w.indent + "#+" + k.Key + ":") 181 if k.Value != "" { 182 w.WriteString(" " + k.Value) 183 } 184 w.WriteString("\n") 185} 186 187func (w *OrgWriter) WriteInclude(i Include) { 188 w.WriteKeyword(i.Keyword) 189} 190 191func (w *OrgWriter) WriteNodeWithMeta(n NodeWithMeta) { 192 for _, ns := range n.Meta.Caption { 193 w.WriteString("#+CAPTION: ") 194 WriteNodes(w, ns...) 195 w.WriteString("\n") 196 } 197 for _, attributes := range n.Meta.HTMLAttributes { 198 w.WriteString("#+ATTR_HTML: ") 199 w.WriteString(strings.Join(attributes, " ") + "\n") 200 } 201 WriteNodes(w, n.Node) 202} 203 204func (w *OrgWriter) WriteNodeWithName(n NodeWithName) { 205 w.WriteString(fmt.Sprintf("#+NAME: %s\n", n.Name)) 206 WriteNodes(w, n.Node) 207} 208 209func (w *OrgWriter) WriteComment(c Comment) { 210 w.WriteString(w.indent + "# " + c.Content + "\n") 211} 212 213func (w *OrgWriter) WriteList(l List) { WriteNodes(w, l.Items...) } 214 215func (w *OrgWriter) WriteListItem(li ListItem) { 216 originalBuilder, originalIndent := w.Builder, w.indent 217 w.Builder, w.indent = strings.Builder{}, w.indent+strings.Repeat(" ", len(li.Bullet)+1) 218 WriteNodes(w, li.Children...) 219 content := strings.TrimPrefix(w.String(), w.indent) 220 w.Builder, w.indent = originalBuilder, originalIndent 221 w.WriteString(w.indent + li.Bullet) 222 if li.Value != "" { 223 w.WriteString(fmt.Sprintf(" [@%s]", li.Value)) 224 } 225 if li.Status != "" { 226 w.WriteString(fmt.Sprintf(" [%s]", li.Status)) 227 } 228 if len(content) > 0 && content[0] == '\n' { 229 w.WriteString(content) 230 } else { 231 w.WriteString(" " + content) 232 } 233} 234 235func (w *OrgWriter) WriteDescriptiveListItem(di DescriptiveListItem) { 236 indent := w.indent + strings.Repeat(" ", len(di.Bullet)+1) 237 w.WriteString(w.indent + di.Bullet) 238 if di.Status != "" { 239 w.WriteString(fmt.Sprintf(" [%s]", di.Status)) 240 indent = indent + strings.Repeat(" ", len(di.Status)+3) 241 } 242 if len(di.Term) != 0 { 243 term := w.WriteNodesAsString(di.Term...) 244 w.WriteString(" " + term + " ::") 245 indent = indent + strings.Repeat(" ", len(term)+4) 246 } 247 originalBuilder, originalIndent := w.Builder, w.indent 248 w.Builder, w.indent = strings.Builder{}, indent 249 WriteNodes(w, di.Details...) 250 details := strings.TrimPrefix(w.String(), w.indent) 251 w.Builder, w.indent = originalBuilder, originalIndent 252 if len(details) > 0 && details[0] == '\n' { 253 w.WriteString(details) 254 } else { 255 w.WriteString(" " + details) 256 } 257} 258 259func (w *OrgWriter) WriteTable(t Table) { 260 for _, row := range t.Rows { 261 w.WriteString(w.indent) 262 if len(row.Columns) == 0 { 263 w.WriteString(`|`) 264 for i := 0; i < len(t.ColumnInfos); i++ { 265 w.WriteString(strings.Repeat("-", t.ColumnInfos[i].Len+2)) 266 if i < len(t.ColumnInfos)-1 { 267 w.WriteString("+") 268 } 269 } 270 w.WriteString(`|`) 271 272 } else { 273 w.WriteString(`|`) 274 for _, column := range row.Columns { 275 w.WriteString(` `) 276 content := w.WriteNodesAsString(column.Children...) 277 if content == "" { 278 content = " " 279 } 280 n := column.Len - utf8.RuneCountInString(content) 281 if n < 0 { 282 n = 0 283 } 284 if column.Align == "center" { 285 if n%2 != 0 { 286 w.WriteString(" ") 287 } 288 w.WriteString(strings.Repeat(" ", n/2) + content + strings.Repeat(" ", n/2)) 289 } else if column.Align == "right" { 290 w.WriteString(strings.Repeat(" ", n) + content) 291 } else { 292 w.WriteString(content + strings.Repeat(" ", n)) 293 } 294 w.WriteString(` |`) 295 } 296 } 297 w.WriteString("\n") 298 } 299} 300 301func (w *OrgWriter) WriteHorizontalRule(hr HorizontalRule) { 302 w.WriteString(w.indent + "-----\n") 303} 304 305func (w *OrgWriter) WriteText(t Text) { w.WriteString(t.Content) } 306 307func (w *OrgWriter) WriteEmphasis(e Emphasis) { 308 borders, ok := emphasisOrgBorders[e.Kind] 309 if !ok { 310 panic(fmt.Sprintf("bad emphasis %#v", e)) 311 } 312 w.WriteString(borders[0]) 313 WriteNodes(w, e.Content...) 314 w.WriteString(borders[1]) 315} 316 317func (w *OrgWriter) WriteLatexFragment(l LatexFragment) { 318 w.WriteString(l.OpeningPair) 319 WriteNodes(w, l.Content...) 320 w.WriteString(l.ClosingPair) 321} 322 323func (w *OrgWriter) WriteStatisticToken(s StatisticToken) { 324 w.WriteString(fmt.Sprintf("[%s]", s.Content)) 325} 326 327func (w *OrgWriter) WriteLineBreak(l LineBreak) { 328 w.WriteString(strings.Repeat("\n"+w.indent, l.Count)) 329} 330 331func (w *OrgWriter) WriteExplicitLineBreak(l ExplicitLineBreak) { 332 w.WriteString(`\\` + "\n" + w.indent) 333} 334 335func (w *OrgWriter) WriteTimestamp(t Timestamp) { 336 w.WriteString("<") 337 if t.IsDate { 338 w.WriteString(t.Time.Format(datestampFormat)) 339 } else { 340 w.WriteString(t.Time.Format(timestampFormat)) 341 } 342 if t.Interval != "" { 343 w.WriteString(" " + t.Interval) 344 } 345 w.WriteString(">") 346} 347 348func (w *OrgWriter) WriteFootnoteLink(l FootnoteLink) { 349 w.WriteString("[fn:" + l.Name) 350 if l.Definition != nil { 351 w.WriteString(":") 352 WriteNodes(w, l.Definition.Children[0].(Paragraph).Children...) 353 } 354 w.WriteString("]") 355} 356 357func (w *OrgWriter) WriteRegularLink(l RegularLink) { 358 if l.AutoLink { 359 w.WriteString(l.URL) 360 } else if l.Description == nil { 361 w.WriteString(fmt.Sprintf("[[%s]]", l.URL)) 362 } else { 363 w.WriteString(fmt.Sprintf("[[%s][%s]]", l.URL, w.WriteNodesAsString(l.Description...))) 364 } 365} 366 367func (w *OrgWriter) WriteMacro(m Macro) { 368 w.WriteString(fmt.Sprintf("{{{%s(%s)}}}", m.Name, strings.Join(m.Parameters, ","))) 369} 370