telescope-term 1.1 → 1.5

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 (3) hide show
  1. checksums.yaml +4 -4
  2. data/bin/telescope +264 -27
  3. metadata +5 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9d7390a8fecd0f7985cb6c6396edc3611f51daa61afc810e4636487345dda37
4
- data.tar.gz: 525cc188610ac7dc332850e26b95fedada8c77acb6bddb498e260c4b5769e26f
3
+ metadata.gz: 86323c41ff9b3285feda98f66c9a2ff5af15af38101a2b7fdedde5d2238fc4d2
4
+ data.tar.gz: 2cc345bbe5d0b06f05a29e6020b7684cb5655edd4daf6ed16edeb106d8167fa0
5
5
  SHA512:
6
- metadata.gz: 15fb388da4c9bdced12b57cfdce35d466b19df4fc4ffe6ab1597bfeb248c66f05bddc2e1b79454833289e2dd1680a9fd45cf55b2afb6021d9c94c23df6646ea1
7
- data.tar.gz: 78e47762e001126575ad985841efd7fc73c17f8b6cf710fc11616a8f8bad902e744754e2b2439c59e2ced2421a97e87ec784254ee6ff7e84686c7fd1401ff41d
6
+ metadata.gz: 63dcd8986a007b965ef4a4dca822774cc4a714b3ce9d71bc703806984077f4e56b6327b5baad3805dd1bef10cb9e13b4b453abc02f8de1c07e781cddc5a9ed69
7
+ data.tar.gz: d848b5ce07f6e5dabec1e0934c3c321a495783131b2c1e30e7027d2493c49635f73ac997605c11bbd46858447f613745de1410a39a65a169e1104f4a4ef02164
data/bin/telescope CHANGED
@@ -32,9 +32,16 @@ rescue StandardError => e
32
32
  end
33
33
  Pane = Rcurses::Pane
34
34
  require 'date'
35
+ require 'fileutils'
36
+ require 'json'
37
+ require 'csv'
38
+
39
+ # Version
40
+ VERSION = "1.5"
35
41
 
36
42
  # Persistence paths
37
43
  SAVE = File.join(Dir.home, '.telescope')
44
+ CONFIG = File.join(Dir.home, '.telescope_config')
38
45
 
39
46
  # HELP TEXT {{{1
40
47
  HELP1 = " WELCOME TO THE TERMINAL TELESCOPE APPLICATION\n".b + '
@@ -50,8 +57,8 @@ HELP1 = " WELCOME TO THE TERMINAL TELESCOPE APPLICATION\n".b + '
50
57
  '
51
58
 
52
59
  HELP2 = " TERMININAL TELESCOPE APPLICATION\n\n Keys and their actions\n".b + '
53
- t Add telescope (name,app,fl)
54
- e Add eyepiece (name,fl,afov)
60
+ t Add telescope (name,app,fl[,notes])
61
+ e Add eyepiece (name,fl,afov[,notes])
55
62
  ENTER Edit selected
56
63
  TAB Switch panels
57
64
  UP/DOWN Move cursor
@@ -61,7 +68,11 @@ HELP2 = " TERMININAL TELESCOPE APPLICATION\n\n Keys and their actions\n".b + '
61
68
  o Toggle order by Telescope APP and Eyepiece FL
62
69
  SPACE Tag/untag
63
70
  u Untag all
71
+ A Tag all (bulk operation)
64
72
  Ctrl-o Create observation log with tagged equipment
73
+ x Export tagged items to CSV
74
+ X Export all items to JSON
75
+ v Show version information
65
76
  D Delete item
66
77
  r Refresh all panes
67
78
  q/Q Quit (save/no save)
@@ -93,20 +104,69 @@ HELP3 = " Abbreviations and their meaning\n".b + '
93
104
  2BLW = With a 2xBarlow (magnification, then rest)'.fg(229)
94
105
 
95
106
 
