map-fields 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.
@@ -0,0 +1,3 @@
1
+ === 0.1.0 / 2009-06-02
2
+
3
+ * Initial release
@@ -0,0 +1,107 @@
1
+ = map-fields
2
+
3
+ * http://github.com/internuity/map-fields
4
+
5
+ == DESCRIPTION:
6
+
7
+ A Rails plugin which provides a hook to preview and map the fields of an uploaded CSV file to a pre-defined schema
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Captures the post and provides an intermediate view where a user can map their data to the expected schema
12
+ * Provides a default mapping view that can be customised
13
+ * Allows the import to be part of a larger form (The form fields are remembered through the mapping)
14
+
15
+ == SYNOPSIS:
16
+
17
+ ===Lists controller
18
+ class ListsController < AppliactionController
19
+ map_fields :create, ['Title', 'First name', 'Last name'], :file_field => :file, :params => [:list]
20
+
21
+ def index
22
+ @lists = List.find(:all)
23
+ end
24
+
25
+ def new
26
+ @list = List.new
27
+ end
28
+
29
+ def create
30
+ @list = List.new(params[:list])
31
+ if fields_mapped?
32
+ mapped_fields.each do |row|
33
+ @list.contact.create(:title => row[0],
34
+ :first_name => row[1],
35
+ :last_name => row[2])
36
+ end
37
+ flash[:notice] = 'Contact list created'
38
+ redirect_to :action => :index
39
+ else
40
+ render
41
+ end
42
+ rescue MapFields::InconsistentStateError
43
+ flash[:error] = 'Please try again'
44
+ redirect_to :action => :new
45
+ rescue MapFields::MissingFileContentsError
46
+ flash[:error] = 'Please upload a file'
47
+ redirect_to :action => :new
48
+ end
49
+ end
50
+
51
+ ===New view (new.html.erb)
52
+ <h1>Import a new List</h1>
53
+ <% form_for :list, :html => {:multipart => true} do |form| %>
54
+ <div class="field">
55
+ <%= form.label :name %>
56
+ <%= form.text_field :name %>
57
+ </div>
58
+ <div class="field">
59
+ <label for="file">File</label>
60
+ <%= file_field_tag 'file' %>
61
+ </div>
62
+ <div class="buttons">
63
+ <%= form.submit 'Import' %>
64
+ </div>
65
+ <% end %>
66
+
67
+ ===Create view (create.html.erb)
68
+ <h1>Import a new List</h1>
69
+ <p>Please map the details you're importing</p>
70
+ =render :partial => 'map_fields/map_fields'
71
+
72
+ == REQUIREMENTS:
73
+
74
+ * FasterCSV
75
+
76
+ == INSTALL:
77
+
78
+ sudo gem install map-fields
79
+
80
+ or
81
+
82
+ ./script/plugin install git://github.com/internuity/map-fields.git
83
+
84
+ == LICENSE:
85
+
86
+ (The MIT License)
87
+
88
+ Copyright (c) 2009
89
+
90
+ Permission is hereby granted, free of charge, to any person obtaining
91
+ a copy of this software and associated documentation files (the
92
+ 'Software'), to deal in the Software without restriction, including
93
+ without limitation the rights to use, copy, modify, merge, publish,
94
+ distribute, sublicense, and/or sell copies of the Software, and to
95
+ permit persons to whom the Software is furnished to do so, subject to
96
+ the following conditions:
97
+
98
+ The above copyright notice and this permission notice shall be
99
+ included in all copies or substantial portions of the Software.
100
+
101
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
102
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
103
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
104
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
105
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
106
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
107
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'map_fields'
@@ -0,0 +1,164 @@
1
+ require 'fastercsv'
2
+
3
+ module MapFields
4
+ VERSION = '1.0.0'
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ def map_fields
11
+ default_options = {
12
+ :file_field => 'file',
13
+ :params => []
14
+ }
15
+ options = default_options.merge(
16
+ self.class.read_inheritable_attribute(:map_fields_options)
17
+ )
18
+
19
+ if session[:map_fields].nil? || params[options[:file_field]]
20
+ session[:map_fields] = {}
21
+ if params[options[:file_field]].blank?
22
+ @map_fields_error = MissingFileContentsError
23
+ return
24
+ end
25
+
26
+ file_field = params[options[:file_field]]
27
+
28
+ temp_path = File.join(Dir::tmpdir, "map_fields_#{Time.now.to_i}_#{$$}")
29
+ File.open(temp_path, 'wb') do |f|
30
+ f.write file_field.read
31
+ end
32
+
33
+ session[:map_fields][:file] = temp_path
34
+
35
+ @rows = []
36
+ FasterCSV.foreach(temp_path) do |row|
37
+ @rows << row
38
+ break if @rows.size == 10
39
+ end
40
+ expected_fields = self.class.read_inheritable_attribute(:map_fields_fields)
41
+ @fields = ([nil] + expected_fields).inject([]){ |o, e| o << [e, o.size]}
42
+ @parameters = []
43
+ options[:params].each do |param|
44
+ @parameters += ParamsParser.parse(params, param)
45
+ end
46
+ else
47
+ if session[:map_fields][:file].nil? || params[:fields].nil?
48
+ session[:map_fields] = nil
49
+ @map_fields_error = InconsistentStateError
50
+ else
51
+ @mapped_fields = MappedFields.new(session[:map_fields][:file],
52
+ params[:fields],
53
+ params[:ignore_first_row])
54
+ end
55
+ end
56
+ end
57
+
58
+ def mapped_fields
59
+ @mapped_fields
60
+ end
61
+
62
+ def fields_mapped?
63
+ raise @map_fields_error if @map_fields_error
64
+ @mapped_fields
65
+ end
66
+
67
+ def map_field_parameters(&block)
68
+
69
+ end
70
+
71
+ def map_fields_cleanup
72
+ if @mapped_fields
73
+ if session[:map_fields][:file]
74
+ File.delete(session[:map_fields][:file])
75
+ end
76
+ session[:map_fields] = nil
77
+ @mapped_fields = nil
78
+ @map_fields_error = nil
79
+ end
80
+ end
81
+
82
+ module ClassMethods
83
+ def map_fields(action, fields, options = {})
84
+ write_inheritable_array(:map_fields_fields, fields)
85
+ write_inheritable_attribute(:map_fields_options, options)
86
+ before_filter :map_fields, :only => action
87
+ after_filter :map_fields_cleanup, :only => action
88
+ end
89
+ end
90
+
91
+ class MappedFields
92
+ def initialize(file, mapping, ignore_first_row)
93
+ @file = file
94
+ @mapping = {}
95
+ @ignore_first_row = ignore_first_row
96
+ mapping.each do |k,v|
97
+ @mapping[v.to_i - 1] = k.to_i - 1 unless v.to_i == 0
98
+ end
99
+ end
100
+
101
+ def each
102
+ row_number = 1
103
+ FasterCSV.foreach(@file) do |csv_row|
104
+ unless row_number == 1 && @ignore_first_row
105
+ row = []
106
+ @mapping.each do |k,v|
107
+ row[k] = csv_row[v]
108
+ end
109
+ row.class.send(:define_method, :number) { row_number }
110
+ yield(row)
111
+ end
112
+ row_number += 1
113
+ end
114
+ end
115
+ end
116
+
117
+ class InconsistentStateError < StandardError
118
+ end
119
+
120
+ class MissingFileContentsError < StandardError
121
+ end
122
+
123
+ class ParamsParser
124
+ def self.parse(params, field = nil)
125
+ result = []
126
+ params.each do |key,value|
127
+ if field.nil? || field.to_s == key.to_s
128
+ check_values(value) do |k,v|
129
+ result << ["#{key.to_s}#{k}", v]
130
+ end
131
+ end
132
+ end
133
+ result
134
+ end
135
+
136
+ private
137
+ def self.check_values(value, &block)
138
+ result = []
139
+ if value.kind_of?(Hash)
140
+ value.each do |k,v|
141
+ check_values(v) do |k2,v2|
142
+ result << ["[#{k.to_s}]#{k2}", v2]
143
+ end
144
+ end
145
+ elsif value.kind_of?(Array)
146
+ value.each do |v|
147
+ check_values(v) do |k2, v2|
148
+ result << ["[]#{k2}", v2]
149
+ end
150
+ end
151
+ else
152
+ result << ["", value]
153
+ end
154
+ result.each do |arr|
155
+ yield arr[0], arr[1]
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ if defined?(Rails) and defined?(ActionController)
162
+ ActionController::Base.send(:include, MapFields)
163
+ ActionController::Base.view_paths.push File.expand_path(File.join(File.dirname(__FILE__), '..', 'views'))
164
+ end
@@ -0,0 +1,80 @@
1
+
2
+ begin
3
+ require 'bones/smtp_tls'
4
+ rescue LoadError
5
+ require 'net/smtp'
6
+ end
7
+ require 'time'
8
+
9
+ namespace :ann do
10
+
11
+ # A prerequisites task that all other tasks depend upon
12
+ task :prereqs
13
+
14
+ file PROJ.ann.file do
15
+ ann = PROJ.ann
16
+ puts "Generating #{ann.file}"
17
+ File.open(ann.file,'w') do |fd|
18
+ fd.puts("#{PROJ.name} version #{PROJ.version}")
19
+ fd.puts(" by #{Array(PROJ.authors).first}") if PROJ.authors
20
+ fd.puts(" #{PROJ.url}") if PROJ.url.valid?
21
+ fd.puts(" (the \"#{PROJ.release_name}\" release)") if PROJ.release_name
22
+ fd.puts
23
+ fd.puts("== DESCRIPTION")
24
+ fd.puts
25
+ fd.puts(PROJ.description)
26
+ fd.puts
27
+ fd.puts(PROJ.changes.sub(%r/^.*$/, '== CHANGES'))
28
+ fd.puts
29
+ ann.paragraphs.each do |p|
30
+ fd.puts "== #{p.upcase}"
31
+ fd.puts
32
+ fd.puts paragraphs_of(PROJ.readme_file, p).join("\n\n")
33
+ fd.puts
34
+ end
35
+ fd.puts ann.text if ann.text
36
+ end
37
+ end
38
+
39
+ desc "Create an announcement file"
40
+ task :announcement => ['ann:prereqs', PROJ.ann.file]
41
+
42
+ desc "Send an email announcement"
43
+ task :email => ['ann:prereqs', PROJ.ann.file] do
44
+ ann = PROJ.ann
45
+ from = ann.email[:from] || Array(PROJ.authors).first || PROJ.email
46
+ to = Array(ann.email[:to])
47
+
48
+ ### build a mail header for RFC 822
49
+ rfc822msg = "From: #{from}\n"
50
+ rfc822msg << "To: #{to.join(',')}\n"
51
+ rfc822msg << "Subject: [ANN] #{PROJ.name} #{PROJ.version}"
52
+ rfc822msg << " (#{PROJ.release_name})" if PROJ.release_name
53
+ rfc822msg << "\n"
54
+ rfc822msg << "Date: #{Time.new.rfc822}\n"
55
+ rfc822msg << "Message-Id: "
56
+ rfc822msg << "<#{"%.8f" % Time.now.to_f}@#{ann.email[:domain]}>\n\n"
57
+ rfc822msg << File.read(ann.file)
58
+
59
+ params = [:server, :port, :domain, :acct, :passwd, :authtype].map do |key|
60
+ ann.email[key]
61
+ end
62
+
63
+ params[3] = PROJ.email if params[3].nil?
64
+
65
+ if params[4].nil?
66
+ STDOUT.write "Please enter your e-mail password (#{params[3]}): "
67
+ params[4] = STDIN.gets.chomp
68
+ end
69
+
70
+ ### send email
71
+ Net::SMTP.start(*params) {|smtp| smtp.sendmail(rfc822msg, from, to)}
72
+ end
73
+ end # namespace :ann
74
+
75
+ desc 'Alias to ann:announcement'
76
+ task :ann => 'ann:announcement'
77
+
78
+ CLOBBER << PROJ.ann.file
79
+
80
+ # EOF
@@ -0,0 +1,20 @@
1
+
2
+ if HAVE_BONES
3
+
4
+ namespace :bones do
5
+
6
+ desc 'Show the PROJ open struct'
7
+ task :debug do |t|
8
+ atr = if t.application.top_level_tasks.length == 2
9
+ t.application.top_level_tasks.pop
10
+ end
11
+
12
+ if atr then Bones::Debug.show_attr(PROJ, atr)
13
+ else Bones::Debug.show PROJ end
14
+ end
15
+
16
+ end # namespace :bones
17
+
18
+ end # HAVE_BONES
19
+
20
+ # EOF
@@ -0,0 +1,201 @@
1
+
2
+ require 'find'
3
+ require 'rake/packagetask'
4
+ require 'rubygems/user_interaction'
5
+ require 'rubygems/builder'
6
+
7
+ module Bones
8
+ class GemPackageTask < Rake::PackageTask
9
+ # Ruby GEM spec containing the metadata for this package. The
10
+ # name, version and package_files are automatically determined
11
+ # from the GEM spec and don't need to be explicitly provided.
12
+ #
13
+ attr_accessor :gem_spec
14
+
15
+ # Tasks from the Bones gem directory
16
+ attr_reader :bones_files
17
+
18
+ # Create a GEM Package task library. Automatically define the gem
19
+ # if a block is given. If no block is supplied, then +define+
20
+ # needs to be called to define the task.
21
+ #
22
+ def initialize(gem_spec)
23
+ init(gem_spec)
24
+ yield self if block_given?
25
+ define if block_given?
26
+ end
27
+
28
+ # Initialization tasks without the "yield self" or define
29
+ # operations.
30
+ #
31
+ def init(gem)
32
+ super(gem.name, gem.version)
33
+ @gem_spec = gem
34
+ @package_files += gem_spec.files if gem_spec.files
35
+ @bones_files = []
36
+
37
+ local_setup = File.join(Dir.pwd, %w[tasks setup.rb])
38
+ if !test(?e, local_setup)
39
+ Dir.glob(::Bones.path(%w[lib bones tasks *])).each {|fn| bones_files << fn}
40
+ end
41
+ end
42
+
43
+ # Create the Rake tasks and actions specified by this
44
+ # GemPackageTask. (+define+ is automatically called if a block is
45
+ # given to +new+).
46
+ #
47
+ def define
48
+ super
49
+ task :prereqs
50
+ task :package => ['gem:prereqs', "#{package_dir_path}/#{gem_file}"]
51
+ file "#{package_dir_path}/#{gem_file}" => [package_dir_path] + package_files + bones_files do
52
+ when_writing("Creating GEM") {
53
+ chdir(package_dir_path) do
54
+ Gem::Builder.new(gem_spec).build
55
+ verbose(true) {
56
+ mv gem_file, "../#{gem_file}"
57
+ }
58
+ end
59
+ }
60
+ end
61
+
62
+ file package_dir_path => bones_files do
63
+ mkdir_p package_dir rescue nil
64
+
65
+ gem_spec.files = (gem_spec.files +
66
+ bones_files.map {|fn| File.join('tasks', File.basename(fn))}).sort
67
+
68
+ bones_files.each do |fn|
69
+ base_fn = File.join('tasks', File.basename(fn))
70
+ f = File.join(package_dir_path, base_fn)
71
+ fdir = File.dirname(f)
72
+ mkdir_p(fdir) if !File.exist?(fdir)
73
+ if File.directory?(fn)
74
+ mkdir_p(f)
75
+ else
76
+ raise "file name conflict for '#{base_fn}' (conflicts with '#{fn}')" if test(?e, f)
77
+ safe_ln(fn, f)
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ def gem_file
84
+ if @gem_spec.platform == Gem::Platform::RUBY
85
+ "#{package_name}.gem"
86
+ else
87
+ "#{package_name}-#{@gem_spec.platform}.gem"
88
+ end
89
+ end
90
+ end # class GemPackageTask
91
+ end # module Bones
92
+
93
+ namespace :gem do
94
+
95
+ PROJ.gem._spec = Gem::Specification.new do |s|
96
+ s.name = PROJ.name
97
+ s.version = PROJ.version
98
+ s.summary = PROJ.summary
99
+ s.authors = Array(PROJ.authors)
100
+ s.email = PROJ.email
101
+ s.homepage = Array(PROJ.url).first
102
+ s.rubyforge_project = PROJ.rubyforge.name
103
+
104
+ s.description = PROJ.description
105
+
106
+ PROJ.gem.dependencies.each do |dep|
107
+ s.add_dependency(*dep)
108
+ end
109
+
110
+ PROJ.gem.development_dependencies.each do |dep|
111
+ s.add_development_dependency(*dep)
112
+ end
113
+
114
+ s.files = PROJ.gem.files
115
+ s.executables = PROJ.gem.executables.map {|fn| File.basename(fn)}
116
+ s.extensions = PROJ.gem.files.grep %r/extconf\.rb$/
117
+
118
+ s.bindir = 'bin'
119
+ dirs = Dir["{#{PROJ.libs.join(',')}}"]
120
+ s.require_paths = dirs unless dirs.empty?
121
+
122
+ incl = Regexp.new(PROJ.rdoc.include.join('|'))
123
+ excl = PROJ.rdoc.exclude.dup.concat %w[\.rb$ ^(\.\/|\/)?ext]
124
+ excl = Regexp.new(excl.join('|'))
125
+ rdoc_files = PROJ.gem.files.find_all do |fn|
126
+ case fn
127
+ when excl; false
128
+ when incl; true
129
+ else false end
130
+ end
131
+ s.rdoc_options = PROJ.rdoc.opts + ['--main', PROJ.rdoc.main]
132
+ s.extra_rdoc_files = rdoc_files
133
+ s.has_rdoc = true
134
+
135
+ if test ?f, PROJ.test.file
136
+ s.test_file = PROJ.test.file
137
+ else
138
+ s.test_files = PROJ.test.files.to_a
139
+ end
140
+
141
+ # Do any extra stuff the user wants
142
+ PROJ.gem.extras.each do |msg, val|
143
+ case val
144
+ when Proc
145
+ val.call(s.send(msg))
146
+ else
147
+ s.send "#{msg}=", val
148
+ end
149
+ end
150
+ end # Gem::Specification.new
151
+
152
+ Bones::GemPackageTask.new(PROJ.gem._spec) do |pkg|
153
+ pkg.need_tar = PROJ.gem.need_tar
154
+ pkg.need_zip = PROJ.gem.need_zip
155
+ end
156
+
157
+ desc 'Show information about the gem'
158
+ task :debug => 'gem:prereqs' do
159
+ puts PROJ.gem._spec.to_ruby
160
+ end
161
+
162
+ desc 'Write the gemspec '
163
+ task :spec => 'gem:prereqs' do
164
+ File.open("#{PROJ.name}.gemspec", 'w') do |f|
165
+ f.write PROJ.gem._spec.to_ruby
166
+ end
167
+ end
168
+
169
+ desc 'Install the gem'
170
+ task :install => [:clobber, 'gem:package'] do
171
+ sh "#{SUDO} #{GEM} install --local pkg/#{PROJ.gem._spec.full_name}"
172
+
173
+ # use this version of the command for rubygems > 1.0.0
174
+ #sh "#{SUDO} #{GEM} install --no-update-sources pkg/#{PROJ.gem._spec.full_name}"
175
+ end
176
+
177
+ desc 'Uninstall the gem'
178
+ task :uninstall do
179
+ installed_list = Gem.source_index.find_name(PROJ.name)
180
+ if installed_list and installed_list.collect { |s| s.version.to_s}.include?(PROJ.version) then
181
+ sh "#{SUDO} #{GEM} uninstall --version '#{PROJ.version}' --ignore-dependencies --executables #{PROJ.name}"
182
+ end
183
+ end
184
+
185
+ desc 'Reinstall the gem'
186
+ task :reinstall => [:uninstall, :install]
187
+
188
+ desc 'Cleanup the gem'
189
+ task :cleanup do
190
+ sh "#{SUDO} #{GEM} cleanup #{PROJ.gem._spec.name}"
191
+ end
192
+ end # namespace :gem
193
+
194
+
195
+ desc 'Alias to gem:package'
196
+ task :gem => 'gem:package'
197
+
198
+ task :clobber => 'gem:clobber_package'
199
+ remove_desc_for_task 'gem:clobber_package'
200
+
201
+ # EOF