messenger_pigeon 0.1.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9fdb8c895d8bb1bcbe17ca6b03c1c5b9d5817f5a
4
- data.tar.gz: 00b928c5bdb4bbdeda14f782c444ebb3c41579b4
3
+ metadata.gz: 5540adf1f417f66aeb731db15484686df5b00477
4
+ data.tar.gz: 985746fdeba649aa6113885917d6f09bd5205b11
5
5
  SHA512:
6
- metadata.gz: 99d56f47b366c771b0cd3cb1fe5c22943d2807dd26fd863e0a785aa0636fb08f0165b8738374ea5da40a383034422497ff319db63aa1bb5c2aefba1e2a226dd0
7
- data.tar.gz: 23e2a014dd88653a1e4f511f841221036fc254ce62e58d612aac538f02b44f3a09551cb4092d4bf862605a5c22a46e13b642bbd61db0b486cb30dafdcdf8efea
6
+ metadata.gz: 0f804415db00cd5e77abb607f0d1c421e7e33f3b277cac738b49981d92550dcc53eeb327444b6b41467e57195414d49ee20a451ac1a33ccb9eb22d89dd504ded
7
+ data.tar.gz: 1d6c22619511fc04f89a475e8989fa34392f076cb171a97dc945a3172ea4aa0eb8181432dc00c380851a95eb55f47156c6d9afb70a08c1373781846898270acd
data/README.md CHANGED
@@ -1,14 +1,35 @@
1
- # MessengerPigeon
1
+ # MessengerPigeon [![Travis CI](https://travis-ci.org/cshclm/MessengerPigeon.svg?branch=master)](https://travis-ci.org/cshclm/MessengerPigeon)
2
2
 
3
3
  A means of getting data from one location to another.
4
4
 
5
5
  The goal of MessengerPigeon is to provide a highly-configurable and adaptable
6
- animal to take data from any number of sources, and copy that data to any
7
- number of destinations. A pigeon may modify, drop or add to your data while
8
- in transit, but should only do these things when you ask it nicely.
6
+ animal to take record-based data from any number of sources, and copy that
7
+ data to any number of destinations. A pigeon may modify, drop or add to your
8
+ data while in transit, but should only do these things when you ask it nicely.
9
9
 
10
10
  MessengerPigeon is under heavy development.
11
11
 
12
+ ## Installation
13
+ ```
14
+ gem install messenger_pigeon
15
+ ```
16
+
17
+ ## Usage
18
+ Set up a ~/.messenger-pigeon.rc configuration file (as below) then:
19
+ ```
20
+ $ messenger-pigeon
21
+ ```
22
+
23
+ An alternate configuration file can be specified like so
24
+ ```
25
+ $ messenger-pigeon -c my-other-configuration-file
26
+ ```
27
+
28
+ Finally the names of pigeons can be given to let only those out. If no pigeons are listed, all are let-loose.
29
+ ```
30
+ $ messenger-pigeon Gemima
31
+ ```
32
+
12
33
  ## Configuration
13
34
  Example Configuration demonstrating some features:
14
35
 
@@ -19,15 +40,16 @@ Example Configuration demonstrating some features:
19
40
  * Data Filtering
20
41
  * Pre and Post-filter data transformations
21
42
 
22
- ~/.messengerpigeonrc:
43
+ ~/.messenger-pigeon.rc:
23
44
  ```ruby
24
45
  {
25
- targets: {
46
+ target: {
26
47
  'OrgMode' => {
27
- type: MessengerPigeon::OrgMode,
48
+ type: 'OrgMode',
28
49
  options: {
29
50
  file: 'clocks.org',
30
- refile_target: '%{project}',
51
+ heading_selector: '%{project}',
52
+ level_separator: ' - ',
31
53
  data_format: proc do |d|
32
54
  clock = ' CLOCK: [%{date} %{start_time}]--[%{date} %{end_time}] => %{duration}'
33
55
  clock % d
@@ -35,16 +57,16 @@ Example Configuration demonstrating some features:
35
57
  }
36
58
  }
37
59
  },
38
- sources: {
60
+ source: {
39
61
  'TimeTracker' => {
40
- type: MessengerPigeon::CSV,
62
+ type: 'CSV',
41
63
  options: {
42
64
  file_glob: 'test.csv',
43
65
  on_complete: :archive
44
66
  }
45
67
  }
46
68
  },
47
- pigeons: {
69
+ pigeon: {
48
70
  'Gemima' => {
49
71
  source: 'TimeTracker',
50
72
  filters: {
@@ -59,6 +81,9 @@ Example Configuration demonstrating some features:
59
81
  end
60
82
  },
61
83
  post_filter: {}
84
+ logger: proc do |d|
85
+ puts format('[Gemima] Logging %shrs on %s', d[:duration], d[:date])
86
+ end
62
87
  },
63
88
  target: 'OrgMode'
64
89
  }
@@ -67,14 +92,20 @@ Example Configuration demonstrating some features:
67
92
  ```
