adif 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 94e612b5395d8c418a60a99d259471fa670eac0de3732e14ee9845ff788d5427
4
+ data.tar.gz: 4890f1b5f7698e7e9ea6b2ac44a29a1062c719a91993b8a2428685f49948408e
5
+ SHA512:
6
+ metadata.gz: '0678d7604fa7abd64365e05979870d4fa6ea6c49c91685edeafa3c76ad0c13525f7f0207a52f17383a1aefe09aa81603db6294c82bdb11dd750df70f95ba2c76'
7
+ data.tar.gz: 0f7cde07961896616f7091fe11f94572d2f6fc7dc8161d379738a6bbb0b81c3e41495cd7db05324a092ae041fd0149a910109ff1d7957036490477ce3420fbb8
data/CHANGELOG.md ADDED
File without changes
@@ -0,0 +1,132 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official email address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ [INSERT CONTACT METHOD].
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series of
86
+ actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or permanent
93
+ ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within the
113
+ community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.1, available at
119
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120
+
121
+ Community Impact Guidelines were inspired by
122
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123
+
124
+ For answers to common questions about this code of conduct, see the FAQ at
125
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126
+ [https://www.contributor-covenant.org/translations][translations].
127
+
128
+ [homepage]: https://www.contributor-covenant.org
129
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130
+ [Mozilla CoC]: https://github.com/mozilla/diversity
131
+ [FAQ]: https://www.contributor-covenant.org/faq
132
+ [translations]: https://www.contributor-covenant.org/translations
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Chris Dinger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # ADIFrb
2
+
3
+ A rubygem to parse, manipulate, and manage [ADIF](https://adif.org/315/ADIF_315.htm) amateur radio logs and records.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem "adif"
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```
16
+ bundle install
17
+ ```
18
+
19
+ ## Development
20
+
21
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
22
+
23
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
24
+
25
+ ## Contributing
26
+
27
+ Bug reports and pull requests are welcome on GitHub at https://github.com/cdinger/adifrb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/cdinger/adifrb/blob/main/CODE_OF_CONDUCT.md).
28
+
29
+ ## TODO
30
+
31
+ - make Record#uniqueness configurable, with examples (qsl.net has some fuzziness in the qso_start minute)
32
+
33
+ ## License
34
+
35
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
36
+
37
+ ## Code of Conduct
38
+
39
+ Everyone interacting in the Adif project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/adif/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ADIF
4
+ class Log
5
+ class Header
6
+ FIELDS = %w(
7
+ adif_ver
8
+ created_timestamp
9
+ programid
10
+ programversion
11
+ userdefn
12
+ )
13
+ end
14
+ end
15
+ end
data/lib/adif/log.rb ADDED
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "adif/record"
4
+
5
+ module ADIF
6
+ class Log
7
+ include Enumerable
8
+
9
+ attr_accessor :adif_version, :program_id, :program_version, :records
10
+
11
+ def initialize(adif_version: "3.1.5", program_id: "adifrb", program_version: ADIF::VERSION, records: [])
12
+ @adif_version = adif_version
13
+ @program_id = program_id
14
+ @program_version = program_version
15
+ @records = records
16
+ end
17
+
18
+ def self.parse(adif_string)
19
+ records = [Record.new]
20
+
21
+ adif_string.scan(/<(\w+):?(\d+)?>([^<]*)/) do |key, length, value|
22
+ records.last[key.downcase.to_sym] = value.strip
23
+ if key.downcase == "eor"
24
+ records << Record.new
25
+ end
26
+ end
27
+
28
+ # Remove trailing empty record
29
+ records.pop if records.last.empty?
30
+
31
+ Log.new(records: records)
32
+ end
33
+
34
+ def header
35
+ [
36
+ adif_field("adif_ver", "3.1.5"),
37
+ adif_field("created_timestamp", Time.now.utc),
38
+ adif_field("programid", program_id),
39
+ adif_field("programversion", program_version),
40
+ terminator
41
+ ].join
42
+ end
43
+
44
+ def terminator
45
+ "<EOH>"
46
+ end
47
+
48
+ def adif_field(name, value)
49
+ "<#{name.upcase}:#{value.to_s.length}>#{value.to_s} "
50
+ end
51
+
52
+ def to_adif
53
+ ([header] + sorted_records.map(&:to_adif)).join("\n")
54
+ end
55
+
56
+ def to_uniq_adif
57
+ ([header] + uniq_records.map(&:to_adif)).join("\n")
58
+ end
59
+
60
+ def sorted_records
61
+ records.sort do |a, b|
62
+ a.qso_date + a.time_on <=> b.qso_date + b.time_on
63
+ end
64
+ end
65
+
66
+ def uniq_records
67
+ records.uniq(&:uniqueness)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ADIF
4
+ class Record
5
+ class Field
6
+ attr_reader :key, :value
7
+
8
+ def initialize(key, value)
9
+ @key = key
10
+ @value = value
11
+ end
12
+
13
+ def to_adif
14
+ "<#{@key.upcase}:#{value.to_s.length}>#{value} "
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ADIF
4
+ class Record
5
+ module Fields
6
+ FIELDS = {
7
+ # https://adif.org/315/ADIF_315.htm
8
+ "3.1.5" => %w(
9
+ call
10
+ mode
11
+ band
12
+ qso_date
13
+ time_on
14
+ address
15
+ address_intl
16
+ age
17
+ altitude
18
+ ant_az
19
+ ant_el
20
+ ant_path
21
+ arrl_sect
22
+ award_submitted
23
+ award_granted
24
+ a_index
25
+ band
26
+ band_rx
27
+ call
28
+ check
29
+ class
30
+ clublog_qso_upload_date
31
+ clublog_qso_upload_status
32
+ cnty
33
+ cnty_alt
34
+ comment
35
+ comment_intl
36
+ cont
37
+ contacted_op
38
+ contest_id
39
+ country
40
+ country_intl
41
+ cqz
42
+ credit_submitted
43
+ credit_granted
44
+ darc_dok
45
+ dcl_qslrdate
46
+ dcl_qslsdate
47
+ dcl_qsl_rcvd
48
+ dcl_qsl_sent
49
+ distance
50
+ dxcc
51
+ email
52
+ eq_call
53
+ eqsl_qslrdate
54
+ eqsl_qslsdate
55
+ eqsl_qsl_rcvd
56
+ eqsl_qsl_sent
57
+ fists
58
+ fists_cc
59
+ force_init
60
+ freq
61
+ freq_rx
62
+ gridsquare
63
+ gridsquare_ext
64
+ guest_op
65
+ hamlogeu_qso_upload_date
66
+ hamlogeu_qso_upload_status
67
+ hamqth_qso_upload_date
68
+ hamqth_qso_upload_status
69
+ hrdlog_qso_upload_date
70
+ hrdlog_qso_upload_status
71
+ iota
72
+ iota_island_id
73
+ ituz
74
+ k_index
75
+ lat
76
+ lon
77
+ lotw_qslrdate
78
+ lotw_qslsdate
79
+ lotw_qsl_rcvd
80
+ lotw_qsl_sent
81
+ max_bursts
82
+ mode
83
+ morse_key_info
84
+ morse_key_type
85
+ ms_shower
86
+ my_altitude
87
+ my_antenna
88
+ my_antenna_intl
89
+ my_arrl_sect
90
+ my_city
91
+ my_city_intl
92
+ my_cnty
93
+ my_cnty_alt
94
+ my_country
95
+ my_country_intl
96
+ my_cq_zone
97
+ my_darc_dok
98
+ my_dxcc
99
+ my_fists
100
+ my_gridsquare
101
+ my_gridsquare_ext
102
+ my_iota
103
+ my_iota_island_id
104
+ my_itu_zone
105
+ my_lat
106
+ my_lon
107
+ my_morse_key_info
108
+ my_morse_key_type
109
+ my_name
110
+ my_name_intl
111
+ my_postal_code
112
+ my_postal_code_intl
113
+ my_pota_ref
114
+ my_rig
115
+ my_rig_intl
116
+ my_sig
117
+ my_sig_intl
118
+ my_sig_info
119
+ my_sig_info_intl
120
+ my_sota_ref
121
+ my_state
122
+ my_street
123
+ my_street_intl
124
+ my_usaca_counties
125
+ my_vucc_grids
126
+ my_wwff_ref
127
+ name
128
+ name_intl
129
+ notes
130
+ notes_intl
131
+ nr_bursts
132
+ nr_pings
133
+ operator
134
+ owner_callsign
135
+ pfx
136
+ pota_ref
137
+ precedence
138
+ prop_mode
139
+ public_key
140
+ qrzcom_qso_download_date
141
+ qrzcom_qso_download_status
142
+ qrzcom_qso_upload_date
143
+ qrzcom_qso_upload_status
144
+ qslmsg
145
+ qslmsg_intl
146
+ qslmsg_rcvd
147
+ qslrdate
148
+ qslsdate
149
+ qsl_rcvd
150
+ qsl_rcvd_via
151
+ qsl_sent
152
+ qsl_sent_via
153
+ qsl_via
154
+ qso_complete
155
+ qso_date
156
+ qso_date_off
157
+ qso_random
158
+ qth
159
+ qth_intl
160
+ region
161
+ rig
162
+ rig_intl
163
+ rst_rcvd
164
+ rst_sent
165
+ rx_pwr
166
+ sat_mode
167
+ sat_name
168
+ sfi
169
+ sig
170
+ sig_intl
171
+ sig_info
172
+ sig_info_intl
173
+ silent_key
174
+ skcc
175
+ sota_ref
176
+ srx
177
+ srx_string
178
+ state
179
+ station_callsign
180
+ stx
181
+ stx_string
182
+ submode
183
+ swl
184
+ ten_ten
185
+ time_off
186
+ time_on
187
+ tx_pwr
188
+ uksmg
189
+ usaca_counties
190
+ ve_prov
191
+ vucc_grids
192
+ web
193
+ wwff_ref
194
+ ).map(&:to_sym)
195
+ }
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "adif/record/fields"
4
+ require "adif/record/field"
5
+ require "json"
6
+
7
+ module ADIF
8
+ class Record
9
+ include Fields
10
+
11
+ def initialize(data = {})
12
+ @data = data
13
+ end
14
+
15
+ def [](key)
16
+ @data[key].value
17
+ end
18
+
19
+ def []=(key, value)
20
+ @data[key] = value
21
+ end
22
+
23
+ def empty?
24
+ @data.empty?
25
+ end
26
+
27
+ def fields
28
+ key_fields + nonkey_fields
29
+ end
30
+
31
+ def key_fields
32
+ [:call, :mode, :band, :qso_date, :time_on]
33
+ end
34
+
35
+ def nonkey_fields
36
+ FIELDS["3.1.5"] - key_fields
37
+ end
38
+
39
+ def uniqueness
40
+ key_fields.map { @data[it] }.join("")
41
+ end
42
+
43
+ def unique_key
44
+ uniqueness
45
+ end
46
+
47
+ def terminator
48
+ "<EOR>"
49
+ end
50
+
51
+ def field_adif(field)
52
+ "<#{field.upcase}:#{@data[field].to_s.length}>#{@data[field]}"
53
+ end
54
+
55
+ def to_adif
56
+ fields.map do |field|
57
+ field_adif(field) unless @data[field].nil?
58
+ end.compact.join(" ") + " #{terminator}"
59
+ end
60
+
61
+ def to_json
62
+ JSON.generate(@data)
63
+ end
64
+
65
+ def method_missing(name, *args, &block)
66
+ if fields.include?(name)
67
+ @data[name]
68
+ else
69
+ super
70
+ end
71
+ if @data.has_key?(name)
72
+ @data[name]
73
+ else
74
+ raise ArgumentError.new("Method `#{m}` doesn't exist.")
75
+ end
76
+ end
77
+
78
+ def respond_to_missing?(name, include_private = false)
79
+ fields.include?(name) || super
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ADIF
4
+ VERSION = "0.1.0"
5
+ end
data/lib/adif.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "adif/version"
4
+ require_relative "adif/log"
5
+ require_relative "adif/record"
6
+
7
+ module ADIF
8
+ class Error < StandardError; end
9
+ end
data/sig/adif.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Adif
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: adif
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Dinger
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: A rubygem for parsing and manipulating amateur radio ADIF files
13
+ email:
14
+ - cdinger@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - CHANGELOG.md
20
+ - CODE_OF_CONDUCT.md
21
+ - LICENSE.txt
22
+ - README.md
23
+ - Rakefile
24
+ - lib/adif.rb
25
+ - lib/adif/log.rb
26
+ - lib/adif/log/header.rb
27
+ - lib/adif/record.rb
28
+ - lib/adif/record/field.rb
29
+ - lib/adif/record/fields.rb
30
+ - lib/adif/version.rb
31
+ - sig/adif.rbs
32
+ homepage: https://github.com/cdinger/adifrb
33
+ licenses:
34
+ - MIT
35
+ metadata:
36
+ homepage_uri: https://github.com/cdinger/adifrb
37
+ source_code_uri: https://github.com/cdinger/adifrb
38
+ changelog_uri: https://github.com/cdinger/adifrb/blob/main/CHANGELOG.md
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 3.1.0
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubygems_version: 3.6.8
54
+ specification_version: 4
55
+ summary: A rubygem for parsing and manipulating amateur radio ADIF files
56
+ test_files: []