mtik_directory_2_address_list 1.0.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.
data/.simplecov ADDED
@@ -0,0 +1,3 @@
1
+ if ENV['COVERAGE']
2
+ SimpleCov.start
3
+ end
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mtik_directory_2_address_list.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ desc 'Run tests with simplecov enabled'
9
+ task :coverage do
10
+ ENV['COVERAGE'] = 'yes'
11
+ Rake::Task['test'].execute
12
+ end
13
+
14
+ desc 'Run tests'
15
+ task :default => [:test]
@@ -0,0 +1,68 @@
1
+ # coding: utf-8
2
+ require 'optparse'
3
+ require 'syslog'
4
+ require 'mtik_directory_2_address_list'
5
+
6
+ class Args < OptionParser
7
+ attr_reader :prefix, :quiet
8
+ attr_reader :dir, :mtik
9
+
10
+ def initialize
11
+ super 'Usage: <dir> <host> <user> <pass>'
12
+ on('-p', '--prefix PREFIX', "Mikrotik address-list prefix (default: #{@prefix = 'md2al_'})") { |str| @prefix = str }
13
+ on('-q', '--quiet', "Be quiet (default: #{@quiet = false})") { |bool| @quiet = bool }
14
+ parse!
15
+ if ARGV.size != 4
16
+ puts to_s
17
+ puts "Missing required argument"
18
+ exit(2)
19
+ else
20
+ @dir = ARGV.shift
21
+ @mtik = Hash[[:host, :user, :pass].zip(ARGV.shift(3))]
22
+ end
23
+ rescue OptionParser::ParseError => err
24
+ puts to_s
25
+ puts "Error: #{err}"
26
+ exit(2)
27
+ end
28
+ end
29
+
30
+ class Main
31
+ def initialize
32
+ args
33
+ sync
34
+ end
35
+
36
+ def args
37
+ @args ||= Args.new.tap do |args|
38
+ Syslog.open($PROGRAM_NAME, Syslog::LOG_PID, Syslog::LOG_DAEMON)
39
+ args.quiet || MtikDirectory2AddressList::Log.output(&method(:info))
40
+ end
41
+ end
42
+
43
+ def sync
44
+ MtikDirectory2AddressList.sync(args.dir, args.mtik, args.prefix)
45
+ rescue => err
46
+ err("Error: #{err} [#{err.backtrace.first}]")
47
+ sleep(30) ; retry
48
+ end
49
+
50
+ # Send +message+ to syslog and STDERR.
51
+ # @param [String] message
52
+ # @return [void]
53
+ def err(message)
54
+ Syslog.err('%s', message)
55
+ STDERR << message << "\n" if STDERR.tty?
56
+ end
57
+
58
+ # Send +message+ to STDOUT.
59
+ # @param [String] message
60
+ # @return [void]
61
+ def info(message)
62
+ STDOUT << message << "\n" if STDOUT.tty?
63
+ end
64
+ end
65
+
66
+ if __FILE__ == $PROGRAM_NAME
67
+ Main.new
68
+ end
@@ -0,0 +1,77 @@
1
+ # coding: utf-8
2
+
3
+ module MtikDirectory2AddressList
4
+ class Directory
5
+ # Manage a directory of symbolic links.
6
+ #
7
+ # The IP address is the name of a symlink link in +@path+.
8
+ # The address list name is what the symbolic link points to.
9
+ #
10
+ # @param [Hash] params
11
+ # @option params [String] :path The path to the directory containing symbolic links
12
+ def initialize(params)
13
+ @path = params[:path]
14
+ end
15
+
16
+ # Return the address list associated with the specified IP.
17
+ #
18
+ # @param [String] ip The IP to search for
19
+ #
20
+ # @return [String] The value associated with +ip+
21
+ # @return [nil] When the +ip+ is not found
22
+ def [](ip)
23
+ File.readlink(File.join(@path, ip))
24
+ rescue Errno::EINVAL
25
+ nil # Not a symlink
26
+ end
27
+
28
+ # The {#list} method will not enumerate symbolic links that do not match this regular expression.
29
+ # The {#[]} method will read any symbolic link, even if its name does not match this regular expression.
30
+ IP_RE = %r{ \A \d{1,3} \. \d{1,3} \. \d{1,3} \. \d{1,3} \z }x
31
+
32
+ # Enumerates every IP in +@path+.
33
+ #
34
+ # @return [Enumerator] Arrays with: IP, address list
35
+ def list
36
+ Enumerator.new do |y|
37
+ Dir.foreach(@path) do |de| file = File.basename(de)
38
+ (file =~ IP_RE) && (value = self[file]) && (y << [file, value])
39
+ end
40
+ end
41
+ end
42
+
43
+ # Yield whenever +@path+ is modified.
44
+ #
45
+ # @yield Right after being called
46
+ # @yield After every update to +@path+
47
+ #
48
+ # @return [void]
49
+ def watch
50
+ before = nil
51
+ loop do sleep(1)
52
+ next if before == (after = mtime)
53
+ yield ; before = after
54
+ end
55
+ end
56
+
57
+ # Watch +path+, and yield +list+ as a hash.
58
+ #
59
+ # @param [String] path The directory to watch for modifications
60
+ #
61
+ # @yieldparam Hash{String=>String} Map IP addresses to address list names
62
+ #
63
+ # @return [void]
64
+ def self.watch(path)
65
+ dir = self.new(path:path)
66
+ dir.watch do
67
+ yield(Hash[dir.list.to_a])
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def mtime
74
+ File.mtime(@path)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,18 @@
1
+ # coding: utf-8
2
+
3
+ module MtikDirectory2AddressList
4
+ module Log
5
+ module_function
6
+
7
+ # Call #output to replace this method.
8
+ def info
9
+ end
10
+
11
+ # Set the logging method.
12
+ def output(&block)
13
+ define_singleton_method(:info) do |&message|
14
+ block.call(message.call)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,149 @@
1
+ # coding: utf-8
2
+ require 'mtik'
3
+
4
+ module MtikDirectory2AddressList
5
+ class Mikrotik
6
+ class Error < StandardError ; end
7
+ class RouterError < StandardError ; end
8
+ class SyncError < StandardError ; end
9
+
10
+ def initialize(params)
11
+ @prefix = (params[:prefix] || 'md2al_')
12
+ @mtik_params = params.select { |k,_| %w(host user pass).include? k.to_s }
13
+ fetch
14
+ end
15
+
16
+ # Enumerates every IP in address lists prefixed with +@prefix+.
17
+ #
18
+ # @return [Enumerator] Arrays with: IP, address list
19
+ def list
20
+ Enumerator.new do |y|
21
+ @cache.each_value do |re|
22
+ y << [re['address'], re['list']]
23
+ end
24
+ end
25
+ end
26
+
27
+ # Add IP to address list.
28
+ #
29
+ # @param [String] ip The IP address to add
30
+ # @param [String] address_list The address list to add the IP into
31
+ #
32
+ # @raise [SyncError] When the IP already exists in the local cache
33
+ # @raise [RouterError] When the IP already exists in the Mikrotik router
34
+ #
35
+ # @return [self]
36
+ def add(ip, address_list)
37
+ if @cache.key?(ip)
38
+ raise(SyncError, "IP #{ip} already in cache")
39
+ end
40
+ id = address_list_add(ip, @prefix + address_list)
41
+ @cache[ip] = {".id"=>id, "address"=>ip, "list"=>address_list}
42
+ return self
43
+ end
44
+
45
+ # Move IP to a different address list.
46
+ #
47
+ # @param [String] ip The IP address to move
48
+ # @param [String] address_list The address list to move the IP into
49
+ #
50
+ # @raise [SyncError] When the IP is not found in the local cache
51
+ # @raise [RouterError] When the IP is not found in the Mikrotik router
52
+ #
53
+ # @return [self]
54
+ def update(ip, address_list)
55
+ unless (re = @cache[ip])
56
+ raise(SyncError, "IP #{ip} not in cache")
57
+ end
58
+ address_list_set(re['.id'], @prefix + address_list)
59
+ re['list'] = address_list
60
+ return self
61
+ end
62
+
63
+ # Remove IP from its current address list.
64
+ #
65
+ # @param [String] ip The IP address to remove
66
+ #
67
+ # @raise [SyncError] When the IP is not found in the local cache
68
+ # @raise [RouterError] When the IP is not found in the Mikrotik router
69
+ #
70
+ # @return [self]
71
+ def delete(ip)
72
+ unless (re = @cache.delete(ip))
73
+ raise(SyncError, "IP #{ip} not in cache")
74
+ end
75
+ address_list_remove(re['.id'])
76
+ return self
77
+ end
78
+
79
+ # Delete all IPs from address lists prefixed with +@prefix+.
80
+ #
81
+ # @return [self]
82
+ def clear
83
+ @cache.each_value do |re|
84
+ address_list_remove(re['.id'])
85
+ end
86
+ @cache.clear
87
+ return self
88
+ end
89
+
90
+ private
91
+
92
+ # @return [MTik::Connection]
93
+ def connection
94
+ @connection ||= MTik::Connection.new(@mtik_params).tap do
95
+ Log.info { "Connecting to Mikrotik at #{@mtik_params.inspect}" }
96
+ end
97
+ end
98
+
99
+ # @return [String, nil]
100
+ def request(*args)
101
+ Log.info { "Mikrotik request: #{args.inspect}" }
102
+ connection.get_reply_each(*args) do |request|
103
+ while (re = request.reply.shift)
104
+ Log.info { "Mikrotik reply: #{re.inspect}" }
105
+ if re.key?('!re') ; yield(re)
106
+ elsif re.key?('!done') ; return re['ret']
107
+ elsif re.key?('!trap') ; raise(RouterError, re['message'])
108
+ else raise(Error, "Unrecognized Mikrotik reply: #{re.inspect}")
109
+ end
110
+ end
111
+ end
112
+ raise(Error, "Missing reply: no !re, !done, !trap received")
113
+ end
114
+
115
+ # @return [void]
116
+ def fetch
117
+ @cache = {}
118
+ request('/ip/firewall/address-list/print', '=.proplist=.id,address,list') do |re|
119
+ re['list'].sub!(/\A#{Regexp.quote(@prefix)}/) do
120
+ @cache[re['address']] = re
121
+ next '' # replaces @prefix on re['list']
122
+ end
123
+ end
124
+ rescue
125
+ @cache = nil
126
+ raise
127
+ end
128
+
129
+ # @param [String] ip
130
+ # @param [String] address_list
131
+ # @return [String] The ID of the added item
132
+ def address_list_add(ip, address_list)
133
+ request('/ip/firewall/address-list/add', "=address=#{ip}", "=list=#{address_list}")
134
+ end
135
+
136
+ # @param [String] id
137
+ # @param [String] address_list
138
+ # @return [void]
139
+ def address_list_set(id, address_list)
140
+ request('/ip/firewall/address-list/set', "=.id=#{id}", "=list=#{address_list}")
141
+ end
142
+
143
+ # @param [String] id
144
+ # @return [void]
145
+ def address_list_remove(id)
146
+ request('/ip/firewall/address-list/remove', "=.id=#{id}")
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,3 @@
1
+ module MtikDirectory2AddressList
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,40 @@
1
+ # coding: utf-8
2
+ require 'mtik_directory_2_address_list/version'
3
+ require 'mtik_directory_2_address_list/log'
4
+ require 'mtik_directory_2_address_list/directory'
5
+ require 'mtik_directory_2_address_list/mikrotik'
6
+
7
+ module MtikDirectory2AddressList
8
+ module_function
9
+
10
+ # Synchronize a directory containing symbolic links with a Mikrotik address list.
11
+ #
12
+ # @param [String] src The directory where to look for symbolic links
13
+ # @param [Hash] dst The object passed to the +mtik+ gem (keys: +host+, +user+, +pass+)
14
+ # @param [String] prefix The prefix an address list name must have for it to be used
15
+ # @return [nil]
16
+ def sync(src, dst, prefix = "dir_")
17
+ Log.info { "Synchronizing [#{src}] with router at [#{dst[:host]}]" }
18
+ mtik = Mikrotik.new(dst.dup.merge(prefix:prefix))
19
+ Directory.watch(src) do |dm| # Directory Map {ip => address_list}
20
+ mm = Hash[mtik.list.to_a] # Mikrotik Map {ip => address_list}
21
+ dm.each_pair do |d_ip, d_address_list|
22
+ if mm.key? d_ip
23
+ if d_address_list != mm[d_ip]
24
+ Log.info { "Updating IP [#{d_ip}] to list [#{d_address_list}]" }
25
+ mtik.update(d_ip, d_address_list)
26
+ end
27
+ else
28
+ Log.info { "Adding IP [#{d_ip}] to list [#{d_address_list}]" }
29
+ mtik.add(d_ip, d_address_list)
30
+ end
31
+ end
32
+ mm.each_key do |m_ip|
33
+ unless dm.key? m_ip
34
+ Log.info { "Deleting IP [#{m_ip}]" }
35
+ mtik.delete(m_ip)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mtik_directory_2_address_list/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'mtik_directory_2_address_list'
8
+ spec.version = MtikDirectory2AddressList::VERSION
9
+ spec.authors = ['Andre Luiz dos Santos']
10
+ spec.email = ['andre.netvision.com.br@gmail.com']
11
+ spec.summary = %q{Synchronize a directory with a Mikrotik address list}
12
+
13
+ spec.files = `git ls-files -z`.split("\x0")
14
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
15
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
16
+ spec.require_paths = ['lib']
17
+
18
+ spec.add_development_dependency 'bundler', '~> 1.6'
19
+ spec.add_development_dependency 'rake', '~> 10.0'
20
+ spec.add_development_dependency 'minitest', '~> 5.5'
21
+ spec.add_development_dependency 'mocha', '~> 1.1'
22
+ spec.add_development_dependency 'simplecov', '~> 0.9'
23
+
24
+ spec.add_runtime_dependency 'mtik', '~> 4.0'
25
+ end
@@ -0,0 +1,62 @@
1
+ # coding: utf-8
2
+ require 'simplecov'
3
+ gem 'minitest'
4
+ require 'minitest/autorun'
5
+ require 'mocha/setup'
6
+ require 'mtik_directory_2_address_list'
7
+
8
+ describe "OS" do
9
+ # man READLINK(2) says:
10
+ # EINVAL The named file is not a symbolic link.
11
+ it "readlink should yield EINVAL on non-symlinks" do
12
+ begin
13
+ File.readlink("/") # "/" is always a directory
14
+ rescue => err
15
+ assert_instance_of(Errno::EINVAL, err)
16
+ end
17
+ end
18
+ end
19
+
20
+ module MtikDirectory2AddressList
21
+ describe "Directory" do
22
+ describe "sample-1" do
23
+ let :path do
24
+ "/tmp/sample-1"
25
+ end
26
+
27
+ before do
28
+ FileUtils.mkdir(path)
29
+ Dir.chdir(path) do
30
+ FileUtils.touch(%w(ignored 9.8.7.6))
31
+ File.symlink("account-name-1", "1.2.3.4")
32
+ File.symlink("account-name-2", "1.2.3.5")
33
+ end
34
+ end
35
+
36
+ after do
37
+ FileUtils.rm_rf(path)
38
+ end
39
+
40
+ it "should list two links" do
41
+ res = Directory.new(path:path).list
42
+ assert_equal([%w(1.2.3.4 account-name-1), %w(1.2.3.5 account-name-2)], res.to_a)
43
+ end
44
+
45
+ it "should yield on different mtime" do
46
+ dir = Directory.new(path:path)
47
+ dir.stubs(:sleep)
48
+ dir.stubs(:loop).multiple_yields(*4.times)
49
+ File.stubs(:mtime).with(path).returns(10, 10, 20, 20)
50
+ Directory.expects(:new).with(path:path).returns(dir)
51
+ times = 0
52
+ Directory.watch(path) do |dem| # Directory Entries Map
53
+ assert_equal({'1.2.3.4' => 'account-name-1', '1.2.3.5' => 'account-name-2'}, dem)
54
+ times += 1
55
+ end
56
+ # 'watch' should yield first right after starting,
57
+ # and then a second time when the 'mtime' changes.
58
+ assert_equal(2, times)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,113 @@
1
+ # coding: utf-8
2
+ require 'simplecov'
3
+ gem 'minitest'
4
+ require 'minitest/autorun'
5
+ require 'mocha/setup'
6
+ require 'mtik_directory_2_address_list'
7
+
8
+ begin
9
+ file = File.join(File.dirname(__FILE__), 'mikrotik.eval')
10
+ $mtik_params = eval(IO.read(file))
11
+ rescue => err
12
+ puts '*' * 79
13
+ puts "Cannot read Mikrotik configuration file: #{err}"
14
+ puts ''
15
+ puts "Create a file named mikrotik.eval for testing the Mikrotik code."
16
+ puts "Example: {host:'10.9.8.7', user:'login', pass:'123', prefix:'download_speed_'}"
17
+ puts '*' * 79
18
+ else
19
+ module MtikDirectory2AddressList
20
+ describe "Mikrotik" do
21
+ subject do
22
+ Mikrotik.new($mtik_params)
23
+ end
24
+
25
+ before do
26
+ subject.clear
27
+ end
28
+
29
+ it "#add" do
30
+ subject.add('1.2.3.4', 'address-list-name')
31
+ assert_equal([%w(1.2.3.4 address-list-name)], subject.list.to_a)
32
+ end
33
+
34
+ it "raises SyncError on duplicated IP on #add" do
35
+ subject.add('1.2.3.4', 'address-list-name')
36
+ assert_raises(Mikrotik::SyncError) { subject.add('1.2.3.4', 'address-list-name') }
37
+ end
38
+
39
+ it "#list" do
40
+ subject.add('1.2.3.4', 'address-list-name')
41
+ subject.send(:fetch)
42
+ assert_equal([%w(1.2.3.4 address-list-name)], subject.list.to_a)
43
+ end
44
+
45
+ it "#delete" do
46
+ subject.add('1.2.3.4', 'address-list-name')
47
+ subject.send(:fetch)
48
+ subject.delete('1.2.3.4')
49
+ assert_equal([], subject.list.to_a)
50
+ end
51
+
52
+ it "#delete after #add" do
53
+ subject.add('1.2.3.4', 'address-list-name')
54
+ subject.delete('1.2.3.4')
55
+ assert_equal([], subject.list.to_a)
56
+ subject.send(:fetch)
57
+ assert_equal([], subject.list.to_a)
58
+ end
59
+
60
+ it "raises SyncError on missing IP on #delete" do
61
+ assert_raises(Mikrotik::SyncError) { subject.delete('1.2.3.4') }
62
+ end
63
+
64
+ it "#update" do
65
+ subject.add('1.2.3.4', 'address-list-name')
66
+ subject.send(:fetch)
67
+ subject.update('1.2.3.4', 'brand-new-name')
68
+ assert_equal([%w(1.2.3.4 brand-new-name)], subject.list.to_a)
69
+ subject.send(:fetch)
70
+ assert_equal([%w(1.2.3.4 brand-new-name)], subject.list.to_a)
71
+ end
72
+
73
+ it "#update after #add" do
74
+ subject.add('1.2.3.4', 'address-list-name')
75
+ subject.update('1.2.3.4', 'brand-new-name')
76
+ assert_equal([%w(1.2.3.4 brand-new-name)], subject.list.to_a)
77
+ subject.send(:fetch)
78
+ assert_equal([%w(1.2.3.4 brand-new-name)], subject.list.to_a)
79
+ end
80
+
81
+ it "raises SyncError on missing IP on #update" do
82
+ assert_raises(Mikrotik::SyncError) { subject.update('1.2.3.4', 'address-list-name') }
83
+ end
84
+
85
+ it "raises RouterError on !trap" do
86
+ mtik_api = %w(/ip/firewall/address-list/remove =.id=XXX)
87
+ err = assert_raises(Mikrotik::RouterError) { subject.send(:request, *mtik_api) }
88
+ assert_equal('no such item', err.to_s) # ID XXX is impossible/invalid
89
+ end
90
+
91
+ it "raises Error on unrecognized Mikrotik reply" do
92
+ re = {'unexpected'=>nil}
93
+ request = mock(:reply => [re])
94
+ subject.send(:connection).stubs(:get_reply_each).yields(request)
95
+ err = assert_raises(Mikrotik::Error) { subject.send(:request, '/xxx') }
96
+ assert_equal('Unrecognized Mikrotik reply: {"unexpected"=>nil}', err.to_s)
97
+ end
98
+
99
+ it "raises Error on incomplete Mikrotik reply" do
100
+ subject.send(:connection).stubs(:get_reply_each)
101
+ err = assert_raises(Mikrotik::Error) { subject.send(:request, '/xxx') }
102
+ assert_equal('Missing reply: no !re, !done, !trap received', err.to_s)
103
+ end
104
+
105
+ it "nil @cache on fetch error" do
106
+ class MyError < StandardError ; end
107
+ subject.stubs(:request).raises(MyError)
108
+ assert_raises(MyError) { subject.send(:fetch) }
109
+ assert_equal(nil, subject.instance_variable_get(:@cache))
110
+ end
111
+ end
112
+ end
113
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mtik_directory_2_address_list
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Andre Luiz dos Santos
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-01-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.6'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.6'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '10.0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '10.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: minitest
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '5.5'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '5.5'
62
+ - !ruby/object:Gem::Dependency
63
+ name: mocha
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '1.1'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '1.1'
78
+ - !ruby/object:Gem::Dependency
79
+ name: simplecov
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '0.9'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '0.9'
94
+ - !ruby/object:Gem::Dependency
95
+ name: mtik
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: '4.0'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '4.0'
110
+ description:
111
+ email:
112
+ - andre.netvision.com.br@gmail.com
113
+ executables:
114
+ - mtik_directory_2_address_list
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - .simplecov
119
+ - Gemfile
120
+ - Rakefile
121
+ - bin/mtik_directory_2_address_list
122
+ - lib/mtik_directory_2_address_list.rb
123
+ - lib/mtik_directory_2_address_list/directory.rb
124
+ - lib/mtik_directory_2_address_list/log.rb
125
+ - lib/mtik_directory_2_address_list/mikrotik.rb
126
+ - lib/mtik_directory_2_address_list/version.rb
127
+ - mtik_directory_2_address_list.gemspec
128
+ - test/test_directory.rb
129
+ - test/test_mikrotik.rb
130
+ homepage:
131
+ licenses: []
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ none: false
144
+ requirements:
145
+ - - ! '>='
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubyforge_project:
150
+ rubygems_version: 1.8.23
151
+ signing_key:
152
+ specification_version: 3
153
+ summary: Synchronize a directory with a Mikrotik address list
154
+ test_files:
155
+ - test/test_directory.rb
156
+ - test/test_mikrotik.rb