rubysync 0.1.1 → 0.2.1

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.
@@ -1,77 +1,77 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
 
4
- # == Synopsis
5
- #
6
- # Command line tool for running *rubysync* <em>A Free MetaDirectory.</em>
7
- #
8
- # == Usage
9
- #
10
- # rubysync command name [options]
11
- #
12
- # Valid commands are::
13
- # * create {name}:: Create a rubysync configuration directory
14
- #
15
- # * connector {name} -t {type} [--vault {name}] [--client {name}]
16
- # ; Create a connector of the given name in
17
- # ; the current rubysync configuration directory
18
- #
19
- # * fields {name} ; list the fields detected by the named connector
20
- #
21
- # * pipeline {name} ; Create a rubysync pipeline of the given name
22
- # ; in the current rubysync configuration directory
23
- #
24
- # * once {name}::
25
- # Execute the named pipeline within the current configuration directory once and then exit
26
- #
27
- # * example:: Show an example of how this command might be used
28
- #
29
- # == Example
30
- #
31
- # This sets up the skeleton of a configuration for importing comma delimeted
32
- # text files into an xml file.
33
- # <tt>
34
- # $ rubysync create xml_demo
35
- # $ cd xml_demo
36
- # $ rubysync connector my_csv -t csv_file
37
- # $ rubysync connector my_xml -t xml
38
- # </tt>
39
- #
40
- # You would then edit the files::
41
- #
42
- # * +connectors/my_csv_connector.rb+:: where to get the CSV files, field names, etc
43
- # * +connectors/my_xml_connector.rb+:: how to connect to your XML file.
44
- #
45
- # And enter::
46
- # <tt>
47
- # $ rubysync pipeline my_pipeline -C my_csv -V my_xml
48
- # </tt>
49
- #
50
- # You would then edit the file +pipelines/my_pipeline.rb+ to configure the
51
- # policy for synchronizing between the two connectors.
52
- #
53
- # You may then execute the pipeline in one-shot mode (daemon mode is coming)::
54
- #
55
- # <tt>
56
- # $ rubysync once my_pipeline
57
- # </tt>
58
- #
59
- # == Author
60
- # Ritchie Young, 9 to 5 Magic (http://9to5magic.com.au)
61
- #
62
- # == Copyright
63
- # Copyright (c) 2007 Ritchie Young. All rights reserved.
64
- #
65
- # This file is part of RubySync.
66
- #
67
- # RubySync is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
68
- # as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
69
- #
70
- # RubySync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
71
- # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
72
- #
73
- # You should have received a copy of the GNU General Public License along with RubySync; if not, write to the
74
- # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
4
+ # == Synopsis
5
+ #
6
+ # Command line tool for running *rubysync* <em>A Free MetaDirectory.</em>
7
+ #
8
+ # == Usage
9
+ #
10
+ # rubysync command name [options]
11
+ #
12
+ # Valid commands are::
13
+ # * create {name}:: Create a rubysync configuration directory
14
+ #
15
+ # * connector {name} -t {type} [--vault {name}] [--client {name}]
16
+ # ; Create a connector of the given name in
17
+ # ; the current rubysync configuration directory
18
+ #
19
+ # * fields {name} ; list the fields detected by the named connector
20
+ #
21
+ # * pipeline {name} ; Create a rubysync pipeline of the given name
22
+ # ; in the current rubysync configuration directory
23
+ #
24
+ # * once {name}::
25
+ # Execute the named pipeline within the current configuration directory once and then exit
26
+ #
27
+ # * example:: Show an example of how this command might be used
28
+ #
29
+ # == Example
30
+ #
31
+ # This sets up the skeleton of a configuration for importing comma delimeted
32
+ # text files into an xml file.
33
+ # <tt>
34
+ # $ rubysync create xml_demo
35
+ # $ cd xml_demo
36
+ # $ rubysync connector my_csv -t csv_file
37
+ # $ rubysync connector my_xml -t xml
38
+ # </tt>
39
+ #
40
+ # You would then edit the files::
41
+ #
42
+ # * +connectors/my_csv_connector.rb+:: where to get the CSV files, field names, etc
43
+ # * +connectors/my_xml_connector.rb+:: how to connect to your XML file.
44
+ #
45
+ # And enter::
46
+ # <tt>
47
+ # $ rubysync pipeline my_pipeline -C my_csv -V my_xml
48
+ # </tt>
49
+ #
50
+ # You would then edit the file +pipelines/my_pipeline.rb+ to configure the
51
+ # policy for synchronizing between the two connectors.
52
+ #
53
+ # You may then execute the pipeline in one-shot mode (daemon mode is coming)::
54
+ #
55
+ # <tt>
56
+ # $ rubysync once my_pipeline
57
+ # </tt>
58
+ #
59
+ # == Author
60
+ # Ritchie Young, 9 to 5 Magic (http://9to5magic.com.au)
61
+ #
62
+ # == Copyright
63
+ # Copyright (c) 2007 Ritchie Young. All rights reserved.
64
+ #
65
+ # This file is part of RubySync.
66
+ #
67
+ # RubySync is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
68
+ # as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
69
+ #
70
+ # RubySync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
71
+ # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
72
+ #
73
+ # You should have received a copy of the GNU General Public License along with RubySync; if not, write to the
74
+ # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
75
75
 
