rcl 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.
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
+