68
93
 
69
94
  ## Modules
95
+ The following modules are available:
96
+ - CSV (source only)
97
+ - SQL
98
+ - Console (target only)
99
+ - Redmine
100
+ - OrgMode (target only)
70
101
 
71
102
  ### CSV
72
103
  #### Source
73
104
  ```ruby
74
105
  ...
75
- sources: {
106
+ source: {
76
107
  'TimeTracker' => {
77
- type: MessengerPigeon::CSV,
108
+ type: 'CSV',
78
109
  options: {
79
110
  file_glob: 'test.csv',
80
111
  on_complete: :archive
@@ -86,15 +117,46 @@ sources: {
86
117
  #### Target
87
118
  Not yet implemented
88
119
 
89
- ### Console
120
+ ### SQL
121
+ Uses [Sequel](https://github.com/jeremyevans/sequel) to interface to many different types of databases.
122
+
123
+ See [the Sequel documentation](http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html) for listing of supported databases and their connection string formats.
90
124
  #### Source
91
- Not yet implemented.
125
+ ```ruby
126
+ ...
127
+ source: {
128
+ 'Mydatabase' => {
129
+ type: 'SQL',
130
+ options: {
131
+ connection_string: 'sqlite:/',
132
+ query: "SELECT * FROM artists"
133
+ }
134
+ }
135
+ }
136
+ ...
137
+ ```
138
+ #### Target
139
+ ```ruby
140
+ ...
141
+ target: {
142
+ 'Mydatabase' => {
143
+ type: 'SQL',
144
+ options: {
145
+ connection_string: 'sqlite:/',
146
+ table: 'artists'
147
+ }
148
+ }
149
+ }
150
+ ...
151
+ ```
152
+ ### Console
153
+ Target-only.
92
154
  #### Target
93
155
  ```ruby
94
156
  ...
95
- targets: {
157
+ target: {
96
158
  'Console => {
97
- type: MessengerPigeon::Console,
159
+ type: 'Console',
98
160
  options: {}
99
161
  }
100
162
  }
@@ -106,12 +168,13 @@ Not yet implemented
106
168
  #### Target
107
169
  ```ruby
108
170
  ...
109
- targets: {
171
+ target: {
110
172
  'OrgMode' => {
111
- type: MessengerPigeon::OrgMode,
173
+ type: 'OrgMode',
112
174
  options: {
113
175
  file: 'clocks.org',
114
- refile_target: '%{project}',
176
+ heading_selector: '%{project}',
177
+ level_separator: ' - ',
115
178
  data_format: proc do |d|
116
179
  clock = ' CLOCK: [%{date} %{start_time}]--[%{date} %{end_time}] => %{duration}'
117
180
  clock % d
@@ -135,9 +198,9 @@ E.g., 'time_entries' becomes 'TimeEntry'
135
198
  Example for :all to get all issues in Project 2.
136
199
  ```ruby
137
200
  ...
