output.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  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. func printZoneInfoText(out io.Writer, state infoOutput) error {
  40. fmt.Fprintf(out, "%s\n", state.Mode)
  41. for _, z := range state.Zones {
  42. enabled := "off"
  43. if z.Enabled {
  44. enabled = "on"
  45. }
  46. fmt.Fprintf(out, " %-12s %-4s %-28s brightness %3d speed %3d color %s\n",
  47. z.Zone, enabled, z.Effect, z.Brightness, z.Speed, formatInfoColor(z.Color))
  48. }
  49. return nil
  50. }
  51. // formatInfoColor renders a hue and saturation the way the color command takes
  52. // them, so a value read from the keyboard can be written back unchanged.
  53. func formatInfoColor(c infoColor) string {
  54. return fmt.Sprintf("hsv:%d,%d", c.Hue, c.Saturation)
  55. }
  56. // printKeyboardInfoText writes one line per keyboard, with the index --device
  57. // takes, because the index is the only part a user has to act on.
  58. func printKeyboardInfoText(out io.Writer, devices []keyboardLine) error {
  59. if len(devices) == 0 {
  60. fmt.Fprintln(out, "No QMK keyboard found.")
  61. return nil
  62. }
  63. for _, d := range devices {
  64. line := fmt.Sprintf("%d %s 0x%04X/0x%04X", d.Index, d.Name, d.VendorID, d.ProductID)
  65. if d.Effects {
  66. line += " (effect names)"
  67. }
  68. fmt.Fprintln(out, line)
  69. }
  70. return nil
  71. }
  72. // keyboardLine is one keyboard as the info command reports it. Whether it has
  73. // effect names is a fact about the tool, not about a data file: it is true when
  74. // a definition covers the board or a catalog is compiled in for it. Its channels
  75. // are deliberately absent, because this command does not open the board and
  76. // cannot know which ones it has.
  77. type keyboardLine struct {
  78. Index int `json:"index"`
  79. Path string `json:"path"`
  80. VendorID uint16 `json:"vendorId"`
  81. ProductID uint16 `json:"productId"`
  82. Name string `json:"name"`
  83. Effects bool `json:"effects"`
  84. }
  85. // describeDevices turns discovered devices into what the info command reports:
  86. // the name a board answers to and whether this tool has effect names for it.
  87. func describeDevices(devices []device.Device) []keyboardLine {
  88. lines := make([]keyboardLine, 0, len(devices))
  89. for _, d := range devices {
  90. display, _ := applyDefinitionLabels(d.VendorID, d.ProductID)
  91. catalog, _, _ := resolveCatalogFor(d.VendorID, d.ProductID)
  92. // No channel list here: this command does not open the board, so it
  93. // cannot know which channels it has, and reporting the ones its
  94. // definition names would claim more than it knows. `info` opens the
  95. // keyboard and reports those.
  96. lines = append(lines, keyboardLine{
  97. Index: d.Index,
  98. Path: d.Path,
  99. VendorID: d.VendorID,
  100. ProductID: d.ProductID,
  101. Name: boardName(d, display),
  102. Effects: catalog != nil,
  103. })
  104. }
  105. return lines
  106. }