bblib 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0ee410b186ccd9feaaf7a4bc76bb6919aa1ecc3a
4
+ data.tar.gz: 260e264c246c8a894078386376874d09ee0ac877
5
+ SHA512:
6
+ metadata.gz: 113718b874eb1e0c9fae613e6c6d1ece6f5e1cbef055c2f8f7cd5903911803b2d48d43b3d595e6acd4596038e34a61af0d0954b9459b06b78433d9453cae6dc6
7
+ data.tar.gz: 045421131ce65287668aa2c89b835f1b566bca620d346b2f4783b400aa6b714c1626cd7bb459c2116a9d96475ee3bf41eb8b274ab84ccb24302c69d776468022
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.gem
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.6
4
+ before_install: gem install bundler -v 1.10.6
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bblib.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Brandon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,211 @@
1
+ # BBLib
2
+
3
+ BBLib (Brandon-Black-Lib) is a collection of various reusable methods and classes to extend the Ruby language. Currently the library is in an early state and is being written generally for education purposes. As such, large changes will likely occur and some functions may be incomplete or inaccurate until 1.0.
4
+
5
+ One of my primary goals with the core BBLib code is to keep it as lightweight as possible. This means you will not find dependencies outside of the Ruby core libraries in this code. Further modules that do have larger dependencies will be released in separate gems.
6
+
7
+ For a full breakdown of what is currently in this library, scroll down.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'bblib'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install bblib
24
+
25
+ ## Usage
26
+
27
+ BBLib is currently broken up into the following categories:
28
+ * File
29
+ * Hash
30
+ * Math
31
+ * Net
32
+ * String
33
+ * Time
34
+
35
+ ### File
36
+ #### File Scanners
37
+
38
+ Various simple file scan methods are available. All of these are toggleable-recursive and can be passed filters using an wildcarding supported by the Ruby Dir.glob() method.
39
+
40
+ ```ruby
41
+ # Scan for any files or folders in a path
42
+ BBLib.scan_dir 'C:/path/to/files'
43
+
44
+ #=> 'C:/path/to/files/readme.md'
45
+ #=> 'C:/path/to/files/license.txt'
46
+ #=> 'C:/path/to/files/folder/'
47
+ ```
48
+
49
+ If you need only files or dirs but not both, the following two convenience methods are also available:
50
+ ```ruby
51
+ # Scan for files ONLY
52
+ BBLib.scan_files 'C:/path/to/files'
53
+
54
+ #=> 'C:/path/to/files/readme.md'
55
+ #=> 'C:/path/to/files/license.txt'
56
+
57
+ # Scan for folders ONLY
58
+ BBLib.scan_dirs 'C:/path/to/files'
59
+
60
+ #=> 'C:/path/to/files/folder/'
61
+ ```
62
+
63
+ All of the scan methods also allow for the following named arguments:
64
+ * **recursive**: Default is false. Set to true to recursively scan directories
65
+ * **filter**: Default is nil. Can take either a String or Array of Strings. These strings will be used as filters so that only matching files or dirs are returned (Ex: '*.jpg', which would return all jpg files.)
66
+
67
+ ```ruby
68
+ # Scan for any 'txt' or 'jpg' files recursively in a dir
69
+ BBLib.scan_dir 'C:/path/to/files', recursive: true, filter: ['*.jpg', '*.txt']
70
+
71
+ #=> 'C:/path/to/files/license.txt'
72
+ #=> 'C:/path/to/files/folder/profile.jpg'
73
+ #=> 'C:/path/to/files/folder/another_folder/text.txt'
74
+ ```
75
+
76
+ In addition, both _scan_files_ and _scan_dirs_ also support a **mode** named argument. By default, this argument is set to :path. In _scan_files_ if :file is passed to :mode, a ruby File object will be returned rather than a String representation of the path. Similarily, if :dir is passed to _scan_dirs_ a ruby Dir object is returned.
77
+
78
+ #### File Size Parsing
79
+
80
+ A file size parser is available that analyzes known patterns in a string to construct a numeric file size. This is very useful for parsing the output from outside applications or from web scrapers.
81
+
82
+ ```ruby
83
+ # Turn a string into a file size (in bytes)
84
+ BBLib.parse_file_size "1MB 100KB"
85
+
86
+ #=> 1150976.0
87
+ ```
88
+
89
+ By default the output is in bytes, however, this can be modified using the named argument **output**.
90
+
91
+ ```ruby
92
+ # Turn a string into a file size (in bytes)
93
+ BBLib.parse_file_size "1MB 100KB", output: :megabyte
94
+
95
+ #=> 1.09765625
96
+
97
+ # The method can also be called directly on a string
98
+
99
+ "1.5 Mb".parse_file_size output: :kilobyte
100
+
101
+ #=> 1536.0
102
+ ```
103
+
104
+ All of the following are options for output:
105
+ * :byte
106
+ * :kilobyte
107
+ * :megabyte
108
+ * :gigabyte
109
+ * :terabtye
110
+ * :petabtye
111
+ * :exabtye
112
+ * :zettabtye
113
+ * :yottabtye
114
+
115
+ Additionally, ANY matching pattern in the string is added to the total, so a string such as "1MB 1megabyte" would yield the equivalent of "2MB". File sizes can also be intermingled with any other text, so "The file is 2 megabytes in size." would successfully parse the file size as 2 megabytes.
116
+
117
+ #### Other Methods
118
+
119
+ **string_to_file**
120
+
121
+ This method is a convenient way to write a string to disk as file. It simply takes a path and a string. By default if the path does not exist it will attempt to create it. This can be controlled using the mkpath argument.
122
+
123
+ ```ruby
124
+ # Write a string to disk
125
+ string = "This is my wonderful string."
126
+ BBLib.string_to_file '/home/user/my_file', string
127
+
128
+ # OR to avoid the creation of the path if it doesn't exist:
129
+
130
+ BBLib.string_to_file '/home/user/my_file', string, false
131
+
132
+ # OR call the method directly on the string
133
+
134
+ string.to_file '/home/user/another_file', true
135
+ ```
136
+
137
+ ### Hash
138
+
139
+ #### Deep Merge
140
+
141
+ A simple implementation of a deep merge algorithm that merges two hashes including nested hashes within them. It can also merge arrays (default) within the hashes and merge values into arrays (not default) rather than overwriting the values with the right side hash.
142
+
143
+ ```ruby
144
+ h1 = ({value: 1231, array: [1, 2], hash: {a: 1, b_hash: {c: 2, d:3}}})
145
+ h2 = ({value: 5, array: [6, 7], hash: {a: 1, z: nil, b_hash: {c: 9, d:10, y:10}}})
146
+
147
+ # Default behavior merges arrays and overwrites non-array/hash values
148
+ h1.deep_merge h2
149
+
150
+ #=> {:value=>5, :array=>[1, 2, 6, 7], :hash=>{:a=>1, :b_hash=>{:c=>9, :d=>10, :y=>10}, :z=>nil}}
151
+
152
+ # Don't overwrite colliding values, instead, place them into an array together
153
+ h1.deep_merge h2, overwrite_vals: false
154
+
155
+ #=> {:value=>[1231, 5], :array=>[1, 2, 6, 7], :hash=>{:a=>[1, 1], :b_hash=>{:c=>[2, 9], :d=>[3, 10], :y=>10}, :z=>nil}}
156
+
157
+ # Don't merge arrays, instead, overwrite them.
158
+ h1.deep_merge h2, merge_arrays: false
159
+
160
+ #=> {:value=>5, :array=>[6, 7], :hash=>{:a=>1, :b_hash=>{:c=>9, :d=>10, :y=>10}, :z=>nil}}
161
+ ```
162
+
163
+ A **!** version of _deep_merge_ is also available to modify the hash in place rather than returning a new hash.
164
+
165
+ #### Keys To Sym
166
+
167
+ Convert all keys within a hash (including nested keys) to symbols. This is useful after parsing json if you prefer to work with symbols rather than strings. An inplace (**!**) version of the method is also available.
168
+
169
+ ```ruby
170
+ h = {"author" => "Tom Clancy", "books" => ["Rainbow Six", "The Hunt for Red October"]}
171
+ h.keys_to_sym
172
+
173
+ #=> {:author=>"Tom Clancy", :books=>["Rainbow Six", "The Hunt for Red October"]}
174
+ ```
175
+
176
+ #### Reverse
177
+
178
+ Similar to reverse for Array. Calling this will reverse the current order of the Hash's keys. An inplace version is also available.
179
+
180
+ ```ruby
181
+ h = {a:1, b:2, c:3, d:4}
182
+ h.reverse
183
+
184
+ #=> {:d=>4, :c=>3, :b=>2, :a=>1}
185
+ ```
186
+
187
+ ### Math
188
+
189
+
190
+ ### Net
191
+ Currently empty...
192
+
193
+ ### String
194
+
195
+
196
+ ### Time
197
+
198
+
199
+ ## Development
200
+
201
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
202
+
203
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
204
+
205
+ ## Contributing
206
+
207
+ Bug reports and pull requests are welcome on GitHub at https://github.com/bblack16/bblib. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
208
+
209
+ ## License
210
+
211
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bblib/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "bblib"
8
+ spec.version = BBLib::VERSION
9
+ spec.authors = ["Brandon Black"]
10
+ spec.email = ["d2sm10@hotmail.com"]
11
+
12
+ spec.summary = %q{A library containing many reusable, basic functions.}
13
+ spec.description = %q{See summary for now...}
14
+ spec.homepage = "https://github.com/bblack16/bblib-ruby"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18
+ # delete this section to allow pushing this gem to any host.
19
+ # if spec.respond_to?(:metadata)
20
+ # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
21
+ # else
22
+ # raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
+ # end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) || f.end_with?('.gem') }
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.10"
31
+ spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "rspec"
33
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "bblib"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,13 @@
1
+ require_relative "bblib/version"
2
+ require_relative "string/bbstring"
3
+ require_relative "file/bbfile"
4
+ require_relative "time/bbtime"
5
+ require_relative "hash/bbhash"
6
+ require_relative "math/bbmath"
7
+ require 'fileutils'
8
+
9
+ module BBLib
10
+
11
+ CONFIGS_PATH = 'config/'
12
+
13
+ end
@@ -0,0 +1,3 @@
1
+ module BBLib
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,78 @@
1
+
2
+
3
+ module BBLib
4
+
5
+ # Scan for files and directories. Can be set to be recursive and can also have filters applied.
6
+ def self.scan_dir path = Dir.pwd, filter: nil, recursive: false
7
+ if !filter.nil?
8
+ filter = [filter].flatten.map{ |f| path.to_s + (recursive ? '/**/' : '/') + f.to_s }
9
+ else
10
+ filter = path.to_s + (recursive ? '/**/*' : '/*')
11
+ end
12
+ Dir.glob(filter)
13
+ end
14
+
15
+ # Uses BBLib.scan_dir but returns only files. Mode can be used to return strings (:path) or File objects (:file)
16
+ def self.scan_files path, filter: nil, recursive: false, mode: :path
17
+ BBLib.scan_dir(path, filter: filter, recursive: recursive).map{ |f| File.file?(f) ? (mode == :file ? File.new(f) : f) : nil}.reject{ |r| r.nil? }
18
+ end
19
+
20
+ # Uses BBLib.scan_dir but returns only directories. Mode can be used to return strings (:path) or Dir objects (:dir)
21
+ def self.scan_dirs path, filter: nil, recursive: false, mode: :path
22
+ BBLib.scan_dir(path, filter: filter, recursive: recursive).map{ |f| File.directory?(f) ? (mode == :dir ? Dir.new(f) : f ) : nil}.reject{ |r| r.nil? }
23
+ end
24
+
25
+ # Shorthand method to write a string to dist. By default the path is created if it doesn't exist.
26
+ def self.string_to_file path, str, mkpath = true
27
+ if !Dir.exists?(path) && mkpath
28
+ FileUtils.mkpath File.dirname(path)
29
+ end
30
+ File.write(path, str.to_s)
31
+ end
32
+
33
+ # A file size parser for strings. Extracts any known patterns for file sizes.
34
+ def self.parse_file_size str, output: :byte
35
+ output = FILE_SIZES.keys.find{ |f| f == output || FILE_SIZES[f][:exp].include?(output.to_s.downcase) } || :byte
36
+ bytes = 0.0
37
+ FILE_SIZES.each do |k, v|
38
+ v[:exp].each do |e|
39
+ numbers = str.scan(/(?=\w|\D|\A)\d?\.?\d+[[:space:]]*#{e}(?=\W|\d|\z)/i)
40
+ numbers.each{ |n| bytes+= n.to_f * v[:mult] }
41
+ end
42
+ end
43
+ return bytes / FILE_SIZES[output][:mult]
44
+ end
45
+
46
+ FILE_SIZES = {
47
+ byte: { mult: 1, exp: ['b', 'byt', 'byte'] },
48
+ kilobyte: { mult: 1024, exp: ['kb', 'kilo', 'k', 'kbyte', 'kilobtye'] },
49
+ megabyte: { mult: 1048576, exp: ['mb', 'mega', 'm', 'mib', 'mbyte', 'megabtye'] },
50
+ gigabyte: { mult: 1073741824, exp: ['gb', 'giga', 'g', 'gbyte', 'gigabtye'] },
51
+ terabtye: { mult: 1099511627776, exp: ['tb', 'tera', 't', 'tbyte', 'terabtye'] },
52
+ petabtye: { mult: 1125899906842624, exp: ['pb', 'peta', 'p', 'pbyte', 'petabtye'] },
53
+ exabtye: { mult: 1152921504606846976, exp: ['eb', 'exa', 'e', 'ebyte', 'exabtye'] },
54
+ zettabtye: { mult: 1180591620717411303424, exp: ['zb', 'zetta', 'z', 'zbyte', 'zettabtye'] },
55
+ yottabtye: { mult: 1208925819614629174706176, exp: ['yb', 'yotta', 'y', 'ybyte', 'yottabtye'] }
56
+ }
57
+
58
+ end
59
+
60
+ class File
61
+ def dirname
62
+ File.dirname(self.path)
63
+ end
64
+ end
65
+
66
+ class String
67
+ def to_file path, mkpath = true
68
+ BBLib.string_to_file path, self, mkpath
69
+ end
70
+
71
+ def file_name with_extension = true
72
+ self[(self.include?('/') ? self.rindex('/').to_i+1 : 0)..(with_extension ? -1 : self.rindex('.').to_i-1)]
73
+ end
74
+
75
+ def parse_file_size output: :byte
76
+ BBLib.parse_file_size(self, output:output)
77
+ end
78
+ end
@@ -0,0 +1,33 @@
1
+ class Hash
2
+
3
+ # Merges with another hash but also merges all nested hashes and arrays/values.
4
+ # Based on method found @ http://stackoverflow.com/questions/9381553/ruby-merge-nested-hash
5
+ def deep_merge with, merge_arrays: true, overwrite_vals: true
6
+ merger = proc{ |k, v1, v2| v1.is_a?(Hash) && v2.is_a?(Hash) ? v1.merge(v2, &merger) : (merge_arrays && v1.is_a?(Array) && v2.is_a?(Array) ? (v1 + v2) : (overwrite_vals ? v2 : [v1, v2].flatten)) }
7
+ self.merge(with, &merger)
8
+ end
9
+
10
+ def deep_merge! with, merge_arrays: true, overwrite_vals: true
11
+ replace self.deep_merge(with, merge_arrays: merge_arrays, overwrite_vals: overwrite_vals)
12
+ end
13
+
14
+ # Converts the keys of the hash as well as any nested hashes to symbols.
15
+ # Based on method found @ http://stackoverflow.com/questions/800122/best-way-to-convert-strings-to-symbols-in-hash
16
+ def keys_to_sym
17
+ self.inject({}){|memo,(k,v)| memo[k.to_sym] = (Hash === v ? v.keys_to_sym : v); memo}
18
+ end
19
+
20
+ def keys_to_sym!
21
+ replace self.keys_to_sym
22
+ end
23
+
24
+ # Reverses the order of keys in the Hash
25
+ def reverse
26
+ self.to_a.reverse.to_h
27
+ end
28
+
29
+ def reverse!
30
+ replace self.reverse
31
+ end
32
+
33
+ end
@@ -0,0 +1,12 @@
1
+ module BBLib
2
+
3
+ # Used to keep any numeric number between a set of bounds. Passing nil as min or max represents no bounds in that direction.
4
+ # min and max are inclusive to the allowed bounds.
5
+ def self.keep_between num, min, max
6
+ raise "Argument must be numeric: #{num} (#{num.class})" unless Numeric === num
7
+ if !min.nil? && num < min then num = min end
8
+ if !max.nil? && num > max then num = max end
9
+ return num
10
+ end
11
+
12
+ end
@@ -0,0 +1 @@
1
+ # TO BE WRITTEN
@@ -0,0 +1,97 @@
1
+
2
+ require_relative 'matching.rb'
3
+ require_relative 'roman.rb'
4
+ require_relative 'fuzzy_matcher.rb'
5
+
6
+ module BBLib
7
+
8
+ ##############################################
9
+ # General Functions
10
+ ##############################################
11
+
12
+ # Quickly remove any symbols from a string leaving onl alpha-numeric characters and white space.
13
+ def self.drop_symbols str
14
+ str.gsub(/[^\w\s\d]|_/, '')
15
+ end
16
+
17
+ # Extract all integers from a string. Use extract_floats if numbers may contain decimal places.
18
+ def self.extract_integers str, convert: true
19
+ str.scan(/\d+/).map{ |d| convert ? d.to_i : d }
20
+ end
21
+
22
+ # Extracts all integers or decimals from a string into an array.
23
+ def self.extract_floats str, convert: true
24
+ str.scan(/\d+\.?\d+|\d+/).map{ |f| convert ? f.to_f : f }
25
+ end
26
+
27
+ # Alias for extract_floats
28
+ def self.extract_numbers str, convert: true
29
+ BBLib.extract_floats str, convert:convert
30
+ end
31
+
32
+ # Used to move the position of the articles 'the', 'a' and 'an' in strings for normalization.
33
+ def self.move_articles str, position = :front, capitalize = true
34
+ return str unless [:front, :back, :none].include? position
35
+ articles = ["the", "a", "an"]
36
+ articles.each do |a|
37
+ starts, ends = str.downcase.start_with?(a + ' '), str.downcase.end_with?(' ' + a)
38
+ if starts && position != :front
39
+ if position == :none
40
+ str = str[(a.length + 1)..str.length]
41
+ elsif position == :back
42
+ str = str[(a.length + 1)..str.length] + (!ends ? ", #{capitalize ? a.capitalize : a}" : '')
43
+ end
44
+ end
45
+ if ends && position != :back
46
+ if position == :none
47
+ str = str[0..-(a.length + 2)]
48
+ elsif position == :front
49
+ str = (!starts ? "#{capitalize ? a.capitalize : a} " : '') + str[0..-(a.length + 2)]
50
+ end
51
+ end
52
+ end
53
+ while str.strip.end_with?(',')
54
+ str.strip!
55
+ str.chop!
56
+ end
57
+ str
58
+ end
59
+
60
+ end
61
+
62
+ class String
63
+ # Multi-split. Similar to split, but can be passed an array of delimiters to split on.
64
+ def msplit delims, keep_empty: false
65
+ return [self] unless !delims.nil? && !delims.empty?
66
+ ar = [self]
67
+ delims.each do |d|
68
+ ar.map!{ |a| a.split d }
69
+ ar.flatten!
70
+ end
71
+ keep_empty ? ar : ar.reject{ |l| l.empty? }
72
+ end
73
+
74
+ def move_articles position, capitalize = true
75
+ BBLib.move_articles self, position, capitalize
76
+ end
77
+
78
+ def move_articles! position, capitalize = true
79
+ replace BBLib.move_articles(self, position, capitalize)
80
+ end
81
+
82
+ def drop_symbols
83
+ BBLib.drop_symbols self
84
+ end
85
+
86
+ def drop_symbols!
87
+ replace BBLib.drop_symbols(self)
88
+ end
89
+
90
+ def extract_integers convert: true
91
+ BBLib.extract_integers self, convert:convert
92
+ end
93
+
94
+ def extract_numbers convert: true
95
+ BBLib.extract_numbers self, convert:convert
96
+ end
97
+ end
@@ -0,0 +1,75 @@
1
+
2
+ module BBLib
3
+
4
+ class FuzzyMatcher
5
+ attr_reader :threshold
6
+ attr_accessor :case_sensitive, :remove_symbols, :move_articles, :convert_roman
7
+
8
+ def initialize threshold: 75, case_sensitive: true, remove_symbols: false, move_articles: false, convert_roman: true
9
+ self.threshold = threshold
10
+ @case_sensitive, @remove_symbols, @move_articles, @convert_roman = case_sensitive, remove_symbols, move_articles, convert_roman
11
+ end
12
+
13
+ # Calculates a percentage match between string a and string b.
14
+ def similarity a, b
15
+ return 100.0 if a == b
16
+ prep_strings a, b
17
+ score, total_weight = 0, ALGORITHMS.map{|a, v| v[:weight] }.inject{ |sum, w| sum+=w }
18
+ ALGORITHMS.each do |algo, vals|
19
+ next unless vals[:weight] > 0
20
+ score+= @a.send(vals[:signature], @b) * vals[:weight]
21
+ end
22
+ score / total_weight
23
+ end
24
+
25
+ # Checks to see if the match percentage between Strings a and b are equal to or greater than the threshold.
26
+ def match? a, b
27
+ similarity(a, b) >= @threshold.to_f
28
+ end
29
+
30
+ # Returns the best match from array b to string a based on percent.
31
+ def best_match a, b
32
+ similarities(a, b).max_by{ |k, v| v}[0]
33
+ end
34
+
35
+ # Returns a hash of array 'b' with the percentage match to a. If sort is true, the hash is sorted desc by match percent.
36
+ def similarities a, b, sort: false
37
+ matches = Hash.new
38
+ [b].flatten.each{ |m| matches[m] = self.similarity(a, m) }
39
+ sort ? matches.sort_by{ |k, v| v }.reverse.to_h : matches
40
+ end
41
+
42
+ def threshold= threshold
43
+ @threshold = BBLib.keep_between(threshold, 0, 100)
44
+ end
45
+
46
+ def set_weight algorithm, weight
47
+ return nil unless ALGORITHMS.include? algorithm
48
+ ALGORITHMS[algorithm] = BBLib.keep_between(weight, 0, nil)
49
+ end
50
+
51
+ def algorithms
52
+ ALGORITHMS.keys
53
+ end
54
+
55
+ private
56
+
57
+ ALGORITHMS = {
58
+ levenshtein: {weight: 10, signature: :levenshtein_similarity},
59
+ composition: {weight: 5, signature: :composition_similarity},
60
+ numeric: {weight: 0, signature: :numeric_similarity},
61
+ phrase: {weight: 0, signature: :phrase_similarity},
62
+ qwerty: {weight: 0, signature: :qwerty_similarity}
63
+ }
64
+
65
+ def prep_strings a, b
66
+ @a, @b = a.to_s.dup.strip, b.to_s.dup.strip
67
+ if !@case_sensitive then @a.downcase!; @b.downcase! end
68
+ if @remove_symbols then @a.drop_symbols!; @b.drop_symbols! end
69
+ if @convert_roman then @a.from_roman!; @b.from_roman! end
70
+ if @move_articles then @a.move_articles!(:front, @case_sensitive); @b.move_articles! :front, @case_sensitive end
71
+ end
72
+
73
+ end
74
+
75
+ end
@@ -0,0 +1,119 @@
1
+ ##############################################
2
+ # String Comparison Algorithms
3
+ ##############################################
4
+
5
+ module BBLib
6
+
7
+ # A simple rendition of the levenshtein distance algorithm
8
+ def self.levenshtein_distance a, b, case_sensitive = false
9
+ if !case_sensitive then a, b = a.downcase, b.downcase end
10
+ costs = (0..b.length).to_a
11
+ (1..a.length).each do |i|
12
+ costs[0], nw = i, i - 1
13
+ (1..b.length).each do |j|
14
+ costs[j], nw = [costs[j] + 1, costs[j-1] + 1, a[i-1] == b[j-1] ? nw : nw + 1].min, costs[j]
15
+ end
16
+ end
17
+ costs[b.length]
18
+ end
19
+
20
+ # Calculates a percentage based match using the levenshtein distance algorithm
21
+ def self.levenshtein_similarity a, b, case_sensitive = false
22
+ distance = BBLib.levenshtein_distance a, b, case_sensitive
23
+ max = [a.length, b.length].max.to_f
24
+ return ((max - distance.to_f) / max) * 100.0
25
+ end
26
+
27
+ # Calculates a percentage based match of two strings based on their character composition.
28
+ def self.composition_similarity a, b, case_sensitive = false
29
+ if !case_sensitive then a, b = a.downcase, b.downcase end
30
+ if a.length <= b.length then t = a; a = b; b = t; end
31
+ matches, temp = 0, b
32
+ a.chars.each do |c|
33
+ if temp.chars.include? c
34
+ matches+=1
35
+ temp.sub! c, ''
36
+ end
37
+ end
38
+ (matches / [a.length, b.length].max.to_f )* 100.0
39
+ end
40
+
41
+ # Calculates a percentage based match between two strings based on the similarity of word matches.
42
+ def self.phrase_similarity a, b, case_sensitive = false
43
+ if !case_sensitive then a, b = a.downcase, b.downcase end
44
+ temp = b.split ' '
45
+ matches = 0
46
+ a.split(' ').each do |w|
47
+ if temp.include? w
48
+ matches+=1
49
+ temp.delete_at temp.find_index w
50
+ end
51
+ end
52
+ (matches.to_f / [a.split(' ').size, b.split(' ').size].max.to_f) * 100.0
53
+ end
54
+
55
+ # Extracts all numbers from two strings and compares them and generates a percentage of match.
56
+ # Percentage calculations here need to be weighted better...TODO
57
+ def self.numeric_similarity a, b, case_sensitive = false
58
+ if !case_sensitive then a, b = a.downcase, b.downcase end
59
+ a, b = a.extract_numbers, b.extract_numbers
60
+ return 100.0 if a.empty? && b.empty?
61
+ matches = []
62
+ for i in 0..[a.size, b.size].max-1
63
+ matches << 1.0 / ([a[i].to_f, b[i].to_f].max - [a[i].to_f, b[i].to_f].min + 1.0)
64
+ end
65
+ (matches.inject{ |sum, m| sum + m } / matches.size.to_f) * 100.0
66
+ end
67
+
68
+ # A simple character distance calculator that uses qwerty key positions to determine how similar two strings are.
69
+ # May be useful for typo detection.
70
+ def self.qwerty_similarity a, b
71
+ a, b = a.downcase.strip, b.downcase.strip
72
+ if a.length <= b.length then t = a; a = b; b = t; end
73
+ qwerty = {
74
+ 1 => ['1','2','3','4','5','6','7','8','9','0'],
75
+ 2 => ['q','w','e','r','t','y','u','i','o','p'],
76
+ 3 => ['a','s','d','f','g','h','j','k','l'],
77
+ 4 => ['z','x','c','v','b','n','m']
78
+ }
79
+ count, offset = 0, 0
80
+ a.chars.each do |c|
81
+ if b.length <= count
82
+ offset+=10
83
+ else
84
+ ai = qwerty.keys.find{ |f| qwerty[f].include? c }.to_i
85
+ bi = qwerty.keys.find{ |f| qwerty[f].include? b.chars[count] }.to_i
86
+ offset+= (ai - bi).abs
87
+ offset+= (qwerty[ai].index(c) - qwerty[bi].index(b.chars[count])).abs
88
+ end
89
+ count+=1
90
+ end
91
+ offset
92
+ end
93
+ end
94
+
95
+ class String
96
+ def levenshtein_distance str, case_sensitive = false
97
+ BBLib.levenshtein_distance self, str, case_sensitive
98
+ end
99
+
100
+ def levenshtein_similarity str, case_sensitive = false
101
+ BBLib.levenshtein_similarity self, str, case_sensitive
102
+ end
103
+
104
+ def composition_similarity str, case_sensitive = false
105
+ BBLib.composition_similarity self, str, case_sensitive
106
+ end
107
+
108
+ def phrase_similarity str, case_sensitive = false
109
+ BBLib.phrase_similarity self, str, case_sensitive
110
+ end
111
+
112
+ def numeric_similarity str, case_sensitive = false
113
+ BBLib.numeric_similarity self, str, case_sensitive
114
+ end
115
+
116
+ def qwerty_similarity str
117
+ BBLib.qwerty_similarity self, str
118
+ end
119
+ end
@@ -0,0 +1,70 @@
1
+
2
+ module BBLib
3
+
4
+ # Converts any integer up to 1000 to a roman numeral string_a
5
+ def self.to_roman num
6
+ roman = {1000 => 'M', 900 => 'CM', 500 => 'D', 400 => 'CD', 100 => 'C', 90 => 'XC', 50 => 'L',
7
+ 40 => 'XL', 10 => 'X', 9 => 'IX', 5 => 'V', 4 => 'IV', 3 => 'III', 2 => 'II', 1 => 'I'}
8
+ numeral = ""
9
+ roman.each do |n, r|
10
+ if num >= n
11
+ num-= n
12
+ numeral+= r
13
+ end
14
+ end
15
+ numeral
16
+ end
17
+
18
+ def self.string_to_roman str
19
+ sp = str.split ' '
20
+ sp.map! do |s|
21
+ if s.to_i.to_s == s
22
+ BBLib.to_roman s.to_i
23
+ else
24
+ s
25
+ end
26
+ end
27
+ sp.join ' '
28
+ end
29
+
30
+
31
+ def self.from_roman str
32
+ sp = str.split(' ')
33
+ (0..1000).each do |n|
34
+ num = BBLib.to_roman n
35
+ if !sp.select{ |i| i[/#{num}/i]}.empty?
36
+ for i in 0..(sp.length-1)
37
+ if sp[i].upcase == num
38
+ sp[i] = n.to_s
39
+ end
40
+ end
41
+ end
42
+ end
43
+ sp.join ' '
44
+ end
45
+
46
+ end
47
+
48
+ class Numeric
49
+ def to_roman
50
+ BBLib.to_roman self.to_i
51
+ end
52
+ end
53
+
54
+ class String
55
+ def from_roman
56
+ BBLib.from_roman self
57
+ end
58
+
59
+ def from_roman!
60
+ replace self.from_roman
61
+ end
62
+
63
+ def to_roman
64
+ BBLib.string_to_roman self
65
+ end
66
+
67
+ def to_roman!
68
+ replace self.to_roman
69
+ end
70
+ end
@@ -0,0 +1,84 @@
1
+
2
+
3
+ module BBLib
4
+
5
+ # Parses known time based patterns out of a string to construct a numeric duration.
6
+ def self.parse_duration str, output: :sec
7
+ secs = 0.0
8
+ TIME_EXPS.each do |k, v|
9
+ v[:exp].each do |e|
10
+ numbers = str.downcase.scan(/(?=\w|\D|\A)\d?\.?\d+[[:space:]]*#{e}(?=\W|\d|\z)/i)
11
+ numbers.each do |n|
12
+ secs+= n.to_i * v[:mult]
13
+ end
14
+ end
15
+ end
16
+ secs / (TIME_EXPS[output][:mult].to_f rescue 1)
17
+ end
18
+
19
+ # Turns a numeric input into a time string.
20
+ def self.to_duration num, input: :sec, stop: :mili, style: :medium
21
+ return nil unless Numeric === num || num > 0
22
+ if ![:full, :medium, :short].include?(style) then style = :medium end
23
+ expression = []
24
+ n, done = num * TIME_EXPS[input.to_sym][:mult], false
25
+ TIME_EXPS.reverse.each do |k, v|
26
+ next unless !done
27
+ div = n / v[:mult]
28
+ if div > 1
29
+ expression << "#{div.floor} #{v[:styles][style]}#{div.floor > 1 && style != :short ? "s" : nil}"
30
+ n-= div.floor * v[:mult]
31
+ end
32
+ if k == stop then done = true end
33
+ end
34
+ expression.join ' '
35
+ end
36
+
37
+ TIME_EXPS = {
38
+ mili: {
39
+ mult: 0.001,
40
+ styles: {full: 'milisecond', medium: 'mili', short: 'ms'},
41
+ exp: ['ms', 'mil', 'mils', 'mili', 'milis', 'milisecond', 'miliseconds', 'milsec', 'milsecs', 'msec', 'msecs', 'msecond', 'mseconds']},
42
+ sec: {
43
+ mult: 1,
44
+ styles: {full: 'second', medium: 'sec', short: 's'},
45
+ exp: ['s', 'sec', 'secs', 'second', 'seconds']},
46
+ min: {
47
+ mult: 60,
48
+ styles: {full: 'minute', medium: 'min', short: 'm'},
49
+ exp: ['m', 'mn', 'mns', 'min', 'mins', 'minute', 'minutes']},
50
+ hour: {
51
+ mult: 3600,
52
+ styles: {full: 'hour', medium: 'hr', short: 'h'},
53
+ exp: ['h', 'hr', 'hrs', 'hour', 'hours']},
54
+ day: {
55
+ mult: 86400,
56
+ styles: {full: 'day', medium: 'day', short: 'd'},
57
+ exp: ['d', 'day' 'days']},
58
+ week: {
59
+ mult: 604800,
60
+ styles: {full: 'week', medium: 'wk', short: 'w'},
61
+ exp: ['w', 'wk', 'wks', 'week', 'weeks']},
62
+ month: {
63
+ mult: 2592000,
64
+ styles: {full: 'month', medium: 'mo', short: 'mo'},
65
+ exp: ['mo', 'mon', 'mons', 'month', 'months', 'mnth', 'mnths', 'mth', 'mths']},
66
+ year: {
67
+ mult: 31536000,
68
+ styles: {full: 'year', medium: 'yr', short: 'y'},
69
+ exp: ['y', 'yr', 'yrs', 'year', 'years']}
70
+ }
71
+
72
+ end
73
+
74
+ class String
75
+ def parse_duration to = :sec
76
+ BBLib.parse_duration self, to
77
+ end
78
+ end
79
+
80
+ class Numeric
81
+ def to_duration input: :sec, stop: :mili, style: :medium
82
+ BBLib.to_duration self, input: input, stop: stop, style: style
83
+ end
84
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bblib
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Brandon Black
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-01-10 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.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: See summary for now...
56
+ email:
57
+ - d2sm10@hotmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".travis.yml"
65
+ - CODE_OF_CONDUCT.md
66
+ - Gemfile
67
+ - LICENSE.txt
68
+ - README.md
69
+ - Rakefile
70
+ - bblib.gemspec
71
+ - bin/console
72
+ - bin/setup
73
+ - lib/bblib.rb
74
+ - lib/bblib/version.rb
75
+ - lib/file/bbfile.rb
76
+ - lib/hash/bbhash.rb
77
+ - lib/math/bbmath.rb
78
+ - lib/net/bbnet.rb
79
+ - lib/string/bbstring.rb
80
+ - lib/string/fuzzy_matcher.rb
81
+ - lib/string/matching.rb
82
+ - lib/string/roman.rb
83
+ - lib/time/bbtime.rb
84
+ homepage: https://github.com/bblack16/bblib-ruby
85
+ licenses:
86
+ - MIT
87
+ metadata: {}
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 2.4.8
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: A library containing many reusable, basic functions.
108
+ test_files: []