107
+ # CONFIGURATION {{{1
108
+ # Default configuration
109
+ @config = {
110
+ 'colors' => {
111
+ 'ts_header_bg' => '00524b',
112
+ 'ep_header_bg' => '4c3c1d',
113
+ 'tag_color' => 46,
114
+ 'cursor_bg' => 234,
115
+ 'text_color' => 248,
116
+ 'check_good' => 112,
117
+ 'check_bad' => 208
118
+ },
119
+ 'auto_backup' => true,
120
+ 'backup_count' => 5
121
+ }
122
+
123
+ # Load configuration if it exists
124
+ if File.exist?(CONFIG)
125
+ begin
126
+ loaded_config = eval(File.read(CONFIG))
127
+ @config.merge!(loaded_config) if loaded_config.is_a?(Hash)
128
+ rescue
129
+ # Use defaults if config file is corrupted
130
+ end
131
+ end
132
+
96
133
  # INITIALIZATION {{{1
97
134
  # Data stores
98
- @ts = [] # Telescopes: [name, app, fl]
99
- @ep = [] # Eyepieces: [name, fl, afov]
135
+ @ts = [] # Telescopes: [name, app, fl, notes]
136
+ @ep = [] # Eyepieces: [name, fl, afov, notes]
100
137
  @tstag = [] # Telescope tags
101
138
  @eptag = [] # Eyepiece tags
102
139
  @cursor_ts = 0 # Telescope cursor index
103
140
  @cursor_ep = 0 # Eyepiece cursor index
104
141
 
142
+ # BACKUP SYSTEM {{{1
143
+ def create_backup
144
+ return unless @config['auto_backup'] && File.exist?(SAVE)
145
+
146
+ backup_dir = File.join(Dir.home, '.telescope_backups')
147
+ Dir.mkdir(backup_dir) unless Dir.exist?(backup_dir)
148
+
149
+ timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
150
+ backup_file = File.join(backup_dir, ".telescope_#{timestamp}")
151
+
152
+ FileUtils.cp(SAVE, backup_file)
153
+
154
+ # Clean old backups
155
+ backups = Dir.glob(File.join(backup_dir, '.telescope_*')).sort
156
+ while backups.size > @config['backup_count']
157
+ File.delete(backups.shift)
158
+ end
159
+ end
160
+
105
161
  # Load saved data if present
106
162
  SAVE = File.join(Dir.home, '.telescope') # Persistence path
107
163
 
108
164
  if File.exist?(SAVE)
165
+ create_backup
109
166
  load SAVE # expects plaintext: @ts = [...] and @ep = [...]
167
+ # Convert old format to new format with notes
168
+ @ts.map! { |t| t.size == 3 ? t + [''] : t }
169
+ @ep.map! { |e| e.size == 3 ? e + [''] : e }
110
170
  else
111
171
  @ts = []
112
172
  @ep = []
@@ -155,6 +215,40 @@ def tfov(tfl, epfl, afov); (afov.to_f / magx(tfl, epfl)); end
155
215
  def pupl(app, tfl, epfl); (app.to_f / magx(tfl, epfl)); end
156
216
  def magx(tfl, epfl); (tfl.to_f / epfl); end
157
217
 
218
+ # VALIDATION FUNCTIONS {{{1
219
+ def validate_telescope_input(input_array)
220
+ return false unless input_array.size >= 3
221
+
222
+ name, app, fl = input_array[0..2]
223
+ return false if name.nil? || name.strip.empty?
224
+ return false unless app.to_f > 0
225
+ return false unless fl.to_f > 0
226
+
227
+ true
228
+ end
229
+
230
+ def validate_eyepiece_input(input_array)
231
+ return false unless input_array.size >= 3
232
+
233
+ name, fl, afov = input_array[0..2]
234
+ return false if name.nil? || name.strip.empty?
235
+ return false unless fl.to_f > 0
236
+ return false unless afov.to_f > 0 && afov.to_f <= 180
237
+
238
+ true
239
+ end
240
+
241
+ def safe_file_write(file, content)
242
+ begin
243
+ File.write(file, content)
244
+ true
245
+ rescue => e
246
+ @pST.say(" Error writing file: #{e.message} - Press any key")
247
+ getchr
248
+ false
249
+ end
250
+ end
251
+
158
252
  # FUNCTIONS {{{1
