typelib 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+