cheap_imports 0.0.2

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.
Files changed (58) hide show
  1. data/.bzr/README +3 -0
  2. data/.bzr/branch/branch.conf +2 -0
  3. data/.bzr/branch/format +1 -0
  4. data/.bzr/branch/last-revision +1 -0
  5. data/.bzr/branch/tags +0 -0
  6. data/.bzr/branch-format +1 -0
  7. data/.bzr/checkout/conflicts +1 -0
  8. data/.bzr/checkout/dirstate +0 -0
  9. data/.bzr/checkout/format +1 -0
  10. data/.bzr/checkout/merge-hashes +6 -0
  11. data/.bzr/repository/format +1 -0
  12. data/.bzr/repository/indices/3b0e60c570a8cfc21b7d22db489579e7.iix +0 -0
  13. data/.bzr/repository/indices/3b0e60c570a8cfc21b7d22db489579e7.rix +0 -0
  14. data/.bzr/repository/indices/3b0e60c570a8cfc21b7d22db489579e7.six +5 -0
  15. data/.bzr/repository/indices/3b0e60c570a8cfc21b7d22db489579e7.tix +0 -0
  16. data/.bzr/repository/indices/d139719d6bdd037de4ec3718dcf1c0c4.iix +0 -0
  17. data/.bzr/repository/indices/d139719d6bdd037de4ec3718dcf1c0c4.rix +0 -0
  18. data/.bzr/repository/indices/d139719d6bdd037de4ec3718dcf1c0c4.six +5 -0
  19. data/.bzr/repository/indices/d139719d6bdd037de4ec3718dcf1c0c4.tix +0 -0
  20. data/.bzr/repository/indices/d5dffeca0774033e54854302570cb330.iix +0 -0
  21. data/.bzr/repository/indices/d5dffeca0774033e54854302570cb330.rix +0 -0
  22. data/.bzr/repository/indices/d5dffeca0774033e54854302570cb330.six +5 -0
  23. data/.bzr/repository/indices/d5dffeca0774033e54854302570cb330.tix +0 -0
  24. data/.bzr/repository/indices/e5571628132ac072d270e0a249095c14.iix +0 -0
  25. data/.bzr/repository/indices/e5571628132ac072d270e0a249095c14.rix +0 -0
  26. data/.bzr/repository/indices/e5571628132ac072d270e0a249095c14.six +5 -0
  27. data/.bzr/repository/indices/e5571628132ac072d270e0a249095c14.tix +0 -0
  28. data/.bzr/repository/pack-names +0 -0
  29. data/.bzr/repository/packs/3b0e60c570a8cfc21b7d22db489579e7.pack +0 -0
  30. data/.bzr/repository/packs/d139719d6bdd037de4ec3718dcf1c0c4.pack +0 -0
  31. data/.bzr/repository/packs/d5dffeca0774033e54854302570cb330.pack +0 -0
  32. data/.bzr/repository/packs/e5571628132ac072d270e0a249095c14.pack +0 -0
  33. data/.bzrignore +2 -0
  34. data/History.txt +4 -0
  35. data/README.txt +47 -0
  36. data/Rakefile +38 -0
  37. data/bin/cheap_imports +8 -0
  38. data/lib/cheap_imports/cheap_imports.rb +154 -0
  39. data/lib/cheap_imports/import.rb +64 -0
  40. data/lib/cheap_imports.rb +52 -0
  41. data/spec/cheap_imports_spec.rb +7 -0
  42. data/spec/orms/active_record_spec.rb +20 -0
  43. data/spec/orms/datamapper_spec.rb +53 -0
  44. data/spec/spec_helper.rb +16 -0
  45. data/tasks/ann.rake +80 -0
  46. data/tasks/bones.rake +20 -0
  47. data/tasks/gem.rake +201 -0
  48. data/tasks/git.rake +40 -0
  49. data/tasks/notes.rake +27 -0
  50. data/tasks/post_load.rake +34 -0
  51. data/tasks/rdoc.rake +51 -0
  52. data/tasks/rubyforge.rake +55 -0
  53. data/tasks/setup.rb +292 -0
  54. data/tasks/spec.rake +54 -0
  55. data/tasks/svn.rake +47 -0
  56. data/tasks/test.rake +40 -0
  57. data/tasks/zentest.rake +36 -0
  58. metadata +123 -0