76
76
 
77
77
  lib_path = File.dirname(__FILE__) + '/../lib'
@@ -87,12 +87,15 @@ class Controller < SimpleConsole::Controller
87
87
 
88
88
  before_filter :configure_logging
89
89
 
90
- params :string => {:p => :pipe,
91
- :t => :type,
92
- :V => :vault,
93
- :C => :client},
94
- :int =>{:v => :verbose,
95
- :d => :delay}
90
+ params(
91
+ :int =>{:v => :verbose, :d => :delay},
92
+ :string => {:p => :pipe,
93
+ :t => :type,
94
+ :V => :vault,
95
+ :C => :client},
96
+ :bool =>{:n => :no_edit}
97
+ )
98
+
96
99
 
97
100
  def default
98
101
  #RDoc::usage 'Usage'
@@ -124,36 +127,41 @@ class Controller < SimpleConsole::Controller
124
127
  end
125
128
  end
126
129
 
127
-
128
-
129
130
  # Create a Rubysync project directory
130
131
  def create
131
132
  config_path = params[:id]
132
133
  ensure_dir_exists([
133
- config_path,
134
- "#{config_path}/pipelines",
135
- "#{config_path}/connectors",
136
- "#{config_path}/shared",
137
- "#{config_path}/shared/pipelines",
138
- "#{config_path}/shared/connectors",
139
- "#{config_path}/shared/lib",
140
- "#{config_path}/log",
141
- "#{config_path}/db"
142
- ])
134
+ config_path,
135
+ "#{config_path}/pipelines",
136
+ "#{config_path}/connectors",
137
+ "#{config_path}/shared",
138
+ "#{config_path}/shared/pipelines",
139
+ "#{config_path}/shared/connectors",
140
+ "#{config_path}/shared/lib",
141
+ "#{config_path}/log",
142
+ "#{config_path}/db"
143
+ ])
143
144
  end
144
145
 
145
146
  # Create a connector configuration file
146
147
  def connector
147
148
  name = params[:id]
148
149
  type = params[:type]
149
- unless name and type
150
- puts "Usage: rubysync connector connector_name -t connector_type"
150
+ unless name
151
+ puts "Usage: rubysync connector connector_name [-t connector_type]"
151
152
  return
152
153
  end
