sifttter-redux 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,135 @@
1
+ #| ======================================================
2
+ #| CliManager Module
3
+ #| Singleton to manage common CLI interfacing
4
+ #| ======================================================
5
+ module CliMessage
6
+
7
+ ERROR = 1
8
+ INFO = 2
9
+ SECTION = 3
10
+ WARNING = 4
11
+
12
+ #| ------------------------------------------------------
13
+ #| error method
14
+ #|
15
+ #| Outputs a formatted-red error message.
16
+ #| @param message The message to output
17
+ #| @return Void
18
+ #| ------------------------------------------------------
19
+ def self.error(message, addNewline = true)
20
+ if addNewline
21
+ puts "---> ERROR: #{message}".red
22
+ else
23
+ print "---> ERROR: #{message}".red
24
+ end
25
+
26
+ @@last_message_type = ERROR
27
+ end
28
+
29
+ #| ------------------------------------------------------
30
+ #| finish_message method
31
+ #|
32
+ #| Finishes a previous message by appending "DONE" in the
33
+ #| correct color.
34
+ #| @return Void
35
+ #| ------------------------------------------------------
36
+ def self.finish_message(message)
37
+ case @@last_message_type
38
+ when ERROR
39
+ puts message.red
40
+ when INFO
41
+ puts message.blue
42
+ when SECTION
43
+ puts message.green
44
+ when WARNING
45
+ puts message.yellow
46
+ end
47
+ end
48
+
49
+ #| ------------------------------------------------------
50
+ #| info method
51
+ #|
52
+ #| Outputs a formatted-blue informational message.
53
+ #| @param message The message to output
54
+ #| @return Void
55
+ #| ------------------------------------------------------
56
+ def self.info(message, addNewline = true)
57
+ if addNewline
58
+ puts "---> INFO: #{message}".blue
59
+ else
60
+ print "---> INFO: #{message}".blue
61
+ end
62
+
63
+ @@last_message_type = INFO
64
+ end
65
+
66
+ #| ------------------------------------------------------
67
+ #| prompt method
68
+ #|
69
+ #| Outputs a prompt, collects the user's response, and
70
+ #| returns it.
71
+ #| @param prompt The prompt to output
72
+ #| @param default The default option
73
+ #| @return String
74
+ #| ------------------------------------------------------
75
+ def self.prompt(prompt, default)
76
+ print "#{prompt} [default: #{default}]: "
77
+ choice = $stdin.gets.chomp
78
+ if choice.empty?
79
+ return default
80
+ else
81
+ return choice
82
+ end
83
+ end
84
+
85
+ #| ------------------------------------------------------
86
+ #| section method
87
+ #|
88
+ #| Outputs a formatted-orange section message.
89
+ #| @param message The message to output
90
+ #| @return Void
91
+ #| ------------------------------------------------------
92
+ def self.section(message, addNewline = true)
93
+ if addNewline
94
+ puts "#### #{message}".green
95
+ else
96
+ print "#### #{message}".green
97
+ end
98
+
99
+ @@last_message_type = SECTION
100
+ end
101
+
102
+ #| ------------------------------------------------------
103
+ #| success method
104
+ #|
105
+ #| Outputs a formatted-green success message.
106
+ #| @param message The message to output
107
+ #| @return Void
108
+ #| ------------------------------------------------------
109
+ def self.success(message, addNewline = true)
110
+ if addNewline
111
+ puts "---> SUCCESS: #{message}".green
112
+ else
113
+ print "---> SUCCESS: #{message}".green
114
+ end
115
+
116
+ @@last_message_type = WARNING
117
+ end
118
+
119
+ #| ------------------------------------------------------
120
+ #| warning method
121
+ #|
122
+ #| Outputs a formatted-yellow warning message.
123
+ #| @param message The message to output
124
+ #| @return Void
125
+ #| ------------------------------------------------------
126
+ def self.warning(message, addNewline = true)
127
+ if addNewline
128
+ puts "---> WARNING: #{message}".yellow
129
+ else
130
+ print "---> WARNING: #{message}".yellow
131
+ end
132
+
133
+ @@last_message_type = WARNING
134
+ end
135
+ end
@@ -0,0 +1,136 @@
1
+ require 'singleton'
2
+
3
+ #| ======================================================
4
+ #| ConfigManager Class
5
+ #|
6
+ #| Singleton to manage the YAML config file
7
+ #| for this script
8
+ #| ======================================================
9
+
10
+ class ConfigManager
11
+ include Singleton
12
+
13
+ attr_accessor :configFile
14
+
15
+ #| ------------------------------------------------------
16
+ #| initialize method
17
+ #|
18
+ #| Initializes this singleton with data from the config
19
+ #| file. If the file doesn't exist, an empty hash is
20
+ #| created in anticipation of future config saves.
21
+ #| @return Void
22
+ #| ------------------------------------------------------
23
+ def initialize
24
+ @configFile = SifttterRedux::SRD_CONFIG_FILEPATH
25
+
26
+ if File.exists?(SifttterRedux::SRD_CONFIG_FILEPATH)
27
+ @data = YAML.load_file(SifttterRedux::SRD_CONFIG_FILEPATH)
28
+ @data.each do |k, v|
29
+ define_singleton_method(k) { return v }
30
+ end
31
+ else
32
+ @data = {}
33
+ end
34
+ end
35
+
36
+ #| ------------------------------------------------------
37
+ #| _dump method
38
+ #|
39
+ #| Convenience method that dumps the configuration hash's
40
+ #| data.
41
+ #| @return Void
42
+ #| ------------------------------------------------------
43
+ def _dump
44
+ puts @data
45
+ end
46
+
47
+ #| ------------------------------------------------------
48
+ #| add_to_section method
49
+ #|
50
+ #| Adds a hash to the configuration data.
51
+ #| @param hash The hash to add to configuration
52
+ #| @param section The section into which the hash goes
53
+ #| @return Void
54
+ #| ------------------------------------------------------
55
+ def add_to_section(hash, section)
56
+ unless @data.has_key?(section)
57
+ CliMessage.warning("Attempting to insert into a non-existing section: #{section}; skipping...")
58
+ return
59
+ end
60
+
61
+ @data[section].merge!(hash)
62
+ end
63
+
64
+ #| ------------------------------------------------------
65
+ #| create_section method
66
+ #|
67
+ #| Creates a new section in the configuration hash.
68
+ #| @param section The section to create
69
+ #| @return Void
70
+ #| ------------------------------------------------------
71
+ def create_section(section)
72
+ if @data.has_key?(section)
73
+ CliMessage.warning("Attempting to create existing section (#{section}); skipping...")
74
+ return
75
+ end
76
+
77
+ define_singleton_method(section) { return @data[section] }
78
+ @data.merge!(section => {})
79
+ end
80
+
81
+ #| ------------------------------------------------------
82
+ #| delete_section method
83
+ #|
84
+ #| Deletes a section in the configuration hash.
85
+ #| @param section The section to delete
86
+ #| @return Void
87
+ #| ------------------------------------------------------
88
+ def delete_section(section)
89
+ unless @data.has_key?(section)
90
+ CliMessage.warning("Attempting to delete non-existing section (#{section}); skipping...")
91
+ return
92
+ end
93
+
94
+ remove_singleton_method(section)
95
+ @data.delete(section)
96
+ end
97
+
98
+ #| ------------------------------------------------------
99
+ #| remove_singleton_method method
100
+ #|
101
+ #| Removes a hash from the configuration data based on
102
+ #| its key.
103
+ #| @param hash The hash key remove
104
+ #| @param section The section from which the key comes
105
+ #| @return Void
106
+ #| ------------------------------------------------------
107
+ def remove_from_section(key, section)
108
+ unless @data.has_key?(section) && @data[section].has_key?(key)
109
+ CliMessage.warning("Attempting to remove a non-existing key: #{section}.#{key}; skipping...")
110
+ return
111
+ end
112
+
113
+ @data[section].delete(key)
114
+ end
115
+
116
+ #| ------------------------------------------------------
117
+ #| reset method
118
+ #|
119
+ #| Clears out the configuration data by resetting the hash.
120
+ #| @return Void
121
+ #| ------------------------------------------------------
122
+ def reset
123
+ @data = {}
124
+ end
125
+
126
+ #| ------------------------------------------------------
127
+ #| save_configuration method
128
+ #|
129
+ #| Saves the configuration data to the filesystem.
130
+ #| @return File
131
+ #| ------------------------------------------------------
132
+ def save_configuration
133
+ return File.open(@configFile, 'w') { |f| f.write(@data.to_yaml) }
134
+ end
135
+
136
+ end
@@ -0,0 +1,15 @@
1
+ module SifttterRedux
2
+ VERSION = "0.2.0"
3
+
4
+ # Sifttter and Sifttter Redux Constants
5
+ SRD_CONFIG_FILEPATH = File.join(ENV['HOME'], '.sifttter_redux')
6
+ SFT_LOCAL_FILEPATH = "/tmp/sifttter"
7
+ SFT_REMOTE_FILEPATH = "/Apps/ifttt/sifttter"
8
+
9
+ # Dropbox Upload Constants
10
+ DBU_LOCAL_FILEPATH = "/usr/local/opt"
11
+
12
+ # Day One Constants
13
+ DO_REMOTE_FILEPATH = "/Apps/Day\\ One/Journal.dayone/entries"
14
+ DO_LOCAL_FILEPATH = "/tmp/dayone"
15
+ end
@@ -0,0 +1,102 @@
1
+ require 'chronic'
2
+
3
+ class DateRangeMakerError < StandardError
4
+ def initialize(msg = "You've triggered a DateRangeMakerError")
5
+ super
6
+ end
7
+ end
8
+
9
+ class BadChronicDateError < DateRangeMakerError
10
+ def initialize(msg = "Invalid date provided to Chronic...")
11
+ super
12
+ end
13
+ end
14
+
15
+ class BadDateOrderError < DateRangeMakerError
16
+ def initialize(msg = "The start date must be before or equal to the end date...")
17
+ super
18
+ end
19
+ end
20
+
21
+ class InvalidFlagsError < DateRangeMakerError
22
+ def initialize(msg = "You can't specify -t without specifying -f...")
23
+ super
24
+ end
25
+ end
26
+
27
+ #| ======================================================
28
+ #| DateRangeMaker Class
29
+ #|
30
+ #| Singleton to manage the YAML config file
31
+ #| for this script
32
+ #| ======================================================
33
+ class DateRangeMaker
34
+
35
+ def initialize
36
+
37
+ end
38
+
39
+ def last_seven_days(include_today = false)
40
+ if (include_today)
41
+ _r = (Date.today - 7..Date.today)
42
+ else
43
+ _r = (Date.today - 7...Date.today)
44
+ end
45
+
46
+ return _r
47
+ end
48
+
49
+ def range(options = {})
50
+
51
+ opts = {
52
+ :start_date => Date.today,
53
+ :end_date => nil,
54
+ :include_today => false
55
+ }
56
+
57
+ options.each do |k, v|
58
+ k = k.to_sym
59
+ raise ArgumentError, "Unknown property: #{k}" unless opts.key?(k)
60
+ opts[k] = v
61
+ end
62
+
63
+ begin
64
+ chronic_start_date = Chronic.parse(opts[:start_date]).to_date
65
+ rescue
66
+ raise BadChronicDateError.new("Invalid date provided to Chronic: #{opts[:start_date]}") if !opts[:start_date].nil?
67
+ nil
68
+ end
69
+
70
+ begin
71
+ chronic_end_date = Chronic.parse(opts[:end_date]).to_date
72
+ rescue
73
+ raise BadChronicDateError.new("Invalid date provided to Chronic: #{opts[:end_date]}") if !opts[:end_date].nil?
74
+ nil
75
+ end
76
+
77
+ raise InvalidFlagsError.new if (opts[:start_date].nil? && !opts[:end_date].nil?)
78
+ raise BadDateOrderError.new if (chronic_end_date && chronic_start_date > chronic_end_date)
79
+
80
+ if (!chronic_start_date.nil?)
81
+ if (chronic_end_date.nil?)
82
+ if (opts[:include_today])
83
+ dates = (chronic_start_date..Date.today)
84
+ else
85
+ dates = (chronic_start_date...Date.today)
86
+ end
87
+ else
88
+ dates = (chronic_start_date..chronic_end_date)
89
+ end
90
+ end
91
+
92
+ return dates
93
+ end
94
+
95
+ def today
96
+ return (Date.today..Date.today)
97
+ end
98
+
99
+ def yesterday
100
+ return (Date.today - 1..Date.today - 1)
101
+ end
102
+ end
@@ -0,0 +1,190 @@
1
+ #| ======================================================
2
+ #| METHODS
3
+ #| ======================================================
4
+
5
+ #| ------------------------------------------------------
6
+ #| collect_preferences method
7
+ #|
8
+ #| Collects preferences from the user and stores the
9
+ #| entered values into a configuration file.
10
+ #| @return Void
11
+ #| ------------------------------------------------------
12
+ def collect_preferences
13
+ CliMessage.section('COLLECTING PREFERENCES...')
14
+
15
+ pref = CliMessage.prompt("Location for downloaded Sifttter files from Dropbox", SifttterRedux::SFT_LOCAL_FILEPATH)
16
+ $config.add_to_section({"sifttter_local_filepath" => pref}, "sifttter_redux")
17
+
18
+ pref = CliMessage.prompt("Location of Sifttter files in Dropbox", SifttterRedux::SFT_REMOTE_FILEPATH)
19
+ $config.add_to_section({"sifttter_remote_filepath" => pref}, "sifttter_redux")
20
+
21
+ pref = CliMessage.prompt("Location for downloaded Day One files from Dropbox", SifttterRedux::DO_LOCAL_FILEPATH)
22
+ $config.add_to_section({"dayone_local_filepath" => pref}, "sifttter_redux")
23
+
24
+ pref = CliMessage.prompt("Location of Day One files in Dropbox", SifttterRedux::DO_REMOTE_FILEPATH)
25
+ $config.add_to_section({"dayone_remote_filepath" => pref}, "sifttter_redux")
26
+ end
27
+
28
+ #| ------------------------------------------------------
29
+ #| download_sifttter_files method
30
+ #|
31
+ #| Downloads Sifttter files from Dropbox
32
+ #| @return Void
33
+ #| ------------------------------------------------------
34
+ def download_sifttter_files
35
+ # Download all Sifttter files from Dropbox.
36
+ CliMessage.info('Downloading Sifttter files...', false)
37
+ `#{$db_uploader} download #{$config.sifttter_redux["sifttter_remote_filepath"]} #{$config.sifttter_redux["sifttter_local_filepath"]}`
38
+ CliMessage.finish_message('DONE.')
39
+ end
40
+
41
+ #| ------------------------------------------------------
42
+ #| initialize_procedures method
43
+ #|
44
+ #| Initializes Sifttter Redux by downloading and collecting
45
+ #| all necessary items and info.
46
+ #| @return Void
47
+ #| ------------------------------------------------------
48
+ def initialize_procedures
49
+ $config.reset
50
+ $config.create_section("sifttter_redux")
51
+ $config.add_to_section({"config_location" => $config.configFile}, "sifttter_redux")
52
+
53
+ install_db_uploader
54
+ collect_preferences
55
+
56
+ CliMessage.section("INITIALIZATION COMPLETE!")
57
+
58
+ $config.save_configuration
59
+ end
60
+
61
+ #| ------------------------------------------------------
62
+ #| install_db_uploader method
63
+ #|
64
+ #| Installs Dropbox Uploader to a user-specified location
65
+ #| by cloning the git repository.
66
+ #| @return Void
67
+ #| ------------------------------------------------------
68
+ def install_db_uploader
69
+ valid_directory_chosen = false
70
+
71
+ CliMessage.section('DOWNLOADING DROPBOX UPLOADER...')
72
+
73
+ # Create a new configuration section for Dropbox-Uploader
74
+ $config.create_section("db_uploader")
75
+
76
+ until valid_directory_chosen
77
+ # Prompt the user for a location to save Dropbox Uploader. "
78
+ db_uploader_location = CliMessage.prompt("Location for Dropbox-Uploader", SifttterRedux::DBU_LOCAL_FILEPATH)
79
+ db_uploader_location.chop! if db_uploader_location.end_with?('/')
80
+ db_uploader_location = "/usr/local/opt" if db_uploader_location.empty?
81
+
82
+ # If the entered directory exists, clone the repository.
83
+ if File.directory?(db_uploader_location)
84
+ valid_directory_chosen = true
85
+ db_uploader_location << "/Dropbox-Uploader"
86
+
87
+ # If, for some reason, Dropbox Uploader alread exists at this location,
88
+ # skip the clone.
89
+ if File.directory?(db_uploader_location)
90
+ CliMessage.info("You seem to already have Dropbox Uploader at this location; skipping...")
91
+ else
92
+ %x{git clone https://github.com/andreafabrizi/Dropbox-Uploader.git #{db_uploader_location}}
93
+ end
94
+
95
+ # Save config data to YAML.
96
+ $config.add_to_section({"local_filepath" => db_uploader_location}, "db_uploader")
97
+ else
98
+ puts "Sorry, but #{db_uploader_location} isn't a valid directory."
99
+ end
100
+ end
101
+ end
102
+
103
+ #| ------------------------------------------------------
104
+ #| run_sifttter method
105
+ #|
106
+ #| Modified form of Sifttter
107
+ #|
108
+ #| Sifttter: An IFTTT-to-Day One Logger by Craig Eley 2014
109
+ #| Based on tp-dailylog.rb by Brett Terpstra 2012
110
+ #| @param date The date to use when scanning Sifttter files
111
+ #| @return Void
112
+ #| ------------------------------------------------------
113
+ def run_sifttter(date)
114
+ uuid_command = "uuidgen" if OS.mac?
115
+ uuid_command = "uuid" if OS.linux?
116
+ uuid = %x{#{uuid_command}}.gsub(/-/,'').strip
117
+
118
+ date_for_title = date.strftime('%B %d, %Y')
119
+ datestamp = date.to_time.utc.iso8601
120
+ starred = false
121
+
122
+ template = ERB.new <<-XMLTEMPLATE
123
+ <?xml version="1.0" encoding="UTF-8"?>
124
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
125
+ <plist version="1.0">
126
+ <dict>
127
+ <key>Creation Date</key>
128
+ <date><%= datestamp %></date>
129
+ <key>Entry Text</key>
130
+ <string><%= entrytext %></string>
131
+ <key>Starred</key>
132
+ <<%= starred %>/>
133
+ <key>Tags</key>
134
+ <array>
135
+ <string>daily logs</string>
136
+ </array>
137
+ <key>UUID</key>
138
+ <string><%= uuid %></string>
139
+ </dict>
140
+ </plist>
141
+ XMLTEMPLATE
142
+
143
+ date_regex = "#{date.strftime('%B')} 0?#{date.strftime('%-d')}, #{date.strftime('%Y')}"
144
+ time_regex = "\d{1,2}:\d{1,2}\s?[AaPpMm]{2}"
145
+
146
+ files = %x{find #{$config.sifttter_redux["sifttter_local_filepath"]} -type f -name '*.txt' | grep -v -i daily | sort}
147
+
148
+ projects = []
149
+ files.split("\n").each do |file|
150
+ if File.exists?(file.strip)
151
+ f = File.open(file.strip, encoding: 'UTF-8')
152
+ lines = f.read
153
+ f.close
154
+ project = "### " + File.basename(file).gsub(/^.*?\/([^\/]+)$/,"\\1") + "\n"
155
+
156
+ found_completed = false
157
+ lines.each_line do |line|
158
+ if line =~ /&/
159
+ line.gsub!(/[&]/, 'and')
160
+ end
161
+ if line =~ /#{date_regex}/
162
+ found_completed = true
163
+ project += line.gsub(/@done/,'').gsub(/#{date_regex}\s(-|at)\s/, '').gsub(/#{time_regex}\s-\s/, '').strip + "\n"
164
+ end
165
+ end
166
+ end
167
+ if found_completed
168
+ projects.push(project)
169
+ end
170
+ end
171
+
172
+ if projects.length <=0
173
+ CliMessage.error("No entries found...")
174
+ exit!
175
+ end
176
+
177
+ if projects.length > 0
178
+ entrytext = "# Things done on #{date_for_title}\n\n"
179
+ projects.each do |project|
180
+ entrytext += project.gsub(/.txt/, ' ') + "\n\n"
181
+ end
182
+
183
+ Dir.mkdir($config.sifttter_redux["dayone_local_filepath"]) if !Dir.exists?($config.sifttter_redux["dayone_local_filepath"])
184
+
185
+ fh = File.new(File.expand_path($config.sifttter_redux["dayone_local_filepath"] + "/" + uuid + ".doentry"), 'w+')
186
+ fh.puts template.result(binding)
187
+ fh.close
188
+ CliMessage.success("Entry logged for #{date_for_title}...")
189
+ end
190
+ end
@@ -0,0 +1,47 @@
1
+ #| ======================================================
2
+ #| OS Module
3
+ #|
4
+ #| Module to easily find the running operating system
5
+ #| ======================================================
6
+ module OS
7
+
8
+ #| ------------------------------------------------------
9
+ #| linux? method
10
+ #|
11
+ #| Returns true if the host OS is Linux (false otherwise).
12
+ #| @return Bool
13
+ #| ------------------------------------------------------
14
+ def OS.linux?
15
+ OS.unix? and not OS.mac?
16
+ end
17
+
18
+ #| ------------------------------------------------------
19
+ #| mac? method
20
+ #|
21
+ #| Returns true if the host OS is OS X (false otherwise).
22
+ #| @return Bool
23
+ #| ------------------------------------------------------
24
+ def OS.mac?
25
+ (/darwin/ =~ RUBY_PLATFORM) != nil
26
+ end
27
+
28
+ #| ------------------------------------------------------
29
+ #| unix? method
30
+ #|
31
+ #| Returns true if the host OS is Unix (false otherwise).
32
+ #| @return Bool
33
+ #| ------------------------------------------------------
34
+ def OS.unix?
35
+ !OS.windows?
36
+ end
37
+
38
+ #| ------------------------------------------------------
39
+ #| windows? method
40
+ #|
41
+ #| Returns true if the host OS is Windows (false otherwise).
42
+ #| @return Bool
43
+ #| ------------------------------------------------------
44
+ def OS.windows?
45
+ (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
46
+ end
47
+ end
@@ -0,0 +1,6 @@
1
+ require 'Sifttter-Redux/cli-message.rb'
2
+ require 'Sifttter-Redux/config-manager.rb'
3
+ require 'Sifttter-Redux/constants.rb'
4
+ require 'Sifttter-Redux/date-range-maker.rb'
5
+ require 'Sifttter-Redux/methods.rb'
6
+ require 'Sifttter-Redux/os.rb'
@@ -0,0 +1,56 @@
1
+ require 'date'
2
+ require 'test_helper'
3
+ require File.join(File.dirname(__FILE__), '..', 'lib/Sifttter-Redux/date-range-maker.rb')
4
+
5
+ class DefaultTest < Test::Unit::TestCase
6
+
7
+ def setup
8
+ $drm = DateRangeMaker.new
9
+ end
10
+
11
+ def test_today
12
+ assert_equal($drm.today, (Date.today..Date.today))
13
+ end
14
+
15
+ def test_yesterday
16
+ assert_equal($drm.yesterday, (Date.today - 1..Date.today - 1))
17
+ end
18
+
19
+ def test_last_7_days
20
+ assert_equal($drm.last_seven_days, (Date.today - 7...Date.today))
21
+ end
22
+
23
+ def test_last_7_days_include_today
24
+ assert_equal($drm.last_seven_days(true), (Date.today - 7..Date.today))
25
+ end
26
+
27
+ def test_range_only_start_date
28
+ assert_equal($drm.range({:start_date => "2014-02-01"}), (Date.parse("2014-02-01")...Date.today))
29
+ end
30
+
31
+ def test_range_only_start_date_include_today
32
+ assert_equal($drm.range({:start_date => "2014-02-01", :include_today => true}), (Date.parse("2014-02-01")..Date.today))
33
+ end
34
+
35
+ def test_range_start_date_and_end_date
36
+ assert_equal($drm.range({:start_date => "2014-02-01", :end_date => "2014-02-05"}), (Date.parse("2014-02-01")..Date.parse("2014-02-05")))
37
+ end
38
+
39
+ def test_range_bad_dates
40
+ assert_raise BadChronicDateError do
41
+ $drm.range({:start_date => "Bad Start Date", :end_date => "Bad End Date"})
42
+ end
43
+ end
44
+
45
+ def test_range_end_date_with_no_start_date
46
+ assert_raise InvalidFlagsError do
47
+ $drm.range({:start_date => nil, :end_date => Date.today})
48
+ end
49
+ end
50
+
51
+ def test_range_end_date_before_start_date
52
+ assert_raise BadDateOrderError do
53
+ $drm.range({:start_date => Date.today, :end_date => Date.today - 1})
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,9 @@
1
+ require 'test/unit'
2
+
3
+ # Add test libraries you want to use here, e.g. mocha
4
+
5
+ class Test::Unit::TestCase
6
+
7
+ # Add global extensions to the test case class here
8
+
9
+ end