159
253
  def refresh_all #{{{2
160
254
  Rcurses.clear_screen
@@ -175,7 +269,7 @@ def render_ts #{{{2
175
269
  name = t[0]
176
270
  app = t[1].to_i
177
271
  tfl = t[2].to_i
178
- tag_ts = @tstag[i] ? ' ' + '▐'.b.fg(46) : ' '
272
+ tag_ts = @tstag[i] ? ' ' + '▐'.b.fg(@config['colors']['tag_color']) : ' '
179
273
  txt = tag_ts
180
274
  txt += name.to_s.ljust(18)
181
275
  txt += app.to_s.rjust(7)
@@ -203,8 +297,8 @@ def render_ts #{{{2
203
297
  pad = @pTS.w - Rcurses.display_width(txt.pure)
204
298
  pad = 0 if pad.negative?
205
299
  txt += ' ' * pad
206
- txt = txt.bg(234) if i == @pTS.index
207
- txt = txt.fg(248) if i != @pTS.index
300
+ txt = txt.bg(@config['colors']['cursor_bg']) if i == @pTS.index
301
+ txt = txt.fg(@config['colors']['text_color']) if i != @pTS.index
208
302
  @pTS.text += txt
209
303
  end
210
304
  @pTS.refresh
@@ -213,12 +307,51 @@ end
213
307
 
214
308
  def ep_nice(app, tfl, e) #{{{2
215
309
  r = (tfl / app)
216
- out = ' '
217
- out += (e/6 > r ? ' ✓'.fg(112) : ' ✗'.fg(208))
218
- out += (e/3 > r && e/6 <= r ? ' ✓'.fg(112) : ' ✗'.fg(208))
219
- out += (e/1.5 > r && e/3 <= r ? ' ✓'.fg(112) : ' ✗'.fg(208))
220
- out += (e >= r && e/1.5 <= r ? ' ✓'.fg(112) : ' ✗'.fg(208))
221
- out += (e < r ? ' '.fg(112) : ' ✗'.fg(208))
310
+ out = ''
311
+ # Enhanced color coding with background highlights for optimal ranges - aligned with header
312
+ # Header: " *FLD GLXY PLNT DBL* >2*< "
313
+ # Positions: 6 12 18 24 30
314
+
315
+ out += ' ' # 5 spaces to center under *FLD (position 6)
316
+
317
+ if e/6 > r
318
+ out += ' ✓ '.fg(@config['colors']['check_good']).bg(22) # Dark green bg for star fields
319
+ else
320
+ out += ' ✗ '.fg(@config['colors']['check_bad'])
321
+ end
322
+
323
+ out += ' ' # 3 spaces to center under GLXY (position 12)
324
+
325
+ if e/3 > r && e/6 <= r
326
+ out += ' ✓ '.fg(@config['colors']['check_good']).bg(17) # Dark blue bg for galaxies
327
+ else
328
+ out += ' ✗ '.fg(@config['colors']['check_bad'])
329
+ end
330
+
331
+ out += ' ' # 3 spaces to center under PLNT (position 18)
332
+
333
+ if e/1.5 > r && e/3 <= r
334
+ out += ' ✓ '.fg(@config['colors']['check_good']).bg(52) # Dark magenta bg for planets
335
+ else
336
+ out += ' ✗ '.fg(@config['colors']['check_bad'])
337
+ end
338
+
339
+ out += ' ' # 3 spaces to center under DBL* (position 24)
340
+
341
+ if e >= r && e/1.5 <= r
342
+ out += ' ✓ '.fg(@config['colors']['check_good']).bg(94) # Orange bg for double stars
343
+ else
344
+ out += ' ✗ '.fg(@config['colors']['check_bad'])
345
+ end
346
+
347
+ out += ' ' # 3 spaces to center under >2*< (position 30)
348
+
349
+ if e < r
350
+ out += ' ✓ '.fg(@config['colors']['check_good']).bg(88) # Dark red bg for tight doubles
351
+ else
352
+ out += ' ✗ '.fg(@config['colors']['check_bad'])
353
+ end
354
+
222
355
  out