153
- if base_path
154
- File.open("#{base_path}/connectors/#{name}_connector.rb", "w") do |file|
155
- file.puts connector_template(name, type)
154
+ if base_path
155
+ filename = "#{base_path}/connectors/#{name}_connector.rb"
156
+ unless File.exists?(filename)
157
+ puts "Require -t connector_type when creating a connector" unless type
158
+ if template = connector_template(name, type)
159
+ File.open(filename, "w") do |file|
160
+ file.puts template
161
+ end
162
+ end
156
163
  end
164
+ edit filename
157
165
  else
158
166
  puts 'Change into a config dir and try again or create a config dir with "rubysync create"'
159
167
  end
@@ -167,6 +175,31 @@ class Controller < SimpleConsole::Controller
167
175
  @field_names = connector && connector.fields || []
168
176
  end
169
177
 
178
+ def show
179
+ params[:id] =~ /(.+?)\[(.+?)\]/o
180
+ connector_name = $1
181
+ path = $2
182
+ unless connector_name and path
183
+ puts "Usage: rubysync show connector[path]"
184
+ else
185
+ connector = connector_called(connector_name)
186
+ if connector
187
+ unless connector.respond_to?(:'[]')
188
+ puts "Connector '#{connector_name}' doesnt support random access."
189
+ else
190
+ connector.started
191
+ value = connector[path]
192
+ connector.stopped
193
+ unless value
194
+ puts "Path '#{path}' not found in connector '#{connector_name}'"
195
+ else
196
+ puts value.to_yaml
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+
170
203
  def pipeline
171
204
  name = params[:id]
172
205
  vault_name = params[:vault]
@@ -176,9 +209,15 @@ class Controller < SimpleConsole::Controller
176
209
  return
177
210
  end
178
211
  if base_path
179
- File.open("#{base_path}/pipelines/#{name}_pipeline.rb", "w") do |file|
180
- file.puts pipeline_template(name, vault_name, client_name)
212
+ filename = "#{base_path}/pipelines/#{name}_pipeline.rb"
213
+ unless File.exists?(filename)
214
+ if template = pipeline_template(name, vault_name, client_name)
215
+ File.open(filename, "w") do |file|
216
+ file.puts template
217
+ end
218
+ end
181
219
  end
220
+ edit filename
182
221
  else
183
222
  puts 'Change into a config dir and try again or create a config dir with "rubysync create"'
184
223
  end
@@ -187,7 +226,17 @@ class Controller < SimpleConsole::Controller
187
226
 
188
227
 
189
228
  private
190
-
229
+
230
+ def edit filename
231
+ unless params[:no_edit]
232
+ if ENV['EDITOR']
233
+ exec "#{ENV['EDITOR']} #{filename}"
234
+ else
235
+ log.warn "Set the EDITOR environment variable to enable automatic editing of config files."
236
+ end
237
+ end
238
+ end
239
+
191
240
  def configure_logging
192
241
  log_levels = [::Logger::WARN, ::Logger::INFO, ::Logger::DEBUG]
193
242
  verbosity = [(params[:verbose]||0), log_levels.size-1].min
@@ -200,8 +249,8 @@ end
200
249
  class View < SimpleConsole::View
201
250
 
202
251
 
203
- def default
204
- puts <<"END"
252
+ def default
253
+ puts <<"END"
205
254
  Usage:
206
255
 
207
256
  rubysync command name [options]
@@ -222,16 +271,20 @@ Usage:
222
271
  ; Execute the named pipeline within the current
223
272
  ; configuration directory once and then exit
224
273
 
274
+ * show {name}[{path}] ; Display the entry at path for connector specified
275
+ ; name
276
+
225
277
  * start {name} ; Execute the named pipeline
226
278
 
227
279
  * example ; Show an example of how this command might be used
228
280
 
281
+
229
282
  END
230
- end
283
+ end
231
284
 
232
285
 
233
- def example
234
- puts <<"END"
286
+ def example
287
+ puts <<"END"
235
288
  This sets up the skeleton of a configuration for importing comma delimeted
