structr 0.0.2

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