twiddling 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.mdl_rules.rb +2 -0
  4. data/.mdlrc +2 -0
  5. data/.quiet_quality.yml +9 -0
  6. data/.rspec +1 -0
  7. data/.rubocop.yml +21 -0
  8. data/.standard.yml +3 -0
  9. data/Gemfile +3 -0
  10. data/LICENSE +21 -0
  11. data/README.md +116 -0
  12. data/bin/cfg2tw7 +13 -0
  13. data/bin/tw72cfg +9 -0
  14. data/bin/twiddling +9 -0
  15. data/configs/v7/default.cfg +0 -0
  16. data/configs/v7/default.tw7 +168 -0
  17. data/configs/v7/ericspace2.cfg +0 -0
  18. data/configs/v7/ericspace2.tw7 +203 -0
  19. data/fixtures/v7/README.md +31 -0
  20. data/fixtures/v7/button-mode-keyboard.cfg +0 -0
  21. data/fixtures/v7/button-mode-keyboard.tw7 +16 -0
  22. data/fixtures/v7/cycle-config-chord.cfg +0 -0
  23. data/fixtures/v7/cycle-config-chord.tw7 +12 -0
  24. data/fixtures/v7/empty.cfg +0 -0
  25. data/fixtures/v7/empty.tw7 +10 -0
  26. data/fixtures/v7/haptic-feedback-off.cfg +0 -0
  27. data/fixtures/v7/haptic-feedback-off.tw7 +16 -0
  28. data/fixtures/v7/idle-time-8m.cfg +0 -0
  29. data/fixtures/v7/idle-time-8m.tw7 +12 -0
  30. data/fixtures/v7/key-repeat-1020.cfg +0 -0
  31. data/fixtures/v7/key-repeat-1020.tw7 +12 -0
  32. data/fixtures/v7/key-repeat-disabled.cfg +0 -0
  33. data/fixtures/v7/key-repeat-disabled.tw7 +12 -0
  34. data/fixtures/v7/large.cfg +0 -0
  35. data/fixtures/v7/large.tw7 +142 -0
  36. data/fixtures/v7/mini-buttons.cfg +0 -0
  37. data/fixtures/v7/mini-buttons.tw7 +14 -0
  38. data/fixtures/v7/modifier-key.cfg +0 -0
  39. data/fixtures/v7/modifier-key.tw7 +12 -0
  40. data/fixtures/v7/multi-char.cfg +0 -0
  41. data/fixtures/v7/multi-char.tw7 +12 -0
  42. data/fixtures/v7/nav-invert-x-axis.cfg +0 -0
  43. data/fixtures/v7/nav-invert-x-axis.tw7 +16 -0
  44. data/fixtures/v7/nav-sensitivity-lowered.cfg +0 -0
  45. data/fixtures/v7/nav-sensitivity-lowered.tw7 +12 -0
  46. data/fixtures/v7/nav-up-east.cfg +0 -0
  47. data/fixtures/v7/nav-up-east.tw7 +12 -0
  48. data/fixtures/v7/no-right-mouse-button.cfg +0 -0
  49. data/fixtures/v7/no-right-mouse-button.tw7 +12 -0
  50. data/fixtures/v7/no-t0-dedicated.cfg +0 -0
  51. data/fixtures/v7/no-t0-dedicated.tw7 +12 -0
  52. data/fixtures/v7/shifted-key.cfg +0 -0
  53. data/fixtures/v7/shifted-key.tw7 +12 -0
  54. data/fixtures/v7/single-unmodified-key.cfg +0 -0
  55. data/fixtures/v7/single-unmodified-key.tw7 +12 -0
  56. data/formats/tw7.md +222 -0
  57. data/formats/v7-cfg.md +272 -0
  58. data/lib/twiddling/cli/convert.rb +70 -0
  59. data/lib/twiddling/cli/diff.rb +192 -0
  60. data/lib/twiddling/cli/help.rb +26 -0
  61. data/lib/twiddling/cli/read.rb +56 -0
  62. data/lib/twiddling/cli/search.rb +131 -0
  63. data/lib/twiddling/cli/twiddling.rb +33 -0
  64. data/lib/twiddling/cli.rb +8 -0
  65. data/lib/twiddling/v7/chord.rb +48 -0
  66. data/lib/twiddling/v7/chord_constants.rb +82 -0
  67. data/lib/twiddling/v7/config.rb +102 -0
  68. data/lib/twiddling/v7/config_constants.rb +30 -0
  69. data/lib/twiddling/v7/data/default_base.cfg +0 -0
  70. data/lib/twiddling/v7/reader/chord.rb +49 -0
  71. data/lib/twiddling/v7/reader/config.rb +64 -0
  72. data/lib/twiddling/v7/reader/settings.rb +26 -0
  73. data/lib/twiddling/v7/reader/string_table.rb +69 -0
  74. data/lib/twiddling/v7/settings.rb +21 -0
  75. data/lib/twiddling/v7/string_table.rb +21 -0
  76. data/lib/twiddling/v7/string_table_entry.rb +14 -0
  77. data/lib/twiddling/v7/tw7/button_formatter.rb +63 -0
  78. data/lib/twiddling/v7/tw7/button_parser.rb +78 -0
  79. data/lib/twiddling/v7/tw7/effect_formatter.rb +78 -0
  80. data/lib/twiddling/v7/tw7/effect_parser.rb +121 -0
  81. data/lib/twiddling/v7/tw7/parser.rb +133 -0
  82. data/lib/twiddling/v7/tw7/printer.rb +105 -0
  83. data/lib/twiddling/v7/tw7/settings_formatter.rb +96 -0
  84. data/lib/twiddling/v7/tw7/settings_parser.rb +97 -0
  85. data/lib/twiddling/v7/validator.rb +93 -0
  86. data/lib/twiddling/v7/writer/chord.rb +36 -0
  87. data/lib/twiddling/v7/writer/config.rb +79 -0
  88. data/lib/twiddling/v7/writer/settings.rb +19 -0
  89. data/lib/twiddling/v7/writer/string_table.rb +40 -0
  90. data/lib/twiddling/v7.rb +34 -0
  91. data/lib/twiddling/version.rb +3 -0
  92. data/lib/twiddling.rb +8 -0
  93. data/tmp/.gitkeep +0 -0
  94. data/twiddling.gemspec +46 -0
  95. metadata +284 -0