data/.bzr/README ADDED
@@ -0,0 +1,3 @@
1
+ This is a Bazaar control directory.
2
+ Do not change any files in this directory.
3
+ See http://bazaar-vcs.org/ for more information about Bazaar.
@@ -0,0 +1,2 @@
1
+ parent_location = http://ananelson.com/code/cheap_imports/
2
+ push_location = sftp://anaslist@ananelson.com/~/sites/ananelson.com/code/cheap_imports/
@@ -0,0 +1 @@
1
+ Bazaar Branch Format 6 (bzr 0.15)
@@ -0,0 +1 @@
1
+ 4 ana@ananelson.com-20091103102943-5xrd13o5d0grzb4j
data/.bzr/branch/tags ADDED
File without changes
@@ -0,0 +1 @@
1
+ Bazaar-NG meta directory, format 1
@@ -0,0 +1 @@
1
+ BZR conflict list format 1
Binary file
@@ -0,0 +1 @@
1
+ Bazaar Working Tree Format 4 (bzr 0.15)
@@ -0,0 +1,6 @@
1
+ BZR merge-modified list format 1
2
+ file_id: cheap_imports.rb-20090721140243-7zd6ayu1gjqkmv5d-22
3
+ hash: 8e31b226cd9163bcc12089d289659a8938b11212
4
+
5
+ file_id: cheap_imports.rb-20090721140243-7zd6ayu1gjqkmv5d-16
6
+ hash: 6d4bcc8c2461ffbf20b3535190cf8995002959da
@@ -0,0 +1 @@
1
+ Bazaar pack repository format 1 (needs bzr 0.92)
@@ -0,0 +1,5 @@
1
+ Bazaar Graph Index 1
2
+ node_ref_lists=0
3
+ key_elements=1
4
+ len=0
5
+
@@ -0,0 +1,5 @@
1
+ Bazaar Graph Index 1
2
+ node_ref_lists=0
3
+ key_elements=1
4
+ len=0
5
+
@@ -0,0 +1,5 @@
1
+ Bazaar Graph Index 1
2
+ node_ref_lists=0
3
+ key_elements=1
4
+ len=0
5
+
@@ -0,0 +1,5 @@
1
+ Bazaar Graph Index 1
2
+ node_ref_lists=0
3
+ key_elements=1
4
+ len=0
5
+
Binary file
data/.bzrignore ADDED
@@ -0,0 +1,2 @@
1
+ pkg
2
+ coverage
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 1.0.0 / 2009-07-20
2
+
3
+ * 1 major enhancement
4
+ * Birthday!
data/README.txt ADDED
@@ -0,0 +1,47 @@
1
+ cheap_imports
2
+ by Ana Nelson
3
+
4
+ == DESCRIPTION:
5
+
6
+ Imports delimited text files to Active Record or Datamapper, with validations.
7
+
8
+ == FEATURES/PROBLEMS:
9
+
10
+ * FIXME (list of features or problems)
11
+
12
+ == SYNOPSIS:
13
+
14
+ FIXME (code sample of usage)
15
+
16
+ == REQUIREMENTS:
17
+
18
+ * FIXME (list of requirements)
19
+
20
+ == INSTALL:
21
+
22
+ * FIXME (sudo gem install, anything else)
23
+
24
+ == LICENSE:
25
+
26
+ (The MIT License)
27
+
28
+ Copyright (c) 2008 FIXME (different license?)
29
+
30
+ Permission is hereby granted, free of charge, to any person obtaining
31
+ a copy of this software and associated documentation files (the
32
+ 'Software'), to deal in the Software without restriction, including
33
+ without limitation the rights to use, copy, modify, merge, publish,
34
+ distribute, sublicense, and/or sell copies of the Software, and to
35
+ permit persons to whom the Software is furnished to do so, subject to
36
+ the following conditions:
37
+
38
+ The above copyright notice and this permission notice shall be
39
+ included in all copies or substantial portions of the Software.
40
+
41
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
42
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
43
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
44
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
45
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
46
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
47
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+
5
+ begin
6
+ require 'bones'
7
+ Bones.setup
8
+ rescue LoadError
9
+ begin
10
+ load 'tasks/setup.rb'
11
+ rescue LoadError
12
+ raise RuntimeError, '### please install the "bones" gem ###'
13
+ end
14
+ end
15
+
16
+ ensure_in_path 'lib'
17
+ require 'cheap_imports'
18
+
19
+ PROJ.name = 'cheap_imports'
20
+ PROJ.authors = 'Ana Nelson'
21
+ PROJ.email = 'ana@ananelson.com'
22
+ PROJ.url = 'http://ananelson.com'
23
+ PROJ.version = CheapImports::VERSION
24
+ PROJ.rubyforge.name = 'cheap_imports'
25
+
26
+ PROJ.spec.opts << '--color'
27
+
28
+ require 'rake'
29
+ require 'spec/rake/spectask'
30
+ require 'spec/rake/verify_rcov'
31
+
32
+ task :default => :verify_rcov
33
+
34
+ desc "Verify RCOV coverage above threshold"
35
+ RCov::VerifyTask.new(:verify_rcov => 'spec:rcov') do |t|
36
+ t.threshold = 87.7
37
+ t.index_html = 'coverage/index.html'
38
+ end
data/bin/cheap_imports ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(
4
+ File.join(File.dirname(__FILE__), %w[.. lib cheap_imports]))
5
+
6
+ # Put your code here
7
+
8
+ # EOF
@@ -0,0 +1,154 @@
1
+ module CheapImports
2
+ def self.included(base)
3
+ base.extend CheapImportsClassMethods
4
+
5
+ # Detect what ORM we are using.
6
+ if base.superclass.name == "ActiveRecord::Base"
7
+ @cheap_imports_orm = "ActiveRecord"
8
+ elsif base.included_modules.collect {|m| m.name }.include?("DataMapper::Resource")
9
+ @cheap_imports_orm = "DataMapper"
10
+ base.extend DataMapperMethods
11
+ else
12
+ raise "unknown ORM!"
13
+ end
14
+ end
15
+
16
+ module DataMapperMethods
17
+ def column_names
18
+ properties.collect {|p| p.name.to_s }
19
+ end
20
+
21
+ def columns
22
+ properties
23
+ end
24
+ end
25
+
26
+ module CheapImportsClassMethods
27
+ def init_recognizable_hashes
28
+ default_hash = {}
29
+ column_names.each do |n|
30
+ next if n === 'id'
31
+ default_hash[n.to_sym] = n
32
+ end
33
+ raise "default hash is empty" if default_hash.empty?
34
+ @recognizable_hashes ||= {:default => default_hash}
35
+ end
36
+
37
+ # Add a new import definition.
38
+ #
39
+ # imports :my_descriptive_hash_name => {
40
+ # :database_column_name => "CORRESPONDING_TABLE_HEADER_TEXT"
41
+ # }
42
+ def imports(hash)
43
+ init_recognizable_hashes
44
+ @recognizable_hashes.merge!(hash)
45
+ end
46
+
47
+ # Don't override this. You probably want to override import_rash instead.
48
+ def import(raw, args)
49
+ style = recognize_hash_style(raw)
50
+
51
+ # Want to be able to access this data in the raw hash using either the native
52
+ # key or our nicer symbolic key.
53
+ @recognizable_hashes[style].each do |k, v|
54
+ raw[k] = raw[v]
55
+ end
56
+
57
+ hash = fetch_default_hash(raw, style)
58
+ hash[:imported_at] = args[:imported_at] if column_names.include?("imported_at")
59
+ hash[:history] = "Imported from #{style.to_s} at #{args[:imported_at].to_s}." if column_names.include?("history")
60
+ hash[:source] = style.to_s if column_names.include?("source")
61
+ import_rash(raw, args, style, hash)
62
+ end
63
+
64
+ def import_rash(raw, args, style, hash)
65
+ delete_prior hash[:imported_at]
66
+ create(hash)
67
+ end
68
+
69
+ def delete_prior(imported_at)
70
+ delete_all "imported_at < '#{imported_at}'" if column_names.include?("imported_at")
71
+ end
72
+
73
+ def fetch_default_hash(raw, style)
74
+ hash = {}
75
+ columns.each do |c|
76
+ column_name = c.name.to_sym
77
+ next if column_name == :id
78
+ keys = @recognizable_hashes[style].keys
79
+
80
+ raw_value = nil
81
+ if keys.include?(column_name)
82
+ raw_value = value_of_from(column_name, raw)
83
+ elsif keys.include?("#{name.downcase}_#{column_name}".to_sym)
84
+ raw_value = value_of_from("#{name.downcase}_#{column_name}".to_sym, raw)
85
+ end
86
+ next if raw_value.nil?
87
+
88
+ value = nil
89
+
90
+ type = c.type.to_s.downcase
91
+ case type
92
+ when "string", "text", "integer", "boolean"
93
+ if raw_value === ""
94
+ value = nil
95
+ else
96
+ value = raw_value
97
+ end
98
+ when "date", "datetime", "time"
99
+ date_format_string = @recognizable_hashes[style]["#{column_name}_format".to_sym]
100
+ date_format_string ||= @recognizable_hashes[style]["#{name.downcase}_#{column_name}_format".to_sym]
101
+ raise "nil date_format_string for #{style} #{column_name}" if date_format_string.empty?
102
+ begin
103
+ value = Date.strptime(raw_value, date_format_string) if type === "date"
104
+ value = DateTime.strptime(raw_value, date_format_string) if type === "datetime"
105
+ value = Time.strptime(raw_value, date_format_string) if type === "time"
106
+ rescue ArgumentError
107
+ value = nil
108
+ end
109
+ when "float", "decimal"
110
+ value = BigDecimal.new(raw_value.gsub(/[^0-9\.\-]/, ''))
111
+ else
112
+ raise "unhandled type: #{c.type.to_s.downcase}"
113
+ end
114
+ hash[column_name] = value
115
+ end
116
+ hash
117
+ end
118
+
119
+ # Fetch a row from the raw value hash.
120
+ def value_of_from(field_name, value_hash)
121
+ specification = @recognizable_hashes[recognize_hash_style(value_hash)]
122
+ raise "specification hash should not be nil!" if specification.nil?
123
+ value = value_hash[specification[field_name]].to_s.strip
124
+ block_given? ? yield(value) : value
125
+ end
126
+
127
+ # Can we import this type of hash?
128
+ # All classes have a default hash so we can always import data which was previously exported from the database.
129
+ # TODO Get rid of nasty workaround for dated_on_format keys.
130
+ # Return the name of style, or false if no style is matched.
131
+ def recognize_hash_style(raw_hash, debug = false)
132
+ @recognizable_hashes.each do |s, h|
133
+ puts "trying style: #{s} in class #{self.name}" if debug
134
+
135
+ match = true
136
+ h.each do |k, v|
137
+ this_key_matches = (raw_hash.has_key?(v.to_s) || v.to_s =~ /^\%/)
138
+ puts "key: #{k} matches: #{this_key_matches}" if debug
139
+ match = match && this_key_matches
140
+ end
141
+
142
+ puts "overall match: #{match}" if debug
143
+ return s if match
144
+ end
145
+ false
146
+ end
147
+ end
148
+ end
149
+
150
+ class NilClass
151
+ def id_or_nil
152
+ nil
153
+ end
154
+ end
@@ -0,0 +1,64 @@
1
+ module Import
2
+ # Feel free to add extra delimiters to this array.
3
+ DELIMITERS = ["\t", '|', ","]
4
+
5
+ def Import.import(klasses, params_data, args = {}, debug = false)
6
+ args.merge!({:imported_at => Time.now.strftime("%Y-%m-%d %H:%M:%S")}) unless args.has_key?(:imported_at)
7
+ klasses = [klasses] unless klasses.is_a?(Array)
8
+ delim = auto_detect_delimiter(params_data.to_s)
9
+ header_row = nil
10
+
11
+ objects = []
12
+
13
+ FasterCSV.parse(params_data, {:col_sep => delim, :row_sep => :auto}) do |row|
14
+ next if row.join("") !~ /\w/ # Skip blank rows.
15
+
16
+ header_row ||= row
17
+ next if header_row.join("") === row.join("") # Skip over duplicate header rows.
18
+
19
+ # Clean up the data, get rid of leading/trailing spaces.
20
+ row.collect {|str| str.to_s.strip!}
21
+
22
+ h = Hash[*header_row.zip(row).flatten]
23
+ klasses.each do |c|
24
+ style = c.recognize_hash_style(h, debug)
25
+ objects << c.import(h, args) if style
26
+ end
27
+ end
28
+
29
+ # Return an array of everything that was imported in this batch.
30
+ objects
31
+ end
32
+
33
+ def Import.import_from_file(klasses, filename, debug = false)
34
+ args = {}
35
+ args[:date] = Date.strptime($1) if filename =~ /([0-9]{4}-[0-9]{2}-[0-9]{2})/
36
+ import(klasses, File.read(filename), args, debug)
37
+ end
38
+
39
+ private
40
+
41
+ def Import.import_if_valid_else_ignore
42
+ begin
43
+ yield
44
+ rescue Exception => e
45
+ puts e.inspect
46
+ return nil
47
+ end
48
+ end
49
+
50
+ # Guess the correct text file column delimiter. We look at the first row
51
+ # of data and the most common character from the DELIMITERS array is
52
+ # assumed to be the delimiter. You can add additional characters to the
53
+ # DELIMITERS array but to avoid the risk of false positives it's probably
54
+ # best not to add too many.
55
+ def Import.auto_detect_delimiter(sample_text)
56
+ sample_text = sample_text.split(/$/)[0] || ""
57
+ delim = DELIMITERS[0]
58
+ len = DELIMITERS.length
59
+ DELIMITERS[1-len, len-1].each do |char|
60
+ delim = char if sample_text.count(char) > sample_text.count(delim)
61
+ end
62
+ delim
63
+ end
64
+ end
@@ -0,0 +1,52 @@
1
+ module CheapImports
2
+
3
+ # :stopdoc:
4
+ VERSION = '0.0.2'
5
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
6
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
7
+ # :startdoc:
8
+
9
+ # Returns the version string for the library.
10
+ #
11
+ def self.version
12
+ VERSION
13
+ end
14
+
15
+ # Returns the library path for the module. If any arguments are given,
16
+ # they will be joined to the end of the libray path using
17
+ # <tt>File.join</tt>.
18
+ #
19
+ def self.libpath( *args )
20
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
21
+ end
22
+
23
+ # Returns the lpath for the module. If any arguments are given,
24
+ # they will be joined to the end of the path using
25
+ # <tt>File.join</tt>.
26
+ #
27
+ def self.path( *args )
28
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
29
+ end
30
+
31
+ # Utility method used to require all files ending in .rb that lie in the
32
+ # directory below this file that has the same name as the filename passed
33
+ # in. Optionally, a specific _directory_ name can be passed in such that
34
+ # the _filename_ does not have to be equivalent to the directory.
35
+ #
36
+ def self.require_all_libs_relative_to( fname, dir = nil )
37
+ dir ||= ::File.basename(fname, '.*')
38
+ search_me = ::File.expand_path(
39
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
40
+
41
+ Dir.glob(search_me).sort.each {|rb| require rb}
42
+ end
43
+
44
+ end # module CheapImports
45
+
46
+ CheapImports.require_all_libs_relative_to(__FILE__)
47
+
48
+ begin
49
+ require "fastercsv"
50
+ rescue LoadError
51
+ raise "FasterCSV is required!" unless RUBY_VERSION >= "1.9.0"
52
+ end
@@ -0,0 +1,7 @@
1
+
2
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
3
+
4
+ describe CheapImports do
5
+ end
6
+
7
+ # EOF
@@ -0,0 +1,20 @@
1
+ # class NewClass < ActiveRecord::Base
2
+ # validates_presence_of :code
3
+ # validates_uniqueness_of :code
4
+ # end
5
+ #
6
+ # context "is_recognized_hash_style" do
7
+ # setup do
8
+ # NewClass.connection.execute("CREATE TABLE new_classes (id INT AUTO_INCREMENT, code TEXT, name TEXT, PRIMARY KEY(id));")
9
+ # @obj = NewClass.create(:code => 'ABC', :name => 'Full Object Name')
10
+ # @hsh = {:newclass_code => "DEF", :newclass_name => "Another Object Name"}
11
+ # end
12
+ #
13
+ # # specify "should raise an error when called on a class with no recognize_hash_style method" do
14
+ # # lambda { NewClass.is_recognized_hash_style?(@hsh) }.should raise_error(RuntimeError, "NewClass does not have a recognize_hash_style method")
15
+ # # end
16
+ #
17
+ # teardown do
18
+ # NewClass.connection.execute("DROP TABLE new_classes;")
19
+ # end
20
+ # end
@@ -0,0 +1,53 @@
1
+ require File.join(File.dirname(__FILE__), '..', %w[spec_helper])
2
+
3
+ require "dm-core"
4
+ DataMapper.setup(:default, 'sqlite3::memory:')
5
+
6
+ class Cat
7
+ include DataMapper::Resource
8
+ property :id, Serial
9
+ property :name, String
10
+ property :age, Integer
11
+
12
+ include CheapImports
13
+ end
14
+
15
+ class Appointment
16
+ include DataMapper::Resource
17
+ property :id, Serial
18
+ property :cat_id, Integer
19
+ property :starts_at, DateTime
20
+
21
+ include CheapImports
22
+ imports :legacy_data => {
23
+ :starts_at => "APPOINTMENT START TIME",
24
+ :starts_at_format => "%d %b %Y %H:%M",
25
+ :cat_name => "ANIMAL IDENTIFIER"
26
+ }
27
+ end
28
+
29
+ DataMapper.auto_migrate!
30
+
31
+ describe Cat, "importing default" do
32
+ it "should import without any config" do
33
+ data = %{name\tage\nFluffy\t7}
34
+ imported_cats = Import.import(Cat, data)
35
+
36
+ cats_named_fluffy = Cat.all(:name => "Fluffy")
37
+
38
+ cats_named_fluffy.should have(1).items
39
+ cats_named_fluffy.first.age.should eql(7)
40
+ end
41
+ end
42
+
43
+ describe Appointment, "importing a specified config" do
44
+ it "should description" do
45
+ data = %{ANIMAL IDENTIFIER,APPOINTMENT START TIME\n}
46
+ data << %{Fluffy,5 Jan 2009 10:30\n}
47
+
48
+ appointments = Import.import(Appointment, data)
49
+ a = appointments.first
50
+
51
+ a.starts_at.should eql(DateTime.new(2009, 1, 5, 10, 30))
52
+ end
53
+ end
@@ -0,0 +1,16 @@
1
+
2
+ require File.expand_path(
3
+ File.join(File.dirname(__FILE__), %w[.. lib cheap_imports]))
4
+
5
+ Spec::Runner.configure do |config|
6
+ # == Mock Framework
7
+ #
8
+ # RSpec uses it's own mocking framework by default. If you prefer to
9
+ # use mocha, flexmock or RR, uncomment the appropriate line:
10
+ #
11
+ # config.mock_with :mocha
12
+ # config.mock_with :flexmock
13
+ # config.mock_with :rr
14
+ end
15
+
16
+ # EOF
data/tasks/ann.rake ADDED
@@ -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