hpoydar-chronic_duration 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) Henry Poydar
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,40 @@
1
+ Chronic Duration
2
+ ================
3
+
4
+ A simple Ruby natural language parser for elapsed time. (For example, 4 hours and 30 minutes, 6 minutes 4 seconds, 3 days, etc.) Returns all results in seconds. Will return an integer unless you get tricky and need a float. (4 minutes and 13.47 seconds, for example.)
5
+
6
+ Installation
7
+ ------------
8
+
9
+ $ sudo gem sources -a http://gems.github.com
10
+ $ sudo gem install hpoydar-chronic_duration
11
+
12
+ Usage
13
+ -----
14
+
15
+ >> require 'chronic_duration'
16
+ => true
17
+ >> ChronicDuration.parse('4 minutes and 30 seconds')
18
+ => 270
19
+
20
+ Nil is returned if the string can't be parsed
21
+
22
+ Examples of parse-able strings:
23
+
24
+ * '12.4 secs'
25
+ * '1:20'
26
+ * '1:20.51'
27
+ * '4:01:01'
28
+ * '3 mins 4 sec'
29
+ * '2 hrs 20 min'
30
+ * '2h20min'
31
+ * '6 mos 1 day'
32
+ * '47 yrs 6 mos and 4d'
33
+
34
+
35
+ TODO
36
+ ----
37
+
38
+ * Benchmark and optimize
39
+ * Context specific matching (E.g., for '4m30s', assume 'm' is minutes)
40
+ * Smartly parse vacation-like durations (E.g., '4 days and 3 nights')
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require 'rake/rdoctask'
2
+ require 'spec/rake/spectask'
3
+
4
+ task :default => :spec
5
+
6
+ desc 'Run specs'
7
+ Spec::Rake::SpecTask.new('spec') do |task|
8
+ task.spec_files = FileList['spec/**/*_spec.rb']
9
+ end
10
+
11
+ Rake::RDocTask.new do |task|
12
+ task.rdoc_dir = 'doc'
13
+ task.title = 'chronic_duration'
14
+ task.options << '--line-numbers' << '--inline-source' << '--main' << 'README'
15
+ task.rdoc_files.include 'README'
16
+ task.rdoc_files.include 'lib/**/*.rb'
17
+ end
@@ -0,0 +1,118 @@
1
+ module ChronicDuration
2
+ extend self
3
+
4
+ def parse(string)
5
+ result = calculate_from_words(cleanup(string))
6
+ result == 0 ? nil : result
7
+ end
8
+
9
+ private
10
+
11
+ def calculate_from_words(string)
12
+ val = 0
13
+ words = string.split(' ')
14
+ words.each_with_index do |v, k|
15
+ if v =~ float_matcher
16
+ val += (convert_to_number(v) * duration_units_seconds_multiplier(words[k + 1] || 'seconds'))
17
+ end
18
+ end
19
+ val
20
+ end
21
+
22
+ def cleanup(string)
23
+ res = filter_by_type(string)
24
+ res = res.gsub(float_matcher) {|n| " #{n} "}.squeeze(' ').strip
25
+ res = filter_through_white_list(res)
26
+ end
27
+
28
+ def convert_to_number(string)
29
+ string.to_f % 1 > 0 ? string.to_f : string.to_i
30
+ end
31
+
32
+ def duration_units_list
33
+ %w(seconds minutes hours days weeks months years)
34
+ end
35
+ def duration_units_seconds_multiplier(unit)
36
+ return 0 unless duration_units_list.include?(unit)
37
+ case unit
38
+ when 'years'; 31557600 # accounts for leap years
39
+ when 'months'; 3600 * 24 * 30
40
+ when 'weeks'; 3600 * 24 * 7
41
+ when 'days'; 3600 * 24
42
+ when 'hours'; 3600
43
+ when 'minutes'; 60
44
+ when 'seconds'; 1
45
+ end
46
+ end
47
+
48
+ def error_message
49
+ 'Sorry, that duration could not be parsed'
50
+ end
51
+
52
+ # Parse 3:41:59 and return 3 hours 41 minutes 59 seconds
53
+ def filter_by_type(string)
54
+ if string.gsub(' ', '') =~ /#{float_matcher}(:#{float_matcher})+/
55
+ res = []
56
+ string.gsub(' ', '').split(':').reverse.each_with_index do |v,k|
57
+ return unless duration_units_list[k]
58
+ res << "#{v} #{duration_units_list[k]}"
59
+ end
60
+ res = res.reverse.join(' ')
61
+ else
62
+ res = string
63
+ end
64
+ res
65
+ end
66
+
67
+ def float_matcher
68
+ /[0-9]*\.?[0-9]+/
69
+ end
70
+
71
+ # Get rid of unknown words and map found
72
+ # words to defined time units
73
+ def filter_through_white_list(string)
74
+ res = []
75
+ string.split(' ').each do |word|
76
+ if word =~ float_matcher
77
+ res << word.strip
78
+ next
79
+ end
80
+ res << mappings[word.strip] if mappings.has_key?(word.strip)
81
+ end
82
+ res.join(' ')
83
+ end
84
+
85
+ def mappings
86
+ {
87
+ 'seconds' => 'seconds',
88
+ 'second' => 'seconds',
89
+ 'secs' => 'seconds',
90
+ 'sec' => 'seconds',
91
+ 's' => 'seconds',
92
+ 'minutes' => 'minutes',
93
+ 'minute' => 'minutes',
94
+ 'mins' => 'minutes',
95
+ 'min' => 'minutes',
96
+ 'm' => 'minutes',
97
+ 'hours' => 'hours',
98
+ 'hour' => 'hours',
99
+ 'hrs' => 'hours',
100
+ 'hr' => 'hours',
101
+ 'h' => 'hours',
102
+ 'days' => 'days',
103
+ 'day' => 'days',
104
+ 'dy' => 'days',
105
+ 'd' => 'days',
106
+ 'months' => 'months',
107
+ 'mos' => 'months',
108
+ 'years' => 'years',
109
+ 'yrs' => 'years',
110
+ 'y' => 'years'
111
+ }
112
+ end
113
+
114
+ def white_list
115
+ self.mappings.map {|k, v| k}
116
+ end
117
+
118
+ end
@@ -0,0 +1,86 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe ChronicDuration, 'gem' do
4
+
5
+ it "should build" do
6
+ spec = eval(File.read("#{File.dirname(__FILE__)}/../chronic_duration.gemspec"))
7
+ FileUtils.rm_f(File.dirname(__FILE__) + "/../chronic_duration-#{spec.version}.gem")
8
+ system "cd #{File.dirname(__FILE__)}/.. && gem build chronic_duration.gemspec -q --no-verbose"
9
+ File.exists?(File.dirname(__FILE__) + "/../chronic_duration-#{spec.version}.gem").should be_true
10
+ FileUtils.rm_f(File.dirname(__FILE__) + "/../chronic_duration-#{spec.version}.gem")
11
+ end
12
+
13
+ end
14
+
15
+ describe ChronicDuration, '.parse' do
16
+
17
+ @exemplars = {
18
+ '1:20' => 60 + 20,
19
+ '1:20.51' => 60 + 20.51,
20
+ '4:01:01' => 4 * 3600 + 60 + 1,
21
+ '3 mins 4 sec' => 3 * 60 + 4,
22
+ '2 hrs 20 min' => 2 * 3600 + 20 * 60,
23
+ '2h20min' => 2 * 3600 + 20 * 60,
24
+ '6 mos 1 day' => 6 * 30 * 24 * 3600 + 24 * 3600,
25
+ '2.5 hrs' => 2.5 * 3600,
26
+ '47 yrs 6 mos and 4.5d' => 47 * 31557600 + 6 * 30 * 24 * 3600 + 4.5 * 24 * 3600
27
+ }
28
+
29
+ it "should return nil if the string can't be parsed" do
30
+ ChronicDuration.parse('gobblygoo').should be_nil
31
+ end
32
+
33
+ it "should return a float if seconds are in decimals" do
34
+ ChronicDuration.parse('12 mins 3.141 seconds').is_a?(Float).should be_true
35
+ end
36
+
37
+ it "should return an integer unless the seconds are in decimals" do
38
+ ChronicDuration.parse('12 mins 3 seconds').is_a?(Integer).should be_true
39
+ end
40
+
41
+ @exemplars.each do |k, v|
42
+ it "should properly parse a duration like #{k}" do
43
+ ChronicDuration.parse(k).should == v
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ # Some of the private methods deserve some spec'ing to aid
50
+ # us in development...
51
+
52
+ describe ChronicDuration, "private methods" do
53
+
54
+ describe ".filter_by_type" do
55
+
56
+ it "should take a chrono-formatted time like 3:14 and return a human time like 3 minutes 14 seconds" do
57
+ ChronicDuration.instance_eval("filter_by_type('3:14')").should == '3 minutes 14 seconds'
58
+ end
59
+
60
+ it "should take a chrono-formatted time like 12:10:14 and return a human time like 12 hours 10 minutes 14 seconds" do
61
+ ChronicDuration.instance_eval("filter_by_type('12:10:14')").should == '12 hours 10 minutes 14 seconds'
62
+ end
63
+
64
+ it "should return the input if it's not a chrono-formatted time" do
65
+ ChronicDuration.instance_eval("filter_by_type('4 hours')").should == '4 hours'
66
+ end
67
+
68
+ end
69
+
70
+ describe ".cleanup" do
71
+
72
+ it "should clean up extraneous words" do
73
+ ChronicDuration.instance_eval("cleanup('4 days and 11 hours')").should == '4 days 11 hours'
74
+ end
75
+
76
+ it "should cleanup extraneous spaces" do
77
+ ChronicDuration.instance_eval("cleanup(' 4 days and 11 hours')").should == '4 days 11 hours'
78
+ end
79
+
80
+ it "should insert spaces where there aren't any" do
81
+ ChronicDuration.instance_eval("cleanup('4m11.5s')").should == '4 minutes 11.5 seconds'
82
+ end
83
+
84
+ end
85
+
86
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
4
+ require 'chronic_duration'
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hpoydar-chronic_duration
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Henry Poydar
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-11 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A simple Ruby natural language parser for elapsed time. (For example, 4 hours and 30 minutes, 6 minutes 4 seconds, 3 days, etc.) Returns all results in seconds. Will return an integer unless you get tricky and need a float. (4 minutes and 13.47 seconds, for example.)
17
+ email: henry@poydar.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/chronic_duration.rb
26
+ - LICENSE
27
+ - Rakefile
28
+ - README
29
+ has_rdoc: true
30
+ homepage: http://github.com/hpoydar/chronic_duration
31
+ post_install_message:
32
+ rdoc_options:
33
+ - --line-numbers
34
+ - --inline-source
35
+ - --main
36
+ - README
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ requirements: []
52
+
53
+ rubyforge_project:
54
+ rubygems_version: 1.2.0
55
+ signing_key:
56
+ specification_version: 2
57
+ summary: A Ruby natural language parser for elapsed time
58
+ test_files:
59
+ - spec/chronic_duration_spec.rb
60
+ - spec/spec_helper.rb
61
+ - Rakefile