output.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "github.com/spf13/cobra"
  6. "netdome.biz/paul/qmk-rgb/internal/device"
  7. intrgb "netdome.biz/paul/qmk-rgb/internal/rgb"
  8. "netdome.biz/paul/qmk-rgb/internal/via"
  9. )
  10. // The commands below print text, because that is what a person reads, and JSON
  11. // only when it is asked for. The flag is persistent, so it reads the same
  12. // everywhere: `qmk-rgb-tool --json info` and `qmk-rgb-tool info --json` are the
  13. // same command. An agent that parses the output passes it; a person does not
  14. // have to learn a second shape for the same information.
  15. var jsonOutput bool
  16. func addJSONFlag(cmd *cobra.Command) {
  17. cmd.PersistentFlags().BoolVar(&jsonOutput, "json", false, "Print JSON instead of text")
  18. }
  19. // printEffectListText writes one effect per line, its ID in brackets, under a
  20. // heading per channel. The ID is in the line because it is what a user needs to
  21. // check a name against the register, and what `effect <index>` takes.
  22. func printEffectListText(out io.Writer, catalog *intrgb.Catalog, channels []via.Channel, display map[uint16]string, source string) error {
  23. for _, ch := range channels {
  24. effects := catalog.Effects(ch)
  25. if len(effects) == 0 {
  26. continue
  27. }
  28. fmt.Fprintf(out, "%s (%s)\n", channelName(ch, display), ch.Subsystem())
  29. for _, e := range effects {
  30. fmt.Fprintf(out, " %s (%d)\n", e.Name, e.ID)
  31. }
  32. }
  33. if source != "" {
  34. fmt.Fprintf(out, "\nNames from %s\n", source)
  35. }
  36. return nil
  37. }
  38. // printZoneInfoText writes one line per zone, which is what a person compares,
  39. // and nothing else. A header naming the keyboard's "mode" was the first zone's
  40. // effect, so a board running three different effects printed one of them twice,
  41. // as a line above the others that looked like it applied to all of them. The
  42. // effect of every channel is on its own line; `mode` remains in the JSON, which
  43. // is a shape this tool has always had.
  44. func printZoneInfoText(out io.Writer, state infoOutput) error {
  45. for _, z := range state.Zones {
  46. enabled := "off"
  47. if z.Enabled {
  48. enabled = "on"
  49. }
  50. fmt.Fprintf(out, " %-12s %-4s %-28s brightness %3d speed %3d color %s\n",
  51. z.Zone, enabled, z.Effect, z.Brightness, z.Speed, formatInfoColor(z.Color))
  52. }
  53. return nil
  54. }
  55. // formatInfoColor renders a hue and saturation the way the color command takes
  56. // them, so a value read from the keyboard can be written back unchanged.
  57. func formatInfoColor(c infoColor) string {
  58. return fmt.Sprintf("hsv:%d,%d", c.Hue, c.Saturation)
  59. }
  60. // printKeyboardInfoText writes one line per keyboard, with the index --device
  61. // takes, because the index is the only part a user has to act on.
  62. func printKeyboardInfoText(out io.Writer, devices []keyboardLine) error {
  63. if len(devices) == 0 {
  64. fmt.Fprintln(out, "No QMK keyboard found.")
  65. return nil
  66. }
  67. for _, d := range devices {
  68. line := fmt.Sprintf("%d %s 0x%04X/0x%04X", d.Index, d.Name, d.VendorID, d.ProductID)
  69. if d.Effects {
  70. line += " (effect names)"
  71. }
  72. fmt.Fprintln(out, line)
  73. }
  74. return nil
  75. }
  76. // keyboardLine is one keyboard as the info command reports it. Whether it has
  77. // effect names is a fact about the tool, not about a data file: it is true when
  78. // a definition covers the board or a catalog is compiled in for it. Its channels
  79. // are deliberately absent, because this command does not open the board and
  80. // cannot know which ones it has.
  81. type keyboardLine struct {
  82. Index int `json:"index"`
  83. Path string `json:"path"`
  84. VendorID uint16 `json:"vendorId"`
  85. ProductID uint16 `json:"productId"`
  86. Name string `json:"name"`
  87. Effects bool `json:"effects"`
  88. }
  89. // describeDevices turns discovered devices into what the info command reports:
  90. // the name a board answers to and whether this tool has effect names for it.
  91. func describeDevices(devices []device.Device) []keyboardLine {
  92. lines := make([]keyboardLine, 0, len(devices))
  93. for _, d := range devices {
  94. display, _ := applyDefinitionLabels(d.VendorID, d.ProductID)
  95. catalog, _, _ := resolveCatalogFor(d.VendorID, d.ProductID)
  96. // No channel list here: this command does not open the board, so it
  97. // cannot know which channels it has, and reporting the ones its
  98. // definition names would claim more than it knows. `info` opens the
  99. // keyboard and reports those.
  100. lines = append(lines, keyboardLine{
  101. Index: d.Index,
  102. Path: d.Path,
  103. VendorID: d.VendorID,
  104. ProductID: d.ProductID,
  105. Name: boardName(d, display),
  106. Effects: catalog != nil,
  107. })
  108. }
  109. return lines
  110. }