search_ui 0.0.9 → 0.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 710fcef9a16190a9c6f59491c8ba25f44e82cd5a9718489658ed2f1b2a893487
4
- data.tar.gz: cbec273ce822d45c805d92325ffa7e4970d079a1bd2680a4732a413002cfeaf8
3
+ metadata.gz: e7d5feb21158fa490f48360724a1e4bd0110a1dd64d89c6bbb79be5bf8de3019
4
+ data.tar.gz: e162cee1ef6035b7ea1305360c88d260b5ebb0b5ecf91fe573a2d0be5c50eaca
5
5
  SHA512:
6
- metadata.gz: 3f398a7c9be2b14ac3aaee4cf38a22b1b4871dedc4c5250a251cd1376beb88fe8b00ae495a1baaf184d8bf05c742f7dd399a1c4888eec225e17d9affdb33ba30
7
- data.tar.gz: 1de4d6e0313b493136e1f42cf1aeebdbf023e09c5840dbd8dfaf28a881f2e317fb9814b0813d69ec033bc4f4c7b33f96bfb4daba8e2bf44d7ab3fe9cdb5ba3d8
6
+ metadata.gz: 66b6d79977ab604dc5cb1fec8bb6c5e9467ac80af1a7ad4e95dab2466aa6c5d48f98e5b4e3fd820a1522f16dfcd88e02690759a09ef8d906f21a3d8e6d32a079
7
+ data.tar.gz: eebad124b4790a0059b7ce0fd7b337cbb55d439035a04e869ca6d040130a333b3d63531ed21d40b0745200493831cc653513ff3c4b7d2324a94172ac67d8fa38
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ -r spec_helper
data/Rakefile CHANGED
@@ -9,9 +9,13 @@ GemHadar do
9
9
  email 'flori@ping.de'
10
10
  homepage "https://github.com/flori/#{name}"
11
11
  summary 'Library to provide a user interface for searching in a console'
12
- description 'This library allows a user to search an array of objects
13
- interactively in the console by matching the pattern a user inputs to an
14
- array of objects and pick one of the remaining objects.'
12
+ description <<~EOT
13
+ This library allows a user to search an array of objects
14
+ interactively in the console by matching the pattern a user inputs to an
15
+ array of objects and pick one of the remaining objects.
16
+ EOT
17
+
18
+ test_dir 'spec'
15
19
 
16
20
  ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', 'coverage', '.AppleDouble',
17
21
  'tags', '.DS_Store', '.yardoc', 'doc'
@@ -22,10 +26,11 @@ GemHadar do
22
26
  executables << 'search_it'
23
27
 
24
28
  required_ruby_version '>= 2.0'
25
- dependency 'tins', '~>1.0'
29
+ dependency 'tins', '~>1.0'
26
30
  dependency 'term-ansicolor', '~>1.0'
27
- dependency 'amatch', '~>0.0'
28
- development_dependency 'simplecov', '~>0.0'
31
+ dependency 'amatch', '~>0.0'
32
+ development_dependency 'rspec', '~>3.0'
33
+ development_dependency 'simplecov', '~>0.0'
29
34
  development_dependency 'debug'
30
35
  licenses << 'MIT'
31
36
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.9
1
+ 0.2.0
@@ -18,35 +18,54 @@ class SearchUI::Search
18
18
  include Term::ANSIColor
19
19
  extend Term::ANSIColor
20
20
 
21
- # Initializes a new SearchUI::Search instance with the specified parameters.
21
+ # Represents the current state of the search interface.
22
22
  #
