spanner-lfittl 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,43 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ spanner-lfittl (0.0.3)
5
+ activesupport
6
+ spanner-lfittl
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ activesupport (3.2.3)
12
+ i18n (~> 0.6)
13
+ multi_json (~> 1.0)
14
+ diff-lcs (1.1.3)
15
+ git (1.2.5)
16
+ i18n (0.6.0)
17
+ jeweler (1.8.3)
18
+ bundler (~> 1.0)
19
+ git (>= 1.2.5)
20
+ rake
21
+ rdoc
22
+ json (1.7.3)
23
+ multi_json (1.3.6)
24
+ rake (0.9.2.2)
25
+ rdoc (3.12)
26
+ json (~> 1.4)
27
+ rspec (2.11.0)
28
+ rspec-core (~> 2.11.0)
29
+ rspec-expectations (~> 2.11.0)
30
+ rspec-mocks (~> 2.11.0)
31
+ rspec-core (2.11.1)
32
+ rspec-expectations (2.11.2)
33
+ diff-lcs (~> 1.1.3)
34
+ rspec-mocks (2.11.1)
35
+
36
+ PLATFORMS
37
+ ruby
38
+
39
+ DEPENDENCIES
40
+ jeweler
41
+ rake
42
+ rspec
43
+ spanner-lfittl!
data/README.rdoc ADDED
@@ -0,0 +1,24 @@
1
+ = Spanner
2
+
3
+ Easy way to parse natural language time spans as periods expressed in seconds. Supports float point notions of spans as well.
4
+
5
+ == Usage
6
+
7
+ require 'spanner'
8
+
9
+ Spanner.parse('1s')
10
+ => 1
11
+
12
+ Spanner.parse('23 hours 12 minutes')
13
+ => 83520
14
+
15
+ Spanner.format(83520)
16
+ => '23 hours 12 minutes'
17
+
18
+ == Authors
19
+
20
+ Original author:
21
+ Joshua Hull <joshbuddy@gmail.com>
22
+
23
+ Newer contributions:
24
+ Lukas Fittl <lukas@fittl.com>
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ require 'rspec/core/rake_task'
5
+ $:.push File.expand_path("../lib", __FILE__)
6
+
7
+ task :default => :spec
8
+
9
+ RSpec::Core::RakeTask.new(:spec)
10
+
11
+ begin
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |s|
14
+ s.name = "spanner-lfittl"
15
+ s.description = s.summary = "Natural language time span parsing & formatting"
16
+ s.email = "lukas@fittl.com"
17
+ s.homepage = "http://github.com/lfittl/spanner"
18
+ s.authors = ["Lukas Fittl", "Joshua Hull"]
19
+ s.files = FileList["[A-Z]*", "{lib,spec}/**/*"]
20
+ s.add_dependency 'activesupport'
21
+ s.add_development_dependency 'jeweler'
22
+ s.add_development_dependency 'rake'
23
+ s.add_development_dependency 'rspec'
24
+ end
25
+ Jeweler::GemcutterTasks.new
26
+ rescue LoadError
27
+ puts "Jeweler not available. Run 'bundle install'."
28
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.3
data/lib/spanner.rb ADDED
@@ -0,0 +1,115 @@
1
+ require 'date'
2
+ require 'active_support/inflector'
3
+
4
+ class Spanner
5
+
6
+ ParseError = Class.new(RuntimeError)
7
+
8
+ def self.parse(str, opts = nil)
9
+ Spanner.new(opts).parse(str)
10
+ end
11
+
12
+ def self.format(distance, opts = nil)
13
+ Spanner.new(opts).format(distance)
14
+ end
15
+
16
+ attr_reader :value, :raise_on_error, :from
17
+
18
+ def initialize(opts)
19
+ @value = value
20
+ @on_error = opts && opts.key?(:on_error) ? opts[:on_error] : :raise
21
+ @length_of_month = opts && opts[:length_of_month]
22
+
23
+ @from = if opts && opts.key?(:from)
24
+ case opts[:from]
25
+ when :now
26
+ Time.new.to_i
27
+ else
28
+ opts[:from].to_i
29
+ end
30
+ else
31
+ 0
32
+ end
33
+ end
34
+
35
+ def self.days_in_month(year, month)
36
+ (Date.new(year, 12, 31) << (12-month)).day
37
+ end
38
+
39
+ def length_of_month
40
+ @length_of_month ||= Spanner.parse("#{Spanner.days_in_month(Time.new.year, Time.new.month)} days")
41
+ end
42
+
43
+ def error(err)
44
+ if on_error == :raise
45
+ raise ParseError.new(err)
46
+ end
47
+ end
48
+
49
+ def parse(value)
50
+ parts = []
51
+ part_contextualized = nil
52
+ value.to_s.scan(/[\+\-]?(?:\d*\.\d+|\d+)|[a-z]+/i).each do |part|
53
+ part_as_float = Float(part) rescue nil
54
+ if part_as_float
55
+ parts << part_as_float
56
+ part_contextualized = nil
57
+ else
58
+ if part_contextualized
59
+ error "Part has already been contextualized with #{part_contextualized}"
60
+ return nil
61
+ end
62
+
63
+ if parts.empty?
64
+ parts << 1
65
+ end
66
+
67
+ # part is context
68
+ multiplier = case part
69
+ when 's', 'sec', 'second', 'seconds' then 1
70
+ when 'h', 'hour', 'hours', 'hrs' then 3600
71
+ when 'm', 'min', 'minute', 'minutes' then 60
72
+ when 'd', 'day', 'days' then 86_400
73
+ when 'w', 'wks', 'week', 'weeks' then 604_800
74
+ when 'months', 'month', 'M' then length_of_month
75
+ when 'years', 'year', 'y' then 31_556_926
76
+ when /\As/ then 1
77
+ when /\Am/ then 60
78
+ when /\Ah/ then 3600
79
+ when /\Ad/ then 86_400
80
+ when /\Aw/ then 604_800
81
+ when /\AM/ then length_of_month
82
+ when /\Ay/ then 31_556_926
83
+ end
84
+
85
+ part_contextualized = part
86
+ parts << (parts.pop * multiplier) if multiplier
87
+ end
88
+ end
89
+
90
+ if parts.empty?
91
+ nil
92
+ else
93
+ value = parts.inject(from) {|s, p| s += p}
94
+ value.ceil == value ? value.ceil : value
95
+ end
96
+ end
97
+
98
+ def format(distance)
99
+ parts = {}
100
+ parts[:years], distance = distance.divmod(31_556_926)
101
+ parts[:months], distance = distance.divmod(length_of_month)
102
+ parts[:weeks], distance = distance.divmod(604_800)
103
+ parts[:days], distance = distance.divmod(86_400)
104
+ parts[:hours], distance = distance.divmod(3600)
105
+ parts[:minutes], parts[:seconds] = distance.divmod(60)
106
+
107
+ output = []
108
+ parts.each do |name, value|
109
+ next if value == 0
110
+ name = name.to_s.singularize if value.between?(-1, 1)
111
+ output << "%d %s" % [value, name]
112
+ end
113
+ output.join(" ")
114
+ end
115
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spanner do
4
+
5
+ it "should return nil for empty strings" do
6
+ Spanner.parse('').should be_nil
7
+ end
8
+
9
+ it "should assume seconds" do
10
+ Spanner.parse('1').should == 1
11
+ Spanner.parse(1).should == 1
12
+ end
13
+
14
+ #simple
15
+ { '.5s' => 0.5, '1s' => 1, '1.5s' => 1.5, '1m' => 60, '1.5m' => 90, '1d' => 86400, '1.7233312d' => 148895.81568, '1M' => Spanner.days_in_month(Time.new.year, Time.new.month) * 24 * 60 * 60 }.each do |input, output|
16
+ it "should parse #{input} and return #{output}" do
17
+ Spanner.parse(input).should == output
18
+ end
19
+ end
20
+
21
+ #complex
22
+ { '1m23s' => 83, '3h20min' => 3600*3+20*60 }.each do |input, output|
23
+ it "should parse #{input} and return #{output}" do
24
+ Spanner.parse(input).should == output
25
+ end
26
+ end
27
+
28
+ #singular
29
+ { '1 day' => 3600*24, '5 day' => 3600*24*5, '1 hour' => 3600 }.each do |input, output|
30
+ it "should parse #{input} and return #{output}" do
31
+ Spanner.parse(input).should == output
32
+ end
33
+ end
34
+
35
+ it "should let you set the length of a month" do
36
+ Spanner.parse("4 months", :length_of_month => 1234).should == 4936
37
+ end
38
+
39
+ it "should accept time as from option" do
40
+ now = Time.new
41
+ Spanner.parse('23s', :from => now).should == now.to_i + 23
42
+ end
43
+
44
+ it "should accept special :now as from option" do
45
+ Spanner.parse('23s', :from => :now).should == Time.new.to_i + 23
46
+ end
47
+ end
48
+
49
+ describe Spanner, "format" do
50
+ { 3600*24 => '1 day', 3600*24*5 => '5 days', 3620 => '1 hour 20 seconds', 175814631 => '5 years 6 months 3 weeks 1 day 16 hours 20 minutes 1 second' }.each do |input, output|
51
+ it "should format #{input} as #{output}" do
52
+ Spanner.format(input).should == output
53
+ end
54
+ end
55
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,7 @@
1
+ --colour
2
+ --format
3
+ specdoc
4
+ --loadby
5
+ mtime
6
+ --reverse
7
+ --backtrace
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'rspec'
3
+
4
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
5
+
6
+ require 'spanner'
metadata ADDED
@@ -0,0 +1,176 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spanner-lfittl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Lukas Fittl
9
+ - Joshua Hull
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-08-07 00:00:00.000000000Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: spanner-lfittl
17
+ requirement: &2152789000 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *2152789000
26
+ - !ruby/object:Gem::Dependency
27
+ name: jeweler
28
+ requirement: &2152788380 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *2152788380
37
+ - !ruby/object:Gem::Dependency
38
+ name: rake
39
+ requirement: &2152787660 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *2152787660
48
+ - !ruby/object:Gem::Dependency
49
+ name: rspec
50
+ requirement: &2152787160 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *2152787160
59
+ - !ruby/object:Gem::Dependency
60
+ name: jeweler
61
+ requirement: &2152786500 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *2152786500
70
+ - !ruby/object:Gem::Dependency
71
+ name: rake
72
+ requirement: &2152785780 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: *2152785780
81
+ - !ruby/object:Gem::Dependency
82
+ name: rspec
83
+ requirement: &2152784800 !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: *2152784800
92
+ - !ruby/object:Gem::Dependency
93
+ name: activesupport
94
+ requirement: &2152784200 !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ type: :runtime
101
+ prerelease: false
102
+ version_requirements: *2152784200
103
+ - !ruby/object:Gem::Dependency
104
+ name: jeweler
105
+ requirement: &2152783360 !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ type: :development
112
+ prerelease: false
113
+ version_requirements: *2152783360
114
+ - !ruby/object:Gem::Dependency
115
+ name: rake
116
+ requirement: &2152782160 !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ type: :development
123
+ prerelease: false
124
+ version_requirements: *2152782160
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: &2152780400 !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ! '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: *2152780400
136
+ description: Natural language time span parsing & formatting
137
+ email: lukas@fittl.com
138
+ executables: []
139
+ extensions: []
140
+ extra_rdoc_files:
141
+ - README.rdoc
142
+ files:
143
+ - Gemfile
144
+ - Gemfile.lock
145
+ - README.rdoc
146
+ - Rakefile
147
+ - VERSION
148
+ - lib/spanner.rb
149
+ - spec/spanner_spec.rb
150
+ - spec/spec.opts
151
+ - spec/spec_helper.rb
152
+ homepage: http://github.com/lfittl/spanner
153
+ licenses: []
154
+ post_install_message:
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ none: false
160
+ requirements:
161
+ - - ! '>='
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ required_rubygems_version: !ruby/object:Gem::Requirement
165
+ none: false
166
+ requirements:
167
+ - - ! '>='
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ requirements: []
171
+ rubyforge_project:
172
+ rubygems_version: 1.8.15
173
+ signing_key:
174
+ specification_version: 3
175
+ summary: Natural language time span parsing & formatting
176
+ test_files: []