hpoydar-chronic_duration 0.2.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.
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