223
356
  end
224
357
 
@@ -230,7 +363,7 @@ def render_ep #{{{2
230
363
  name = e[0]
231
364
  epfl = e[1].to_f
232
365
  afov = e[2].to_f
233
- tag_ep = @eptag[i] ? ' ' + '▐'.b.fg(46) : ' '
366
+ tag_ep = @eptag[i] ? ' ' + '▐'.b.fg(@config['colors']['tag_color']) : ' '
234
367
  txt = tag_ep
235
368
  txt += name.to_s.ljust(18)
236
369
  txt += epfl.to_s.rjust(7)
@@ -255,14 +388,14 @@ def render_ep #{{{2
255
388
  dms2 = "#{deg2}°#{min2_s}'#{sec2_s}\""
256
389
  txt += dms2.rjust(11)
257
390
  txt += (pupl(app, tfl, epfl) / 2).rts(1).rjust(6)
258
- txt = txt.fg(248) if i != @pEP.index
391
+ txt = txt.fg(@config['colors']['text_color']) if i != @pEP.index
259
392
  txt += ep_nice(app, tfl, epfl)
260
393
  txt[0] = '→' if i == @pEP.index
261
394
  # ANSI safe padding
262
395
  pad = @pEP.w - Rcurses.display_width(txt.pure)
263
396
  pad = 0 if pad.negative?
264
397
  txt += ' ' * pad
265
- txt = txt.bg(234) if i == @pEP.index
398
+ txt = txt.bg(@config['colors']['cursor_bg']) if i == @pEP.index
266
399
  @pEP.text += txt
267
400
  end
268
401
  @pEP.refresh
@@ -349,15 +482,89 @@ def observe #{{{2
349
482
  getchr
350
483
  end
351
484
 
485
+ def export_csv #{{{2
486
+ date = Date.today.iso8601
487
+ file = File.join(Dir.home, "telescope_export_#{date}.csv")
488
+
489
+ CSV.open(file, 'w') do |csv|
490
+ # Export tagged telescopes
491
+ csv << ['TYPE', 'NAME', 'PARAM1', 'PARAM2', 'PARAM3', 'NOTES']
492
+ sel_ts = @tstag.each_index.select { |i| @tstag[i] }
493
+ sel_ts.each do |ts_idx|
494
+ t = @ts[ts_idx]
495
+ csv << ['TELESCOPE', t[0], t[1], t[2], '', t[3] || '']
496
+ end
497
+
498
+ # Export tagged eyepieces
499
+ sel_ep = @eptag.each_index.select { |i| @eptag[i] }
500
+ sel_ep.each do |ep_idx|
501
+ e = @ep[ep_idx]
502
+ csv << ['EYEPIECE', e[0], e[1], e[2], '', e[3] || '']
503
+ end
504
+ end
505
+
506
+ @pST.say(" Exported tagged items to #{file} - Press any key")
507
+ getchr
508
+ end
509
+
510
+ def export_json #{{{2
511
+ date = Date.today.iso8601
512
+ file = File.join(Dir.home, "telescope_export_#{date}.json")
513
+
514
+ data = {
515
+ 'export_date' => date,
516
+ 'telescopes' => @ts.map { |t| { 'name' => t[0], 'aperture' => t[1], 'focal_length' => t[2], 'notes' => t[3] || '' } },
517
+ 'eyepieces' => @ep.map { |e| { 'name' => e[0], 'focal_length' => e[1], 'afov' => e[2], 'notes' => e[3] || '' } }
518
+ }
519
+
520
+ File.write(file, JSON.pretty_generate(data))
521
+ @pST.say(" Exported all items to #{file} - Press any key")
522
+ getchr
523
+ end
524
+
525
+ def show_version #{{{2
526
+ local_version = VERSION
527
+
528
+ begin
529
+ remote_version = Gem.latest_version_for('telescope-term').version
530
+ version_info = +" VERSION INFORMATION\n\n"
531
+ version_info << "Local version: #{local_version}\n"
532
+ version_info << "Latest RubyGems version: #{remote_version}\n\n"
533
+
534
+ if Gem::Version.new(remote_version) > Gem::Version.new(local_version)
535
+ version_info << "Update available! Run: gem update telescope-term".fg(@config['colors']['check_bad'])
536
+ else
537
+ version_info << "You have the latest version!".fg(@config['colors']['check_good'])
538
+ end
539
+
540
+ version_info << "\n\nGem info: https://rubygems.org/gems/telescope-term"
541
+ version_info << "\nSource code: https://github.com/isene/telescope"
542
+ rescue StandardError => e
543
+ version_info = +" VERSION INFORMATION\n\n"
544
+ version_info << "Local version: #{local_version}\n"
545
+ version_info << "Could not check latest version: #{e.message}".fg(@config['colors']['check_bad'])
546
+ version_info << "\n\nGem info: https://rubygems.org/gems/telescope-term"
547
+ version_info << "\nSource code: https://github.com/isene/telescope"
548
+ end
549
+
550
+ @pObs.clear
551
+ @pObs.full_refresh
552
+ @pObs.say(version_info)
553
+ @pST.say(" Press any key to continue")
554
+ getchr
555
+ refresh_all
556
+ end
557
+
558
+
352
559
  # PANE SETUP {{{1