236
289
  text files into an XML file.
237
290
 
@@ -259,101 +312,138 @@ puts <<"END"
259
312
 
260
313
  $ rubysync start my
261
314
  END
262
- end
315
+ end
263
316
 
264
- def fields
265
- puts @field_names.join("\n")
266
- end
317
+ def fields
318
+ puts @field_names.join("\n")
319
+ end
267
320
 
268
321
  end
269
322
 
270
323
 
271
- def connector_template name, type
272
- type_class_name = "RubySync::Connectors::#{type.to_s.camelize}Connector"
273
- type_class = eval(type_class_name)
274
- sample_config = (type_class && type_class.respond_to?("sample_config")) ?
275
- type_class.sample_config : ""
276
- return <<-"end;"
277
- class #{name.to_s.camelize}Connector < #{type_class_name}
324
+ def connector_template name, type
325
+ return unless type_class = class_for_name("RubySync::Connectors::#{class_name_for type, 'connector'}")
326
+ sample_config = (type_class && type_class.respond_to?("sample_config")) ?
327
+ type_class.sample_config : ""
328
+ return <<-"end;"
329
+ class #{class_name_for(name, 'connector')} < #{type_class.name}
278
330
  #{sample_config}
279
331
  end
280
- end;
281
- end
332
+ end;
333
+ end
282
334
 
283
335
 
284
- def pipeline_template name, vault_name, client_name
285
- vault = (vault_name)? ::RubySync::Connectors::BaseConnector.class_for(vault_name) : nil
286
- vault_fields = vault && vault.fields || []
336
+ def pipeline_template name, vault_name, client_name
337
+ vault = (vault_name)? class_for_name("RubySync::Connectors::#{class_name_for vault_name, 'connector'}") : nil
338
+ vault_fields = vault && vault.fields || []
287
339
 
288
- client = (client_name)? ::RubySync::Connectors::BaseConnector.class_for(client_name) : nil
289
- client_fields = client && client.fields || []
340
+ client = (client_name)? class_for_name("RubySync::Connectors::#{class_name_for client_name, 'connector'}") : nil
341
+ client_fields = client && client.fields || []
290
342
 
291
- vault_specifier = (vault_name)? "vault :#{vault_name}" : "#vault :vault_connector_name"
292
- client_specifier = (client_name)? "client :#{client_name}" : "#client :client_connector_name"
293
- return <<-"end;"
343
+ return nil if vault_name && !vault or client_name && !client
344
+
345
+ vault_specifier = (vault_name)? "vault :#{vault_name}" : "#vault :vault_connector_name"
346
+ client_specifier = (client_name)? "client :#{client_name}" : "#client :client_connector_name"
347
+ return <<-"end;"
294
348
  class #{name.to_s.camelize}Pipeline < RubySync::Pipelines::BasePipeline
295
349
 
296
350
  #{client_specifier}
297
351
 
298
352
  #{vault_specifier}
299
353
 
300
- # Remove any fields that you don't want to set in the client from the vault
301
- allow_out #{allow_through(vault_fields)}
302
-
303
- # Remove any fields that you don't want to set in the vault from the client
354
+ # Remove any client fields that have no bearing on the final entry in the vault.
304
355
  allow_in #{allow_through(client_fields)}
305
356
 
357
+ # "in" means going from client to vault
306
358
  # If the client and vault have different names for the same field, define the
307
- # the mapping here. For example, if the vault has a field called "first name" and
308
- # the client has a field called givenName you may put:
359
+ # the mapping here. For example, if the vault has a field called "first name"
360
+ # and the client has a field called 'givenName' you may write:
361
+ #
309
362
  # map 'first name', 'givenName'
310
363
  #
