rcl 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,107 @@
1
+ == What
2
+ Ramsey's Common Library version 0.0.3
3
+
4
+ This is simply a collection of Ruby modules that perform useful tasks. There
5
+ are methods that do simple things like check permissions on files and
6
+ directories, create and manipulate timestamps and authentication tokens, and
7
+ so on. There are a couple of interesting modules that do interesting things
8
+ like RELAXNG schema validation of XML files and an interactive command shell
9
+ with history (with big props to the GNU Readline library).
10
+
11
+ Briefly, the modules and their respective methods are:
12
+
13
+ Base#initialize
14
+ Base#dump
15
+ Base#create
16
+ Base#modify
17
+ Base#update_modify
18
+ Base#access
19
+ Base#update_access
20
+
21
+ Perms#check_dir
22
+ Perms#check_dir_r
23
+ Perms#check_dir_w
24
+ Perms#check_dir_rw
25
+ Perms#check_file
26
+ Perms#check_file_r
27
+ Perms#check_file_w
28
+ Perms#check_file_rw
29
+ Perms#check_owned
30
+
31
+ Shell#initialize
32
+ Shell#register
33
+ Shell#unregister
34
+ Shell#run
35
+ Shell#history
36
+
37
+ Timestamp#new
38
+ Timestamp#create
39
+ Timestamp#modify
40
+ Timestamp#access
41
+ Timestamp#update_create
42
+ Timestamp#update_modify
43
+ Timestamp#update
44
+ Timestamp#dump
45
+
46
+ Token#initialize
47
+ Token#authenticate
48
+
49
+ URL#encode
50
+ URL#decode
51
+ URL#encoded?
52
+
53
+ XML#preprocess
54
+ XML#validate
55
+
56
+ == How
57
+ require 'rubygems'
58
+ require 'rcl'
59
+
60
+ == Examples
61
+ When you run the example programs, do it like so:
62
+
63
+ ruby examples/shell.rb
64
+
65
+ Use these example programs to see how you might use and otherwise integrate RCL
66
+ functionality into your own programs.
67
+
68
+ == Install
69
+ I added a Rakefile and a gem spec. To build the gem, just type:
70
+
71
+ rake gem
72
+
73
+ To install the gem, just type:
74
+
75
+ sudo gem install rcl-0.0.3.gem
76
+
77
+ == Notes
78
+ I haven't tested this code with YARV, so YMMV.
79
+
80
+ A summary of the international standard date and time notation
81
+ http://www.cl.cam.ac.uk/~mgk25/iso-time.html
82
+
83
+ REXML support for RELAXNG document validation is incomplete and buggy. The
84
+ XML::preprocess method solves a particularly thorny issue which causes
85
+ REXML::RELAXNG to fail in the presence of whitespace due to formatting. Given
86
+ a pathname, this function will remove leading and trailing whitespace from
87
+ individual lines. The processed document will be returned to the caller for
88
+ further processing.
89
+
90
+ == External Code and Acknowledgements
91
+ Occasionally I run across interesting snippets of Ruby code on the 'net that
92
+ don't seem to have a reliable home. They could be orphaned projects or just a
93
+ random post on a mailing list somewhere. If I find the code to be useful, I
94
+ will add it to the lib/ext directory. Not wishing to steal anyone's thunder,
95
+ here are acknowledgements for the current contents of the lib/ext directory:
96
+
97
+ - ANSIColour 0.1 by Robert Ryan (rjr@rollmop.org)
98
+ http://raa.ruby-lang.org/project/term-ansicolour/
99
+
100
+ - dir.rb by Ara T. Howard (ara.t.howard@noaa.gov)
101
+
102
+ - YAML Helper by Xavier Shay
103
+ http://rhnh.net/projects/yaml+helper
104
+
105
+ I do not claim copyright or ownership of these included bits of code. I am
106
+ merely including them in one place because I use them all the time and wanted
107
+ to make them more available to my own projects.
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # rcl.rb
4
+ # yesmar@speakeasy.net
5
+
6
+ module RCL
7
+ RCL::NAME = 'rcl'
8
+ RCL::VERSION = '0.0.2'
9
+ RCL::COPYRIGHT = 'Copyright (c) 2008 Ramsey Dow'
10
+ def self.copyright() RCL::COPYRIGHT end
11
+ def self.version() RCL::VERSION end
12
+ def self.libdir() File.expand_path(__FILE__).gsub(%r/\.rb$/, '') end
13
+ end
14
+
15
+ $LOAD_PATH.unshift(File.dirname(__FILE__)+'/rcl/ext')
16
+
17
+ require 'ansicolour'
18
+ require 'dir'
19
+ require 'yaml_helper'
20
+
21
+ $LOAD_PATH.unshift(File.dirname(__FILE__)+'/rcl')
22
+
23
+ require 'base'
24
+ require 'perms'
25
+ require 'shell'
26
+ require 'timestamp'
27
+ require 'token'
28
+ require 'url'
29
+ require 'xml'
30
+
31
+ # TODO: need to fix any lingering inheritance issues
32
+ # TODO: bad developer, no unit tests
33
+ # TODO: need to finish developing example programs
34
+
35
+ raise RuntimeError, 'This library is for require only' if $0 == __FILE__
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # base.rb
4
+ # yesmar@speakeasy.net
5
+
6
+ module RCL
7
+ class Base
8
+ # initialize instance
9
+ def initialize
10
+ @timestamp = Timestamp.new
11
+
12
+ nil
13
+ end
14
+
15
+ # return creation time
16
+ def create
17
+ @timestamp.create
18
+ end
19
+
20
+ # return modification time
21
+ def modify
22
+ @timestamp.modify
23
+ end
24
+
25
+ # update modification time
26
+ def update_modify
27
+ @timestamp.update_modify
28
+ end
29
+
30
+ # return access time
31
+ def access
32
+ @timestamp.access
33
+ end
34
+
35
+ # update access time
36
+ def update_access
37
+ @timestamp.update_access
38
+ end
39
+
40
+ # dump instance state to specified output stream
41
+ def dump(indent=0, stream=STDOUT)
42
+ raise ArgumentError, 'nil indent' if indent.nil?
43
+ raise ArgumentError, 'invalid indent class' if indent.class != Fixnum
44
+ raise ArgumentError, 'indent range error' if indent < 0
45
+ raise ArgumentError, 'nil stream' if stream.nil?
46
+ raise ArgumentError, 'invalid stream class' if stream.class != IO
47
+
48
+ # instance
49
+ indent.times { stream << ' ' }
50
+ stream << "#{self.class}<#{self.object_id}> instance:\n"
51
+
52
+ indent += 2
53
+
54
+ # @timestamp
55
+ @timestamp.dump(indent, stream)
56
+
57
+ nil
58
+ end
59
+
60
+ private
61
+
62
+ @timestamp
63
+ end
64
+ end
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # perms.rb
4
+ # yesmar@speakeasy.net
5
+
6
+ module RCL
7
+ module Perms
8
+ # check that specified directory exists
9
+ def self.check_dir(pathname)
10
+ raise ArgumentError, 'nil pathname' if pathname.nil?
11
+ raise ArgumentError, 'invalid pathname class' if pathname.class != String
12
+ raise ArgumentError, 'empty pathname' if pathname.empty?
13
+ raise ArgumentError, "#{pathname} does not exist" \
14
+ if !File.exists?(pathname)
15
+ raise ArgumentError, "#{pathname} is not a directory" \
16
+ if !File.directory?(pathname)
17
+
18
+ true
19
+ end
20
+
21
+ # check that specified directory exists and is readable
22
+ def self.check_dir_r(pathname)
23
+ check_dir(pathname)
24
+ raise ArgumenError, "#{pathname} is not readable" \
25
+ if !File.readable?(pathname)
26
+
27
+ true
28
+ end
29
+
30
+ # check that specified directory exists and is writable
31
+ def self.check_dir_w(pathname)
32
+ check_dir(pathname)
33
+ raise ArgumentError, "#{pathname} is not writable" \
34
+ if !File.writable?(pathname)
35
+
36
+ true
37
+ end
38
+
39
+ # check that specified directory exists and is both readable and writable
40
+ def self.check_dir_rw(pathname)
41
+ check_dir(pathname)
42
+ check_dir_r(pathname)
43
+ check_dir_w(pathname)
44
+
45
+ true
46
+ end
47
+
48
+ # check that specified file exists
49
+ def self.check_file(pathname)
50
+ raise ArgumentError, 'nil pathname' if pathname.nil?
51
+ raise ArgumentError, 'invalid pathname class' if pathname.class != String
52
+ raise ArgumentError, 'empty pathname' if pathname.empty?
53
+ raise ArgumentError, "#{pathname} does not exist" \
54
+ if !File.exist?(pathname)
55
+ raise ArgumentError, "#{pathname} is not a directory" \
56
+ if !File.file?(pathname)
57
+
58
+ true
59
+ end
60
+
61
+ # check that specified file exists and is readable
62
+ def self.check_file_r(pathname)
63
+ check_file(pathname)
64
+ raise ArgumentError, "#{pathname} is not readable" \
65
+ if !File.readable?(pathname)
66
+
67
+ true
68
+ end
69
+
70
+ # check that specified file exists and is writable
71
+ def self.check_file_w(pathname)
72
+ check_file(pathname)
73
+ raise ArgumentError, "#{pathname} is not writable" \
74
+ if !File.writable?(pathname)
75
+
76
+ true
77
+ end
78
+
79
+ # check that specified file exists and is both readable and writable
80
+ def self.check_file_rw(pathname)
81
+ check_file(pathname)
82
+ check_file_r(pathname)
83
+ check_file_w(pathname)
84
+
85
+ true
86
+ end
87
+
88
+ # check file group and ownership
89
+ def self.check_owned(pathname)
90
+ raise ArgumentError, 'nil pathname' if pathname.nil?
91
+ raise ArgumentError, 'invalid pathname class' if pathname.class != String
92
+ raise ArgumentError, 'empty pathname' if pathname.empty?
93
+ raise ArgumentError, "#{pathname} does not have correct group" \
94
+ if !File.grpowned?(pathname)
95
+ raise ArgumentError, "#{pathname} does not have correct ownership" \
96
+ if !File.owned?(pathname)
97
+
98
+ true
99
+ end
100
+ end
101
+ end
102
+
103
+ raise RuntimeError, 'This library is for require only' if $0 == __FILE__
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # shell.rb
4
+ # yesmar@speakeasy.net
5
+
6
+ require 'readline'
7
+
8
+ module RCL
9
+ class Shell < Base
10
+ attr_accessor :prompt
11
+ attr_reader :history
12
+
13
+ Command = Struct.new('COMMAND', :command, :timestamp)
14
+
15
+ def initialize(processor, banner, prompt=DefaultPrompt)
16
+ raise ArgumentError, 'nil processor' if processor.nil?
17
+ raise ArgumentError, 'invalid processor class' \
18
+ if processor.class != Symbol
19
+ if !banner.nil?
20
+ raise ArgumentError, 'invalid banner class' if banner.class != Symbol
21
+ end
22
+ raise ArgumentError, 'nil prompt' if prompt.nil?
23
+ raise ArgumentError, 'invalid prompt class' if prompt.class != String
24
+
25
+ @banner = banner
26
+ @process = processor
27
+ @commands = {}
28
+ @prompt = (prompt[prompt.length-1].chr != ' ') ? "#{prompt} " : prompt
29
+ @history = []
30
+
31
+ nil
32
+ end
33
+
34
+ # TODO: Shell#register needs love
35
+ def register(command, *args)
36
+ raise ArgumentError, 'nil command' if command.nil?
37
+ raise ArgumentError, 'invalid command class' if command.class != String
38
+ raise ArgumentError, 'empty command' if command.empty?
39
+ if !args.nil?
40
+ raise ArgumentError, 'invalid args class' if args.class != Array
41
+ raise ArgumentError, 'empty args' if args.empty?
42
+ args.each do |arg|
43
+ raise ArgumentError, 'nil arg' if arg.nil?
44
+ raise ArgumentError, 'invalid arg class' if arg.class != String
45
+ raise ArgumentError, 'empty arg' if arg.empty?
46
+ end
47
+ end
48
+
49
+ @commands[command] = args
50
+
51
+ nil
52
+ end
53
+
54
+ # TODO: Shell#unregister needs love
55
+ def unregister(command)
56
+ raise ArgumentError, 'nil command' if command.nil?
57
+ raise ArgumentError, 'invalid command class' if command.class != String
58
+ raise ArgumentError, 'empty command' if command.empty?
59
+
60
+ if @commands.has_key?(command)
61
+ return @commands.delete(command)
62
+ end
63
+
64
+ nil
65
+ end
66
+
67
+ def run(*args)
68
+ if !@banner.nil?
69
+ send(@banner)
70
+ puts
71
+ end
72
+
73
+ loop do
74
+ buffer = Readline::readline(@prompt, true)
75
+ break if buffer.nil?
76
+ buffer.chomp!
77
+ next if buffer.nil?
78
+ buffer.strip!
79
+
80
+ # double bang repeatsw last command
81
+ if buffer =~ /^!!$/
82
+ if !@history.last.nil?
83
+ @history << Command.new(@history.last.command, Time.now)
84
+ exec(@history.last.command)
85
+ else
86
+ puts '!!: event not found'
87
+ end
88
+ next
89
+ end
90
+
91
+ # repeat specified command from history buffer
92
+ if buffer =~ /^!(\-)?(\d*)$/
93
+ if $1.nil?
94
+ # forward bang history
95
+ pos = $2.to_i-1
96
+ if pos < 0 || pos > @history.size-1
97
+ puts "#{buffer}: event not found"
98
+ next
99
+ else
100
+ if !@history[pos].command.nil?
101
+ @history << Command.new(@history[pos].command, Time.now)
102
+ exec(@history[pos].command)
103
+ end
104
+ end
105
+ else
106
+ # reverse bang history
107
+ pos = $2.to_i-1
108
+ if pos < 0 || pos > @history.size-1
109
+ puts "#{buffer}: event not found"
110
+ next
111
+ else
112
+ if !@history[pos].command.nil?
113
+ @history << Command.new(@history[-pos].command, Time.now)
114
+ exec(@history[-(pos+1)].command)
115
+ end
116
+ end
117
+ end
118
+ next
119
+ end
120
+
121
+ next if buffer.empty?
122
+
123
+ @history << Command.new(buffer, Time.now)
124
+ exec(buffer)
125
+ end
126
+
127
+ puts
128
+ end
129
+
130
+ def history
131
+ i = 1
132
+ @history.each do |cb|
133
+ printf("%4d: +%d %s\n", i, cb.timestamp.to_i, cb.command)
134
+ i += 1
135
+ end
136
+
137
+ nil
138
+ end
139
+
140
+ # TODO: need to add Shell#dump method
141
+
142
+ private
143
+
144
+ def exec(command)
145
+ raise ArgumentError, 'nil command' if command.nil?
146
+ raise ArgumentError, 'invalid command class' if command.class != String
147
+ raise ArgumentError, 'empty command' if command.empty?
148
+
149
+ cmd_ary = command.split(/[ \t]/)
150
+
151
+ cmd = cmd_ary[0]
152
+ args = cmd_ary[1..-1]
153
+
154
+ send(@process, cmd, args)
155
+ end
156
+
157
+ DefaultPrompt = ':'
158
+
159
+ @banner
160
+ @process
161
+ @commands
162
+ @prompt
163
+ @history
164
+ end
165
+ end
166
+
167
+ raise RuntimeError, 'This library is for require only' if $0 == __FILE__
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # timestamp.rb
4
+ # yesmar@speakeasy.net
5
+
6
+ module RCL
7
+ class Timestamp
8
+ attr_reader :create, :modify, :access
9
+
10
+ def initialize
11
+ @create = Time.now
12
+ @modify = nil
13
+ @access = nil
14
+
15
+ nil
16
+ end
17
+
18
+ def update_modify
19
+ @modify = Time.now
20
+
21
+ nil
22
+ end
23
+
24
+ def update_access
25
+ @access = Time.now
26
+
27
+ nil
28
+ end
29
+
30
+ def update
31
+ update_modify
32
+ update_access
33
+
34
+ nil
35
+ end
36
+
37
+ def dump(indent=0, stream=STDOUT)
38
+ raise ArgumentError, 'nil indent' if indent.nil?
39
+ raise ArgumentError, 'invalid indent class' if indent.class != Fixnum
40
+ raise ArgumentError, 'indent range error' if indent < 0
41
+ raise ArgumentError, 'nil stream' if stream.nil?
42
+ raise ArgumentError, 'invalid stream class' if stream.class != IO
43
+
44
+ # instance
45
+ indent.times { stream << ' ' }
46
+ stream << "#{self.class}<#{self.object_id}> instance:\n"
47
+
48
+ indent += 2
49
+
50
+ # create
51
+ indent.times { stream << ' ' }
52
+ stream << "create: #{@create}\n"
53
+
54
+ # modify
55
+ indent.times { stream << ' ' }
56
+ stream << "modify: #{!@modify.nil? ? @modify : '<unset>'}\n"
57
+
58
+ # access
59
+ indent.times { stream << ' ' }
60
+ stream << "access: #{!@access.nil? ? @access : '<unset>'}\n"
61
+
62
+ nil
63
+ end
64
+
65
+ private
66
+
67
+ @create
68
+ @modify
69
+ @access
70
+ end
71
+
72
+ # convert milliseconds to hours, minutes, and seconds
73
+ def self.ms_to_s(ms)
74
+ raise ArgumentError, 'nil ms' if ms.nil?
75
+ raise ArgumentError, 'invalid ms class' if !ms.is_a?(Integer)
76
+
77
+ s = ms % 60
78
+ m = (ms / 60) % 60
79
+ h = (ms / 3600) % 24
80
+
81
+ str = []
82
+
83
+ str << h.to_s << 'h ' if h >= 1
84
+ str << m.to_s << 'm ' if m >= 1
85
+ str << s.to_s << 's ' if s > 0
86
+
87
+ return str.join
88
+
89
+ end
90
+ end
91
+
92
+ raise RuntimeError, 'This library is for require only' if $0 == __FILE__
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # token.rb
4
+ # yesmar@speakeasy.net
5
+
6
+ require 'digest/sha2'
7
+
8
+ module RCL
9
+ class Token
10
+ def initialize(key)
11
+ raise ArgumentError, 'nil key' if key.nil?
12
+ raise ArgumentError, 'invalid key class' if key.class != String
13
+ raise ArgumentError, 'empty key' if key.empty?
14
+
15
+ srand
16
+
17
+ @salt = [Array.new(6) { rand(256).chr }.join].pack("m").chomp
18
+ @hash = Digest::SHA256.hexdigest(@salt+key)
19
+
20
+ nil
21
+ end
22
+
23
+ def authenticate(key)
24
+ raise ArgumentError, 'nil key' if key.nil?
25
+ raise ArgumentError, 'invalid key class' if key.class != String
26
+ raise ArgumentError, 'empty key' if key.empty?
27
+
28
+ Digest::SHA256.hexdigest(@salt+key) == @hash
29
+ end
30
+
31
+ private
32
+
33
+ @salt
34
+ @hash
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # url.rb
4
+ # yesmar@speakeasy.net
5
+
6
+ require 'uri'
7
+
8
+ module RCL
9
+ module URL
10
+ def self.encode(url)
11
+ raise ArgumentError, 'nil url' if url.nil?
12
+ raise ArgumentError, 'invalid url' if url.class != String
13
+ raise ArgumentError, 'empty url' if url.empty?
14
+
15
+ URI::escape(url, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
16
+ end
17
+
18
+ def self.decode(url)
19
+ raise ArgumentError, 'nil url' if url.nil?
20
+ raise ArgumentError, 'invalid url' if url.class != String
21
+ raise ArgumentError, 'empty url' if url.empty?
22
+
23
+ URI::unescape(url)
24
+ end
25
+
26
+ def self.encoded?(url)
27
+ raise ArgumentError, 'nil url' if url.nil?
28
+ raise ArgumentError, 'invalid url' if url.class != String
29
+ raise ArgumentError, 'empty url' if url.empty?
30
+
31
+ return (url =~ /%/) ? true : false
32
+ end
33
+ end
34
+ end
35
+
36
+ raise RuntimeError, 'This library is for require only' if $0 == __FILE__
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # xml.rb
4
+ # yesmar@speakeasy.net
5
+
6
+ require 'rexml/document'
7
+ require 'rexml/validation/relaxng'
8
+
9
+ require 'perms'
10
+
11
+ module RCL
12
+ module XML
13
+ # remove leading and trailing whitespace from elements
14
+ def self.preprocess(pathname)
15
+ Perms::check_file_r(pathname)
16
+
17
+ doc = IO.readlines(pathname)
18
+
19
+ doc.each do |line|
20
+ line.gsub!(/\s+</, '<')
21
+ line.gsub!(/\s+$/, '')
22
+ end
23
+
24
+ doc.join
25
+ end
26
+
27
+ # validate XML document against specified RELAXNG schema
28
+ def self.validate(pathname, schema_pathname=nil)
29
+ Perms::check_file_r(pathname)
30
+
31
+ if schema_pathname.nil?
32
+ schema_pathname = \
33
+ "#{File.basename(pathname, File.extname(pathname))}.xsd"
34
+ end
35
+
36
+ Perms::check_file_r(schema_pathname)
37
+
38
+ begin
39
+ schema = File.new(schema_pathname)
40
+ validator = REXML::Validation::RelaxNG.new(schema)
41
+ parser = REXML::Parsers::TreeParser.new(preprocess(pathname))
42
+ parser.add_listener(validator.reset)
43
+ parser.parse
44
+ rescue REXML::Validation::ValidationException => e
45
+ raise "#{e}"
46
+ rescue Exception => e
47
+ raise "#{e}"
48
+ end
49
+
50
+ true
51
+ end
52
+ end
53
+ end
54
+
55
+ raise RuntimeError, 'This library is for require only' if $0 == __FILE__
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rcl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Ramsey Dow
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-02-27 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: yesmar @nospam@ speakeasy.net
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - lib/rcl.rb
26
+ - lib/rcl/base.rb
27
+ - lib/rcl/perms.rb
28
+ - lib/rcl/shell.rb
29
+ - lib/rcl/timestamp.rb
30
+ - lib/rcl/token.rb
31
+ - lib/rcl/url.rb
32
+ - lib/rcl/xml.rb
33
+ - README
34
+ has_rdoc: true
35
+ homepage: http://rcl.rubyforge.org/
36
+ post_install_message:
37
+ rdoc_options: []
38
+
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ requirements: []
54
+
55
+ rubyforge_project: http://rubyforge.org/projects/rcl/
56
+ rubygems_version: 1.0.1
57
+ signing_key:
58
+ specification_version: 2
59
+ summary: Ramsey's Common Library
60
+ test_files: []
61
+