138
- sources: {
201
+ source: {
139
202
  'Redmine' => {
140
- type: MessengerPigeon::Redmine,
203
+ type: 'Redmine',
141
204
  options: {
142
205
  resource: "Issue",
143
206
  site: 'https://url.to.redmine.org',
@@ -157,9 +220,9 @@ sources: {
157
220
  Example for :specific to get the issue with ID = 30
158
221
  ```ruby
159
222
  ...
160
- sources: {
223
+ source: {
161
224
  'Redmine' => {
162
- type: MessengerPigeon::Redmine,
225
+ type: 'Redmine',
163
226
  options: {
164
227
  resource: "Issue",
165
228
  site: 'https://url.to.redmine.org',
@@ -180,9 +243,9 @@ keys required on the particular resource page for more information.
180
243
 
181
244
  ```ruby
182
245
  ...
183
- targets: {
246
+ target: {
184
247
  'Redmine' => {
185
- type: MessengerPigeon::Redmine,
248
+ type: 'Redmine',
186
249
  options: {
187
250
  resource: "TimeEntry",
188
251
  site: 'https://url.to.redmine.org',
@@ -191,7 +254,7 @@ targets: {
191
254
  }
192
255
  },
193
256
  },
194
- pigeons: {
257
+ pigeon: {
195
258
  'Gemima' => {
196
259
  filters: {
197
260
  issue_id: proc { |a| !a.nil? },
data/bin/messenger-pigeon CHANGED
@@ -3,4 +3,5 @@
3
3
  require 'messenger_pigeon'
4
4
 
5
5
  opts = MessengerPigeon::CLI.parse_options
6
- MessengerPigeon.flock(opts)
6
+ flock = MessengerPigeon::Flock.new(opts, ARGV)
7
+ flock.release
@@ -2,37 +2,47 @@ require 'optparse'
2
2
 
3
3
  require 'messenger_pigeon/version'
4
4
  require 'messenger_pigeon/pigeon'
5
- require 'messenger_pigeon/orgmode'
6
- require 'messenger_pigeon/csv'
7
- require 'messenger_pigeon/redmine'
8
- require 'messenger_pigeon/console'
9
5
  require 'messenger_pigeon/config'
6
+ require 'messenger_pigeon/endpoint_manager'
10
7
  require 'messenger_pigeon/cli'
11
8
 
9
+ require 'messenger_pigeon/modules/orgmode'
10
+ require 'messenger_pigeon/modules/console'
11
+ require 'messenger_pigeon/modules/redmine'
12
+ require 'messenger_pigeon/modules/csv'
13
+
12
14
  # Messenger Pigeon entry
13
15
  module MessengerPigeon
14
- module_function
16
+ # A flock of Pigeons
17
+ # Manage configuration and dispatch
18
+ class Flock
19
+ def initialize(opts, args)
20
+ @c = Config.new(opts[:config], args)
21
+ @endpoints = {
22
+ source: EndpointManager.new(:source),
23
+ target: EndpointManager.new(:target)
24
+ }
25
+ end
15
26
 
16
- def flock(opts)
17
- Config.load(opts[:config])
18
- pigeons = []
19
- Config.conf[:pigeons].each do |name, instr|
20
- next unless Config.pigeons.empty? || Config.pigeons.include?(name)
21
- s = prepare_endpoint :sources, instr[:source]
22
- t = prepare_endpoint :targets, instr[:target]
23
- pigeon = Pigeon.new s, t, instr
24
- pigeons << pigeon.fly
27
+ def release
28
+ @c.conf[:pigeon].each do |name, instructions|
29
+ next unless @c.release_pigeon? name
30
+ release_pigeon prepare_endpoints(instructions), instructions
31
+ end
32
+ @endpoints.values.each(&:finalise)
33
+ end
34
+
35
+ private
36
+
37
+ def prepare_endpoints(instructions)
38
+ @endpoints.collect do |k, v|
39
+ v.prepare instructions[k], @c.conf[k][instructions[k]]
40
+ end
25
41
  end
26
- pigeons.each(&:finalise)
27
- end
28
42
 
29
- def prepare_endpoint(endpoint, name)
30
- v = Config.conf[endpoint][name]
31
- m = v[:type]
32
- if endpoint == :sources
33
- m::Source.new v[:options]
34
- elsif endpoint == :targets
35
- m::Target.new v[:options]
43
+ def release_pigeon(eps, instructions)
44
+ pigeon = Pigeon.new(*eps, instructions)
45
+ pigeon.fly
36
46
  end
37
47
  end
38
48
  end
@@ -1,4 +1,5 @@
1
1
  module MessengerPigeon
2
+ # Helper for runs from the command line
2
3
  module CLI
3
4
  module_function
4
5
 
@@ -8,12 +9,11 @@ module MessengerPigeon
8
9
  '~/.messenger-pigeon.rc')
9
10
  }
10
11
  OptionParser.new do |opts|
11
- opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
12
+ opts.banner = "Usage: messenger-pigeon [options] [pigeons]"
12
13
  opts.on('-c', '--config file', 'Configuration file') do |f|
13
14
  options[:config] = f
14
15
  end
15
16
  end.parse!
16
- Config.pigeons = ARGV
17
17
  options
18
18
  end
19
19
  end
@@ -1,14 +1,46 @@
1
1
  module MessengerPigeon
2
2
  # MessengerPigeon Configuration
3
- module Config
4
- extend self
5
-
3
+ class Config
6
4
  attr_reader :conf
7
- attr_accessor :pigeons
8
5
 
9
- def load(config)
10
- @conf ||= {}
11
- @conf.merge!(binding.eval(File.read(config)))
6
+ def initialize(config, pigeons = [])
7
+ @conf = {}
8
+ @pigeons = pigeons
9
+ update config
10
+ end
11
+
12
+ def release_pigeon?(name)
13
+ @pigeons.empty? || @pigeons.include?(name)
14
+ end
15
+
16
+ private
17
+
18
+ def update(new_conf)
19
+ temp_conf = @conf.clone
20
+ case new_conf
21
+ when String
22
+ temp_conf.merge!(load_file new_conf)
23
+ when Hash
24
+ temp_conf.merge! new_conf
25
+ else
26
+ fail "Unsupported configuration datatype: #{new_conf.class}"
27
+ end
28
+ apply_config temp_conf
29
+ end
30
+
31
+ # TODO: should do a whole lot more checking. Config pretty expressive and
32
+ # we want to catch problems ASAP.
33
+ def apply_config(config)
34
+ [:pigeon, :target, :source].each do |setting|
35
+ fail "Configuration invalid: #{setting.to_s}" unless
36
+ config[setting] && config[setting].class == Hash &&
37
+ !config[setting].keys.empty?
38
+ end
39
+ @conf = config
40
+ end
41
+
42
+ def load_file(config)
43
+ binding.eval(File.read(File.expand_path(config)))
12
44
  end
13
45
  end
14
46
  end
@@ -0,0 +1,33 @@
1
+ module MessengerPigeon
2
+ # Create and manage instances of Sources or Destinations
3
+ class EndpointManager
4
+ attr_reader :instances
5
+
6
+ def initialize(direction)
7
+ @instances = {}
8
+ @direction = direction.to_s.capitalize
9
+ end
10
+
11
+ def prepare(name, endpoint_defn)
12
+ fail 'Endpoint definition not defined' unless endpoint_defn.class == Hash
13
+ type = endpoint_defn[:type] || fail('Endpoint type not set')
14
+ @instances[name] || build_instance(name, type, endpoint_defn[:options])
15
+ end
16
+
17
+ def direction
18
+ @direction.downcase.to_sym
19
+ end
20
+
21
+ def finalise
22
+ @instances.values.each(&:complete)
23
+ @instances = {}
24
+ end
25
+
26
+ private
27
+
28
+ def build_instance(name, type, options)
29
+ o = Kernel.const_get("MessengerPigeon").const_get("#{type}").const_get("#{@direction}")
30
+ @instances[name] = o.new(options)
31
+ end
32
+ end
33
+ end
@@ -1,14 +1,6 @@
1
-
2
1
  module MessengerPigeon
3
2
  # Console source/target module
4
3
  module Console
5
- # Source definition
6
- class Source
7
- def initialize
8
- fail 'Not Implemented'
9
- end
10
- end
11
-
12
4
  # Target definition
13
5
  class Target
14
6
  def initialize(_options)
@@ -18,7 +10,7 @@ module MessengerPigeon
18
10
  puts data
19
11
  end
20
12
 
21
- def write
13
+ def complete
22
14
  end
23
15
  end
24
16
  end
@@ -7,12 +7,12 @@ module MessengerPigeon
7
7
  class Source
8
8
  def initialize(options)
9
9
  # :file_glob
10
- @files = Dir.glob(options[:file_glob])
10
+ @files = Dir.glob(File.expand_path options[:file_glob])
11
11
  @on_complete = options[:on_complete]
12
12
  end
13
13
 
14
14
  def read
15
- @data = @files.collect { |f| read_file f }.flatten
15
+ @files.collect { |f| read_file f }.flatten
16
16
  end
17
17
 
18
18
  def complete
@@ -28,7 +28,7 @@ module MessengerPigeon
28
28
  @files.each do |f|
29
29
  archive_dir = (File.dirname f) + '/archive/'
30
30
  Dir.mkdir archive_dir unless Dir.exist? archive_dir
31
- File.rename f, archive_dir + (File.basename f)
31
+ File.rename(f, archive_dir + (File.basename f)) if File.exist? f
32
32
  end
33
33
  end
34
34
 
@@ -0,0 +1,95 @@
1
+
2
+
3
+ module MessengerPigeon
4
+ # OrgMode source/target module
5
+ module OrgMode
6
+ # Source definition
7
+ class Source
8
+ def initialize(_options = {})
9
+ fail 'Not Implemented'
10
+ end
11
+ end
12
+
13
+ # Target definition
14
+ class Target
15
+ def initialize(options = nil)
16
+ @options = options
17
+ open_file File.expand_path(options[:file])
18
+ @known_headings = known_headings
19
+ end
20
+
21
+ def filedata
22
+ (@filedata.join "\n") + "\n"
23
+ end
24
+
25
+ def known_headings
26
+ res = []
27
+ @filedata.each do |l|
28
+ /^\*+\ (?<heading>.+?)\ +(:[^ ]*:)?$/ =~ l
29
+ res.push heading if heading
30
+ end
31
+ res
32
+ end
33
+
34
+ def find_target(headings, star_count, start_line, end_line)
35
+ if headings.empty?
36
+ # Found the target
37
+ return start_line + 1
38
+ end
39
+ target_heading = headings.pop
40
+ @filedata[start_line..end_line].each do |line|
41
+ if /^\*{#{star_count}}\ #{target_heading}(\ +(:[^ ]*:)?)?$/ =~ line
42
+ return find_target(headings, star_count + 1, start_line, find_next_heading(star_count, start_line, end_line))
43
+ end
44
+ start_line += 1
45
+ end
46
+ # Left-over headings
47
+ ([target_heading] + headings).each do |heading|
48
+ @filedata.insert end_line, "#{'*' * star_count} #{heading}"
49
+ star_count += 1
50
+ end_line += 1
51
+ end
52
+ end_line
53
+ end
54
+
55
+ def find_next_heading(star_count, start_line, end_line)
56
+ @filedata[start_line + 1..end_line].each do |line|
57
+ if /^\*{#{star_count}}\ (?<heading>.+?)\ +(:[^ ]*:)?$/ =~ line
58
+ return start_line
59
+ end
60
+ start_line += 1
61
+ end
62
+ end_line
63
+ end
64
+
65
+ def update(data)
66
+ selector = @options[:heading_selector] % data
67
+ headings = selector.split(@options[:level_separator])
68
+ star_count = 1
69
+ target = find_target headings.reverse, star_count, 0, @filedata.length
70
+ data_string = @options[:data_format].call data
71
+ @filedata.insert target, data_string
72
+ end
73
+
74
+ def complete
75
+ @fd.seek 0
76
+ @fd.write filedata
77
+ @fd.close
78
+ end
79
+
80
+ private
81
+
82
+ def open_file file
83
+ if File.exist? file
84
+ @fd = File.open(file, 'r+')
85
+ @fd.seek 0
86
+ @filedata = @fd.read nil
87
+ else
88
+ @fd = File.open(file, 'w')
89
+ @filedata = ''
90
+ end
91
+ @filedata = @filedata.split(/[\n\r]/)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -3,8 +3,32 @@ require 'active_resource'
3
3
  module MessengerPigeon
4
4
  # Redmine source/target module
5
5
  module Redmine
6
+ # Module mixin for redmine activeresource init
7
+ module Init
8
+ @@known_instances = {}
9
+
10
+ def initialize(options)
11
+ @options = options
12
+ o = get_resource options[:resource]
13
+ o.site = options[:site]
14
+ o.user = options[:user]
15
+ o.password = options[:password]
16
+ o.include_root_in_json = true
17
+ o.format = JsonFormatter.new
18
+ @resource = o
19
+ end
20
+
21
+ private
22
+
23
+ def get_resource(name)
24
+ return @@known_instances[name] unless @@known_instances[name].nil?
25
+ ar_cl = Class.new ActiveResource::Base
26
+ @@known_instances[name] = Object.const_set(name, ar_cl)
27
+ end
28
+ end
29
+
6
30
  # Handle JSON returned by Redmine
7
- class RedmineFormatter
31
+ class JsonFormatter
8
32
  include ActiveResource::Formats::JsonFormat
9
33
 
10
34
  def decode(json)
@@ -23,55 +47,43 @@ module MessengerPigeon
23
47
 
24
48
  # Source definition
25
49
  class Source
26
- def initialize(options)
27
- @options = options
28
- ar_cl = Class.new ActiveResource::Base
29
- o = Object.const_set(options[:resource], ar_cl)
30
- o.site = options[:site]
31
- o.user = options[:user]
32
- o.password = options[:password]
33
- o.include_root_in_json = true
34
- o.format = RedmineFormatter.new
35
- @resource = o
36
- end
50
+ include Init
37
51
 
38
52
  def read
39
53
  if @options[:mode] == :specific
40
54
  [@resource.find(@options[:key]).attributes]
41
55
  elsif @options[:mode] == :all
42
- res = @resource.find(:all, params: @options[:params])
43
- res.map do |r|
44
- attrs = r.attributes
45
- attrs.each do |k, v|
46
- attrs[k] = v.attributes if v.respond_to? :attributes
47
- end
48
- end
56
+ read_all
49
57
  end
50
58
  end
51
59
 
52
60
  def complete
53
61
  end
62
+
63
+ private
64
+
65
+ def read_all
66
+ res = @resource.find(:all, params: @options[:params])
67
+ res.map do |r|
68
+ attrs = r.attributes
69
+ attrs.each do |k, v|
70
+ # Convert 1 level of nested attributes
71
+ attrs[k] = v.attributes if v.respond_to? :attributes
72
+ end
73
+ end
74
+ end
54
75
  end
55
76
 
56
77
  # Target definition
57
78
  class Target
58
- def initialize(options)
59
- @options = options
60
- ar_cl = Class.new ActiveResource::Base
61
- o = Object.const_set(options[:resource], ar_cl)
62
- o.site = options[:site]
63
- o.user = options[:user]
64
- o.password = options[:password]
65
- o.include_root_in_json = true
66
- @resource = o
67
- end
79
+ include Init
68
80
 
69
81
  def update(data)
70
82
  m = @resource.new data
71
83
  $stderr.puts issue.errors.full_messages unless m.save
72
84
  end
73
85
 
74
- def write
86
+ def complete
75
87
  end
76
88
  end
77
89
  end
@@ -0,0 +1,40 @@
1
+ require 'sequel'
2
+
3
+ module MessengerPigeon
4
+ # Redmine source/target module
5
+ module SQL
6
+ # Source definition
7
+ module Init
8
+ attr_accessor :conn
9
+
10
+ def initialize(options)
11
+ @options = options
12
+ cs = options[:connection_string]
13
+ @conn = Sequel.connect(cs)
14
+ end
15
+ end
16
+
17
+ class Source
18
+ include Init
19
+
20
+ def read
21
+ @conn.fetch @options[:query]
22
+ end
23
+ end
24
+
25
+ # Module mixin for redmine activeresource init
26
+ class Target
27
+ include Init
28
+
29
+ def update(data)
30
+ tbl = @conn[@options[:table].intern]
31
+ data.each do |row|
32
+ tbl.insert row
33
+ end
34
+ end
35
+
36
+ def complete
37
+ end
38
+ end
39
+ end
40
+ end
@@ -8,32 +8,33 @@ module MessengerPigeon
8
8
  @filters = pigeon_config[:filters]
9
9
  @transforms = pigeon_config[:transforms]
10
10
  @generators = pigeon_config[:generators]
11
+ @logger = pigeon_config[:logger]
11
12
  end
12
13
 
13
14
  def fly
14
15
  @source.read.each do |d|
15
16
  transforms :pre_filter, d
16
17
  generators d
17
- @target.update(transforms :post_filter, d) if filter d
18
+ if filter d
19
+ d = transforms :post_filter, d
20
+ @target.update d
21
+ @logger.call d if @logger
22
+ end
18
23
  end
19
24
  self
20
25
  end
21
26
 
22
- def finalise
23
- @target.write
24
- @source.complete
25
- end
26
-
27
27
  private
28
28
 
29
29
  def filter(data)
30
30
  return if @filters.nil?
31
31
  @filters.each do |k, v|
32
- if v.class == Regexp
32
+ case v
33
+ when Regexp
33
34
  return false unless data[k] =~ v
34
- elsif v.class == String
35
+ when String
35
36
  return false unless data[k] == v
36
- elsif v.class == Proc
37
+ when Proc
37
38
  return false unless v.call data[k]
38
39
  end
39
40
  end
@@ -41,7 +42,7 @@ module MessengerPigeon
41
42
  end
42
43
 
43
44
  def transforms(type, data)
44
- return data if @transforms.nil? || @transforms[:type].nil?
45
+ return data if @transforms.nil? || @transforms[type].nil?
45
46
  @transforms[type].each do |k, v|
46
47
  data[k] = v.call data[k]
47
48
  end
@@ -1,3 +1,3 @@
1
1
  module MessengerPigeon
2
- VERSION = '0.1.0'
2
+ VERSION = '0.3.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: messenger_pigeon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Mann
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-10 00:00:00.000000000 Z
11
+ date: 2015-09-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -42,19 +42,48 @@ dependencies:
42
42
  name: activeresource
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - '='
46
46
  - !ruby/object:Gem::Version
47
47
  version: '4.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - '='
53
53
  - !ruby/object:Gem::Version
54
54
  version: '4.0'
55
- description: The goal of MessengerPigeon is to provide a highly-configurable and general
56
- mechanism to take data from any number of sources, and copy that data to any number
57
- of destinations. Possibly after some modest filtering and transformations.
55
+ - !ruby/object:Gem::Dependency
56
+ name: sequel
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '4.26'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4.26'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.3'
83
+ description: The goal of MessengerPigeon is to provide a highly-configurable and adaptable
84
+ animal to take record-based data from any number of sources, and copy that data
85
+ to any number of destinations. A pigeon may modify, drop or add to your data while
86
+ in transit, but should only do these things when you ask it nicely.
58
87
  email:
59
88
  - chris@bitpattern.com.au
60
89
  executables:
@@ -68,11 +97,13 @@ files:
68
97
  - lib/messenger_pigeon.rb
69
98
  - lib/messenger_pigeon/cli.rb
70
99
  - lib/messenger_pigeon/config.rb
71
- - lib/messenger_pigeon/console.rb
72
- - lib/messenger_pigeon/csv.rb
73
- - lib/messenger_pigeon/orgmode.rb
100
+ - lib/messenger_pigeon/endpoint_manager.rb
101
+ - lib/messenger_pigeon/modules/console.rb
102
+ - lib/messenger_pigeon/modules/csv.rb
103
+ - lib/messenger_pigeon/modules/orgmode.rb
104
+ - lib/messenger_pigeon/modules/redmine.rb
105
+ - lib/messenger_pigeon/modules/sql.rb
74
106
  - lib/messenger_pigeon/pigeon.rb
75
- - lib/messenger_pigeon/redmine.rb
76
107
  - lib/messenger_pigeon/version.rb
77
108
  homepage: https://github.com/cshclm/MessengerPigeon
78
109
  licenses:
@@ -86,7 +117,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
86
117
  requirements:
87
118
  - - ">="
88
119
  - !ruby/object:Gem::Version
89
- version: '0'
120
+ version: 1.9.3
90
121
  required_rubygems_version: !ruby/object:Gem::Requirement
91
122
  requirements:
92
123
  - - ">="
@@ -1,56 +0,0 @@
1
-
2
-
3
- module MessengerPigeon
4
- # OrgMode source/target module
5
- module OrgMode
6
- # Source definition
7
- class Source
8
- def initialize(_options = {})
9
- fail 'Not Implemented'
10
- end
11
- end
12
-
13
- # Target definition
14
- class Target
15
- def initialize(options = nil)
16
- @options = options
17
- if File.exist? options[:file]
18
- @fd = File.open(options[:file], 'r+')
19
- @fd.seek 0
20
- @filedata = @fd.read nil
21
- else
22
- @fd = File.open(options[:file], 'w')
23
- @filedata = ''
24
- end
25
- @known_headings = known_headings
26
- end
27
-
28
- def known_headings
29
- res = []
30
- @filedata.each_line do |l|
31
- /^\*+\ (?<heading>.+?)(:[^ ]*:)?$/ =~ l
32
- res.push heading if heading
33
- end
34
- res
35
- end
36
-
37
- def update(data)
38
- heading = @options[:refile_target] % data
39
- data_string = @options[:data_format].call data
40
- if @known_headings.include? heading
41
- @filedata.sub!(/^(\* #{heading}.*)$/,
42
- "\\1\n#{data_string}")
43
- else
44
- @filedata += "* #{heading}\n#{data_string}\n"
45
- @known_headings.push heading
46
- end
47
- end
48
-
49
- def write
50
- @fd.seek 0
51
- @fd.write @filedata
52
- @fd.close
53
- end
54
- end
55
- end
56
- end