structr 0.0.2

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.
@@ -0,0 +1,5 @@
1
+ Manifest
2
+ doc
3
+ pkg
4
+ coverage
5
+ tags
data/.watchr ADDED
@@ -0,0 +1,21 @@
1
+
2
+ def run(*args)
3
+ system "ruby -rubygems -Ilib:test #{args.join(' ')}"
4
+ end
5
+
6
+ def run_tests
7
+ system "rake test"
8
+ end
9
+
10
+ def underscore(file)
11
+ file.gsub('/', '_')
12
+ end
13
+
14
+ watch('test/test_.*\.rb') {|md| run md[0] }
15
+ watch('lib/(.*)\.rb') { run_tests }
16
+ watch('test/helper.rb') { run_tests }
17
+
18
+ run_tests
19
+
20
+ Signal.trap("QUIT") { abort("\n") }
21
+ Signal.trap("INT") { run_tests }
@@ -0,0 +1,74 @@
1
+ = Structr
2
+
3
+ Bind plain text to Ruby classes.
4
+ NO TESTS!
5
+
6
+ Inspired by ROXML http://github.com/Empact/roxml/tree/master
7
+
8
+ == Usage
9
+
10
+ require 'structr'
11
+
12
+ Load = Struct.new(:one, :five, :fifteen)
13
+ ProcessItem = Struct.new(:pid, :user)
14
+
15
+ class Top
16
+ include Structr
17
+
18
+ converter :load do |(one, five, fifteen)|
19
+ Load.new(one.to_f, five.to_f, fifteen.to_f)
20
+ end
21
+
22
+ converter :process do |(pid, user)|
23
+ ProcessItem.new(pid.to_i, user)
24
+ end
25
+
26
+ field_accessor :uptime, /top - (\d+):(\d+):(\d+)/ do |h, m, s|
27
+ h.to_i * 3600 + m.to_i * 60 + s.to_i
28
+ end
29
+ field_accessor :cpu_string, /(Cpu.*?)\n/
30
+ load_accessor :load, /load average: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)/
31
+ int_accessor :tasks, /Tasks:\s+(\d+)/
32
+ int_accessor :memory, /Mem:\s+(\d+)/
33
+ field :processes, /^\s*(\d+)\s(\S+)/, &converter(:process)
34
+
35
+ def processes
36
+ @processes
37
+ end
38
+
39
+ def users
40
+ @processes.map {|p| p.user }.uniq
41
+ end
42
+
43
+ def highest_pid
44
+ @processes.map {|p| p.pid }.sort.last
45
+ end
46
+ end
47
+
48
+ top = Top.structr(`top -b -n 1`)
49
+
50
+ puts "Load is #{top.load.one}"
51
+ puts "Up since #{top.uptime} seconds"
52
+ puts top.cpu_string
53
+ puts "#{top.tasks} Tasks"
54
+ puts "#{top.memory / 1024}MB memory available"
55
+ puts "Users: #{top.users.join(", ")}"
56
+ puts "Highest PID: #{top.highest_pid}"
57
+
58
+ === Examples
59
+
60
+ See examples/ for further examples
61
+
62
+ == Installation
63
+
64
+ === Gem
65
+
66
+ gem source -a http://gems.github.com
67
+ gem install splattael-structr
68
+
69
+ == Authors:
70
+ * Peter Suschlik
71
+
72
+ == Changelog
73
+
74
+ See CHANGELOG file.
@@ -0,0 +1,54 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ require 'jeweler'
9
+ Jeweler::Tasks.new do |gem|
10
+ gem.name = "structr"
11
+ gem.summary = 'Parse plain text to Ruby classes.'
12
+ gem.email = "peter-structr@suschlik.de"
13
+ gem.homepage = "http://github.com/splattael/structr"
14
+ gem.authors = ["Peter Suschlik"]
15
+
16
+ gem.has_rdoc = true
17
+ gem.extra_rdoc_files = [ "README.rdoc" ]
18
+
19
+ gem.add_development_dependency "riot", ">= 0.10.4"
20
+ gem.add_development_dependency "riot_notifier", ">= 0.0.5"
21
+
22
+ gem.test_files = Dir.glob('test/test_*.rb')
23
+ end
24
+
25
+ Jeweler::GemcutterTasks.new
26
+
27
+ # Test
28
+ require 'rake/testtask'
29
+ Rake::TestTask.new(:test) do |test|
30
+ test.test_files = FileList.new('test/test_*.rb')
31
+ test.libs << 'test'
32
+ test.verbose = true
33
+ end
34
+
35
+ # RDoc
36
+ Rake::RDocTask.new do |rd|
37
+ rd.title = "Parse plain text with Ruby classes"
38
+ rd.main = "README.rdoc"
39
+ rd.rdoc_files.include("README.rdoc", "lib/*.rb")
40
+ rd.rdoc_dir = "doc"
41
+ end
42
+
43
+
44
+ # Misc
45
+ desc "Tag files for vim"
46
+ task :ctags do
47
+ dirs = $LOAD_PATH.select {|path| File.directory?(path) }
48
+ system "ctags -R #{dirs.join(" ")}"
49
+ end
50
+
51
+ desc "Find whitespace at line ends"
52
+ task :eol do
53
+ system "grep -nrE ' +$' *"
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.2
@@ -0,0 +1,42 @@
1
+ require 'structr'
2
+
3
+ LineItem = Struct.new(:number, :description, :price)
4
+
5
+ class Invoice
6
+ include Structr
7
+
8
+ converter :line_item do |*matched|
9
+ LineItem.new(*matched)
10
+ end
11
+
12
+ int_accessor :number, /#(\d+)/
13
+ field_accessor :address, /Address:\n(.*?)\n(.*?)\n(.*?)\n/
14
+ date_accessor :date, /on (\d+-\d+-\d+)/
15
+ line_item :line_items, /^(\d+)\.\s+(.*?)\s+(\d+,\d+)$/
16
+
17
+ def items
18
+ Array(@line_items)
19
+ end
20
+ end
21
+
22
+ string = (STDIN.tty? ? DATA : STDIN).read
23
+
24
+ invoice = Invoice.structr(string)
25
+ p invoice
26
+ p invoice.items
27
+ p invoice.number
28
+
29
+ __END__
30
+ This is #23 on 2009-04-30
31
+
32
+ Address:
33
+ Evergreen Street 23
34
+ 12345 Malrose
35
+ Nirvana
36
+
37
+ 1. Root server 23,42
38
+ 2. Domain 45,00
39
+ 3. Another 32,00
40
+
41
+ # TODO
42
+ * Subtypes? LineItem
@@ -0,0 +1,47 @@
1
+ require 'structr'
2
+
3
+ Load = Struct.new(:one, :five, :fifteen)
4
+ ProcessItem = Struct.new(:pid, :user)
5
+
6
+ class Top
7
+ include Structr
8
+
9
+ converter :load do |(one, five, fifteen)|
10
+ Load.new(one.to_f, five.to_f, fifteen.to_f)
11
+ end
12
+
13
+ converter :process do |(pid, user)|
14
+ ProcessItem.new(pid.to_i, user)
15
+ end
16
+
17
+ field_accessor :uptime, /top - (\d+):(\d+):(\d+)/ do |h, m, s|
18
+ h.to_i * 3600 + m.to_i * 60 + s.to_i
19
+ end
20
+ field_accessor :cpu_string, /(Cpu.*?)\n/
21
+ load_accessor :load, /load average: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)/
22
+ int_accessor :tasks, /Tasks:\s+(\d+)/
23
+ int_accessor :memory, /Mem:\s+(\d+)/
24
+ field :processes, /^\s*(\d+)\s(\S+)/, &converter(:process)
25
+
26
+ def processes
27
+ @processes
28
+ end
29
+
30
+ def users
31
+ @processes.map {|p| p.user }.uniq
32
+ end
33
+
34
+ def highest_pid
35
+ @processes.map {|p| p.pid }.sort.last
36
+ end
37
+ end
38
+
39
+ top = Top.structr(`top -b -n 1`)
40
+
41
+ puts "Load is #{top.load.one}"
42
+ puts "Up since #{top.uptime} seconds"
43
+ puts top.cpu_string
44
+ puts "#{top.tasks} Tasks"
45
+ puts "#{top.memory / 1024}MB memory available"
46
+ puts "Users: #{top.users.join(", ")}"
47
+ puts "Highest PID: #{top.highest_pid}"
@@ -0,0 +1,112 @@
1
+
2
+ module Structr
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+
10
+ Converter = {
11
+ :int => proc {|m| m.to_i },
12
+ :float => proc {|m| m.to_f },
13
+ :date => proc {|m| Date.parse(m) },
14
+ :string => proc {|m| m },
15
+ }
16
+
17
+ def field(name, regexp, options={}, &block)
18
+ fields << FieldDefinition.new(name, regexp, &block)
19
+
20
+ case options[:accessor]
21
+ when :reader
22
+ attr_reader(name)
23
+ when :writer
24
+ attr_writer(name)
25
+ when :accessor, true
26
+ attr_accessor(name)
27
+ end
28
+ end
29
+
30
+ def field_accessor(name, regexp, &block)
31
+ field(name, regexp, :accessor => true, &block)
32
+ end
33
+
34
+ def field_reader(name, regexp, &block)
35
+ field(name, regexp, :accessor => :reader, &block)
36
+ end
37
+
38
+ def field_writer(name, regexp, &block)
39
+ field(name, regexp, :accessor => :writer, &block)
40
+ end
41
+
42
+ def fields
43
+ @fields ||= []
44
+ end
45
+
46
+ def structr(content, *options)
47
+ instance = new(*options)
48
+ fields.each do |field|
49
+ value = field.match(content)
50
+ value = value.first if value.size < 2
51
+ if instance.respond_to?(field.setter)
52
+ instance.send(field.setter, value)
53
+ else
54
+ instance.instance_variable_set(field.ivar, value)
55
+ end
56
+ end
57
+ instance
58
+ end
59
+
60
+ def converter(name, proc=nil, &block)
61
+ if block = proc || block
62
+ Converter[name] = block
63
+ else
64
+ Converter[name]
65
+ end
66
+ end
67
+
68
+ def method_missing(method, *args, &block)
69
+ name = method.to_s.gsub(/_(accessor|reader|writer)/, '')
70
+ if converter = converter(name.to_sym)
71
+ field(args[0], args[1], :accessor => $1 ? $1.to_sym : nil, &converter)
72
+ else
73
+ super(method, *args, &block)
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ class FieldDefinition
80
+
81
+ attr_accessor :name, :block
82
+
83
+ def initialize(name, regexp, &block)
84
+ @name, @regexp, @block = name.to_s, regexp, block
85
+ end
86
+
87
+ def setter
88
+ :"#{name}="
89
+ end
90
+
91
+ def getter
92
+ :"#{name}"
93
+ end
94
+
95
+ def ivar
96
+ :"@#{name}"
97
+ end
98
+
99
+ def match(string)
100
+ string.scan(@regexp).map do |matched|
101
+ matched = matched.first if matched.size == 1
102
+ if @block
103
+ @block.call(*matched)
104
+ else
105
+ matched
106
+ end
107
+ end
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -0,0 +1,57 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{structr}
8
+ s.version = "0.0.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Peter Suschlik"]
12
+ s.date = %q{2009-12-07}
13
+ s.email = %q{peter-structr@suschlik.de}
14
+ s.extra_rdoc_files = [
15
+ "README.rdoc"
16
+ ]
17
+ s.files = [
18
+ ".gitignore",
19
+ ".watchr",
20
+ "README.rdoc",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "examples/invoice.rb",
24
+ "examples/top.rb",
25
+ "lib/structr.rb",
26
+ "structr.gemspec",
27
+ "test/helper.rb",
28
+ "test/test_field_descriptor.rb",
29
+ "test/test_structr.rb"
30
+ ]
31
+ s.homepage = %q{http://github.com/splattael/structr}
32
+ s.rdoc_options = ["--charset=UTF-8"]
33
+ s.require_paths = ["lib"]
34
+ s.rubygems_version = %q{1.3.5}
35
+ s.summary = %q{Parse plain text to Ruby classes.}
36
+ s.test_files = [
37
+ "test/test_field_descriptor.rb",
38
+ "test/test_structr.rb"
39
+ ]
40
+
41
+ if s.respond_to? :specification_version then
42
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
43
+ s.specification_version = 3
44
+
45
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
46
+ s.add_development_dependency(%q<riot>, [">= 0.10.4"])
47
+ s.add_development_dependency(%q<riot_notifier>, [">= 0.0.5"])
48
+ else
49
+ s.add_dependency(%q<riot>, [">= 0.10.4"])
50
+ s.add_dependency(%q<riot_notifier>, [">= 0.0.5"])
51
+ end
52
+ else
53
+ s.add_dependency(%q<riot>, [">= 0.10.4"])
54
+ s.add_dependency(%q<riot_notifier>, [">= 0.0.5"])
55
+ end
56
+ end
57
+
@@ -0,0 +1,6 @@
1
+ require 'structr'
2
+
3
+ require 'riot'
4
+ require 'riot_notifier'
5
+
6
+ Riot.reporter = RiotNotifier::Libnotify
@@ -0,0 +1,31 @@
1
+ require 'helper'
2
+
3
+ include Structr
4
+
5
+ context Struct::FieldDefinition do
6
+ context "with single" do
7
+ setup { FieldDefinition.new(:number, /(\d+)/) }
8
+
9
+ asserts("name") { topic.name }.equals("number")
10
+ asserts("getter") { topic.getter }.equals(:number)
11
+ asserts("setter") { topic.setter }.equals(:number=)
12
+ asserts("ivar") { topic.ivar }.equals(:@number)
13
+ asserts("no block") { topic.block }.nil
14
+ asserts("no match") { topic.match("no match") }.equals([])
15
+ asserts("match without block") { topic.match("23 < 42") }.equals(["23", "42"])
16
+ asserts("match with block") do
17
+ topic.block = proc { |m| m.to_i }
18
+ topic.match("23 < 42")
19
+ end.equals([23, 42])
20
+ end
21
+
22
+ context "multiple" do
23
+ setup { FieldDefinition.new(:number, /(\d+),(\d+)/) }
24
+
25
+ asserts("match without block") { topic.match("costs 23,42$") }.equals([["23", "42"]])
26
+ asserts("matech with block") do
27
+ topic.block = proc {|(dollar, cents)| dollar.to_i * 100 + cents.to_i }
28
+ topic.match("costs 23,42$")
29
+ end.equals([2342])
30
+ end
31
+ end
@@ -0,0 +1,146 @@
1
+ require 'helper'
2
+
3
+ context Structr do
4
+ setup do
5
+ Class.new do
6
+ include Structr
7
+ end
8
+ end
9
+
10
+ asserts("module inclusion") { topic.included_modules.include?(Structr) }
11
+ %w(field field_reader field_writer field_accessor converter structr fields).each do |method|
12
+ asserts("responds to #{method}").respond_to(method)
13
+ end
14
+ asserts("no fields") { topic.fields.empty? }
15
+
16
+ asserts("error on no field name") { topic.field }.raises(ArgumentError)
17
+ asserts("error on no regexp") { topic.field(:name) }.raises(ArgumentError)
18
+
19
+ context "with field" do
20
+ context "added" do
21
+ setup do
22
+ topic.field :foo, %r{}
23
+ topic.field :bar, %r{}
24
+ topic.field :baz, %r{}
25
+ topic
26
+ end
27
+
28
+ asserts("3 added") { topic.fields.size }.equals(3)
29
+ end
30
+
31
+ context "defaults" do
32
+ setup do
33
+ topic.field(:no_acc_1, %r{})
34
+ topic.field(:no_acc_2, %r{}, :accessor => false)
35
+ end
36
+ [ :acc_1, :acc_2 ].each do |field|
37
+ asserts("no getter for #{field}") { !topic.respond_to?(field) }
38
+ asserts("no setter for #{field}") { !topic.respond_to?(:"#{field}=") }
39
+ end
40
+ end
41
+
42
+ context "accessors" do
43
+ setup do
44
+ topic.field(:acc_1, %r{}, :accessor => true)
45
+ topic.field(:acc_2, %r{}, :accessor => :accessor)
46
+ topic.field_accessor(:acc_3, %r{})
47
+ topic.new
48
+ end
49
+
50
+ [ :acc_1, :acc_2, :acc_3 ].each do |method|
51
+ asserts("has getter for #{method}") { topic.respond_to?(method) }
52
+ asserts("has setter for #{method}") { topic.respond_to?(:"#{method}=") }
53
+ end
54
+ end
55
+
56
+ context "readers" do
57
+ setup do
58
+ topic.field(:rdr_1, %r{}, :accessor => :reader)
59
+ topic.field_reader(:rdr_2, %r{})
60
+ topic.new
61
+ end
62
+
63
+ [ :rdr_1, :rdr_2 ].each do |method|
64
+ asserts("has getter for #{method}") { topic.respond_to?(method) }
65
+ asserts("has no sett for #{method}") { !topic.respond_to?(:"#{method}=") }
66
+ end
67
+ end
68
+
69
+ context "writers" do
70
+ setup do
71
+ topic.field(:wrt_1, %r{}, :accessor => :writer)
72
+ topic.field_writer(:wrt_2, %r{})
73
+ topic.new
74
+ end
75
+
76
+ [ :wrt_1, :wrt_2 ].each do |method|
77
+ asserts("has no getter for #{method}") { !topic.respond_to?(method) }
78
+ asserts("has setter for #{method}") { topic.respond_to?(:"#{method}=") }
79
+ end
80
+ end
81
+ end # with field
82
+
83
+ context "with converter" do
84
+ [ :int, :float, :date, :string ].each do |name|
85
+ asserts("default converter #{name} is a Proc") { topic.converter(name) }.kind_of(Proc)
86
+ end
87
+
88
+ [ :unkn, :unkn_reader, :unkn_writer, :unkn_accessor ].each do |field_name|
89
+ asserts("raises NoMethodError for #{field_name}") { topic.send(field_name, %r{}) }.raises(NoMethodError)
90
+ end
91
+
92
+ context "added" do
93
+ setup do
94
+ topic.converter(:proced, proc { |p| p })
95
+ topic.converter(:blocked) { |p| p }
96
+ topic
97
+ end
98
+
99
+ asserts("proced converter is a Proc") { topic.converter(:proced) }.kind_of(Proc)
100
+ asserts("blocked converter is a Proc") { topic.converter(:blocked) }.kind_of(Proc)
101
+ end
102
+
103
+ context "by-passed" do
104
+ setup do
105
+ pass = proc { |p| p }
106
+
107
+ topic.converter :pass, &pass
108
+ topic.field(:block, /(\d+)/) { |p| pass.call(p) }
109
+ topic.field(:param, /(\d+)/, &topic.converter(:pass))
110
+ topic.pass(:mm, /(\d+)/)
111
+ topic.pass(:mm_overwritten, /(\d+)/) { |i| fail("I am overwritten") }
112
+ topic
113
+ end
114
+
115
+ asserts("block field passes 1") { topic.fields[0].block.call(1) }.equals(1)
116
+ asserts("param field passes 1") { topic.fields[1].block.call(1) }.equals(1)
117
+ asserts("mm field passes 1") { topic.fields[2].block.call(1) }.equals(1)
118
+ asserts("mm_overwritten field passes 1") { topic.fields[3].block.call(1) }.equals(1)
119
+ end
120
+
121
+ context "using method missing" do
122
+ setup do
123
+ topic.int(:no_acc, %r{})
124
+ topic.int_reader(:rdr, %r{})
125
+ topic.int_writer(:wrt, %r{})
126
+ topic.int_accessor(:acc, %r{})
127
+ topic.new
128
+ end
129
+
130
+ asserts("no getter for no_acc") { !topic.respond_to?(:no_acc) }
131
+ asserts("no setter for no_acc") { !topic.respond_to?(:no_acc=) }
132
+ asserts("getter for rdr") { topic.respond_to?(:rdr) }
133
+ asserts("no setter for rdr") { !topic.respond_to?(:rdr=) }
134
+ asserts("no getter for wrt") { !topic.respond_to?(:wrt) }
135
+ asserts("setter for wrt") { topic.respond_to?(:wrt=) }
136
+ asserts("getter for acc") { topic.respond_to?(:acc) }
137
+ asserts("setter for acc") { topic.respond_to?(:acc=) }
138
+
139
+ end
140
+
141
+ end # with converters
142
+
143
+ # TODO test structr
144
+ # TODO integration tests
145
+
146
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: structr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Peter Suschlik
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-07 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: riot
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.10.4
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: riot_notifier
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.5
34
+ version:
35
+ description:
36
+ email: peter-structr@suschlik.de
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ files:
44
+ - .gitignore
45
+ - .watchr
46
+ - README.rdoc
47
+ - Rakefile
48
+ - VERSION
49
+ - examples/invoice.rb
50
+ - examples/top.rb
51
+ - lib/structr.rb
52
+ - structr.gemspec
53
+ - test/helper.rb
54
+ - test/test_field_descriptor.rb
55
+ - test/test_structr.rb
56
+ has_rdoc: true
57
+ homepage: http://github.com/splattael/structr
58
+ licenses: []
59
+
60
+ post_install_message:
61
+ rdoc_options:
62
+ - --charset=UTF-8
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ requirements: []
78
+
79
+ rubyforge_project:
80
+ rubygems_version: 1.3.5
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: Parse plain text to Ruby classes.
84
+ test_files:
85
+ - test/test_field_descriptor.rb
86
+ - test/test_structr.rb