@@ -0,0 +1,96 @@
1
+ module Twiddling
2
+ module V7
3
+ module Tw7
4
+ # Formats the settings section of a .tw7 file.
5
+ # Only emits settings that differ from defaults.
6
+ module SettingsFormatter
7
+ include ConfigConstants
8
+
9
+ DEFAULTS = {
10
+ idle_time: 600,
11
+ key_repeat_delay: 100,
12
+ key_repeat: true,
13
+ haptic: true,
14
+ keyboard_mode: false,
15
+ nav_sensitivity: 4,
16
+ nav_invert_x: false,
17
+ nav_direction: 0,
18
+ t1_modifier: :none,
19
+ t2_modifier: :l_option,
20
+ t3_modifier: :l_control,
21
+ t4_modifier: :l_shift,
22
+ f0l_dedicated: :mouse_right,
23
+ f0m_dedicated: :mouse_middle,
24
+ f0r_dedicated: :mouse_left,
25
+ t0_dedicated: :mouse_left
26
+ }.freeze
27
+
28
+ module_function
29
+
30
+ def format(config)
31
+ current = extract_settings(config)
32
+ lines = []
33
+
34
+ current.each do |key, value|
35
+ next if DEFAULTS[key] == value
36
+ lines << "#{key}: #{format_value(value)}"
37
+ end
38
+
39
+ lines
40
+ end
41
+
42
+ def extract_settings(config)
43
+ extract_header_settings(config)
44
+ .merge(extract_thumb_settings(config))
45
+ .merge(extract_dedicated_settings(config))
46
+ end
47
+
48
+ def extract_header_settings(config)
49
+ extract_timing_settings(config).merge(extract_flag_settings(config))
50
+ end
51
+
52
+ def extract_timing_settings(config)
53
+ {idle_time: config.idle_time, key_repeat_delay: config.key_repeat}
54
+ end
55
+
56
+ def extract_flag_settings(config)
57
+ {
58
+ key_repeat: (config.flags_1 & 0x01) != 0,
59
+ haptic: (config.flags_1 & 0x08) != 0,
60
+ keyboard_mode: (config.flags_1 & 0x02) != 0,
61
+ nav_sensitivity: (config.flags_2 >> 3) & 0x1F,
62
+ nav_invert_x: (config.flags_2 & 0x04) != 0,
63
+ nav_direction: config.flags_2 & 0x03
64
+ }
65
+ end
66
+
67
+ def extract_thumb_settings(config)
68
+ {
69
+ t1_modifier: THUMB_MODIFIERS[config.thumb_modifiers[0]],
70
+ t2_modifier: THUMB_MODIFIERS[config.thumb_modifiers[1]],
71
+ t3_modifier: THUMB_MODIFIERS[config.thumb_modifiers[2]],
72
+ t4_modifier: THUMB_MODIFIERS[config.thumb_modifiers[3]]
73
+ }
74
+ end
75
+
76
+ def extract_dedicated_settings(config)
77
+ {
78
+ f0l_dedicated: DEDICATED_FUNCTIONS[config.dedicated_buttons[0]],
79
+ f0m_dedicated: DEDICATED_FUNCTIONS[config.dedicated_buttons[1]],
80
+ f0r_dedicated: DEDICATED_FUNCTIONS[config.dedicated_buttons[2]],
81
+ t0_dedicated: DEDICATED_FUNCTIONS[config.dedicated_buttons[3]]
82
+ }
83
+ end
84
+
85
+ def format_value(value)
86
+ case value
87
+ when true then "true"
88
+ when false then "false"
89
+ when Symbol then value.to_s
90
+ else value.to_s
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,97 @@
1
+ module Twiddling
2
+ module V7
3
+ module Tw7
4
+ # Parses the settings section of a .tw7 file into Config attributes.
5
+ module SettingsParser
6
+ include ConfigConstants
7
+
8
+ THUMB_MODIFIER_NAMES = THUMB_MODIFIERS.to_h { |code, name| [name.to_s, code] }.freeze
9
+ DEDICATED_NAMES = DEDICATED_FUNCTIONS.to_h { |code, name| [name.to_s, code] }.freeze
10
+
11
+ module_function
12
+
13
+ # Returns a hash of Config-compatible attributes from settings lines.
14
+ # Missing settings use defaults from the empty.cfg baseline.
15
+ def parse(lines)
16
+ raw = parse_lines(lines)
17
+ build_attrs(raw)
18
+ end
19
+
20
+ private
21
+
22
+ def parse_lines(lines)
23
+ lines.each_with_object({}) do |line, hash|
24
+ key, value = line.split(":", 2).map(&:strip)
25
+ hash[key] = value
26
+ end
27
+ end
28
+
29
+ def build_attrs(raw) # rubocop:disable Metrics/AbcSize
30
+ {
31
+ idle_time: int_val(raw, "idle_time", 600),
32
+ key_repeat: int_val(raw, "key_repeat_delay", 100),
33
+ flags_1: build_flags_1(raw),
34
+ flags_2: build_flags_2(raw),
35
+ thumb_modifiers: build_thumb_modifiers(raw),
36
+ dedicated_buttons: build_dedicated(raw)
37
+ }
38
+ end
39
+
40
+ def build_flags_1(raw)
41
+ flags = 0
42
+ flags |= 0x01 if bool_val(raw, "key_repeat", true)
43
+ flags |= 0x02 if bool_val(raw, "keyboard_mode", false)
44
+ flags |= 0x08 if bool_val(raw, "haptic", true)
45
+ flags
46
+ end
47
+
48
+ def build_flags_2(raw)
49
+ sensitivity = int_val(raw, "nav_sensitivity", 4)
50
+ invert_x = bool_val(raw, "nav_invert_x", false) ? 1 : 0
51
+ direction = int_val(raw, "nav_direction", 0)
52
+ (sensitivity << 3) | (invert_x << 2) | direction
53
+ end
54
+
55
+ def build_thumb_modifiers(raw)
56
+ [
57
+ thumb_val(raw, "t1_modifier", :none),
58
+ thumb_val(raw, "t2_modifier", :l_option),
59
+ thumb_val(raw, "t3_modifier", :l_control),
60
+ thumb_val(raw, "t4_modifier", :l_shift)
61
+ ]
62
+ end
63
+
64
+ def build_dedicated(raw)
65
+ [
66
+ dedicated_val(raw, "f0l_dedicated", :mouse_right),
67
+ dedicated_val(raw, "f0m_dedicated", :mouse_middle),
68
+ dedicated_val(raw, "f0r_dedicated", :mouse_left),
69
+ dedicated_val(raw, "t0_dedicated", :mouse_left)
70
+ ]
71
+ end
72
+
73
+ def int_val(raw, key, default)
74
+ raw.key?(key) ? raw[key].to_i : default
75
+ end
76
+
77
+ def bool_val(raw, key, default)
78
+ raw.key?(key) ? raw[key] == "true" : default
79
+ end
80
+
81
+ def thumb_val(raw, key, default)
82
+ name = raw.key?(key) ? raw[key] : default.to_s
83
+ THUMB_MODIFIER_NAMES[name.to_s] || raise(ArgumentError, "Unknown thumb modifier: #{name}")
84
+ end
85
+
86
+ def dedicated_val(raw, key, default)
87
+ name = raw.key?(key) ? raw[key] : default.to_s
88
+ DEDICATED_NAMES[name.to_s] || raise(ArgumentError, "Unknown dedicated function: #{name}")
89
+ end
90
+
91
+ module_function :parse_lines, :build_attrs, :build_flags_1, :build_flags_2,
92
+ :build_thumb_modifiers, :build_dedicated, :int_val, :bool_val,
93
+ :thumb_val, :dedicated_val
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,93 @@
1
+ module Twiddling
2
+ module V7
3
+ class Validator
4
+ Error = Struct.new(:field, :message, keyword_init: true) do
5
+ def to_s = "#{field}: #{message}"
6
+ end
7
+
8
+ ValidationError = Class.new(Twiddling::Error)
9
+
10
+ def initialize(config)
11
+ @config = config
12
+ end
13
+
14
+ def validate
15
+ errors = []
16
+ check_format_version(errors)
17
+ check_chord_count(errors)
18
+ check_sorted_bitmasks(errors)
19
+ check_no_duplicate_bitmasks(errors)
20
+ check_string_table_offsets(errors)
21
+ errors
22
+ end
23
+
24
+ def validate!
25
+ errors = validate
26
+ return if errors.empty?
27
+
28
+ messages = errors.map(&:to_s).join("; ")
29
+ raise ValidationError, messages
30
+ end
31
+
32
+ private
33
+
34
+ def check_format_version(errors)
35
+ return if @config.format_version == 7
36
+
37
+ errors << Error.new(
38
+ field: :format_version,
39
+ message: "expected 7, got #{@config.format_version}"
40
+ )
41
+ end
42
+
43
+ def check_chord_count(errors)
44
+ return if @config.chord_count <= 0xFFFF
45
+
46
+ errors << Error.new(
47
+ field: :chord_count,
48
+ message: "exceeds u16 max (#{@config.chord_count})"
49
+ )
50
+ end
51
+
52
+ def check_sorted_bitmasks(errors)
53
+ bitmasks = @config.chords.map(&:bitmask)
54
+ return if bitmasks == bitmasks.sort
55
+
56
+ errors << Error.new(
57
+ field: :chords,
58
+ message: "chords are not sorted by bitmask"
59
+ )
60
+ end
61
+
62
+ def check_no_duplicate_bitmasks(errors)
63
+ seen = {}
64
+ @config.chords.each_with_index do |chord, idx|
65
+ if seen[chord.bitmask]
66
+ errors << Error.new(
67
+ field: :chords,
68
+ message: "duplicate bitmask 0x%08x at indices %d and %d" %
69
+ [chord.bitmask, seen[chord.bitmask], idx]
70
+ )
71
+ else
72
+ seen[chord.bitmask] = idx
73
+ end
74
+ end
75
+ end
76
+
77
+ def check_string_table_offsets(errors)
78
+ multichar_chords = @config.chords.select { |c| c.type_name == :multichar }
79
+ return if multichar_chords.empty?
80
+
81
+ multichar_chords.each do |chord|
82
+ next if chord.string_keys && !chord.string_keys.empty?
83
+
84
+ errors << Error.new(
85
+ field: :chords,
86
+ message: "multi-char chord (bitmask 0x%08x) has no string keys" %
87
+ chord.bitmask
88
+ )
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,36 @@
1
+ module Twiddling
2
+ module V7
3
+ module Writer
4
+ # Serializes a V7::Chord back to 8 bytes of binary data.
5
+ #
6
+ # For multi-char chords, a string_table_offset must be provided
7
+ # to encode the correct byte offset into the modifier_type field.
8
+ # For other chord types, modifier_type is written as-is.
9
+ class Chord
10
+ include ChordConstants
11
+
12
+ def initialize(chord, string_table_offset: nil)
13
+ @chord = chord
14
+ @string_table_offset = string_table_offset
15
+ end
16
+
17
+ def to_binary
18
+ [@chord.bitmask, effective_modifier_type, @chord.keycode].pack("Vvv")
19
+ end
20
+
21
+ private
22
+
23
+ # For multi-char chords being written with a new offset,
24
+ # rebuild modifier_type from the type byte + offset.
25
+ # Otherwise, preserve the original modifier_type.
26
+ def effective_modifier_type
27
+ if @string_table_offset
28
+ TYPE_MULTICHAR | (@string_table_offset << 8)
29
+ else
30
+ @chord.modifier_type
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,79 @@
1
+ module Twiddling
2
+ module V7
3
+ module Writer
4
+ # Serializes a V7::Config back to the v7 binary format.
5
+ #
6
+ # Produces three sections concatenated:
7
+ # 1. 128-byte header
8
+ # 2. 8-byte chord entries
9
+ # 3. String table (for multi-char chords)
10
+ #
11
+ # String table offsets in multi-char chord entries are
12
+ # recomputed during serialization to match the output layout.
13
+ class Config
14
+ include ConfigConstants
15
+
16
+ def initialize(config)
17
+ @config = config
18
+ end
19
+
20
+ def to_binary
21
+ header_binary + chords_binary + string_table_binary
22
+ end
23
+
24
+ private
25
+
26
+ def header_binary
27
+ header_fields + @config.reserved_10 + settings_binary + index_table_field
28
+ end
29
+
30
+ def header_fields
31
+ [
32
+ @config.version, @config.format_version,
33
+ @config.flags_1, @config.flags_2, @config.flags_3,
34
+ @config.chord_count, @config.idle_time,
35
+ @config.key_repeat, @config.reserved_0e
36
+ ].pack("VCCCCvvvv")
37
+ end
38
+
39
+ def settings_binary
40
+ Writer::Settings.new(@config.settings).to_binary
41
+ end
42
+
43
+ def index_table_field
44
+ @config.index_table.pack("C32")
45
+ end
46
+
47
+ def chords_binary
48
+ offsets = compute_string_offsets
49
+ @config.chords.each_with_index.map { |chord, i|
50
+ Writer::Chord.new(chord, string_table_offset: offsets[i]).to_binary
51
+ }.join
52
+ end
53
+
54
+ def string_table_binary
55
+ Writer::StringTable.new(build_string_table).to_binary
56
+ end
57
+
58
+ def build_string_table
59
+ entries = []
60
+ offset = 0
61
+ @config.chords.each do |chord|
62
+ next unless chord.string_keys
63
+ entries << StringTableEntry.new(keys: chord.string_keys, byte_offset: offset)
64
+ offset += (chord.string_keys.length + 1) * 4
65
+ end
66
+ V7::StringTable.new(entries)
67
+ end
68
+
69
+ def compute_string_offsets
70
+ table = build_string_table
71
+ @config.chords.map do |chord|
72
+ next unless chord.string_keys
73
+ table.entries.find { |e| e.keys == chord.string_keys }&.byte_offset
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,19 @@
1
+ module Twiddling
2
+ module V7
3
+ module Writer
4
+ # Serializes a V7::Settings back to 32 bytes of binary data
5
+ # for the config header (offsets 0x40-0x5F).
6
+ class Settings
7
+ def initialize(settings)
8
+ @settings = settings
9
+ end
10
+
11
+ def to_binary
12
+ @settings.thumb_modifiers.pack("V4") +
13
+ @settings.dedicated_buttons.pack("C4") +
14
+ @settings.reserved
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,40 @@
1
+ module Twiddling
2
+ module V7
3
+ module Writer
4
+ # Serializes a V7::StringTable back to the binary format used
5
+ # at the end of a v7 config file.
6
+ #
7
+ # Each entry becomes a sequence of (modifier u16 LE, hid_code
8
+ # u16 LE) pairs followed by a null terminator (0x0000, 0x0000).
9
+ # Entries are concatenated in order - their byte offsets in the
10
+ # output match the offsets stored on each StringTableEntry.
11
+ class StringTable
12
+ def initialize(string_table)
13
+ @string_table = string_table
14
+ end
15
+
16
+ # Returns the binary string table as a packed byte string.
17
+ def to_binary
18
+ @string_table.entries
19
+ .flat_map { |entry| encode_entry(entry) }
20
+ .pack("v*")
21
+ end
22
+
23
+ private
24
+
25
+ # Encodes a single entry as a flat array of u16 values:
26
+ # [mod1, hid1, mod2, hid2, ..., 0, 0]
27
+ def encode_entry(entry)
28
+ encode_keys(entry.keys) + null_terminator
29
+ end
30
+
31
+ # Flattens key hashes into [modifier, hid_code, ...] pairs.
32
+ def encode_keys(keys)
33
+ keys.flat_map { |sk| [sk[:modifier], sk[:hid_code]] }
34
+ end
35
+
36
+ def null_terminator = [0, 0]
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,34 @@
1
+ module Twiddling
2
+ module V7
3
+ module Reader
4
+ end
5
+
6
+ module Writer
7
+ end
8
+ end
9
+ end
10
+
11
+ require_relative "v7/string_table_entry"
12
+ require_relative "v7/string_table"
13
+ require_relative "v7/chord_constants"
14
+ require_relative "v7/config_constants"
15
+ require_relative "v7/chord"
16
+ require_relative "v7/settings"
17
+ require_relative "v7/validator"
18
+ require_relative "v7/config"
19
+ require_relative "v7/reader/string_table"
20
+ require_relative "v7/reader/chord"
21
+ require_relative "v7/reader/settings"
22
+ require_relative "v7/reader/config"
23
+ require_relative "v7/writer/string_table"
24
+ require_relative "v7/writer/chord"
25
+ require_relative "v7/writer/settings"
26
+ require_relative "v7/writer/config"
27
+ require_relative "v7/tw7/button_formatter"
28
+ require_relative "v7/tw7/button_parser"
29
+ require_relative "v7/tw7/effect_formatter"
30
+ require_relative "v7/tw7/effect_parser"
31
+ require_relative "v7/tw7/settings_formatter"
32
+ require_relative "v7/tw7/settings_parser"
33
+ require_relative "v7/tw7/printer"
34
+ require_relative "v7/tw7/parser"
@@ -0,0 +1,3 @@
1
+ module Twiddling
2
+ VERSION = "0.1"
3
+ end
data/lib/twiddling.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "bindata"
2
+
3
+ module Twiddling
4
+ Error = Class.new(StandardError)
5
+ end
6
+
7
+ require_relative "twiddling/v7"
8
+ require_relative "twiddling/cli"
data/tmp/.gitkeep ADDED
File without changes
data/twiddling.gemspec ADDED
@@ -0,0 +1,46 @@
1
+ require_relative "lib/twiddling/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "twiddling"
5
+ spec.version = Twiddling::VERSION
6
+ spec.authors = ["Eric Mueller"]
7
+ spec.email = ["nevinera@gmail.com"]
8
+
9
+ spec.summary = "A cli tool for reading/writing/editing Twiddler v7 configuration files"
10
+ spec.description = <<~DESC
11
+ Edit twiddler4 (v7) configuration files as text, rather than using the clunky online tool.
12
+
13
+ Adding a full configuration by hand is _extremely_ tedious, and you shouldn't have to do i.
14
+ DESC
15
+ spec.homepage = "https://github.com/nevinera/twiddling"
16
+ spec.license = "MIT"
17
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.3.8")
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = spec.homepage
21
+
22
+ spec.require_paths = ["lib"]
23
+ spec.bindir = "bin"
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ `git ls-files -z`
26
+ .split("\x0")
27
+ .reject { |f| f.start_with?("spec") }
28
+ end
29
+ spec.executables = Dir.chdir(File.expand_path(__dir__)) do
30
+ `git ls-files -z bin/`
31
+ .split("\x0")
32
+ .map { |path| path.sub(/^bin\//, "") }
33
+ end
34
+
35
+ spec.add_development_dependency "rspec", "~> 3.13"
36
+ spec.add_development_dependency "rspec-its", "~> 1.3"
37
+ spec.add_development_dependency "simplecov", "~> 0.22.0"
38
+ spec.add_development_dependency "pry", "~> 0.14"
39
+ spec.add_development_dependency "standard", ">= 1.35.1"
40
+ spec.add_development_dependency "rubocop", ">= 1.62"
41
+ spec.add_development_dependency "debug", "~> 1.7"
42
+ spec.add_development_dependency "mdl", "~> 0.12"
43
+ spec.add_development_dependency "quiet_quality", "~> 1.5"
44
+
45
+ spec.add_dependency "bindata", "~> 2.5"
46
+ end