23
- # @param match [ Proc ] a procedure that takes a string and returns an array
24
- # of matching objects
25
- # @param query [ Proc ] a procedure that takes the current answer, matches,
26
- # and selector index to generate a query result
27
- # @param found [ Proc ] a procedure that takes the current answer, matches,
28
- # jand selector index to determine if a selection has been made
29
- # @param output [ IO ] the output stream to display the search interface
30
- # (defaults to STDOUT)
31
- # @param prompt [ String ] the prompt template to display during searching
32
- # (defaults to 'Search? %s')
23
+ # @attr answer [ String ] the current input string entered by the user
24
+ # @attr selector [ Integer ] the index of the currently selected match
25
+ State = Struct.new(:answer, :selector)
26
+
27
+ # Initializes a new SearchUI::Search instance to manage an interactive
28
+ # console search interface. This method sets up the filtering logic, display
29
+ # formatting, and selection criteria required to drive the search loop, as
30
+ # well as the output stream and prompt.
31
+ #
32
+ # @param match [ Proc ] a procedure that accepts a search pattern string and returns
33
+ # an array of matching objects.
34
+ # @param query [ Proc ] a procedure that accepts the current answer, the list of
35
+ # matches, and the current selector index to generate the string representation
36
+ # of the results list.
37
+ # @param found [ Proc ] a procedure that accepts the current answer, the list of
38
+ # matches, and the current selector index to determine the final selected object.
39
+ # @param output [ IO ] the output stream where the interface will be rendered.
40
+ # Defaults to STDOUT.
41
+ # @param prompt [ String ] a format string used as the user prompt.
42
+ # Defaults to 'Search? %s'.
43
+ # @param state [ SearchUI::Search::State, nil ] an initial state object containing
44
+ # the starting answer and selector position. Defaults to a new State with an
45
+ # empty answer and selector set to 0.
33
46
  def initialize(
34
47
  match:,
35
48
  query:,
36
49
  found:,
37
50
  output: STDOUT,
38
- prompt: 'Search? %s'
51
+ prompt: 'Search? %s',
52
+ state: nil
39
53
  )
40
54
  @match = match
41
55
  @query = query
42
56
  @found = found
43
57
  @output = output
44
58
  @prompt = prompt
45
- @selector = 0
46
- @max_selector = nil
47
- @answer = ''
59
+ @state = state || State.new('', 0)
48
60
  end
49
61
 
62
+ # Reads the current internal state of the search interface, containing the
63
+ # current input answer and the active selection index.
64
+ #
65
+ # @return [ SearchUI::Search::State ] the current state object tracking
66
+ # the user's search progress and cursor position
67
+ attr_reader :state
68
+
50
69
  # Starts the interactive search interface and handles user input until a
51
70
  # selection is made or the process is cancelled.
52
71
  #
@@ -54,16 +73,16 @@ class SearchUI::Search
54
73
  # is made, or nil if the process is cancelled
55
74
  def start
56
75
  @output.print reset
57
- @matches = @match.(@answer)
58
- @selector = @selector.clamp(0, [ @matches.size - 1, 0 ].max)
59
- result = @query.(@answer, @matches, @selector)
76
+ @matches = @match.(@state.answer)
77
+ @state.selector = @state.selector.clamp(0, [ @matches.size - 1, 0 ].max)
78
+ result = @query.(@state.answer, @matches, @state.selector)
60
79
  loop do
61
80
  @output.print clear_screen
62
- @output.print move_home { @prompt % @answer + ?\n + result }
81
+ @output.print move_home { @prompt % @state.answer + ?\n + result }
63
82
  case getc
64
83
  when true
65
84
  @output.print clear_screen, move_home, reset
66
- if result = @found.(@answer, @matches, @selector)
85
+ if result = @found.(@state.answer, @matches, @state.selector)
67
86
  return result
68
87
  else
69
88
  return nil
@@ -71,26 +90,33 @@ class SearchUI::Search
71
90
  when false
72
91
  return nil
73
92
  end
74
- @matches = @match.(@answer)
75
- @selector = @selector.clamp(0, [ @matches.size - 1, 0 ].max)
76
- result = @query.(@answer, @matches, @selector)
93
+ @matches = @match.(@state.answer)
94
+ @state.selector = @state.selector.clamp(0, [ @matches.size - 1, 0 ].max)
95
+ result = @query.(@state.answer, @matches, @state.selector)
77
96
  end
78
97
  end
79
98
 
80
99
  private
81
100
 
82
- # Reads and processes a single character input from stdin, handling special
101
+ # Reads and processes a single character input from STDIN, handling special
83
102
  # key sequences and updating the search state accordingly.
84
103
  #
85
104
  # This method manages raw terminal input to capture user keystrokes,
86
- # interpreting control characters and escape sequences for navigation,
87
- # selection, and editing operations. It temporarily disables terminal echo
88
- # and sets raw mode to ensure proper input handling.
105
+ # interpreting control characters and ANSI escape sequences:
106
+ # - Up/Down arrows: Navigate the result selector.
107
+ # - Enter (`\r`): Confirms the current selection.
108
+ # - Ctrl-C (`\x03`): Cancels the search operation.
109
+ # - Ctrl-K (`\v`): Clears the current search answer.
110
+ # - Backspace (`\x7f`): Deletes the last character of the answer.
111
+ # - Note: Any modification to the search answer resets the selector to 0.
112
+ #
113
+ # It temporarily disables terminal echo and sets raw mode to ensure proper
114
+ # input handling.
89
115
  #