353
560
  # Top telescopes, eyepieces below, status at bottom
354
561
  @max_h, @max_w = IO.console.winsize
355
562
  @pTS = Pane.new( 2, 2, @max_w - 2, 8, nil, nil)
356
- @pTSh = Pane.new( 2, 2, @max_w - 2, 1, 255, "00524b")
357
- @pTSa = Pane.new( 1, @max_h, @max_w, 1, 255, "00524b")
563
+ @pTSh = Pane.new( 2, 2, @max_w - 2, 1, 255, @config['colors']['ts_header_bg'])
564
+ @pTSa = Pane.new( 1, @max_h, @max_w, 1, 255, @config['colors']['ts_header_bg'])
358
565
  @pEP = Pane.new( 2, 11, @max_w - 2, @max_h - 12, nil, nil)
359
- @pEPh = Pane.new( 2, 11, @max_w - 2, 1, 255, "4c3c1d")
360
- @pEPa = Pane.new( 1, @max_h, @max_w, 1, 255, "4c3c1d")
566
+ @pEPh = Pane.new( 2, 11, @max_w - 2, 1, 255, @config['colors']['ep_header_bg'])
567
+ @pEPa = Pane.new( 1, @max_h, @max_w, 1, 255, @config['colors']['ep_header_bg'])
361
568
  @pST = Pane.new( 1, @max_h, @max_w, 1, 255, 236)
362
569
 
363
570
  @pHlp = Pane.new( @max_w/2 - 30, @max_h/2 - 13, 60, 26, 252, 233)
@@ -366,7 +573,7 @@ end
366
573
  @pTSh.text = ' TELESCOPES APP(mm) FL(mm) F/? <MGN xEYE MINx MAXx SEP-R SEP-D *FLD GLXY PLNT DBL* >2*< MOON SUN'.b
367
574
  @pEPh.text = ' EYEPIECES FL(mm) AFOV'.b
368
575
  @pEPh.text += ' MAGX TFOV PPL 2blw tfov ppl'.b.i.fg("00827b")
369
- @pEPh.text += ' *FLD GLXY PLNT DBL* >2*<'.bg("4c3c1d")
576
+ @pEPh.text += ' *FLD GLXY PLNT DBL* >2*<'.bg(@config['colors']['ep_header_bg'])
370
577
  @pST.text = ' t/e = Add telescope/eyepiece, ENTER = Edit item, q/Q = Quit, ? = Help'
371
578
  @pHlp.border = true
372
579
  @pObs.border = true
@@ -381,6 +588,7 @@ end
381
588
  @ep_unsorted = @ep.dup
382
589
  @focus = @pTS
383
590
  @current = @ts
591
+ @comparison_mode = false
384
592
 
385
593
  # TRAP WIN SIZE CHANGE {{{1
