cabrillo 0.0.1b

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 (4) hide show
  1. checksums.yaml +7 -0
  2. data/lib/cabrillo.rb +248 -0
  3. data/lib/contest_validators.rb +131 -0
  4. metadata +45 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f9aa61856cc36ff7dff85e30c469c30619b59e7de1081f308a75b553f6cee8ca
4
+ data.tar.gz: 44ff1ba9a385c320f02384ddb209de73d0222c57d7df595bd9c0e7d2c045aaeb
5
+ SHA512:
6
+ metadata.gz: bed3037e4cb58779fe66c9548eacd4643bbb37fa0155c22590ca31dd94c47ce63ebb0f4e791ed578101678ec43b760113beae98bd6cf419f85067c2f83ea7ad7
7
+ data.tar.gz: 4397437c7b9ee26707742446e1adc683a41ce8f7f06e95ae17e3d47b1c4ed0fac2d6ae0cca613523c96fded8f55d2fbfbf1bc2852f64176ac8c1a12ae256d3f6
data/lib/cabrillo.rb ADDED
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env ruby
2
+ # Cabrillo - Amateur Radio Log Library
3
+ #
4
+ # This library handles the parsing and generation of the Cabrillo ham radio
5
+ # logging format, commonly used by the ARRL for contesting.
6
+ #
7
+ # Written by Ricky Elrod (github: @CodeBlock) and released an MIT license.
8
+ # https://www.github.com/CodeBlock/cabrillo-gem
9
+
10
+ $: << File.dirname(__FILE__)
11
+ require 'contest_validators'
12
+ require 'date'
13
+ require 'time'
14
+
15
+ # TODO: Split these into their own gem because they are handy. :-)
16
+ class String
17
+ def to_hz
18
+ freq_split = self.split('.')
19
+ hertz = freq_split[0].to_i * 1000000 # MHz
20
+
21
+ # Handle KHz
22
+ if not freq_split[1].nil?
23
+ freq_split[1] += '0' while freq_split[1].length < 3
24
+ hertz += freq_split[1].to_i * 1000 # KHz
25
+ end
26
+
27
+ # Handle Hz
28
+ if not freq_split[2].nil?
29
+ freq_split[2] += '0' while freq_split[2].length < 3
30
+ hertz += freq_split[2].to_i # Hz
31
+ end
32
+ hertz
33
+ end
34
+ end
35
+
36
+ class Integer
37
+ def to_mhz
38
+ self.to_s.reverse.gsub(/(.{3})(?=.)/, '\1.\2').reverse
39
+ end
40
+ end
41
+ # END TODO
42
+
43
+ class InvalidDataError < StandardError; end
44
+
45
+ class Cabrillo
46
+ @raise_on_invalid_data = true
47
+
48
+ CABRILLO_VERSION = '3.0' # The current version of the spec, our default.
49
+
50
+ # Public: Creates an instance of Cabrillo from a Hash of log data
51
+ #
52
+ # options - A Hash which contains data from a cabrillo log
53
+ #
54
+ # Returns an instance of Cabrillo.
55
+ def initialize(options = {})
56
+ # Let all the given entries automagically become instance variables.
57
+ options.each do |key, value|
58
+ instance_variable_set("@#{key}", value)
59
+ this = class << self; self; end
60
+ this.class_eval { attr_accessor key }
61
+ end
62
+
63
+ # Defaults and sanity checks can go here if they need to.
64
+ @version = options[:version] || CABRILLO_VERSION
65
+ end
66
+
67
+ # Public: Return the collected data as a Hash.
68
+ #
69
+ # Returns the data that was parsed (or given) as a Hash.
70
+ def to_hash
71
+ h = {}
72
+ self.instance_variables.each do |variable|
73
+ h[variable[1..-1].to_sym] = self.instance_variable_get(variable)
74
+ end
75
+ h
76
+ end
77
+
78
+ class << self
79
+ attr_accessor :raise_on_invalid_data
80
+
81
+ # Public: Parses a log (a string containing newlines) into a Cabrillo
82
+ # instance.
83
+ #
84
+ # log_contents - A String containing the entire to parse.
85
+ #
86
+ # TODO: Use a parsing lib like Treetop maybe?
87
+ #
88
+ # Returns an instance of Cabrillo.
89
+ def parse(log_contents)
90
+ cabrillo_info = Hash.new { |h,k| h[k] = [] }
91
+ log_contents.lines.each do |line|
92
+ line = line.strip
93
+
94
+ # Ignore comments. (See README.md for info.)
95
+ next if line.start_with? '#' or line.start_with? '//' or line.empty?
96
+
97
+ # Info that can only appear once.
98
+ cabrillo_info.merge! split_basic_line(line, 'START-OF-LOG', :version)
99
+ cabrillo_info.merge! split_basic_line(line, 'CALLSIGN', :callsign)
100
+ cabrillo_info.merge! split_basic_line(line, 'CATEGORY-ASSISTED', :category_assisted, ContestValidators::CATEGORY_ASSISTED)
101
+ cabrillo_info.merge! split_basic_line(line, 'CATEGORY-BAND', :category_band, ContestValidators::CATEGORY_BAND)
102
+ cabrillo_info.merge! split_basic_line(line, 'CATEGORY-MODE', :category_mode, ContestValidators::CATEGORY_MODE)
103
+ cabrillo_info.merge! split_basic_line(line, 'CATEGORY-OPERATOR', :category_operator, ContestValidators::CATEGORY_OPERATOR)
104
+ cabrillo_info.merge! split_basic_line(line, 'CATEGORY-POWER', :category_power, ContestValidators::CATEGORY_POWER)
105
+ cabrillo_info.merge! split_basic_line(line, 'CATEGORY-STATION', :category_station, ContestValidators::CATEGORY_STATION)
106
+ cabrillo_info.merge! split_basic_line(line, 'CATEGORY-TIME', :category_time, ContestValidators::CATEGORY_TIME)
107
+ cabrillo_info.merge! split_basic_line(line, 'CATEGORY-TRANSMITTER', :category_transmitter, ContestValidators::CATEGORY_TRANSMITTER)
108
+ cabrillo_info.merge! split_basic_line(line, 'CATEGORY-OVERLAY', :category_overlay, ContestValidators::CATEGORY_OVERLAY)
109
+ cabrillo_info.merge! split_basic_line(line, 'CLAIMED-SCORE', :claimed_score, ContestValidators::CLAIMED_SCORE)
110
+ cabrillo_info.merge! split_basic_line(line, 'CLUB', :club)
111
+ cabrillo_info.merge! split_basic_line(line, 'CONTEST', :contest, ContestValidators::CONTEST)
112
+ cabrillo_info.merge! split_basic_line(line, 'CREATED-BY', :created_by)
113
+ cabrillo_info.merge! split_basic_line(line, 'EMAIL', :email)
114
+ cabrillo_info.merge! split_basic_line(line, 'LOCATION', :location)
115
+ cabrillo_info.merge! split_basic_line(line, 'NAME', :name)
116
+ cabrillo_info.merge! split_basic_line(line, 'ADDRESS-CITY', :address_city)
117
+ cabrillo_info.merge! split_basic_line(line, 'ADDRESS-STATE-PROVINCE', :address_state_province)
118
+ cabrillo_info.merge! split_basic_line(line, 'ADDRESS-POSTALCODE', :address_postalcode)
119
+ cabrillo_info.merge! split_basic_line(line, 'ADDRESS-COUNTRY', :address_country)
120
+
121
+ # TODO: It would be great to remove some of the redundancy here.
122
+ address = split_basic_line(line, 'ADDRESS', :address)
123
+ cabrillo_info[:address] << address[:address] unless address.empty?
124
+
125
+ soapbox = split_basic_line(line, 'SOAPBOX', :soapbox)
126
+ cabrillo_info[:soapbox] << soapbox[:soapbox] unless soapbox.empty?
127
+
128
+ club = split_basic_line(line, 'CLUB', :club)
129
+ cabrillo_info[:club] << club[:club] unless club.empty?
130
+
131
+ operators = split_basic_line(line, 'OPERATORS', :operators)
132
+ cabrillo_info[:operators] << club[:operators] unless operators.empty?
133
+
134
+ # If we already parsed in a contest then we're good. If not, we don't
135
+ # know what to parse as, so skip.
136
+ if line.start_with? "QSO: " and cabrillo_info[:contest]
137
+ cabrillo_info[:qsos] << parse_qso(line, cabrillo_info[:contest])
138
+ end
139
+ end
140
+ Cabrillo.new(cabrillo_info)
141
+ end
142
+
143
+ # Public: A wrapper to Cabrillo.parse() to parse a log from a file.
144
+ #
145
+ # file_path - The path to the logfile to parse.
146
+ #
147
+ # Returns what Cabrillo.parse() returns, an instance of Cabrillo.
148
+ def parse_file(file_path)
149
+ Cabrillo.parse(IO.read(file_path))
150
+ end
151
+
152
+
153
+ private
154
+ # Private: Parses a specific line of the log, in most cases.
155
+ #
156
+ # line - The String of log line to parse.
157
+ # key - The key to look for in the line.
158
+ # hash_key - The key to use in the resulting Hash.
159
+ #
160
+ # Throws an Exception if validators are given but the data does not match
161
+ # one of them.
162
+ #
163
+ # Returns a Hash of {:hash_key => value_from_parsed_line} or nil if the key
164
+ # wasn't found.
165
+ def split_basic_line(line, key, hash_key, validators = [])
166
+ line_key, line_value = line.split(/:\s+/, 2)
167
+
168
+ if line_key == key
169
+ okay = true
170
+ unless validators.empty?
171
+ okay = false
172
+ validators.each do |v|
173
+ okay = true and break if v.class.to_s == 'Regexp' and line_value =~ v
174
+ okay = true and break if v.class.to_s == 'String' and line_value == v
175
+ end
176
+ end
177
+
178
+ if okay || !@raise_on_invalid_data
179
+ { hash_key => line_value.strip }
180
+ elsif !validators.empty? && @raise_on_invalid_data
181
+ raise InvalidDataError, "Invalid value given for key `#{line_key}`."
182
+ end
183
+ else
184
+ { }
185
+ end
186
+ end
187
+
188
+ # Private: Parses a QSO: line based on the contest type.
189
+ #
190
+ # qso_line - The Strnig containing the line of the logfile that we are
191
+ # parsing. Starts with "QSO:"
192
+ # contest - A String, the name of the contest that we are parsing.
193
+ #
194
+ # Returns a Hash containing the parsed result.
195
+ def parse_qso(qso_line, contest)
196
+ if @raise_on_invalid_data
197
+ raise InvalidDataError, "Invalid contest: #{contest}" unless ContestValidators::CONTEST.include? contest
198
+ raise InvalidDataError, "Line does not start with 'QSO: '" unless qso_line.start_with? "QSO: "
199
+ end
200
+ qso_line.gsub!(/^QSO: /, "")
201
+
202
+ # Basic structure
203
+ qso = {
204
+ :exchange => {
205
+ :sent => {},
206
+ :received => {}
207
+ }
208
+ }
209
+
210
+ # In any and all cases, the first fields are: frequency, mode, date, time.
211
+ # Store the exchange/everything else into an array (using splat) for
212
+ # later.
213
+ qso[:frequency], qso[:mode], date, time, *exchange = qso_line.split
214
+
215
+ # Parse the date and time into a Time object.
216
+ qso[:time] = Time.parse(DateTime.strptime("#{date} #{time}", '%Y-%m-%d %H%M').to_s)
217
+
218
+ # Transmitted callsign always comes first.
219
+ qso[:exchange][:sent][:callsign] = exchange.shift
220
+
221
+ # Parse the rest of the exchange
222
+ case contest
223
+ when 'CQ-160-CW', 'CQ-160-SSB', 'CQ-WPX-RTTY', 'CQ-WPX-CW', 'CQ-WPX-SSB', 'CQ-WW-RTTY', 'CQ-WW-CW', 'CQ-WW-SSB', 'ARRL-DX-CW', 'ARRL-DX-SSB', 'IARU-HF', 'ARRL-10', 'ARRL-160', 'JIDX-CW', 'JIDX-SSB', 'STEW-PERRY', 'OCEANIA-XD-CW', 'OCEANIA-DX-SSB', 'AP-SPRINT', 'NEQP', 'ARRL-FIELD-DAY'
224
+ qso[:exchange][:sent][:rst] = exchange.shift
225
+ qso[:exchange][:sent][:exchange] = exchange.shift
226
+
227
+ qso[:exchange][:received][:callsign] = exchange.shift
228
+ qso[:exchange][:received][:rst] = exchange.shift
229
+ qso[:exchange][:received][:exchange] = exchange.shift
230
+ qso[:exchange][:received][:transmitter_id] = exchange.shift
231
+ when 'ARRL-SS-CW', 'ARRL-SS-SSB'
232
+ qso[:exchange][:sent][:serial_number] = exchange.shift
233
+ qso[:exchange][:sent][:precedence] = exchange.shift
234
+ qso[:exchange][:sent][:check] = exchange.shift
235
+ qso[:exchange][:sent][:arrl_section] = exchange.shift
236
+
237
+ qso[:exchange][:received][:callsign] = exchange.shift
238
+ qso[:exchange][:received][:serial_number] = exchange.shift
239
+ qso[:exchange][:received][:precedence] = exchange.shift
240
+ qso[:exchange][:received][:check] = exchange.shift
241
+ qso[:exchange][:received][:arrl_section] = exchange.shift
242
+ end
243
+
244
+ qso
245
+ end
246
+
247
+ end
248
+ end
@@ -0,0 +1,131 @@
1
+ class ContestValidators
2
+ CATEGORY_ASSISTED = [
3
+ 'ASSISTED',
4
+ 'NON-ASSISTED',
5
+ ]
6
+
7
+ CATEGORY_BAND = [
8
+ 'ALL',
9
+ '160M',
10
+ '80M',
11
+ '40M',
12
+ '20M',
13
+ '15M',
14
+ '10M',
15
+ '6M',
16
+ '2M',
17
+ '222',
18
+ '432',
19
+ '902',
20
+ '1.2G',
21
+ '2.3G',
22
+ '3.4G',
23
+ '5.7G',
24
+ '10G',
25
+ '24G',
26
+ '47G',
27
+ '75G',
28
+ '119G',
29
+ '142G',
30
+ '241G',
31
+ 'Light',
32
+ ]
33
+
34
+ CATEGORY_MODE = [
35
+ 'SSB',
36
+ 'CW',
37
+ 'RTTY',
38
+ 'MIXED',
39
+ ]
40
+
41
+ CATEGORY_OPERATOR = [
42
+ 'SINGLE-OP',
43
+ 'MULTI-OP',
44
+ 'CHECKLOG',
45
+ ]
46
+
47
+ CATEGORY_POWER = [
48
+ 'HIGH',
49
+ 'LOW',
50
+ 'QRP',
51
+ ]
52
+
53
+ CATEGORY_STATION = [
54
+ 'FIXED',
55
+ 'MOBILE',
56
+ 'PORTABLE',
57
+ 'ROVER',
58
+ 'EXPEDITION',
59
+ 'HQ',
60
+ 'SCHOOL',
61
+ ]
62
+
63
+ CATEGORY_TIME = [
64
+ '6-HOURS',
65
+ '12-HOURS',
66
+ '24-HOURS',
67
+ ]
68
+
69
+ CATEGORY_TRANSMITTER = [
70
+ 'ONE',
71
+ 'TWO',
72
+ 'LIMITED',
73
+ 'UNLIMITED',
74
+ 'SWL',
75
+ ]
76
+
77
+ CATEGORY_OVERLAY = [
78
+ 'ROOKIE',
79
+ 'TB-WIRED',
80
+ 'NOVICE-TECH',
81
+ 'OVER-50',
82
+ ]
83
+
84
+ CLAIMED_SCORE = [
85
+ /\d+/,
86
+ ]
87
+
88
+ CONTEST = [
89
+ 'AP-SPRINT',
90
+ 'ARRL-10',
91
+ 'ARRL-160',
92
+ 'ARRL-DX-CW',
93
+ 'ARRL-DX-SSB',
94
+ 'ARRL-SS-CW',
95
+ 'ARRL-SS-SSB',
96
+ 'ARRL-UHF-AUG',
97
+ 'ARRL-VHF-JAN',
98
+ 'ARRL-VHF-JUN',
99
+ 'ARRL-VHF-SEP',
100
+ 'ARRL-RTTY',
101
+ 'BARTG-RTTY',
102
+ 'CQ-160-CW',
103
+ 'CQ-160-SSB',
104
+ 'CQ-WPX-CW',
105
+ 'CQ-WPX-RTTY',
106
+ 'CQ-WPX-SSB',
107
+ 'CQ-VHF',
108
+ 'CQ-WW-CW',
109
+ 'CQ-WW-RTTY',
110
+ 'CQ-WW-SSB',
111
+ 'DARC-WAEDC-CW',
112
+ 'DARC-WAEDC-RTTY',
113
+ 'DARC-WAEDC-SSB',
114
+ 'FCG-FQP',
115
+ 'IARU-HF',
116
+ 'JIDX-CW',
117
+ 'JIDX-SSB',
118
+ 'NA-SPRINT-CW',
119
+ 'NA-SPRINT-SSB',
120
+ 'NCCC-CQP',
121
+ 'NEQP',
122
+ 'OCEANIA-DX-CW',
123
+ 'OCEANIA-DX-SSB',
124
+ 'RDXC',
125
+ 'RSGB-IOTA',
126
+ 'SAC-CW',
127
+ 'SAC-SSB',
128
+ 'STEW-PERRY',
129
+ 'TARA-RTTY',
130
+ ]
131
+ end
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cabrillo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1b
5
+ platform: ruby
6
+ authors:
7
+ - AmirolAhmad
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-07-17 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A ruby library to parse and generate ham radio Cabrillo log files.
14
+ email: dev.amirol@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/cabrillo.rb
20
+ - lib/contest_validators.rb
21
+ homepage: http://rubygems.org/gems/cabrillo
22
+ licenses:
23
+ - MIT
24
+ metadata: {}
25
+ post_install_message:
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">"
37
+ - !ruby/object:Gem::Version
38
+ version: 1.3.1
39
+ requirements: []
40
+ rubyforge_project:
41
+ rubygems_version: 2.7.6
42
+ signing_key:
43
+ specification_version: 4
44
+ summary: Cabrillo!
45
+ test_files: []