90
- # @return [ Boolean, nil ] returns true when the Enter key is pressed to
91
- # confirm selection, false when Ctrl+C is pressed to cancel the operation, or
92
- # nil for all other inputs
93
- # which update the search state and require further processing
116
+ # @return [ Boolean, nil ]
117
+ # - `true`: The Enter key was pressed to confirm selection.
118
+ # - `false`: Ctrl-C was pressed to cancel the operation.
119
+ # - `nil`: Input updated the search state or was ignored.
94
120
  def getc
95
121
  print hide_cursor
96
122
  system 'stty raw -echo'
@@ -103,27 +129,27 @@ class SearchUI::Search
103
129
  STDIN.getc == ?[ or return nil
104
130
  STDIN.getc =~ /\A([AB])\z/ or return nil
105
131
  if $1 == ?A
106
- @selector -= 1
132
+ @state.selector -= 1
107
133
  else
108
- @selector += 1
134
+ @state.selector += 1
109
135
  end
110
- @selector = [ @selector, 0 ].max
136
+ @state.selector = [ @state.selector, 0 ].max
111
137
  nil
112
138
  when ?\r
113
139
  true
114
140
  when "\x7f"
115
- @selector = 0
116
- @answer.chop!
141
+ @state.selector = 0
142
+ @state.answer.chop!
117
143
  nil
118
- when "\v"
119
- @selector = 0
120
- @answer.clear
144
+ when ?\v
145
+ @state.selector = 0
146
+ @state.answer.clear
121
147
  nil
122
148
  when /\A[\x00-\x1f]\z/
123
149
  nil
124
150
  else
125
- @selector = 0
126
- @answer << c
151
+ @state.selector = 0
152
+ @state.answer << c
127
153
  nil
128
154
  end
129
155
  ensure
@@ -1,6 +1,6 @@
1
1
  module SearchUI
2
2
  # SearchUI version
3
- VERSION = '0.0.9'
3
+ VERSION = '0.2.0'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
@@ -0,0 +1,67 @@
1
+ # Wrapper around an arbitrary value that:
2
+ #
3
+ # * Exposes the wrapped object via `value`.
4
+ # * Allows you to supply a custom string representation (`display`).
5
+ # * Delegates all other method calls straight through to the wrapped value.
6
+ #
7
+ # This is handy when you want to keep your objects printable in a
8
+ # particular colour or format (e.g. with Term::ANSIColor) while still
9
+ # behaving like the original object for comparisons, length checks,
10
+ # etc.
11
+ #
12
+ # @example Basic usage
13
+ # wrapper = SearchUI::Wrapper.new('foo', display: '🟢 foo')
14
+ # puts wrapper # => "🟢 foo"
15
+ # wrapper.length # => 3 (delegated to the string)
16
+ class SearchUI::Wrapper < BasicObject
17
+ # Initializes a new wrapper.
18
+ #
19
+ # @param value [Object] The object you want to wrap.
20
+ # @option options [String, Proc] :display Custom display string or block that returns it.
21
+ # Defaults to `value.to_s`.
22
+ def initialize(value, display: value.to_s)
23
+ @value = value
24
+ @display = display
25
+ end
26
+
27
+ # Returns the wrapped object.
28
+ #
29
+ # @return [Object]
30
+ attr_reader :value
31
+
32
+ # Equality comparison.
33
+ #
34
+ # Two wrappers are equal if their underlying values compare equal to the other argument.
35
+ #
36
+ # @param other [Object] The value or another wrapper to compare against.
37
+ # @return [Boolean]
38
+ def ==(other)
39
+ @value == other
40
+ end
41
+
42
+ alias eql? ==
43
+
44
+ # String representation of the wrapped object.
45
+ #
46
+ # If a custom `display` was supplied, that string is returned; otherwise,
47
+ # it falls back to `value.to_s`.
48
+ #
49
+ # @return [String]
50
+ def to_s
51
+ @display.to_s
52
+ end
53
+
54
+ alias to_str to_s
55
+
56
+ # Delegates any unknown method call to the wrapped value.
57
+ #
58
+ # This makes the wrapper behave like a transparent proxy for most
59
+ # operations (e.g. `length`, `upcase`, etc.).
60
+ #
61
+ # @param a [Array] Method name and arguments.
62
+ # @param o [Hash] Keyword arguments.
63
+ # @yield b Optional block passed to the underlying method.
64
+ def method_missing(*a, **o, &b)
65
+ @value.__send__(*a, **o, &b)
66
+ end
67
+ end
data/lib/search_ui.rb CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  # SearchUI is a library that provides interactive console-based searching
3
2
  # capabilities
4
3
  #
@@ -14,4 +13,5 @@ module SearchUI
14
13
  end
15
14
 
16
15
  require 'search_ui/version'
16
+ require 'search_ui/wrapper'
17
17
  require 'search_ui/search'
data/search_ui.gemspec CHANGED
@@ -1,29 +1,31 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: search_ui 0.0.9 ruby lib
2
+ # stub: search_ui 0.2.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "search_ui".freeze
6
- s.version = "0.0.9".freeze
6
+ s.version = "0.2.0".freeze
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
10
10
  s.authors = ["Florian Frank".freeze]
11
11
  s.date = "1980-01-02"
12
- s.description = "This library allows a user to search an array of objects\n interactively in the console by matching the pattern a user inputs to an\n array of objects and pick one of the remaining objects.".freeze
12
+ s.description = "This library allows a user to search an array of objects\ninteractively in the console by matching the pattern a user inputs to an\narray of objects and pick one of the remaining objects.\n".freeze
13
13
  s.email = "flori@ping.de".freeze
14
14
  s.executables = ["search_it".freeze]
15
- s.extra_rdoc_files = ["README.md".freeze, "lib/search_ui.rb".freeze, "lib/search_ui/search.rb".freeze, "lib/search_ui/version.rb".freeze]
16
- s.files = ["Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/search_it".freeze, "lib/search_ui.rb".freeze, "lib/search_ui/search.rb".freeze, "lib/search_ui/version.rb".freeze, "search_ui.gemspec".freeze]
15
+ s.extra_rdoc_files = ["README.md".freeze, "lib/search_ui.rb".freeze, "lib/search_ui/search.rb".freeze, "lib/search_ui/version.rb".freeze, "lib/search_ui/wrapper.rb".freeze]
16
+ s.files = [".rspec".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/search_it".freeze, "lib/search_ui.rb".freeze, "lib/search_ui/search.rb".freeze, "lib/search_ui/version.rb".freeze, "lib/search_ui/wrapper.rb".freeze, "search_ui.gemspec".freeze, "spec/search_ui/wrapper_spec.rb".freeze, "spec/spec_helper.rb".freeze]
17
17
  s.homepage = "https://github.com/flori/search_ui".freeze
18
18
  s.licenses = ["MIT".freeze]
19
19
  s.rdoc_options = ["--title".freeze, "SearchUI -- Search User Interface".freeze, "--main".freeze, "README.md".freeze]
20
20
  s.required_ruby_version = Gem::Requirement.new(">= 2.0".freeze)
21
- s.rubygems_version = "4.0.2".freeze
21
+ s.rubygems_version = "4.0.10".freeze
22
22
  s.summary = "Library to provide a user interface for searching in a console".freeze
23
+ s.test_files = ["spec/search_ui/wrapper_spec.rb".freeze, "spec/spec_helper.rb".freeze]
23
24
 
24
25
  s.specification_version = 4
25
26
 
26
- s.add_development_dependency(%q<gem_hadar>.freeze, ["~> 2.12".freeze])
27
+ s.add_development_dependency(%q<gem_hadar>.freeze, [">= 2.17.1".freeze])
28
+ s.add_development_dependency(%q<rspec>.freeze, ["~> 3.0".freeze])
27
29
  s.add_development_dependency(%q<simplecov>.freeze, ["~> 0.0".freeze])
28
30
  s.add_development_dependency(%q<debug>.freeze, [">= 0".freeze])
29
31
  s.add_runtime_dependency(%q<tins>.freeze, ["~> 1.0".freeze])
@@ -0,0 +1,56 @@
1
+ describe SearchUI::Wrapper do
2
+ let(:value) { 'foo' }
3
+
4
+ c = Term::ANSIColor
5
+
6
+ let(:sw1) { described_class.new(value, display: c.red { value }) }
7
+
8
+ let(:sw2) { described_class.new(value, display: c.blue { value }) }
9
+
10
+ it 'stores the wrapped value' do
11
+ expect(sw1.value).to eq value
12
+ end
13
+
14
+ it 'returns the custom string when to_s is called' do
15
+ expect(sw1.to_s).to eq c.red { 'foo' }
16
+ end
17
+
18
+ it 'falls back to the wrapped object’s to_s if no display given' do
19
+ wrapper = described_class.new(value)
20
+ expect(wrapper.to_s).to eq 'foo'
21
+ end
22
+
23
+ it 'delegates methods to the wrapped value (e.g., length)' do
24
+ expect(sw1.length).to eq 3
25
+ end
26
+
27
+ it 'compares equal when underlying values are equal' do
28
+ expect(sw1 == sw2).to be true
29
+ end
30
+
31
+ it 'is not equal to a different wrapped value' do
32
+ diff_obj = "bar"
33
+ expect(sw1 == described_class.new(diff_obj)).to be false
34
+ end
35
+
36
+ context 'equality with other wrappers' do
37
+ it 'is eq' do
38
+ expect(sw1).to eq sw2
39
+ end
40
+
41
+ it 'is ==' do
42
+ expect(sw1 == sw2).to eq true
43
+ expect(sw2 == sw1).to eq true
44
+ end
45
+
46
+ it 'is eql' do
47
+ expect(sw1).to be_eql sw2
48
+ expect(sw2).to be_eql sw1
49
+ end
50
+ end
51
+
52
+ it 'is equal to its original value' do
53
+ expect(sw1).to eq value
54
+ expect(value).to eq sw1
55
+ end
56
+ end
@@ -0,0 +1,7 @@
1
+ begin
2
+ require 'gem_hadar/simplecov'
3
+ GemHadar::SimpleCov.start
4
+ rescue LoadError
5
+ end
6
+ require 'rspec'
7
+ require 'search_ui'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: search_ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
@@ -11,18 +11,32 @@ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: gem_hadar
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 2.17.1
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 2.17.1
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
14
28
  requirement: !ruby/object:Gem::Requirement
15
29
  requirements:
16
30
  - - "~>"
17
31
  - !ruby/object:Gem::Version
18
- version: '2.12'
32
+ version: '3.0'
19
33
  type: :development
20
34
  prerelease: false
21
35
  version_requirements: !ruby/object:Gem::Requirement
22
36
  requirements:
23
37
  - - "~>"
24
38
  - !ruby/object:Gem::Version
25
- version: '2.12'
39
+ version: '3.0'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: simplecov
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -93,10 +107,10 @@ dependencies:
93
107
  - - "~>"
94
108
  - !ruby/object:Gem::Version
95
109
  version: '0.0'
96
- description: |-
110
+ description: |
97
111
  This library allows a user to search an array of objects
98
- interactively in the console by matching the pattern a user inputs to an
99
- array of objects and pick one of the remaining objects.
112
+ interactively in the console by matching the pattern a user inputs to an
113
+ array of objects and pick one of the remaining objects.
100
114
  email: flori@ping.de
101
115
  executables:
102
116
  - search_it
@@ -106,7 +120,9 @@ extra_rdoc_files:
106
120
  - lib/search_ui.rb
107
121
  - lib/search_ui/search.rb
108
122
  - lib/search_ui/version.rb
123
+ - lib/search_ui/wrapper.rb
109
124
  files:
125
+ - ".rspec"
110
126
  - Gemfile
111
127
  - LICENSE
112
128
  - README.md
@@ -116,7 +132,10 @@ files:
116
132
  - lib/search_ui.rb
117
133
  - lib/search_ui/search.rb
118
134
  - lib/search_ui/version.rb
135
+ - lib/search_ui/wrapper.rb
119
136
  - search_ui.gemspec
137
+ - spec/search_ui/wrapper_spec.rb
138
+ - spec/spec_helper.rb
120
139
  homepage: https://github.com/flori/search_ui
121
140
  licenses:
122
141
  - MIT
@@ -139,7 +158,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
158
  - !ruby/object:Gem::Version
140
159
  version: '0'
141
160
  requirements: []
142
- rubygems_version: 4.0.2
161
+ rubygems_version: 4.0.10
143
162
  specification_version: 4
144
163
  summary: Library to provide a user interface for searching in a console
145
- test_files: []
164
+ test_files:
165
+ - spec/search_ui/wrapper_spec.rb
166
+ - spec/spec_helper.rb