curses_menu 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8dc4a9d14a6f315f6e2fba464d40d58d7a17922ee9afe1aabc1c045a4731a561
4
- data.tar.gz: fde73b88bf3972e2b4353911dea6278cd606a2a8e673feeab8dff41a87a0f3e8
3
+ metadata.gz: '093b7bafe2adec885ebb1cd49607556b54a1925f416597d1ab220519ec6009f8'
4
+ data.tar.gz: f0a6110f5e1ae34026c3d6982a5f2e8c8f402849bd0a7805ed43f90a8b398cad
5
5
  SHA512:
6
- metadata.gz: 6f9e782b2d1264e4b8b2e3b5f721574ba62abb20935a6d61fe6a70647d73386da9fc6c429689f35de3463f2d6aa6ce5d799fe8d17793b05a5621a27128bb3456
7
- data.tar.gz: c39295fce31482e5f3dd91f2a0de36658912c4fd0d6da3d7a18d0ae55671b74dd5d13e6b74037c96b57993716776697f3b5cb01e5da484b660dc459cd2fe9d1a
6
+ metadata.gz: 6b8d1c93130d98de349183ffd888f21de3ea0023e0cd7372817dc7efa0606974c442b0c1bd1d10f946395deb62cac089a332a0fc7f35c13a662c6452147b736e
7
+ data.tar.gz: 15f764995f0baa99dca4da1e73a67c2079c4956f237f3975c3086f6f9678c212f10e6c40e8ae0d3d90ab4375463114c917708fd44ed1a058fe71239a61390254
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # [v0.2.0](https://github.com/Muriel-Salvan/curses_menu/compare/v0.1.0...v0.2.0) (2022-11-01 21:07:07)
2
+
3
+ ### Features
4
+
5
+ * [[feature] [#6] Add lazy evaluation for actions](https://github.com/Muriel-Salvan/curses_menu/commit/0a5a96ae84fecdf982f763b833e8e863231dde73)
6
+
1
7
  # [v0.1.0](https://github.com/Muriel-Salvan/curses_menu/compare/v0.0.6...v0.1.0) (2022-10-12 15:58:25)
2
8
 
3
9
  ### Features
@@ -3,10 +3,21 @@ require 'curses_menu'
3
3
  nbr_visible_items = Curses.stdscr.maxy - 5
4
4
  CursesMenu.new 'Menu items using lazy rendering' do |menu|
5
5
  (nbr_visible_items * 2).times do
6
- menu.item "I am a normal item, rendered at #{Time.now}"
6
+ menu.item "[Rendered at #{Time.now}] - I am a normal item"
7
7
  menu.item(proc do
8
- "I am a lazy item, rendered at #{Time.now}"
8
+ "[Rendered at #{Time.now}] - I am a lazy item"
9
9
  end)
10
+ menu.item(
11
+ "[Rendered at #{Time.now}] - I am a normal item with lazy rendered action",
12
+ actions: proc do
13
+ {
14
+ 'a' => {
15
+ name: "Action rendered at #{Time.now}",
16
+ execute: proc {}
17
+ }
18
+ }
19
+ end
20
+ )
10
21
  end
11
22
  menu.item 'Refresh menu' do
12
23
  :menu_refresh
@@ -1,5 +1,5 @@
1
1
  class CursesMenu
2
2
 
3
- VERSION = '0.1.0'
3
+ VERSION = '0.2.0'
4
4
 
5
5
  end
data/lib/curses_menu.rb CHANGED
@@ -74,9 +74,11 @@ class CursesMenu
74
74
  'Arrows/Home/End' => 'Navigate',
75
75
  'Esc' => 'Exit'
76
76
  }
77
- if current_items[selected_idx][:actions]
77
+ # Keep a cache of actions as they can be loaded in a lazy way for performance
78
+ current_items[selected_idx][:actions_cached] = current_items[selected_idx][:actions].is_a?(Proc) ? current_items[selected_idx][:actions].call : current_items[selected_idx][:actions] unless current_items[selected_idx].key?(:actions_cached)
79
+ if current_items[selected_idx][:actions_cached]
78
80
  display_actions.merge!(
79
- current_items[selected_idx][:actions].to_h do |action_shortcut, action_info|
81
+ current_items[selected_idx][:actions_cached].to_h do |action_shortcut, action_info|
80
82
  [
81
83
  case action_shortcut
82
84
  when KEY_ENTER
@@ -127,9 +129,11 @@ class CursesMenu
127
129
  break
128
130
  else
129
131
  # Check actions
130
- if current_items[selected_idx][:actions]&.key?(user_choice)
132
+ # Keep a cache of actions as they can be loaded in a lazy way for performance
133
+ current_items[selected_idx][:actions_cached] = current_items[selected_idx][:actions].is_a?(Proc) ? current_items[selected_idx][:actions].call : current_items[selected_idx][:actions] unless current_items[selected_idx].key?(:actions_cached)
134
+ if current_items[selected_idx][:actions_cached]&.key?(user_choice)
131
135
  curses_menu_finalize
132
- result = current_items[selected_idx][:actions][user_choice][:execute].call
136
+ result = current_items[selected_idx][:actions_cached][user_choice][:execute].call
133
137
  if result.is_a?(Symbol)
134
138
  case result
135
139
  when :menu_exit
@@ -162,9 +166,10 @@ class CursesMenu
162
166
  #
163
167
  # Parameters::
164
168
  # * *title* (String, CursesRow or Proc): Text to be displayed for this item, or Proc returning this text when needed (lazy loading)
165
- # * *actions* (Hash<Object, Hash<Symbol,Object> >): Associated actions to this item, per shortcut [default: {}]
169
+ # * *actions* (Hash<Object, Hash<Symbol,Object> > or Proc): Associated actions to this item, per shortcut, or Proc returning those actions when needed (lazy loading) [default: {}]
166
170
  # * *name* (String): Name of this action (displayed at the bottom of the menu)
167
171
  # * *execute* (Proc): Code called when this action is selected
172
+ # In case of lazy loading (with a Proc), the title Proc will always be called first.
168
173
  # * *&action* (Proc): Code called if the item is selected (action for the enter key) [optional].
169
174
  # * Result::
170
175
  # * Symbol or Object: If the code returns a symbol, the menu will behave in a specific way:
@@ -172,8 +177,21 @@ class CursesMenu
172
177
  # * *menu_refresh*: The menu will compute again its items.
173
178
  def item(title, actions: {}, &action)
174
179
  menu_item_def = { title: title }
175
- all_actions = action.nil? ? actions : actions.merge(KEY_ENTER => { name: 'Select', execute: action })
176
- menu_item_def[:actions] = all_actions unless all_actions.empty?
180
+ all_actions =
181
+ if action.nil?
182
+ actions
183
+ else
184
+ mapped_default_action = { KEY_ENTER => { name: 'Select', execute: action } }
185
+ if actions.is_a?(Proc)
186
+ # Make sure we keep the lazyness
187
+ proc do
188
+ actions.call.merge(mapped_default_action)
189
+ end
190
+ else
191
+ actions.merge(mapped_default_action)
192
+ end
193
+ end
194
+ menu_item_def[:actions] = all_actions if all_actions.is_a?(Proc) || !all_actions.empty?
177
195
  @current_menu_items << menu_item_def
178
196
  end
179
197
 
@@ -2,7 +2,7 @@ describe CursesMenu do
2
2
 
3
3
  it 'displays a menu with 1 item with lazy rendering' do
4
4
  render_called = false
5
- test_menu(title: 'Menu title') do |menu|
5
+ test_menu do |menu|
6
6
  menu.item(proc do
7
7
  render_called = true
8
8
  'Menu item lazy'
@@ -14,7 +14,7 @@ describe CursesMenu do
14
14
 
15
15
  it 'displays a menu with 1 item in a CursesRow with lazy rendering' do
16
16
  render_called = false
17
- test_menu(title: 'Menu title') do |menu|
17
+ test_menu do |menu|
18
18
  menu.item(proc do
19
19
  render_called = true
20
20
  CursesMenu::CursesRow.new({ cell: { text: 'Menu item lazy' } })
@@ -24,10 +24,61 @@ describe CursesMenu do
24
24
  assert_line 3, 'Menu item lazy'
25
25
  end
26
26
 
27
+ it 'displays menu actions with lazy evaluation' do
28
+ render_called = false
29
+ action_executed = false
30
+ test_menu(keys: ['a']) do |menu|
31
+ menu.item('Menu item lazy', actions: proc do
32
+ render_called = true
33
+ {
34
+ 'a' => {
35
+ name: 'Lazy action',
36
+ execute: proc do
37
+ action_executed = true
38
+ end
39
+ }
40
+ }
41
+ end)
42
+ end
43
+ assert_line 3, 'Menu item lazy'
44
+ assert_line(-1, '= Arrows/Home/End: Navigate | Esc: Exit | a: Lazy action')
45
+ expect(render_called).to be true
46
+ expect(action_executed).to be true
47
+ end
48
+
49
+ it 'displays menu actions with lazy evaluation and a default action' do
50
+ render_called = false
51
+ action_executed = false
52
+ default_executed = false
53
+ test_menu(keys: ['a', CursesMenu::KEY_ENTER]) do |menu|
54
+ menu.item(
55
+ 'Menu item lazy',
56
+ actions: proc do
57
+ render_called = true
58
+ {
59
+ 'a' => {
60
+ name: 'Lazy action',
61
+ execute: proc do
62
+ action_executed = true
63
+ end
64
+ }
65
+ }
66
+ end
67
+ ) do
68
+ default_executed = true
69
+ end
70
+ end
71
+ assert_line 3, 'Menu item lazy'
72
+ assert_line(-1, '= Arrows/Home/End: Navigate | Enter: Select | Esc: Exit | a: Lazy action')
73
+ expect(render_called).to be true
74
+ expect(action_executed).to be true
75
+ expect(default_executed).to be true
76
+ end
77
+
27
78
  it 'doesn\'t lazy render when the item is not displayed' do
28
79
  nbr_visible_items = Curses.stdscr.maxy - 5
29
80
  render_called = false
30
- test_menu(title: 'Menu title') do |menu|
81
+ test_menu do |menu|
31
82
  (nbr_visible_items * 2).times do |idx|
32
83
  menu.item "Menu item #{idx}"
33
84
  end
@@ -39,6 +90,60 @@ describe CursesMenu do
39
90
  expect(render_called).to be false
40
91
  end
41
92
 
93
+ it 'doesn\'t lazy evaluate actions when the item is not displayed' do
94
+ nbr_visible_items = Curses.stdscr.maxy - 5
95
+ render_called = false
96
+ test_menu do |menu|
97
+ (nbr_visible_items * 2).times do |idx|
98
+ menu.item "Menu item #{idx}"
99
+ end
100
+ menu.item('Menu item lazy', actions: proc do
101
+ render_called = true
102
+ {
103
+ 'a' => {
104
+ name: 'Lazy action',
105
+ execute: proc {}
106
+ }
107
+ }
108
+ end)
109
+ end
110
+ expect(render_called).to be false
111
+ end
112
+
113
+ it 'doesn\'t lazy evaluate actions when the item is not selected' do
114
+ render_called = false
115
+ test_menu do |menu|
116
+ menu.item 'Menu item'
117
+ menu.item('Menu item lazy', actions: proc do
118
+ render_called = true
119
+ {
120
+ 'a' => {
121
+ name: 'Lazy action',
122
+ execute: proc {}
123
+ }
124
+ }
125
+ end)
126
+ end
127
+ expect(render_called).to be false
128
+ end
129
+
130
+ it 'lazy evaluates actions as soon as the item is selected' do
131
+ render_called = false
132
+ test_menu(keys: [Curses::KEY_DOWN]) do |menu|
133
+ menu.item 'Menu item'
134
+ menu.item('Menu item lazy', actions: proc do
135
+ render_called = true
136
+ {
137
+ 'a' => {
138
+ name: 'Lazy action',
139
+ execute: proc {}
140
+ }
141
+ }
142
+ end)
143
+ end
144
+ expect(render_called).to be true
145
+ end
146
+
42
147
  it 'keeps lazy rendered titles in a cache while navigating' do
43
148
  nbr_renders = 0
44
149
  test_menu(keys: [Curses::KEY_DOWN, Curses::KEY_DOWN, Curses::KEY_DOWN]) do |menu|
@@ -54,6 +159,26 @@ describe CursesMenu do
54
159
  assert_line(-1, '= Arrows/Home/End: Navigate | Esc: Exit | a: Special action')
55
160
  end
56
161
 
162
+ it 'keeps lazy evaluated actions in a cache while navigating' do
163
+ nbr_renders = 0
164
+ test_menu(keys: [Curses::KEY_DOWN, Curses::KEY_DOWN, Curses::KEY_DOWN]) do |menu|
165
+ menu.item 'Menu item 1'
166
+ menu.item('Menu item 2 lazy', actions: proc do
167
+ nbr_renders += 1
168
+ {
169
+ 'a' => {
170
+ name: 'Lazy action',
171
+ execute: proc {}
172
+ }
173
+ }
174
+ end)
175
+ menu.item 'Menu item 3'
176
+ menu.item 'Menu item 4', actions: { 'a' => { name: 'Special action', execute: proc {} } }
177
+ end
178
+ expect(nbr_renders).to eq 1
179
+ assert_line(-1, '= Arrows/Home/End: Navigate | Esc: Exit | a: Special action')
180
+ end
181
+
57
182
  it 'keeps lazy rendered titles in a cache while navigating across pages' do
58
183
  nbr_renders = 0
59
184
  nbr_visible_items = Curses.stdscr.maxy - 5
@@ -70,6 +195,27 @@ describe CursesMenu do
70
195
  assert_line(-3, 'Menu item Lazy')
71
196
  end
72
197
 
198
+ it 'keeps lazy evaluated actions in a cache while navigating across pages' do
199
+ nbr_renders = 0
200
+ nbr_visible_items = Curses.stdscr.maxy - 5
201
+ test_menu(keys: [Curses::KEY_DOWN, Curses::KEY_DOWN, Curses::KEY_END, Curses::KEY_HOME, Curses::KEY_END, Curses::KEY_HOME, Curses::KEY_END]) do |menu|
202
+ (nbr_visible_items * 2).times do |idx|
203
+ menu.item "Menu item #{idx}"
204
+ end
205
+ menu.item('Menu item lazy', actions: proc do
206
+ nbr_renders += 1
207
+ {
208
+ 'a' => {
209
+ name: 'Lazy action',
210
+ execute: proc {}
211
+ }
212
+ }
213
+ end)
214
+ end
215
+ expect(nbr_renders).to eq 1
216
+ assert_line(-1, '= Arrows/Home/End: Navigate | Esc: Exit | a: Lazy action')
217
+ end
218
+
73
219
  it 'refreshes lazy rendered titles between menu refreshes' do
74
220
  nbr_renders = 0
75
221
  test_menu(keys: [Curses::KEY_DOWN, Curses::KEY_UP, CursesMenu::KEY_ENTER, Curses::KEY_DOWN, Curses::KEY_UP]) do |menu|
@@ -84,6 +230,25 @@ describe CursesMenu do
84
230
  expect(nbr_renders).to eq 2
85
231
  end
86
232
 
233
+ it 'refreshes lazy evaluated actions between menu refreshes' do
234
+ nbr_renders = 0
235
+ test_menu(keys: [Curses::KEY_DOWN, Curses::KEY_UP, CursesMenu::KEY_ENTER, Curses::KEY_DOWN, Curses::KEY_UP]) do |menu|
236
+ menu.item 'Menu item Refresh' do
237
+ :menu_refresh
238
+ end
239
+ menu.item('Menu item lazy', actions: proc do
240
+ nbr_renders += 1
241
+ {
242
+ 'a' => {
243
+ name: 'Lazy action',
244
+ execute: proc {}
245
+ }
246
+ }
247
+ end)
248
+ end
249
+ expect(nbr_renders).to eq 2
250
+ end
251
+
87
252
  it 'does not refresh lazy rendered titles when executing actions' do
88
253
  nbr_renders = 0
89
254
  test_menu(keys: [Curses::KEY_DOWN, Curses::KEY_UP, CursesMenu::KEY_ENTER, Curses::KEY_DOWN, Curses::KEY_UP]) do |menu|
@@ -98,6 +263,25 @@ describe CursesMenu do
98
263
  expect(nbr_renders).to eq 1
99
264
  end
100
265
 
266
+ it 'does not refresh lazy evaluated actions when executing actions' do
267
+ nbr_renders = 0
268
+ test_menu(keys: [Curses::KEY_DOWN, Curses::KEY_UP, CursesMenu::KEY_ENTER, Curses::KEY_DOWN, Curses::KEY_UP]) do |menu|
269
+ menu.item 'Menu item' do
270
+ # Do nothing
271
+ end
272
+ menu.item('Menu item lazy', actions: proc do
273
+ nbr_renders += 1
274
+ {
275
+ 'a' => {
276
+ name: 'Lazy action',
277
+ execute: proc {}
278
+ }
279
+ }
280
+ end)
281
+ end
282
+ expect(nbr_renders).to eq 1
283
+ end
284
+
101
285
  it 'does not refresh lazy rendered titles when getting into sub-menus' do
102
286
  nbr_renders = 0
103
287
  test_menu(
@@ -124,4 +308,76 @@ describe CursesMenu do
124
308
  expect(nbr_renders).to eq 1
125
309
  end
126
310
 
311
+ it 'does not refresh lazy evaluated actions when getting into sub-menus' do
312
+ nbr_renders = 0
313
+ test_menu(
314
+ keys: [
315
+ # Enter sub-menu
316
+ CursesMenu::KEY_ENTER,
317
+ Curses::KEY_DOWN,
318
+ # Back to first menu
319
+ CursesMenu::KEY_ESCAPE,
320
+ Curses::KEY_DOWN
321
+ ]
322
+ ) do |menu, key_presses|
323
+ menu.item 'Sub-menu' do
324
+ described_class.new('Sub-menu title', key_presses: key_presses) do |sub_menu|
325
+ sub_menu.item 'Sub-menu item 1'
326
+ sub_menu.item 'Sub-menu item 2'
327
+ end
328
+ end
329
+ menu.item('Menu item lazy', actions: proc do
330
+ nbr_renders += 1
331
+ {
332
+ 'a' => {
333
+ name: 'Lazy action',
334
+ execute: proc {}
335
+ }
336
+ }
337
+ end)
338
+ end
339
+ expect(nbr_renders).to eq 1
340
+ end
341
+
342
+ it 'always lazy renders titles before lazy evaluating actions' do
343
+ lazy_renders = []
344
+ nbr_visible_items = Curses.stdscr.maxy - 5
345
+ test_menu(keys: [Curses::KEY_END, Curses::KEY_HOME, Curses::KEY_DOWN]) do |menu|
346
+ menu.item(
347
+ proc do
348
+ lazy_renders << :item_1_title
349
+ 'Menu item 1 lazy'
350
+ end,
351
+ actions: proc do
352
+ lazy_renders << :item_1_action
353
+ { 'a' => { name: 'Lazy action 1', execute: proc {} } }
354
+ end
355
+ )
356
+ menu.item(
357
+ proc do
358
+ lazy_renders << :item_2_title
359
+ 'Menu item 2 lazy'
360
+ end,
361
+ actions: proc do
362
+ lazy_renders << :item_2_action
363
+ { 'a' => { name: 'Lazy action 2', execute: proc {} } }
364
+ end
365
+ )
366
+ (nbr_visible_items * 2).times do |idx|
367
+ menu.item "Menu item #{idx}"
368
+ end
369
+ menu.item(
370
+ proc do
371
+ lazy_renders << :item_3_title
372
+ 'Menu item 3 lazy'
373
+ end,
374
+ actions: proc do
375
+ lazy_renders << :item_3_action
376
+ { 'a' => { name: 'Lazy action 3', execute: proc {} } }
377
+ end
378
+ )
379
+ end
380
+ expect(lazy_renders).to eq %i[item_1_title item_2_title item_1_action item_3_title item_3_action item_2_action]
381
+ end
382
+
127
383
  end
@@ -9,6 +9,16 @@ describe CursesMenu do
9
9
  assert_line(-1, '= Arrows/Home/End: Navigate | Esc: Exit')
10
10
  end
11
11
 
12
+ it 'displays a menu with 1 item having a default action' do
13
+ test_menu(title: 'Menu title') do |menu|
14
+ menu.item 'Menu item' do
15
+ # Do nothing
16
+ end
17
+ end
18
+ assert_line 3, 'Menu item'
19
+ assert_line(-1, '= Arrows/Home/End: Navigate | Enter: Select | Esc: Exit')
20
+ end
21
+
12
22
  it 'displays a menu with several items' do
13
23
  test_menu do |menu|
14
24
  menu.item 'Menu item 1'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: curses_menu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Muriel Salvan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-10-12 00:00:00.000000000 Z
11
+ date: 2022-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: curses