telescope-term 1.0 → 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 +263 -33
  3. metadata +5 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a927518be7d857221796c64555e4fec64668911d1279ac9f116ce7b53e17d7d
4
- data.tar.gz: c5107d1239cde8bc48b26e7daefb13ed0d65f16e2fe2bbbde1567f96ac79656b
3
+ metadata.gz: 86323c41ff9b3285feda98f66c9a2ff5af15af38101a2b7fdedde5d2238fc4d2
4
+ data.tar.gz: 2cc345bbe5d0b06f05a29e6020b7684cb5655edd4daf6ed16edeb106d8167fa0
5
5
  SHA512:
6
- metadata.gz: 230a99f5e65020b070110bf2b234638a44fc47f0f9f799f7b7114f2f8b9bda342952d5e1d4a9257bfdff69bcc5404cac76923b823809245ee59cd885b9e70240
7
- data.tar.gz: 9c2f836f026ca88f523cd296290a8f7ac64f3ed813002b6575732b7c5624060a6a9f3f053931cf706efbdc31d2e6328df99395c7671260994f3a09fb78370485
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)
@@ -92,28 +103,70 @@ HELP3 = " Abbreviations and their meaning\n".b + '
92
103
  PPL = Exit pupil (w/selected telescope)
93
104
  2BLW = With a 2xBarlow (magnification, then rest)'.fg(229)
94
105
 
95
- # CLEAR CURSES ON EXIT {{{1
96
- at_exit do
97
- $stdin.cooked!
98
- $stdin.echo = true
99
- Rcurses.clear_screen
100
- Cursor.show
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
101
131
  end
102
132
 
103
133
  # INITIALIZATION {{{1
104
134
  # Data stores
105
- @ts = [] # Telescopes: [name, app, fl]
106
- @ep = [] # Eyepieces: [name, fl, afov]
135
+ @ts = [] # Telescopes: [name, app, fl, notes]
136
+ @ep = [] # Eyepieces: [name, fl, afov, notes]
107
137
  @tstag = [] # Telescope tags
108
138
  @eptag = [] # Eyepiece tags
109
139
  @cursor_ts = 0 # Telescope cursor index
110
140
  @cursor_ep = 0 # Eyepiece cursor index
111
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
+
112
161
  # Load saved data if present
113
162
  SAVE = File.join(Dir.home, '.telescope') # Persistence path
114
163
 
115
164
  if File.exist?(SAVE)
165
+ create_backup
116
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 }
117
170
  else
118
171
  @ts = []
119
172
  @ep = []
@@ -162,6 +215,40 @@ def tfov(tfl, epfl, afov); (afov.to_f / magx(tfl, epfl)); end
162
215
  def pupl(app, tfl, epfl); (app.to_f / magx(tfl, epfl)); end
163
216
  def magx(tfl, epfl); (tfl.to_f / epfl); end
164
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
+
165
252
  # FUNCTIONS {{{1
166
253
  def refresh_all #{{{2
167
254
  Rcurses.clear_screen
@@ -182,7 +269,7 @@ def render_ts #{{{2
182
269
  name = t[0]
183
270
  app = t[1].to_i
184
271
  tfl = t[2].to_i
185
- tag_ts = @tstag[i] ? ' ' + '▐'.b.fg(46) : ' '
272
+ tag_ts = @tstag[i] ? ' ' + '▐'.b.fg(@config['colors']['tag_color']) : ' '
186
273
  txt = tag_ts
187
274
  txt += name.to_s.ljust(18)
188
275
  txt += app.to_s.rjust(7)
@@ -210,8 +297,8 @@ def render_ts #{{{2
210
297
  pad = @pTS.w - Rcurses.display_width(txt.pure)
211
298
  pad = 0 if pad.negative?
212
299
  txt += ' ' * pad
213
- txt = txt.bg(234) if i == @pTS.index
214
- 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
215
302
  @pTS.text += txt
216
303
  end
217
304
  @pTS.refresh
@@ -220,12 +307,51 @@ end
220
307
 
221
308
  def ep_nice(app, tfl, e) #{{{2
222
309
  r = (tfl / app)
223
- out = ' '
224
- out += (e/6 > r ? ' ✓'.fg(112) : ' ✗'.fg(208))
225
- out += (e/3 > r && e/6 <= r ? ' ✓'.fg(112) : ' ✗'.fg(208))
226
- out += (e/1.5 > r && e/3 <= r ? ' ✓'.fg(112) : ' ✗'.fg(208))
227
- out += (e >= r && e/1.5 <= r ? ' ✓'.fg(112) : ' ✗'.fg(208))
228
- 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
+
229
355
  out
230
356
  end
231
357
 
@@ -237,7 +363,7 @@ def render_ep #{{{2
237
363
  name = e[0]
238
364
  epfl = e[1].to_f
239
365
  afov = e[2].to_f
