spanner-lfittl 0.0.3

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/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: []