386
594
  Signal.trap('WINCH') do
@@ -398,7 +606,7 @@ loop do
398
606
  ch = getchr
399
607
  case ch
400
608
  when 'q'
401
- File.write(SAVE, "@ts = #{@ts.inspect}\n@ep = #{@ep.inspect}\n") && exit
609
+ safe_file_write(SAVE, "@ts = #{@ts.inspect}\n@ep = #{@ep.inspect}\n") && exit
402
610
  when 'Q'; exit
403
611
  when '?'
404
612
  @pHlp.full_refresh
@@ -412,22 +620,39 @@ loop do
412
620
  when 'r'
413
621
  refresh_all
414
622
  when 't'
415
- inp=@pTSa.ask('name, app, fl: ','').split(', ')
416
- next unless inp.size == 3
623
+ inp=@pTSa.ask('name, app, fl [, notes]: ','').split(', ')
624
+ unless validate_telescope_input(inp)
625
+ @pST.say(" Invalid input - Press any key")
626
+ getchr
627
+ next
628
+ end
629
+ inp << '' if inp.size == 3 # Add empty notes if not provided
417
630
  @ts<<inp
418
631
  @tstag<<false
419
632
  @ts_unsorted << inp # keep master list updated
420
633
  @current = @ts
421
634
  when 'e'
422
- inp=@pEPa.ask('name, fl, afov: ','').split(', ')
423
- next unless inp.size == 3
635
+ inp=@pEPa.ask('name, fl, afov [, notes]: ','').split(', ')
636
+ unless validate_eyepiece_input(inp)
637
+ @pST.say(" Invalid input - Press any key")
638
+ getchr
639
+ next
640
+ end
641
+ inp << '' if inp.size == 3 # Add empty notes if not provided
424
642
  @ep<<inp
425
643
  @eptag<<false
426
644
  @ep_unsorted << inp # keep master list updated
427
645
  @current = @ep
428
646
  when 'ENTER'
429
647
  val = @current[@focus.index].join(', ')
430
- arr=@pST.ask('Edit: ', val).split(', '); next unless arr.size==3
648
+ arr=@pST.ask('Edit: ', val).split(', ')
649
+ is_valid = @current.equal?(@ts) ? validate_telescope_input(arr) : validate_eyepiece_input(arr)
650
+ unless is_valid
651
+ @pST.say(" Invalid input - Press any key")
652
+ getchr
653
+ next
654
+ end
655
+ arr << '' if arr.size == 3 # Add empty notes if not provided
431
656
  @current[@focus.index] = arr
432
657
  when 'D'
433
658
  if @current.equal?(@ts)
@@ -495,6 +720,18 @@ loop do
495
720
  when 'u'
496
721
  @tstag.fill(false)
497
722
  @eptag.fill(false)
723
+ when 'A'
724
+ if @current.equal?(@ts)
725
+ @tstag.fill(true)
726
+ else
727
+ @eptag.fill(true)
728
+ end
729
+ when 'x'
730
+ export_csv
731
+ when 'X'
732
+ export_json
733
+ when 'v'
734
+ show_version
498
735
  when 'C-O'
499
736
  observe
500
737
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: telescope-term
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.1'
4
+ version: '1.5'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geir Isene
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-05-14 00:00:00.000000000 Z
11
+ date: 2025-07-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rcurses
@@ -28,7 +28,9 @@ description: 'With this program you can list your telescopes and eyepieces and g
28
28
  a set of calculations done for each scope and for the combination of scope and eyepiece.
29
29
  Easy interface. Run the program, then hit ''?'' to show the help file. Version 1.0:
30
30
  A full rewrite using the rcurses library (https://github.com/isene/rcurses) - lots
31
- of improvements. 1.1: Removed tty startup/exit codes as rcurses now handles that.'
31
+ of improvements. 1.1: Removed tty startup/exit codes as rcurses now handles that.
32
+ 1.5: Major feature update with notes, export, configuration, validation, backups,
33
+ and enhanced UI.'
32
34
  email: g@isene.com
33
35
  executables:
34
36
  - telescope