311
- # You can also calculate the values for fields. For more info, see
364
+ # You can also calculate the values for fields. If the vault has a 'fullname'
365
+ # and the client has 'givenName' and 'surname' attributes. You might write:
366
+ #
367
+ # map(:fullname) {value_of(:givenName) + " " + value_of(:surname) }
368
+ #
369
+ # For more info, see
312
370
  # http://rubysync.org/docs/rubysync-transformations/
313
-
314
- # "in" means going from client to vault
315
- in_transform do
371
+ in_event_transform do
316
372
  #{transform_fields(vault_fields, 'vault', 'client')}
317
373
  end
318
374
 
319
- # "out" means going from vault to client
320
- out_transform do
321
- #{transform_fields(client_fields, 'client', 'vault')}
322
- end
323
-
375
+ # if the record has been successfully synchronized already, RubySync will
376
+ # already know about the association and will skip ahead to
377
+ # in_command_transform.
378
+ #
324
379
  # if the vault doesn't already hold an association for this record
325
380
  # from the client, perform a search here to see if a match can be found
381
+ # and if so, return its path.
382
+ # Default behaviour is to attempt to use the value returned by in_place.
326
383
  # in_match do
327
384
  # end
328
385
 
386
+ # If there was exactly one match, RubySync records the association and skips
387
+ # ahead to in_command_transform. Otherwise it considers creating the entry in
388
+ # the vault.
389
+
390
+ # If in_create evaluates to false, it will veto the creation of a record on
391
+ # the vault. This is good for checking you've got the required fields or only
392
+ # creating a subset of the possible records.
393
+ # in_create do
394
+ # #eg only create entries with a (somewhat) valid email address
395
+ # value_of(:email) =~ /%w+@%w+\.%w+/
396
+ # end
397
+
398
+ # Should evaluate to the path for placing a new record in the vault
399
+ # in_place do
400
+ # value_of(:vault_path_field)
401
+ # end
402
+
403
+ # in_command_transform is the same as in_event_transform but occurs after
404
+ # any in_match, in_create or in_place invocations.
405
+ # in_command_transform do
406
+ # drop_changes_to :any, :fields, :you, :dont, :want, :in, :the, :vault
407
+ # end
408
+
409
+
410
+ # End of client to vault processing
411
+ # -----------------------------------
412
+ # Start of vault to client processing
413
+
414
+ # Remove any client fields that have no bearing on the final entry in the client.
415
+ allow_out #{allow_through(vault_fields)}
416
+
417
+
418
+ # "out" means going from vault to client
419
+ # See comment for in_event_transform above
420
+ out_event_transform do
421
+ #{transform_fields(client_fields, 'client', 'vault')}
422
+ end
423
+
329
424
  # if the vault doesn't have an association linking this record to one on
330
425
  # the client, perform a search here to see if an existing client record
331
426
  # matches this record
332
427
  # out_match do
333
428
  # end
334
429
 
335
- # If this evaluates to false, it will veto the creation of a record on
336
- # the vault. Good for checking you've got the required fields etc
337
- # in_create_if do
338
- # end
339
-
340
- # if this evaluates to false for an outgoing event then it will
430
+ # if this evaluates to false for an outgoing event then it will
341
431
  # veto the creation of a new record on the client
342
- # out_create_if do
343
- # end
344
-
345
- # Should evaluate to the path for placing a new record on the vault
346
- # in_place do
432
+ # out_create do
347
433
  # end
348
434
 
349
- # Should evaluate to the path for placing a new record on the client
435
+ # Should evaluate to the path for placing a new record on the client
350
436
  # out_place do
351
437
  # end
352
438
 
439
+ # See comment for in_command_transform above
440
+ out_command_transform do
441
+ # drop_changes_to :any, :fields, :you, :dont, :want, :in, :the, :client
442
+ end
353
443
 
354
444
  end
355
- end;
356
- end
445
+ end;
446
+ end
357
447
 
358
448
  def allow_through fields
359
449
  (fields.empty? ? %w{allow these fields through} : fields).to_ruby