/third_party/src/code.google.com/p/rsc/cmd/labels/main.go

https://gitlab.com/admin-github-cloud/discovery.etcd.io · Go · 228 lines · 174 code · 15 blank · 39 comment · 31 complexity · f3ecf4c0c8f49917b122d335b0f1403a MD5 · raw file

  1. // Copyright 2013 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // Labels converts comma-separated-value records to PostScript mailing labels.
  5. // The output is formatted for 8½"×11" sheets containing thirty 2⅝"x1" labels each,
  6. // such as the Avery 5160.
  7. //
  8. // usage: labels [options] [file...]
  9. //
  10. // Converts CSV records to PostScript mailing labels, using the first three fields
  11. // of each input record as the address.
  12. //
  13. // The options are:
  14. //
  15. // -f font
  16. // Use the named PostScript font (default Times-Roman)
  17. // -m regexp
  18. // Only use input records matching regexp.
  19. // The text being matched is the record with commas separating fields,
  20. // with no quotation marks added.
  21. // -o outfile
  22. // Write labels to outfile (default standard output)
  23. // -p size
  24. // Use text with the given point size (default 12)
  25. // -v vsize
  26. // Use lines of text vsize points apart (default 1.2 * text size)
  27. // -x regexp
  28. // Exclude input records matching regexp.
  29. //
  30. // If the first line of the CSV contains the text "address" (case insensitive),
  31. // it is assumed to be a header for the spreadsheet and is skipped.
  32. //
  33. // Example
  34. //
  35. // Used with googlecsv, labels can take Google spreadsheets as input:
  36. //
  37. // googlecsv 'Mailing List' | labels -f FournierMT-RegularSC > labels.ps
  38. //
  39. package main
  40. import (
  41. "bytes"
  42. "encoding/csv"
  43. "flag"
  44. "fmt"
  45. "io"
  46. "log"
  47. "os"
  48. "regexp"
  49. "strings"
  50. )
  51. func usage() {
  52. fmt.Fprintf(os.Stderr, `usage: labels [options] [file...]
  53. Converts CSV records to PostScript mailing labels, using the first three fields
  54. of each input record as the address.
  55. The options are:
  56. -f font
  57. Use the named PostScript font (default Times-Roman)
  58. -m regexp
  59. Only use input records matching regexp.
  60. The text being matched is the record with commas separating fields,
  61. with no quotation marks added.
  62. -o outfile
  63. Write labels to outfile (default standard output)
  64. -p size
  65. Use text with the given point size (default 12)
  66. -v vsize
  67. Use lines of text vsize points apart (default 1.2 * text size)
  68. -x regexp
  69. Exclude input records matching regexp.
  70. If the first line of the CSV contains the text "address" (case insensitive),
  71. it is assumed to be a header for the spreadsheet and is skipped.
  72. `)
  73. os.Exit(2)
  74. }
  75. var (
  76. font = flag.String("f", "Times-Bold", "")
  77. outfile = flag.String("o", "", "")
  78. ps = flag.Int("p", 12, "")
  79. vs = flag.Int("v", 0, "")
  80. match = flag.String("m", "match", "")
  81. exclude = flag.String("x", "exclude", "")
  82. matchRE *regexp.Regexp
  83. excludeRE *regexp.Regexp
  84. )
  85. func main() {
  86. log.SetFlags(0)
  87. flag.Usage = usage
  88. flag.Parse()
  89. var input [][]string
  90. if flag.NArg() == 0 {
  91. input = readCSV("standard input", os.Stdin)
  92. } else {
  93. for _, file := range flag.Args() {
  94. f, err := os.Open(file)
  95. if err != nil {
  96. log.Fatal(err)
  97. }
  98. input = append(input, readCSV(file, f)...)
  99. f.Close()
  100. }
  101. }
  102. if len(input) > 0 && strings.Contains(strings.ToLower(strings.Join(input[0], ",")), "address") {
  103. // assume this is a heading line
  104. input = input[1:]
  105. }
  106. var buf bytes.Buffer
  107. if *vs == 0 {
  108. *vs = (*ps*12 + 5) / 10
  109. }
  110. fmt.Fprintf(&buf, prolog, *font, *ps, *vs)
  111. nlabel := 0
  112. for _, line := range input {
  113. if len(line) == 0 {
  114. continue
  115. }
  116. join := strings.Join(line, ",")
  117. if matchRE != nil && !matchRE.MatchString(join) ||
  118. excludeRE != nil && excludeRE.MatchString(join) {
  119. continue
  120. }
  121. mark := "mark"
  122. for i, field := range line {
  123. if i >= 3 {
  124. break
  125. }
  126. field = strings.TrimSpace(field)
  127. if field == "" {
  128. continue
  129. }
  130. field = strings.Replace(field, "(", `\(`, -1)
  131. field = strings.Replace(field, ")", `\)`, -1)
  132. for _, f := range strings.Split(field, "\n") {
  133. f = strings.TrimSpace(f)
  134. if f == "" {
  135. continue
  136. }
  137. fmt.Fprintf(&buf, "%s (%s)", mark, f)
  138. mark = ""
  139. }
  140. }
  141. if mark == "" {
  142. nlabel++
  143. fmt.Fprintf(&buf, " label\n")
  144. }
  145. }
  146. fmt.Fprintf(&buf, "endlabels\n")
  147. if nlabel == 0 {
  148. log.Fatal("no labels to create")
  149. }
  150. os.Stdout.Write(buf.Bytes())
  151. }
  152. func readCSV(name string, r io.Reader) [][]string {
  153. rr := csv.NewReader(r)
  154. rr.FieldsPerRecord = -1
  155. recs, err := rr.ReadAll()
  156. if err != nil {
  157. log.Fatalf("parsing %s: %v", name, err)
  158. }
  159. return recs
  160. }
  161. const prolog = `%%!PS-Adobe-2.0
  162. /numlabel 0 def
  163. /%s findfont
  164. /ps %d def
  165. /vs %d def
  166. dup length dict begin
  167. {1 index /FID ne {def} {pop pop} ifelse} forall
  168. /Encoding ISOLatin1Encoding def
  169. currentdict
  170. end
  171. /MyFont exch definefont pop
  172. /MyFont findfont ps scalefont setfont
  173. /inch { 72 mul } bind def
  174. /label {
  175. numlabel 3 mod 2.75 mul 0.125 add 2.625 2 div add inch
  176. 11 numlabel 3 idiv 1 mul 0.5 add 1 2 div add sub inch
  177. moveto
  178. 0 counttomark vs mul ps add vs sub -2 div rmoveto
  179. /max 0 def
  180. counttomark -1 1 {
  181. 1 sub index stringwidth pop
  182. dup max gt { /max exch def } { pop } ifelse
  183. } for
  184. max 2.625 inch gt { /max 2.625 inch def } if
  185. max -2 div 0 rmoveto
  186. counttomark -1 1 {
  187. pop
  188. gsave 0 ps rmoveto show grestore
  189. 0 vs rmoveto
  190. } for
  191. pop
  192. /numlabel numlabel 1 add def
  193. numlabel 30 ge {
  194. showpage
  195. /numlabel 0 def
  196. } if
  197. } def
  198. /endlabels {
  199. numlabel 0 gt { showpage } if
  200. } def
  201. `