240
- tag_ep = @eptag[i] ? ' ' + '▐'.b.fg(46) : ' '
366
+ tag_ep = @eptag[i] ? ' ' + '▐'.b.fg(@config['colors']['tag_color']) : ' '
241
367
  txt = tag_ep
242
368
  txt += name.to_s.ljust(18)
243
369
  txt += epfl.to_s.rjust(7)
@@ -262,14 +388,14 @@ def render_ep #{{{2
262
388
  dms2 = "#{deg2}°#{min2_s}'#{sec2_s}\""
263
389
  txt += dms2.rjust(11)
264
390
  txt += (pupl(app, tfl, epfl) / 2).rts(1).rjust(6)
265
- txt = txt.fg(248) if i != @pEP.index
391
+ txt = txt.fg(@config['colors']['text_color']) if i != @pEP.index
266
392
  txt += ep_nice(app, tfl, epfl)
267
393
  txt[0] = '→' if i == @pEP.index
268
394
  # ANSI safe padding
269
395
  pad = @pEP.w - Rcurses.display_width(txt.pure)
270
396
  pad = 0 if pad.negative?
271
397
  txt += ' ' * pad
272
- txt = txt.bg(234) if i == @pEP.index
398
+ txt = txt.bg(@config['colors']['cursor_bg']) if i == @pEP.index
273
399
  @pEP.text += txt
274
400
  end
275
401
  @pEP.refresh
@@ -356,15 +482,89 @@ def observe #{{{2
356
482
  getchr
357
483
  end
358
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
+
359
559
  # PANE SETUP {{{1
360
560
  # Top telescopes, eyepieces below, status at bottom
361
561
  @max_h, @max_w = IO.console.winsize
362
562
  @pTS = Pane.new( 2, 2, @max_w - 2, 8, nil, nil)
363
- @pTSh = Pane.new( 2, 2, @max_w - 2, 1, 255, "00524b")
364
- @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'])
365
565
  @pEP = Pane.new( 2, 11, @max_w - 2, @max_h - 12, nil, nil)
366
- @pEPh = Pane.new( 2, 11, @max_w - 2, 1, 255, "4c3c1d")
367
- @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'])
368
568
  @pST = Pane.new( 1, @max_h, @max_w, 1, 255, 236)
369
569
 
370
570
  @pHlp = Pane.new( @max_w/2 - 30, @max_h/2 - 13, 60, 26, 252, 233)
@@ -373,7 +573,7 @@ end
373
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
374
574
  @pEPh.text = ' EYEPIECES FL(mm) AFOV'.b
375
575
  @pEPh.text += ' MAGX TFOV PPL 2blw tfov ppl'.b.i.fg("00827b")
376
- @pEPh.text += ' *FLD GLXY PLNT DBL* >2*<'.bg("4c3c1d")
576
+ @pEPh.text += ' *FLD GLXY PLNT DBL* >2*<'.bg(@config['colors']['ep_header_bg'])
377
577
  @pST.text = ' t/e = Add telescope/eyepiece, ENTER = Edit item, q/Q = Quit, ? = Help'
378
578
  @pHlp.border = true
379
579
  @pObs.border = true
@@ -388,6 +588,7 @@ end
388
588
  @ep_unsorted = @ep.dup
389
589
  @focus = @pTS
390
590
  @current = @ts
591
+ @comparison_mode = false
391
592
 
392
593
  # TRAP WIN SIZE CHANGE {{{1
393
594
  Signal.trap('WINCH') do
@@ -405,7 +606,7 @@ loop do
405
606
  ch = getchr
406
607
  case ch
407
608
  when 'q'
408
- 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
409
610
  when 'Q'; exit
410
611
  when '?'
411
612
  @pHlp.full_refresh
@@ -419,22 +620,39 @@ loop do
419
620
  when 'r'
420
621
  refresh_all
421
622
  when 't'
422
- inp=@pTSa.ask('name, app, fl: ','').split(', ')
423
- 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
424
630
  @ts<<inp
425
631
  @tstag<<false
426
632
  @ts_unsorted << inp # keep master list updated
427
633
  @current = @ts
428
634
  when 'e'
429
- inp=@pEPa.ask('name, fl, afov: ','').split(', ')
430
- 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
431
642
  @ep<<inp
432
643
  @eptag<<false
433
644
  @ep_unsorted << inp # keep master list updated
434
645
  @current = @ep
435
646
  when 'ENTER'
436
647
  val = @current[@focus.index].join(', ')
437
- 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
438
656
  @current[@focus.index] = arr
439
657
  when 'D'
440
658
  if @current.equal?(@ts)
@@ -502,6 +720,18 @@ loop do
502
720
  when 'u'
503
721
  @tstag.fill(false)
504
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
505
735
  when 'C-O'
506
736
  observe
507
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.0'
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-06 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.'
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