periodic 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 694acb05f38ad1b232aa218884749f8c1971989c
4
+ data.tar.gz: 5e106d42184155b580315f880c8d4c53c4030b42
5
+ SHA512:
6
+ metadata.gz: 0ea08bb314c34e90f1eef36b91ecf6e1185a55d787b40f43a389abc6c7d22e2fe5fea17fa9c9c40ee2f0aa5cbbff8ad2c216f9eb54b3f5d591173b849b92bb8a
7
+ data.tar.gz: 2bc0d3b6258d72ccb15a8da94e417d8a455078bb1738ea711d0cb550af85f09e45ea27081b12ed5518b26fa3455c78138326ac8653cd3f9d523df8f9dd29f63a
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /vendor/bundle
11
+ *.gem
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 1.8.6
5
+ - 1.9.2
6
+ - 2.2.1
7
+ - 2.2.3
8
+ - 2.3.0
9
+ cache: bundler
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in lamppost.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2016 Chris Kalafarski
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,50 @@
1
+ = Periodic
2
+
3
+
4
+ == Installation
5
+
6
+ $ sudo gem sources -a http://gems.github.com
7
+ $ sudo gem install farski-periodic
8
+
9
+ == Usage
10
+
11
+ ==== Parser
12
+
13
+ Periodic will parse out natural language strings representing durations using different units of time, and return the total number of seconds. When using text labels, it will do it's best to look for any of the following units: seconds, minutes, hours, days, weeks, months, years, decades, centuries, millennia. Units can appear in any order in the string, and may appear more than once.
14
+
15
+ When using a digital format (e.g. 10:30), the parser will default to the smallest sensible units (e.g. 10 minutes 30 seconds), but this can be overridden using the :bias option (e.g. :bias => centuries, 10 centuries 30 decades). Valid options for the :bias are symbols of those units mentioned in the previous paragraph. If a bias is explicitly defined that is too precise for the given string, the smallest sensible unit will be substituted
16
+
17
+ >> Periodic.parse('1 minute')
18
+ => 60
19
+ >> Periodic.parse('60min')
20
+ => 3600
21
+ >> Periodic.parse('1:30')
22
+ => 90
23
+ >> Periodic.parse('1:30', :bias => :hours)
24
+ => 5400
25
+
26
+ ==== Formatting
27
+
28
+ The #format method of Periodic::Duration objects lets you format the number of seconds into different units. Any combination of units can be used to express precise values, the the precision is optioned (i.e. 90 seconds can be output as '1 minute' or '1.5 minutes', or '1 minute 30 seconds'). If you use text labels in the format, they can come either before or after the values, and in both cases the resulting string by default will have zero-value value-label pairs removed (e.g. with a value of 30, and a format of '%m minute %s seconds', by default it will print '30 seconds', not '0 minutes 30 seconds'). Pairs can be forced into the result with a '!' before the directive. When using a digital format (like the default '%y:%d:%h:%m:%s'), you should be sure to include all directives between the largest and smallest unit, though it will work even with missing directives.
29
+
30
+
31
+ The available directives
32
+ /%s/, /%m/, /%h/, /%d/, /%y/
33
+
34
+ >> Duration.new(125).format
35
+ => '2:05'
36
+ >> Duration.new(125).format('%y:%d:!%h:%m:%s')
37
+ => '00:02:05'
38
+ >> Duration.new(125).format('%h hours %m minutes %s seconds')
39
+ => '2 minutes 5 seconds'
40
+ >> Duration.new(125).format('!%h hours %m minutes %s seconds')
41
+ => '0 hours 2 minutes 5 seconds'
42
+ >> Duration.new(125).format('!%h hours %s seconds')
43
+ => '0 hours 125 seconds'
44
+
45
+ == TODO
46
+ [ ] plural text labels should automatically be singularized with 1
47
+
48
+ == COPYRIGHT
49
+
50
+ Copyright (c) 2008 Chris Kalafarski. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rake'
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'lib' << 'test'
6
+ t.pattern = 'test/**/*_test.rb'
7
+ t.verbose = false
8
+ end
9
+
10
+ task :default => :test
data/lib/periodic.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "periodic/duration"
2
+ require "periodic/parser"
3
+
4
+ module Periodic
5
+ extend Periodic::Parser
6
+ extend Periodic::Duration
7
+ end
@@ -0,0 +1,83 @@
1
+ require 'bigdecimal'
2
+
3
+ module Periodic
4
+ module Duration
5
+ module Units
6
+ TIME = Hash.new
7
+ TIME[:seconds] = { :factor => 1, :directive => /%s/ }
8
+ TIME[:minutes] = { :factor => 60, :directive => /%m/ }
9
+ TIME[:hours] = { :factor => 3600, :directive => /%h/ }
10
+ TIME[:days] = { :factor => 3600*24, :directive => /%d/ }
11
+ # TIME[:weeks] = { :factor => 3600*24*7, :directive => /%w/ }
12
+ # TIME[:months] = { :factor => 3600*24*30, :directive => /%n/ }
13
+ TIME[:years] = { :factor => 3600*24*365, :directive => /%y/ }
14
+
15
+ TIME_ORDER = [:seconds, :minutes, :hours, :days, :years] # not working with weeks and months...
16
+ end
17
+
18
+ def self.sanitize_formatted_string(string)
19
+ if string.match(/:/) && !string.match(/[a-zA-Z ]/)
20
+ # add leading zeros where missing...
21
+ string.gsub!(/!(\d):/, '!0\1:')
22
+ string.gsub!(/^(\d):/, '0\1:')
23
+ string.gsub!(/:(\d):/, ':0\1:')
24
+ string.gsub!(/:(\d):/, ':0\1:') # needs to happen twice??
25
+ string.gsub!(/:(\d(.\d)*)$/, ':0\1')
26
+
27
+ # remove leading zero-value digitals
28
+ string.sub!(/[0:]*/, '')
29
+ else
30
+ # if the string starts with a number we can assume the value-label pairs are like '10 minutes'
31
+ if string[0,1].match(/\d/) || string[0,1] == "!"
32
+ string = string.split(/(!?\d[.\d]*[-_:, a-zA-Z]+)/).delete_if{|x| x == ""}.inject(String.new) { |memo, s| memo << ((s.match(/!/) || s.match(/[1-9]/)) ? s : "") }
33
+
34
+ # if starts with a letter we can assume the value-label pairs are like 'minutes: 10'
35
+ else
36
+ string = string.split(/([-A-Za-z: ,]+\d[.\d]*)/).delete_if{|x| x == ""}.inject(String.new) { |memo, s| memo << ((s.match(/!/) || s.match(/[1-9]/)) ? s : "") }
37
+ string.sub!(/([ ,])*([a-zA-Z]+)/, '\2')
38
+ end
39
+
40
+ # remove leading zero-value digitals
41
+ string.sub!(/[0:]*/, '')
42
+ end
43
+ string.strip.gsub(/!/, '')
44
+ end
45
+
46
+ class Duration
47
+ def initialize(seconds)
48
+ @seconds = (seconds.is_a?(Float) ? seconds.to_f : seconds)
49
+ end
50
+
51
+ def format(format = '%y:%d:%h:%m:%s', precision = nil)
52
+ string, nondirective_units, values, smallest_unit_directive = format, [], Hash.new, nil
53
+
54
+ Periodic::Duration::Units::TIME_ORDER.reverse.each_with_index do |unit, i|
55
+ if format =~ Periodic::Duration::Units::TIME[unit][:directive]
56
+ values[unit] = send(unit) + nondirective_units.inject(0) { |total, u| total += (send(u) * (Periodic::Duration::Units::TIME[u][:factor] / Periodic::Duration::Units::TIME[unit][:factor])) }
57
+ smallest_unit_directive = unit
58
+ nondirective_units.clear
59
+ else
60
+ nondirective_units << unit if (send(unit) > 0)
61
+ end
62
+
63
+ # correct for any left over time that's is fractional for all the included units
64
+ values[smallest_unit_directive] += nondirective_units.inject(0) { |total, u| total += (send(u).to_f * Periodic::Duration::Units::TIME[u][:factor] / Periodic::Duration::Units::TIME[smallest_unit_directive][:factor]) } if (!Periodic::Duration::Units::TIME_ORDER.reverse[i+1] && !nondirective_units.empty?)
65
+ end
66
+
67
+ values[smallest_unit_directive] = case precision
68
+ when nil then (values[smallest_unit_directive] % 1 == 0) && !@seconds.is_a?(Float) ? values[smallest_unit_directive].to_i : values[smallest_unit_directive]
69
+ when 0 then values[smallest_unit_directive].to_i
70
+ else (values[smallest_unit_directive] * (10 ** precision)).round / (10 ** precision).to_f
71
+ end
72
+
73
+ return Periodic::Duration.sanitize_formatted_string(values.inject(string) { |str, data| str.sub!(Periodic::Duration::Units::TIME[data[0]][:directive], data[1].to_s) })
74
+ end
75
+
76
+ Periodic::Duration::Units::TIME_ORDER.each_with_index do |unit, i|
77
+ define_method("in_" + unit.to_s) { @seconds.to_f / Periodic::Duration::Units::TIME[unit][:factor] }
78
+ define_method("whole_" + unit.to_s) { (@seconds.to_f / Periodic::Duration::Units::TIME[unit][:factor]).floor }
79
+ define_method(unit) { ((Periodic::Duration::Units::TIME_ORDER[i+1] ? BigDecimal.new(@seconds.to_f.to_s) % BigDecimal.new(Periodic::Duration::Units::TIME[Periodic::Duration::Units::TIME_ORDER[i+1]][:factor].to_f.to_s) : @seconds.to_f) / Periodic::Duration::Units::TIME[unit][:factor].to_f).send(unit == :seconds ? :to_f : :floor) }
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,59 @@
1
+ module Periodic
2
+ module Parser
3
+ def parse(string, options = { :bias => :seconds})
4
+ return Parseable.new(string, options[:bias]).seconds
5
+ end
6
+
7
+ private
8
+
9
+ class Parseable
10
+ def initialize(string, bias)
11
+ @string = string
12
+ validates_inclusion_of_numeral_in_string
13
+ @bias = bias
14
+
15
+ extract_time_parts_from_string
16
+ end
17
+
18
+ def seconds
19
+ units = { :seconds => 1, :minutes => 60, :hours => 3600, :days => 3600*24, :weeks => 3600*24*7, :months => 3600*24*30, :years => 3600*24*365.25, :decades => 3600*24*365.25*10, :centuries => 3600*24*365.25*100, :millennia => 3600*24*365.25*1000 }
20
+ seconds = @time_parts.inject(0) { |total, part| total = total + (part[1] * units[part[0]]) }
21
+ return seconds % 1 == 0 ? seconds.to_i : seconds
22
+ end
23
+
24
+ private
25
+
26
+ def validates_inclusion_of_numeral_in_string
27
+ raise ArgumentError, "String contains no numbers", caller unless @string.match(/\d/)
28
+ end
29
+
30
+ def digital?
31
+ @string.match(/:/)
32
+ end
33
+
34
+ def extract_time_parts_from_string
35
+ @time_parts = Hash.new
36
+ digital? ? extract_time_parts_from_digital : extract_time_parts_from_text
37
+ end
38
+
39
+ def extract_time_parts_from_digital
40
+ units = [:seconds, :minutes, :hours, :days, :weeks, :months, :years, :decades, :centuries, :millennia]
41
+ @string.split(":").reverse.each_with_index do |part, i|
42
+ @time_parts[units[i + ((units.index(@bias) >= @string.split(":").size) ? units.index(@bias) - @string.split(":").size + 1 : 0)]] = part.to_f
43
+ end
44
+ end
45
+
46
+ def extract_time_parts_from_text
47
+ normalize_string
48
+ units = { :s => :seconds, :m => :minutes, :h => :hours, :d => :days, :w => :weeks, :n => :months, :y => :years, :a => :decades, :c => :centuries, :b => :millennia }
49
+ @string.split(' ').each { |part| @time_parts[part.match(/([a-z])/) ? units[part.match(/([a-z])/)[1].to_sym] : @bias] = (@time_parts[part.match(/([a-z])/) ? units[part.match(/([a-z])/)[1].to_sym] : @bias]||0) + part.to_f }
50
+ end
51
+
52
+ def normalize_string
53
+ [/( )/, /(,)/, /(and)/].each{ |m| @string.gsub!(m, '') }
54
+ @string.gsub!(/(\d)([a-zA-Z]+)/, '\1\2 ')
55
+ [{:n=>/(mo\w*)/,:b=>/(m\w*l\w*)/,:a=>/(d\w*c\w*)/}, {:m=>/(m\w*)/,:h=>/(h\w*)/,:d=>/(d\w*)/,:w=>/(w\w*)/,:y=>/(y\w*)/,:c=>/(c\w*)/}, {:s=>/(s\w*)/}].each { |set| set.each{ |k,v| @string.gsub!(v, k.to_s) } }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,3 @@
1
+ module Periodic
2
+ VERSION = "1.2.3"
3
+ end
data/periodic.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "periodic/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "periodic"
8
+ spec.version = Periodic::VERSION
9
+ spec.authors = ["Chris Kalafarski"]
10
+ spec.email = ["chris@farski.com"]
11
+
12
+ spec.summary = "Natural language parser and output formating for durations"
13
+ spec.description = "Natural language parser and output formating for durations in Ruby"
14
+ spec.homepage = "https://github.com/farski/periodic"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+ spec.required_ruby_version = '~> 1.9'
22
+
23
+ if spec.respond_to?(:metadata)
24
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
25
+ end
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.8"
28
+ spec.add_development_dependency "test-unit", "~> 2.5"
29
+ spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "coveralls", "~> 0"
31
+ spec.add_development_dependency "rubocop", "~> 0"
32
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: periodic
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.3
5
+ platform: ruby
6
+ authors:
7
+ - Chris Kalafarski
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-01-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: test-unit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: coveralls
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Natural language parser and output formating for durations in Ruby
84
+ email:
85
+ - chris@farski.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".travis.yml"
92
+ - Gemfile
93
+ - LICENSE
94
+ - README.rdoc
95
+ - Rakefile
96
+ - lib/periodic.rb
97
+ - lib/periodic/duration.rb
98
+ - lib/periodic/parser.rb
99
+ - lib/periodic/version.rb
100
+ - periodic.gemspec
101
+ homepage: https://github.com/farski/periodic
102
+ licenses:
103
+ - MIT
104
+ metadata:
105
+ allowed_push_host: https://rubygems.org
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - "~>"
113
+ - !ruby/object:Gem::Version
114
+ version: '1.9'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.5.1
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: Natural language parser and output formating for durations
126
+ test_files: []