typelib 0.0.1
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/Rakefile +74 -0
- data/lib/typelib.rb +73 -0
- data/lib/typelib/canned.rb +66 -0
- data/test/test_01_basic.rb +90 -0
- data/test/test_02_canned.rb +56 -0
- metadata +66 -0
data/Rakefile
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# $Id$
|
2
|
+
$:.push 'lib'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/packagetask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
require 'rdoc/task'
|
7
|
+
require 'fileutils'
|
8
|
+
require 'typelib'
|
9
|
+
|
10
|
+
include FileUtils::Verbose
|
11
|
+
|
12
|
+
task :default => [ :test, :dist ]
|
13
|
+
|
14
|
+
task :fixperms do
|
15
|
+
chmod(0755, Dir['bin/*'])
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Tests
|
20
|
+
#
|
21
|
+
|
22
|
+
Rake::TestTask.new do |t|
|
23
|
+
t.libs << 'lib'
|
24
|
+
t.test_files = FileList['test/test*.rb']
|
25
|
+
t.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Distribution
|
30
|
+
#
|
31
|
+
|
32
|
+
task :dist => [:fixperms, :repackage, :gem, :rdoc]
|
33
|
+
task :distclean => [:clobber_package, :clobber_rdoc]
|
34
|
+
task :clean => [:distclean]
|
35
|
+
|
36
|
+
#
|
37
|
+
# Documentation
|
38
|
+
#
|
39
|
+
|
40
|
+
RDoc::Task.new do |rd|
|
41
|
+
rd.rdoc_dir = "rdoc"
|
42
|
+
rd.main = "README.rdoc"
|
43
|
+
rd.rdoc_files.include("README.rdoc")
|
44
|
+
rd.rdoc_files.include("./lib/**/*.rb")
|
45
|
+
rd.options = %w(-a)
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Packaging
|
50
|
+
#
|
51
|
+
|
52
|
+
spec = Gem::Specification.new do |s|
|
53
|
+
s.name = "typelib"
|
54
|
+
s.version = TypeLib::VERSION
|
55
|
+
s.author = "Erik Hollensbe"
|
56
|
+
s.email = "erik@hollensbe.org"
|
57
|
+
s.summary = "An on-demand arbitrary check and conversion library that won't destroy your data."
|
58
|
+
|
59
|
+
s.files = Dir["Rakefile"] + Dir["README"] + Dir["lib/**/*"] + Dir['test/**/*']
|
60
|
+
|
61
|
+
s.has_rdoc = true
|
62
|
+
end
|
63
|
+
|
64
|
+
Rake::GemPackageTask.new(spec) do |s|
|
65
|
+
end
|
66
|
+
|
67
|
+
Rake::PackageTask.new(spec.name, spec.version) do |p|
|
68
|
+
p.need_tar_gz = true
|
69
|
+
p.need_zip = true
|
70
|
+
p.package_files.include("./bin/**/*")
|
71
|
+
p.package_files.include("./Rakefile")
|
72
|
+
p.package_files.include("./lib/**/*.rb")
|
73
|
+
p.package_files.include("README")
|
74
|
+
end
|
data/lib/typelib.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
# Typelib is a way of checking and converting data. It operates as a "filter
|
4
|
+
# chain" system which allows it to gradually normalize disparate data into a
|
5
|
+
# common type. Each chain is optionally a part of a list which allows it to
|
6
|
+
# provide several paths in a single external execution.
|
7
|
+
#
|
8
|
+
# The library is arguably very simple and therefore has simple requirements and
|
9
|
+
# needs. This is intentional.
|
10
|
+
#
|
11
|
+
# Please see TypeLib::Filter and TypeLib::FilterList for more information.
|
12
|
+
#
|
13
|
+
module TypeLib
|
14
|
+
|
15
|
+
VERSION = "0.0.1"
|
16
|
+
|
17
|
+
# A FilterList is a ... list of filters. It includes all the methods that
|
18
|
+
# Array contains, plus an additional method -- execute. See
|
19
|
+
# TypeLib::Filter.
|
20
|
+
class FilterList < DelegateClass(Array)
|
21
|
+
# Create a new FilterList. An array of TypeLib::Filter objects is
|
22
|
+
# accepted for construction.
|
23
|
+
def initialize(ary=[])
|
24
|
+
@filters = ary
|
25
|
+
super(@filters)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Execute the checks in this list against +obj+, passing in +addl+
|
29
|
+
# if any additional arguments are provided. If the check passes, the
|
30
|
+
# conversion is run and the chain supplied to the constructor is
|
31
|
+
# followed. If no checks pass, the original item is returned.
|
32
|
+
def execute(obj, *addl)
|
33
|
+
ret = obj
|
34
|
+
@filters.each do |filter|
|
35
|
+
if filter.check(obj, *addl)
|
36
|
+
ret = filter.convert(obj, *addl)
|
37
|
+
break
|
38
|
+
end
|
39
|
+
end
|
40
|
+
return ret
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# TypeLib::Filter is a way of checking, then optionally converting data
|
45
|
+
# based on whether or not the check passes. At that point, an additional
|
46
|
+
# TypeLib::FilterList may be provided which indicates that more checks need
|
47
|
+
# to be followed with the new data.
|
48
|
+
class Filter
|
49
|
+
attr_reader :check_proc, :convert_proc, :filters
|
50
|
+
|
51
|
+
# A TypeLib::Filter consists of a check procedure, a conversion procedure,
|
52
|
+
# and a TypeLib::FilterList which may be empty. Checks return boolean
|
53
|
+
# which indicates whether or not the conversion will be attempted.
|
54
|
+
def initialize(check, convert, filters=FilterList.new)
|
55
|
+
@check_proc = check
|
56
|
+
@convert_proc = convert
|
57
|
+
@filters = filters
|
58
|
+
end
|
59
|
+
|
60
|
+
# Check this object against the filter. If additional data is supplied,
|
61
|
+
# it will be provided to the Filter#check_proc.
|
62
|
+
def check(obj, *addl)
|
63
|
+
check_proc.call(obj, *addl)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Convert this object unconditionally. If additional data is supplied,
|
67
|
+
# it will be provided to the Filter#convert_proc.
|
68
|
+
def convert(obj, *addl)
|
69
|
+
ret = convert_proc.call(obj, *addl)
|
70
|
+
filters.execute(ret, *addl)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'date'
|
3
|
+
require 'typelib'
|
4
|
+
|
5
|
+
module TypeLib
|
6
|
+
# Canned checks are just that -- already written for you.
|
7
|
+
module Canned
|
8
|
+
#
|
9
|
+
# build_strptime_filter works with DateTime objects and a format that
|
10
|
+
# you provide. It will build and return a new TypeLib::Filter that can
|
11
|
+
# check strings for the format you provide and if they pass, convert
|
12
|
+
# them to a DateTime object.
|
13
|
+
#
|
14
|
+
# Due to limitations in the DateTime system itself, you must include
|
15
|
+
# zone information -- if not, you will get a value that is offset to
|
16
|
+
# GMT, which is not what DateTime.now and pals will return. An
|
17
|
+
# ArgumentError will be raised if you do not supply this in the filter.
|
18
|
+
#
|
19
|
+
def build_strptime_filter(format)
|
20
|
+
if format !~ /%z/
|
21
|
+
raise ArgumentError, "format must include %z due to DateTime fail"
|
22
|
+
end
|
23
|
+
|
24
|
+
check = proc do |obj|
|
25
|
+
(DateTime.strptime(obj, format).strftime(format) == obj) rescue false
|
26
|
+
end
|
27
|
+
|
28
|
+
convert = proc { |obj| DateTime.strptime(obj, format) }
|
29
|
+
|
30
|
+
return Filter.new(check, convert)
|
31
|
+
end
|
32
|
+
|
33
|
+
module_function :build_strptime_filter
|
34
|
+
|
35
|
+
# Canned Checks.
|
36
|
+
module Checks
|
37
|
+
STR_IS_INT = proc { |obj| obj.kind_of?(String) and obj =~ /^\d+$/ }
|
38
|
+
STR_IS_DEC = proc { |obj| obj.kind_of?(String) and obj =~ /^[\d.]+$/ }
|
39
|
+
|
40
|
+
IS_NUMERIC = proc { |obj| obj.kind_of?(Numeric) }
|
41
|
+
IS_INTEGER = proc { |obj| obj.kind_of?(Integer) }
|
42
|
+
end
|
43
|
+
|
44
|
+
# Canned Conversions.
|
45
|
+
module Conversions
|
46
|
+
TO_INTEGER = proc { |obj| obj.to_i }
|
47
|
+
TO_FLOAT = proc { |obj| obj.to_f }
|
48
|
+
TO_STRING = proc { |obj| obj.to_s }
|
49
|
+
TO_BINARY = proc { |obj| obj.to_s(2) }
|
50
|
+
TO_HEX = proc { |obj| obj.to_s(16) }
|
51
|
+
|
52
|
+
STR_TO_BIGDECIMAL = proc { |obj| BigDecimal.new(obj.to_s) }
|
53
|
+
end
|
54
|
+
|
55
|
+
# Fully canned filters.
|
56
|
+
module Filters
|
57
|
+
STR_TO_INT = Filter.new(Checks::STR_IS_INT, Conversions::TO_INTEGER)
|
58
|
+
STR_TO_FLOAT = Filter.new(Checks::STR_IS_DEC, Conversions::TO_FLOAT)
|
59
|
+
STR_TO_DEC = Filter.new(Checks::STR_IS_DEC, Conversions::STR_TO_BIGDECIMAL)
|
60
|
+
|
61
|
+
NUM_TO_STR = Filter.new(Checks::IS_NUMERIC, Conversions::TO_STRING)
|
62
|
+
INT_TO_BIN = Filter.new(Checks::IS_INTEGER, Conversions::TO_BINARY)
|
63
|
+
INT_TO_HEX = Filter.new(Checks::IS_INTEGER, Conversions::TO_HEX)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'test-unit'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
$:.unshift 'lib'
|
6
|
+
require 'typelib'
|
7
|
+
|
8
|
+
class TestBasic < Test::Unit::TestCase
|
9
|
+
def test_01_classes
|
10
|
+
assert(TypeLib)
|
11
|
+
assert(TypeLib::FilterList)
|
12
|
+
assert(TypeLib::Filter)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_02_object_properties
|
16
|
+
filters = TypeLib::FilterList.new
|
17
|
+
|
18
|
+
assert_respond_to(filters, :[])
|
19
|
+
assert_respond_to(filters, :<<)
|
20
|
+
assert_respond_to(filters, :execute)
|
21
|
+
|
22
|
+
check = proc { }
|
23
|
+
convert = proc { }
|
24
|
+
filter = TypeLib::Filter.new(check, convert)
|
25
|
+
|
26
|
+
assert_respond_to(filter, :check_proc)
|
27
|
+
assert_respond_to(filter, :convert_proc)
|
28
|
+
assert_respond_to(filter, :filters)
|
29
|
+
assert_respond_to(filter, :check)
|
30
|
+
assert_respond_to(filter, :convert)
|
31
|
+
|
32
|
+
assert_kind_of(Proc, filter.check_proc)
|
33
|
+
assert_kind_of(Proc, filter.convert_proc)
|
34
|
+
assert_kind_of(TypeLib::FilterList, filter.filters)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_03_basic_conversions
|
38
|
+
filters = TypeLib::FilterList.new
|
39
|
+
check = proc { |obj| obj.kind_of?(Integer) }
|
40
|
+
convert = proc { |obj| obj }
|
41
|
+
|
42
|
+
check2 = proc { |obj| obj.kind_of?(String) and obj =~ /^\d+$/ }
|
43
|
+
convert2 = proc { |obj| Integer(obj) }
|
44
|
+
|
45
|
+
filters << TypeLib::Filter.new(check, convert)
|
46
|
+
filters << TypeLib::Filter.new(check2, convert2)
|
47
|
+
|
48
|
+
assert_equal(2, filters.count)
|
49
|
+
assert_equal(1, filters.execute(1))
|
50
|
+
assert_equal(1, filters.execute("1"))
|
51
|
+
|
52
|
+
10.times do
|
53
|
+
x = rand(250000).to_i
|
54
|
+
assert_equal(x, filters.execute(x))
|
55
|
+
assert_equal(x, filters.execute(x.to_s))
|
56
|
+
end
|
57
|
+
|
58
|
+
assert_equal("1.25", filters.execute("1.25"))
|
59
|
+
|
60
|
+
filters << TypeLib::Filter.new(proc { true }, proc { |obj| Integer(obj) })
|
61
|
+
assert_raises(ArgumentError) { filters.execute("1.25") }
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_04_args
|
65
|
+
check = proc { |obj, *addl| addl[0] }
|
66
|
+
convert = proc { |obj, *addl| addl[0] }
|
67
|
+
filter = TypeLib::Filter.new(check, convert)
|
68
|
+
|
69
|
+
assert(filter.check(true, true))
|
70
|
+
assert(!filter.check(true, false))
|
71
|
+
assert_equal("fart", filter.convert(true, "fart"))
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_05_chains
|
75
|
+
filters = TypeLib::FilterList.new
|
76
|
+
|
77
|
+
check = proc { |obj| obj.kind_of?(Integer) }
|
78
|
+
convert = proc { |obj| obj.to_s }
|
79
|
+
|
80
|
+
check2 = proc { |obj| obj.kind_of?(String) and obj =~ /^\d+$/ }
|
81
|
+
convert2 = proc { |obj| obj.to_f }
|
82
|
+
|
83
|
+
filters << TypeLib::Filter.new(check, convert, TypeLib::FilterList.new([TypeLib::Filter.new(check2, convert2)]))
|
84
|
+
filters << TypeLib::Filter.new(check2, convert2)
|
85
|
+
|
86
|
+
assert_equal(1.0, filters.execute(1))
|
87
|
+
assert_kind_of(Float, filters.execute(1))
|
88
|
+
assert_equal(1.0, filters.execute("1"))
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'test-unit'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
$:.unshift 'lib'
|
6
|
+
require 'typelib'
|
7
|
+
require 'typelib/canned'
|
8
|
+
|
9
|
+
class TestCanned < Test::Unit::TestCase
|
10
|
+
include TypeLib::Canned
|
11
|
+
include TypeLib::Canned::Filters
|
12
|
+
|
13
|
+
def create_filterlist(arg)
|
14
|
+
filters = TypeLib::FilterList.new
|
15
|
+
filters << arg
|
16
|
+
return filters
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_01_string_to_numeric
|
20
|
+
filters = create_filterlist(STR_TO_INT)
|
21
|
+
assert_equal(1, filters.execute("1"))
|
22
|
+
assert_kind_of(Integer, filters.execute("1"))
|
23
|
+
|
24
|
+
filters = create_filterlist(STR_TO_FLOAT)
|
25
|
+
assert_equal(1.0, filters.execute("1"))
|
26
|
+
assert_kind_of(Float, filters.execute("1"))
|
27
|
+
|
28
|
+
filters = create_filterlist(STR_TO_DEC)
|
29
|
+
assert_equal(BigDecimal.new("1.0"), filters.execute("1"))
|
30
|
+
assert_kind_of(BigDecimal, filters.execute("1"))
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_02_numeric_to_string
|
34
|
+
filters = create_filterlist(NUM_TO_STR)
|
35
|
+
assert_equal("1", filters.execute(1))
|
36
|
+
assert_kind_of(String, filters.execute(1))
|
37
|
+
|
38
|
+
filters = create_filterlist(INT_TO_BIN)
|
39
|
+
assert_equal("10", filters.execute(2))
|
40
|
+
assert_kind_of(String, filters.execute(2))
|
41
|
+
|
42
|
+
filters = create_filterlist(INT_TO_HEX)
|
43
|
+
assert_equal("a", filters.execute(10))
|
44
|
+
assert_kind_of(String, filters.execute(10))
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_03_datetime
|
49
|
+
filters = create_filterlist(build_strptime_filter("%H:%M:%S%z"))
|
50
|
+
time = DateTime.now
|
51
|
+
newtime = filters.execute(time.strftime("%H:%M:%S%z"))
|
52
|
+
assert_equal(time.to_s, newtime.to_s)
|
53
|
+
|
54
|
+
assert_raises(ArgumentError.new("format must include %z due to DateTime fail")) { build_strptime_filter("%H:%M:%S") }
|
55
|
+
end
|
56
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: typelib
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Erik Hollensbe
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-05-17 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description:
|
22
|
+
email: erik@hollensbe.org
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- Rakefile
|
31
|
+
- lib/typelib/canned.rb
|
32
|
+
- lib/typelib.rb
|
33
|
+
- test/test_01_basic.rb
|
34
|
+
- test/test_02_canned.rb
|
35
|
+
has_rdoc: true
|
36
|
+
homepage:
|
37
|
+
licenses: []
|
38
|
+
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
segments:
|
49
|
+
- 0
|
50
|
+
version: "0"
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
version: "0"
|
58
|
+
requirements: []
|
59
|
+
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.3.6
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: An on-demand arbitrary check and conversion library that won't destroy your data.
|
65
|
+
test_files: []
|
66
|
+
|