tacview_client 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: bcfff1957de3576841382d36257799ee9ff48131bc8cf723ef8272f01b12ef05
4
+ data.tar.gz: a9a5d31d8ab4564a097b5be6d6e25358596438b7564aeb41147d8a56ccea130d
5
+ SHA512:
6
+ metadata.gz: e4038988c5541560159fdc78214d166630e4c1d7d8101622b2fcf3edbbe0e3ee5bd7c9085412613e7be1c2debf3591d18346fe66d29aeeaebffd6eb20edb39bf
7
+ data.tar.gz: 2587a4997a39c703d8b1358a8b2f98565d3165e6433b27c8a03664938a3cc4ceada21ef2e805663d1f01130c54c95a164fd289e6474f519cf9351aff181f246b
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ Gemfile.lock
10
+ *.swp
data/.gitlab-ci.yml ADDED
@@ -0,0 +1,26 @@
1
+ .tests:
2
+ image: "ruby:2.5"
3
+ before_script:
4
+ - apt-get update && apt-get install -y git
5
+ - ruby -v
6
+ - which ruby
7
+ - gem install bundler --no-document
8
+ - bundle install --jobs $(nproc) "${FLAGS[@]}"
9
+
10
+ .rspec:
11
+ extends: .tests
12
+ script:
13
+ - bundle exec rake spec
14
+
15
+ rubocop:
16
+ extends: .tests
17
+ script:
18
+ - bundle exec rake rubocop
19
+
20
+ rspec-ruby:
21
+ extends: .rspec
22
+ image: ruby:latest
23
+
24
+ rspec-jruby:
25
+ extends: .rspec
26
+ image: jruby:latest
data/.jrubyrc ADDED
@@ -0,0 +1 @@
1
+ debug.fullTrace=true
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,19 @@
1
+ Style/BlockComments:
2
+ Exclude:
3
+ # This is the default rspec generated file so leave it be for consistency
4
+ - spec/spec_helper.rb
5
+ Layout/CommentIndentation:
6
+ Exclude:
7
+ # This is the default rspec generated file so leave it be for consistency
8
+ - spec/spec_helper.rb
9
+
10
+ Metrics/BlockLength:
11
+ Exclude:
12
+ # Rubocop does not like the rspec describe block style which is always long
13
+ - spec/**/*_spec.rb
14
+
15
+ Lint/UnusedMethodArgument:
16
+ Exclude:
17
+ # This is Base class that acts as a guide so we don't care about unused
18
+ # parameters
19
+ - lib/tacview_client/base_processor.rb
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ tacview_client
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.6.3
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in tacview_client.gemspec
8
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Jeffrey Jones
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,77 @@
1
+ # Tacview Client
2
+
3
+ A Ruby client that speaks the [Tacview](http://www.tacview.net) protocol and
4
+ can connect to a Tacview compatible server.
5
+
6
+ This Gem is tested against the latest non-preview releases of Ruby and JRuby.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'tacview_client'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install tacview_client
23
+
24
+ ## Usage
25
+
26
+ ### API
27
+
28
+ To connect to a Tacview server with this library you can use the following code
29
+
30
+ ```ruby
31
+
32
+ require 'tacview_client'
33
+
34
+ # See docs for all parameters
35
+ client = TacviewClient::Client.new host: 127.0.0.1,
36
+ processor: ProcessorInstance
37
+ ```
38
+
39
+ This gem uses Inversion Of Control principles and expects you to provide an
40
+ object to receive events for processing. This can be a `Module` or a `Class`
41
+ and it must implement the methods descibed in the `BaseProcessor` class. See
42
+ the documentation of this class for more information.
43
+
44
+ For an example of this see the `ConsoleOutputter` module in the
45
+ `exe/connect_tacview` file
46
+
47
+ #### Logging
48
+
49
+ This gem provides no logging out of the box. If you want to add logging then
50
+ the recommended approach is to create your own logger class and use
51
+ `Module.prepend` to prepend it to the `Client` class (For logging connection
52
+ information) or the `Reader` class's `#route_line` method for logging of the
53
+ raw ACMI lines
54
+
55
+ For an example of this see the `TacviewClientLogger` module in the
56
+ `exe/connect_tacview` file
57
+
58
+ ### Command-line
59
+
60
+ This gem provides a command-line application. Use `connect_tacview --help` for
61
+ invocation information. Thie command-line will connect to a Tacview server and
62
+ print some connection debug information followed by the parsed event stream to
63
+ the terminal
64
+
65
+ ## Development
66
+
67
+ After checking out the repo, run `bin/setup` to install dependencies. Then run
68
+ `rake` to run rubocop and the tests. You can also run `bin/console` for an
69
+ interactive prompt that will allow you to experiment.
70
+
71
+ ## Contributing
72
+
73
+ Bug reports and pull requests are welcome on [GitLab](https://gitlab.com/overlord-bot/tacview-ruby-client).
74
+
75
+ ## License
76
+
77
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rubocop/rake_task'
5
+ require 'rspec/core/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new(:rubocop)
9
+
10
+ task default: :rubocop
11
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'tacview_client'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'optparse'
5
+ require 'bundler/setup'
6
+ require 'tacview_client'
7
+ require 'tacview_client/base_processor'
8
+
9
+ options = {
10
+ password: nil,
11
+ port: 42_674,
12
+ client_name: 'ruby_tacview_client'
13
+ }
14
+
15
+ OptionParser.new do |opts|
16
+ opts.banner = 'Usage: ruby connect_tacview [options]'
17
+
18
+ opts.on('-h', '--host=host', 'Tacview server hostname / IP') do |v|
19
+ options[:host] = v
20
+ end
21
+
22
+ opts.on('-p', '--port=port', 'Tacview server port (Default: 42674)') do |v|
23
+ options[:port] = v
24
+ end
25
+
26
+ opts.on('-a', '--password=password', 'Tacview server password') do |v|
27
+ options[:password] = v
28
+ end
29
+
30
+ opts.on('-c', '--client-name=client', 'Client name (Default:' \
31
+ 'ruby_tacview_client') do |v|
32
+ options[:client_name] = v
33
+ end
34
+ end.parse!
35
+
36
+ if !options[:host] || !options[:host].is_a?(String) || options[:host].empty?
37
+ puts 'Hostname required'
38
+ exit(1)
39
+ end
40
+
41
+ if options[:client_name].empty?
42
+ puts 'client-name cannot be empty'
43
+ exit(1)
44
+ end
45
+
46
+ # Add console logging for this command-line app
47
+ module TacviewClientLogger
48
+ def initialize(host:,
49
+ port: 42_674,
50
+ password: nil,
51
+ processor:,
52
+ client_name: 'ruby_tacview_client')
53
+
54
+ print "Connecting to #{host}:#{port} as #{client_name}"
55
+ puts password ? ' with a password supplied' : ''
56
+
57
+ super(host: host, port: port, password: password, processor: processor,
58
+ client_name: client_name)
59
+ end
60
+
61
+ private
62
+
63
+ def read_handshake
64
+ puts 'Reading Incoming Handshake'
65
+ super
66
+ end
67
+
68
+ def read_handshake_header(header)
69
+ print header.to_s.tr('_', ' ').capitalize + ': '
70
+ result = super
71
+ puts result
72
+ result
73
+ end
74
+
75
+ def send_handshake
76
+ puts 'Sending Handshake'
77
+ super
78
+ end
79
+
80
+ def start_reader
81
+ puts 'Starting event stream reader'
82
+ puts '-' * 80
83
+ super
84
+ end
85
+ end
86
+ # A very simple processor that outputs all events received from a
87
+ # TacviewClient::Reader instance to the console. Useful for simple
88
+ # testing apps
89
+ class ConsoleOutputter < TacviewClient::BaseProcessor
90
+ def update_object(object)
91
+ puts "Object Update : #{object}"
92
+ end
93
+
94
+ def delete_object(object_id)
95
+ puts "Object Deletion: #{object_id}"
96
+ end
97
+
98
+ def update_time(time)
99
+ puts "Time Update : #{time}"
100
+ end
101
+
102
+ def set_property(property:, value:)
103
+ puts "Property Set : #{property}, #{value}"
104
+ end
105
+ end
106
+
107
+ TacviewClient::Client.prepend TacviewClientLogger
108
+
109
+ client = TacviewClient::Client.new host: options[:host],
110
+ port: options[:port],
111
+ password: options[:password],
112
+ client_name: options[:client_name],
113
+ processor: ConsoleOutputter.new
114
+ client.connect
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './tacview_client/version'
4
+ require_relative './tacview_client/client'
5
+
6
+ # Top-level namespace for all classes of the Ruby Tacview Client
7
+ module TacviewClient
8
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TacviewClient
4
+ # An Base Procesor that defines all the methods called by an instance of
5
+ # a TacviewClient::Reader. Use this as a guide on what you need to
6
+ # implement. You can also optionally inherit from this class to make
7
+ # sure you have every method defined in your sub-class
8
+ class BaseProcessor
9
+ # Process an update event for an object
10
+ #
11
+ # On the first appearance of the object there are usually more fields
12
+ # including pilot names, object type etc.
13
+ #
14
+ # For existing objects these events are almost always lat/lon/alt updates
15
+ # only
16
+ #
17
+ # @param event [Hash] A parsed ACMI line. This hash will always include
18
+ # the fields listed below but may also include others depending on the
19
+ # source line.
20
+ # @option event [String] :object_id The hexadecimal format object ID.
21
+ # @option event [BigDecimal] :latitude The object latitude in WGS 84 format.
22
+ # @option event [BigDecimal] :longitude The object latitude in WGS 84
23
+ # format.
24
+ # @option event [BigDecimal] :altitude The object altitude above sea level
25
+ # in meters to 1 decimal place.
26
+ def update_object(event)
27
+ raise NotImplementedError, 'To be implemented by subclass'
28
+ end
29
+
30
+ # Process a delete event for an object
31
+ #
32
+ # @param object_id [String] A hexadecimal number representing the object
33
+ # ID
34
+ def delete_object(object_id)
35
+ raise NotImplementedError, 'To be implemented by subclass'
36
+ end
37
+
38
+ # Process a time update event
39
+ #
40
+ # @param time [BigDecimal] A time update in seconds from the
41
+ # ReferenceTime to 2 decimal places
42
+ def update_time(time)
43
+ raise NotImplementedError, 'To be implemented by subclass'
44
+ end
45
+
46
+ # Set a property
47
+ #
48
+ # @param property [String] The name of the property to be set
49
+ # @param value [String] The value of the property to be set
50
+ def set_property(property:, value:)
51
+ raise NotImplementedError, 'To be implemented by subclass'
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'crc'
5
+
6
+ require_relative 'reader'
7
+
8
+ module TacviewClient
9
+ # The actual client to be instantiated to connect to a Tacview Server
10
+ class Client
11
+ # The underlying stream protocol used by Tacview. Needs to be in-sync
12
+ # between the client and the server.
13
+ STREAM_PROTOCOL = 'XtraLib.Stream.0'
14
+
15
+ # The application level protocol used by Tacview. Needs to be in-sync
16
+ # between the client and the server.
17
+ TACVIEW_PROTOCOL = 'Tacview.RealTimeTelemetry.0'
18
+
19
+ # A null terminator used by Tacview to terminate handshake packages
20
+ HANDSHAKE_TERMINATOR = "\0"
21
+
22
+ # Passwords sent between Tacview clients and servers are hashed using
23
+ # this algorithm
24
+ PASSWORD_HASHER = CRC['CRC-64-ECMA']
25
+
26
+ # Returns a new instance of a Client
27
+ #
28
+ # This is the entry point into the gem. Instantiate an instance of this
29
+ # class to setup the prerequisite data for a connection to a Tacview server.
30
+ # Once done call {#connect} to start processing the Tacview ACMI stream.
31
+ #
32
+ # @param host [String] Server hostname or IP
33
+ # @param port [Integer] Server port
34
+ # @param password [String] Plaintext password required to connect to a
35
+ # password protected Tacview server. Is hashed before transmission.
36
+ # @param client_name [String] Client name to send to the server
37
+ # @param processor [BaseProcessor] The object that processes the events
38
+ # emitted by the {Reader}. Must implement the methods defined by the
39
+ # {BaseProcessor} and can optionally inherit from it.
40
+ def initialize(host:,
41
+ port: 42_674,
42
+ password: nil,
43
+ processor:,
44
+ client_name: 'ruby_tacview_client')
45
+ @host = host
46
+ @port = port
47
+ @password = password
48
+ @processor = processor
49
+ @client_name = client_name
50
+ end
51
+
52
+ # Connect to the Tacview server
53
+ #
54
+ # Actually opens a TCP connection to the Tacview server and starts
55
+ # streaming ACMI lines to an instance of the {Reader} class.
56
+ #
57
+ # This method will only return when the TCP connection has be killed
58
+ # either by a client-side signal or by the server closing the TCP
59
+ # connection.
60
+ def connect
61
+ @connection = TCPSocket.open(@host, @port)
62
+
63
+ read_handshake
64
+
65
+ send_handshake
66
+
67
+ start_reader
68
+ end
69
+
70
+ private
71
+
72
+ # See https://www.tacview.net/documentation/realtime/en/
73
+ # for information on connection negotiation
74
+ def read_handshake
75
+ stream_protocol = read_handshake_header :stream_protocol
76
+ validate_handshake_header STREAM_PROTOCOL, stream_protocol
77
+
78
+ tacview_protocol = read_handshake_header :tacview_protocol
79
+ validate_handshake_header TACVIEW_PROTOCOL, tacview_protocol
80
+
81
+ read_handshake_header :host
82
+
83
+ @connection.gets HANDSHAKE_TERMINATOR
84
+ end
85
+
86
+ # Header parameter included for logging purposes
87
+ def read_handshake_header(_header)
88
+ @connection.gets.chomp
89
+ end
90
+
91
+ def validate_handshake_header(expected, actual)
92
+ return if expected == actual
93
+
94
+ abort_connection
95
+ end
96
+
97
+ def abort_connection
98
+ @connection.close
99
+ exit(1)
100
+ end
101
+
102
+ def send_handshake
103
+ @connection.print [
104
+ STREAM_PROTOCOL,
105
+ TACVIEW_PROTOCOL,
106
+ @client_name,
107
+ hash_password
108
+ ].join("\n") + HANDSHAKE_TERMINATOR
109
+ end
110
+
111
+ def hash_password
112
+ return 0 unless @password
113
+
114
+ PASSWORD_HASHER.crc(@password)
115
+ end
116
+
117
+ def start_reader
118
+ reader = Reader.new(input_source: @connection,
119
+ processor: @processor)
120
+ reader.start_reading
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bigdecimal'
4
+ require_relative './reader/parser'
5
+
6
+ module TacviewClient
7
+ # Reads events from an input source, parses them using the {Parser}
8
+ # and calls the appropriate event processor method
9
+ class Reader
10
+ # The format that matches the beginning of an ACMI update line
11
+ OBJECT_UPDATE_MARKER = '^\h\h+,'
12
+
13
+ # The format that matches the beginning of an ACMI delete line
14
+ OBJECT_DELETION_MARKER = '-'
15
+
16
+ # The format that matches the beginning of an ACMI time update line
17
+ TIME_UPDATE_MARKER = '#'
18
+
19
+ # The format that matches the beginning of an ACMI Property line
20
+ GLOBAL_PROPERTY_MARKER = '0,'
21
+
22
+ # @param input_source [IO, #gets] An {IO} object (or object that implements
23
+ # the {IO#gets} method. Typically this is a Socket or File.
24
+ # @param processor [BaseProcessor] The object that processes the events
25
+ # emitted by the {Reader}. Must implement the methods defined by the
26
+ # {BaseProcessor} and can optionally inherit from it.
27
+ def initialize(input_source:, processor:)
28
+ raise ArgumentError, 'input_source cannot be nil' if input_source.nil?
29
+ raise ArgumentError, 'processor cannot be nil' if processor.nil?
30
+
31
+ @input_source = input_source
32
+ @processor = processor
33
+ end
34
+
35
+ def start_reading
36
+ while (line = @input_source.gets)
37
+ route_line(line.chomp)
38
+ end
39
+ true
40
+ rescue SignalException
41
+ exit
42
+ ensure
43
+ @input_source.close
44
+ end
45
+
46
+ private
47
+
48
+ def route_line(line)
49
+ if line.match?(OBJECT_UPDATE_MARKER)
50
+ object_update(line)
51
+ elsif line[0] == TIME_UPDATE_MARKER
52
+ time_update(line)
53
+ elsif line[0] == OBJECT_DELETION_MARKER
54
+ object_deletion(line)
55
+ elsif line[0..1] == GLOBAL_PROPERTY_MARKER
56
+ global_property(line)
57
+ end
58
+ end
59
+
60
+ def object_update(line)
61
+ result = Parser.new.parse_object_update(line)
62
+ @processor.update_object(result) if result
63
+ end
64
+
65
+ def object_deletion(line)
66
+ @processor.delete_object line[1..-1]
67
+ end
68
+
69
+ def time_update(line)
70
+ @processor.update_time BigDecimal(line[1..-1])
71
+ end
72
+
73
+ def global_property(line)
74
+ key, value = line[2..-1].split('=')
75
+
76
+ if value.end_with?('\\')
77
+ value = [value] + read_multiline
78
+ value = value.inject([]) do |arr, array_line|
79
+ arr << array_line.delete('\\').strip
80
+ end.join("\n")
81
+ end
82
+
83
+ @processor.set_property(property: key, value: value.strip)
84
+ end
85
+
86
+ def read_multiline
87
+ array_lines = []
88
+ while (line = @input_source.readline)
89
+ array_lines << line
90
+ break unless line.end_with?('\\')
91
+ end
92
+
93
+ array_lines
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'strscan'
4
+
5
+ module TacviewClient
6
+ class Reader
7
+ # Parses an event coming out of the Tacview server when they are
8
+ # too complicated for simple extraction.
9
+ #
10
+ # Not to be used directly by library users, should only be called
11
+ # by an instance of the Reader class
12
+ #
13
+ # See https://www.tacview.net/documentation/acmi/en/ for information
14
+ # of the Tacview ACMI format this module is parsing
15
+ # @private
16
+ class Parser
17
+ HEX_NUMBER = /\h+/.freeze
18
+ OPTIONALLY_DECIMAL_NUMBER = /\d+\.?\d+/.freeze
19
+ POSITION_START_INDICATOR = /T=/.freeze
20
+ POSITION_SEPARATOR = /\|/.freeze
21
+ FIELD_SEPARATOR = /,/.freeze
22
+ END_OF_FILE = /$/.freeze
23
+ END_OF_FIELD = Regexp.union(FIELD_SEPARATOR, END_OF_FILE)
24
+
25
+ # Parse an ACMI line associated with the update of an object.
26
+ def parse_object_update(line)
27
+ @scanner = StringScanner.new(line)
28
+ @result = {}
29
+
30
+ parse_object_id
31
+
32
+ return nil if own_vehicle_message?
33
+
34
+ parse_lon_lat_alt
35
+ parse_heading
36
+ parse_fields
37
+
38
+ @result
39
+ end
40
+
41
+ private
42
+
43
+ def parse_object_id
44
+ @result[:object_id] = @scanner.scan(HEX_NUMBER)
45
+ @scanner.skip FIELD_SEPARATOR
46
+ end
47
+
48
+ # Updates without a position are always from own vehicle based on looking
49
+ # through samples. We don't care about these so ignore them
50
+ def own_vehicle_message?
51
+ @scanner.peek(2) != POSITION_START_INDICATOR.source
52
+ end
53
+
54
+ def parse_lon_lat_alt
55
+ @scanner.skip POSITION_START_INDICATOR
56
+
57
+ %i[longitude latitude altitude].each do |field|
58
+ value = @scanner.scan(OPTIONALLY_DECIMAL_NUMBER)
59
+ @result[field] = BigDecimal(value) if value
60
+ @scanner.skip POSITION_SEPARATOR
61
+ end
62
+ end
63
+
64
+ def parse_heading
65
+ return if end_of_message?
66
+
67
+ # Check to see if the heading (9th field which is 4 more
68
+ # separators from our current position) is present
69
+ if @scanner.check_until(END_OF_FIELD)
70
+ .count(POSITION_SEPARATOR.source) == 4
71
+ # If it is then we will save that as well by skipping all the
72
+ # text until the heading value
73
+ @scanner.scan_until(/\|[\|\-?0-9.]*\|/)
74
+ heading = @scanner.scan OPTIONALLY_DECIMAL_NUMBER
75
+ @result[:heading] = BigDecimal(heading) if heading
76
+ end
77
+
78
+ @scanner.scan_until END_OF_FIELD
79
+ end
80
+
81
+ def parse_fields
82
+ until end_of_message?
83
+ field = @scanner.scan_until END_OF_FIELD
84
+ field.chomp! FIELD_SEPARATOR.source
85
+
86
+ key, value = field.split('=', 2)
87
+ @result[key.downcase.to_sym] = value
88
+ end
89
+ end
90
+
91
+ def end_of_message?
92
+ @scanner.eos?
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TacviewClient
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'tacview_client/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'tacview_client'
9
+ spec.version = TacviewClient::VERSION
10
+ spec.authors = ['Jeffrey Jones']
11
+ spec.email = ['jeff@jones.be']
12
+
13
+ spec.summary = 'Tacview client written in ruby'
14
+ spec.description = 'Tacview client written in ruby'
15
+ spec.homepage = 'https://gitlab.com/overlord-bot/tacview-ruby-client'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.metadata['yard.run'] = 'yri'
26
+
27
+ spec.add_dependency 'crc', '~> 0.4'
28
+
29
+ spec.add_development_dependency 'bundler', '~> 2.0'
30
+ spec.add_development_dependency 'rake', '~> 10.0'
31
+ spec.add_development_dependency 'rspec', '~> 3.8'
32
+ spec.add_development_dependency 'rubocop', '~>0.73'
33
+ spec.add_development_dependency 'simplecov', '~>0.17'
34
+ spec.add_development_dependency 'yard', '~>0.9'
35
+ end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tacview_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeffrey Jones
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-09-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: crc
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.73'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.73'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.17'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.17'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.9'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.9'
111
+ description: Tacview client written in ruby
112
+ email:
113
+ - jeff@jones.be
114
+ executables:
115
+ - connect_tacview
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - ".gitlab-ci.yml"
121
+ - ".jrubyrc"
122
+ - ".rspec"
123
+ - ".rubocop.yml"
124
+ - ".ruby-gemset"
125
+ - ".ruby-version"
126
+ - Gemfile
127
+ - LICENSE.txt
128
+ - README.md
129
+ - Rakefile
130
+ - bin/console
131
+ - bin/setup
132
+ - exe/connect_tacview
133
+ - lib/tacview_client.rb
134
+ - lib/tacview_client/base_processor.rb
135
+ - lib/tacview_client/client.rb
136
+ - lib/tacview_client/reader.rb
137
+ - lib/tacview_client/reader/parser.rb
138
+ - lib/tacview_client/version.rb
139
+ - tacview_client.gemspec
140
+ homepage: https://gitlab.com/overlord-bot/tacview-ruby-client
141
+ licenses:
142
+ - MIT
143
+ metadata:
144
+ yard.run: yri
145
+ post_install_message:
146
+ rdoc_options: []
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ requirements: []
160
+ rubygems_version: 3.0.3
161
+ signing_key:
162
+ specification_version: 4
163
+ summary: Tacview client written in ruby
164
+ test_files: []