fdlint 0.1.3 → 0.2.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +2 -9
  3. data/Gemfile.lock +14 -4
  4. data/Rakefile +9 -85
  5. data/bin/fdlint +71 -8
  6. data/lib/fdlint/cli.rb +102 -0
  7. data/lib/{file_validator.rb → fdlint/file_validator.rb} +0 -0
  8. data/lib/fdlint/helper/code_type.rb +55 -0
  9. data/lib/fdlint/helper/file_reader.rb +19 -0
  10. data/lib/fdlint/helper/logger.rb +35 -0
  11. data/lib/fdlint/log_entry.rb +51 -0
  12. data/lib/fdlint/parser/base_parser.rb +151 -0
  13. data/lib/{css/parser.rb → fdlint/parser/css/css_parser.rb} +31 -28
  14. data/lib/{css → fdlint/parser/css}/struct.rb +5 -5
  15. data/lib/{encoding_error.rb → fdlint/parser/encoding_error.rb} +2 -2
  16. data/lib/{html/parser.rb → fdlint/parser/html/html_parser.rb} +26 -20
  17. data/lib/{html → fdlint/parser/html}/query.rb +0 -0
  18. data/lib/{html → fdlint/parser/html}/rule/check_tag_rule.rb +0 -0
  19. data/lib/{html → fdlint/parser/html}/struct.rb +82 -38
  20. data/lib/fdlint/parser/js/expr/expr.rb +71 -0
  21. data/lib/fdlint/parser/js/expr/left_hand.rb +65 -0
  22. data/lib/fdlint/parser/js/expr/operate.rb +94 -0
  23. data/lib/fdlint/parser/js/expr/primary.rb +168 -0
  24. data/lib/{js/parser.rb → fdlint/parser/js/js_parser.rb} +11 -9
  25. data/lib/fdlint/parser/js/stat/if.rb +27 -0
  26. data/lib/fdlint/parser/js/stat/iter.rb +88 -0
  27. data/lib/fdlint/parser/js/stat/stat.rb +120 -0
  28. data/lib/fdlint/parser/js/stat/switch.rb +67 -0
  29. data/lib/fdlint/parser/js/stat/try.rb +30 -0
  30. data/lib/fdlint/parser/js/stat/var.rb +42 -0
  31. data/lib/fdlint/parser/js/struct.rb +257 -0
  32. data/lib/fdlint/parser/node.rb +27 -0
  33. data/lib/fdlint/parser/parse_error.rb +10 -0
  34. data/lib/fdlint/parser/parser_visitable.rb +134 -0
  35. data/lib/{position_info.rb → fdlint/parser/position_info.rb} +12 -1
  36. data/lib/fdlint/parser.rb +3 -0
  37. data/lib/fdlint/printer/base_printer.rb +20 -0
  38. data/lib/fdlint/printer/console_printer.rb +80 -0
  39. data/lib/fdlint/printer/nocolor_printer.rb +29 -0
  40. data/lib/fdlint/printer/vim_printer.rb +21 -0
  41. data/lib/fdlint/printer.rb +1 -0
  42. data/lib/fdlint/rule/dsl.rb +83 -0
  43. data/lib/fdlint/rule/validation.rb +79 -0
  44. data/lib/fdlint/rule.rb +81 -0
  45. data/lib/fdlint/support/core/array.rb +5 -0
  46. data/lib/fdlint/support/core/file.rb +8 -0
  47. data/lib/fdlint/support/core/hash.rb +5 -0
  48. data/lib/fdlint/support/core/nil.rb +7 -0
  49. data/lib/fdlint/support/core/string.rb +7 -0
  50. data/lib/fdlint/support/core_ext.rb +5 -0
  51. data/lib/fdlint/validator.rb +102 -0
  52. data/lib/fdlint/version.rb +3 -0
  53. data/lib/fdlint.rb +5 -0
  54. data/rules.d/css.rule.rb +100 -0
  55. data/rules.d/filename.rule.rb +49 -0
  56. data/rules.d/html.rule.rb +169 -0
  57. data/rules.d/js.jquery.rule.rb +49 -0
  58. data/rules.d/js.rule.rb +205 -0
  59. data/test/default_test.rb +14 -0
  60. data/test/fixtures/js/scope-test.js +15 -2
  61. data/test/test_helper.rb +9 -0
  62. metadata +269 -221
  63. data/lib/base_parser.rb +0 -143
  64. data/lib/cmd_runner.rb +0 -145
  65. data/lib/context.rb +0 -31
  66. data/lib/css/reader.rb +0 -30
  67. data/lib/css/rule/check_compression_rule.rb +0 -48
  68. data/lib/css/rule/checklist.rb +0 -45
  69. data/lib/helper/code_type.rb +0 -50
  70. data/lib/helper/color_string.rb +0 -44
  71. data/lib/helper/file_reader.rb +0 -22
  72. data/lib/helper/strenc.rb +0 -65
  73. data/lib/js/expr/expr.rb +0 -66
  74. data/lib/js/expr/left_hand.rb +0 -63
  75. data/lib/js/expr/operate.rb +0 -92
  76. data/lib/js/expr/primary.rb +0 -166
  77. data/lib/js/rule/all.rb +0 -35
  78. data/lib/js/rule/checklist.rb +0 -41
  79. data/lib/js/rule/file_checker.rb +0 -42
  80. data/lib/js/rule/helper.rb +0 -96
  81. data/lib/js/rule/no_global.rb +0 -87
  82. data/lib/js/stat/if.rb +0 -25
  83. data/lib/js/stat/iter.rb +0 -85
  84. data/lib/js/stat/stat.rb +0 -117
  85. data/lib/js/stat/switch.rb +0 -65
  86. data/lib/js/stat/try.rb +0 -28
  87. data/lib/js/stat/var.rb +0 -40
  88. data/lib/js/struct.rb +0 -248
  89. data/lib/log_entry.rb +0 -49
  90. data/lib/node.rb +0 -28
  91. data/lib/parse_error.rb +0 -13
  92. data/lib/parser_visitable.rb +0 -138
  93. data/lib/printer/base_printer.rb +0 -24
  94. data/lib/printer/console_printer.rb +0 -66
  95. data/lib/printer/nocolor_printer.rb +0 -27
  96. data/lib/printer/vim_printer.rb +0 -19
  97. data/lib/rule.rb +0 -241
  98. data/lib/rule_helper.rb +0 -14
  99. data/lib/runner.rb +0 -225
  100. data/rules.d/css.rule +0 -127
  101. data/rules.d/html.dtd.rule +0 -22
  102. data/rules.d/html.prop.rule +0 -51
  103. data/rules.d/html.tag.rule +0 -136
  104. data/rules.d/js.file.rule +0 -13
  105. data/rules.d/js.jquery.rule +0 -56
  106. data/rules.d/js.mergefile.rule +0 -71
  107. data/rules.d/js.rule +0 -84
data/lib/base_parser.rb DELETED
@@ -1,143 +0,0 @@
1
- require 'strscan'
2
-
3
- require_relative 'node'
4
- require_relative 'parse_error'
5
- require_relative 'position_info'
6
-
7
- module XRay
8
-
9
- class BaseParser
10
-
11
- attr_reader :log
12
-
13
- def initialize(text, log = nil)
14
- @log = log
15
- text = filter_text(prepare_text(text))
16
- @pos_info = PositionInfo.new text
17
- @scanner = StringScanner.new text
18
- @text_size = text.size
19
- end
20
-
21
- def skip_empty
22
- @scanner.skip(/\s*/)
23
- end
24
-
25
- def skip(pattern, not_skip_empty = false)
26
- not_skip_empty || skip_empty
27
- pos = scanner_pos
28
- unless @scanner.skip pattern
29
- parse_error pattern
30
- end
31
- after_skip pattern
32
- pos
33
- end
34
-
35
- def check(pattern, not_skip_empty = false)
36
- skip_empty = !not_skip_empty
37
- if skip_empty && @scanner.check(/\s+/)
38
- last_pos = @scanner.pos
39
- @scanner.skip /\s+/
40
- end
41
- ret = @scanner.check pattern
42
- @scanner.pos = last_pos if last_pos
43
- ret
44
- end
45
-
46
- def scan(pattern, not_skip_empty = false)
47
- node = raw_scan pattern, not_skip_empty
48
- after_scan pattern
49
- node
50
- end
51
-
52
- def raw_scan(pattern, not_skip_empty = false)
53
- not_skip_empty || skip_empty
54
- pos = scanner_pos
55
- text = @scanner.scan pattern
56
- text ? Node.new(text, pos) : parse_error(pattern)
57
- end
58
-
59
- def batch(name, stop = nil, skip_pattern = nil, not_skip_empty = false, &block)
60
- first = true
61
- result = []
62
- while !@scanner.eos? &&
63
- (stop ? batch_check(stop, skip_pattern, not_skip_empty, first) :
64
- block ? block.call : true) &&
65
- item = send(name)
66
- result << item
67
- first = false
68
- end
69
- result
70
- end
71
-
72
- def to_s
73
- self.class.to_s
74
- end
75
-
76
- def reset
77
- @scanner.reset
78
- end
79
-
80
- def eos?
81
- @scanner.eos?
82
- end
83
-
84
- protected
85
-
86
- def filter_text(text)
87
- text
88
- end
89
-
90
- def after_skip(pattern)
91
- end
92
-
93
- def after_scan(pattern)
94
- end
95
-
96
- def parse_warn(message)
97
- pos = scanner_pos
98
- log "#{message}#{pos}", :warn
99
- end
100
-
101
- def parse_error(pattern)
102
- if pattern.respond_to?(:source)
103
- message = "should be #{pattern.source} here"
104
- else
105
- message = pattern.to_s
106
- end
107
- pos = scanner_pos
108
- log "#{message} #{pos}", :error
109
- raise ParseError.new(message, pos)
110
- end
111
-
112
- def log(message, level = :info)
113
- @log && @log.send("#{level}?") &&
114
- @log.send(level, self.to_s + ': ' + message)
115
- end
116
-
117
- def scanner_pos
118
- pos = @text_size - @scanner.rest.size
119
- @pos_info.locate pos
120
- end
121
-
122
- private
123
-
124
- def prepare_text(text)
125
- if text.respond_to? :encode!
126
- text.encode! 'utf-8', :invalid => :replace, :universal_newline => true
127
- else
128
- text.gsub(/\r\n/, "\n").gsub(/\r/, "\n")
129
- end
130
- end
131
-
132
- def batch_check(stop, skip_pattern, not_skip_empty, first)
133
- if check stop, not_skip_empty
134
- false
135
- else
136
- !first && skip_pattern && skip(skip_pattern)
137
- true
138
- end
139
- end
140
-
141
- end
142
-
143
- end
data/lib/cmd_runner.rb DELETED
@@ -1,145 +0,0 @@
1
- #!/usr/bin/env ruby
2
- require 'optparse'
3
- require 'find'
4
- require_relative 'runner'
5
- require_relative 'printer/vim_printer'
6
- require_relative 'printer/console_printer'
7
- require_relative 'printer/nocolor_printer'
8
-
9
- module XRay
10
-
11
- Version = "0.1"
12
-
13
- class CMDOptions
14
-
15
- def self.parse( args )
16
- files = []
17
- options = {
18
- :encoding => 'gb2312',
19
- :colorful => true,
20
- :type => nil,
21
- :check_min => false,
22
- :format => :console
23
- }
24
-
25
- opts = OptionParser.new do |opts|
26
- opts.banner = "Usage: fdlint"
27
- %w(css js html).each do |type|
28
- opts.on("--#{type}", "check #{type} files only") do
29
- options[:type] = type.intern
30
- end
31
- end
32
- opts.on("--charset set", "-c", "file charset") do |enc|
33
- options[:encoding] = enc
34
- end
35
- opts.on("--debug", "-d", "print debug info") do
36
- options[:debug] = true
37
- end
38
- opts.on("--list", "-l", "list results without source, the same as '--format=nocolor'") do
39
- options[:format] = :nocolor
40
- end
41
- opts.on("--checkmin", "-m", "check minified files too. (e.g. *-min.js; *-min.css)") do
42
- options[:check_min] = true
43
- end
44
- opts.on("--format [type]", [:console, :nocolor, :vim], "output format. Can be 'vim', 'console' or 'nocolor'. Default is 'console'") do |f|
45
- options[:format] = f.intern
46
- end
47
- opts.on("--level [log_level]", [:warn, :error, :fatal], "determine the log level. Can be 'warn', 'error' or 'fatal'") do |level|
48
- options[:log_level] = level
49
- end
50
- end
51
-
52
- begin
53
- rest = opts.parse! args
54
- files.concat rest
55
-
56
- if files.empty?
57
- unless $stdin.tty?
58
- str = ARGF.read
59
- options[:text] = str
60
- else
61
- raise ArgumentError.new("")
62
- end
63
- end
64
-
65
- rescue => e
66
- puts e.message.capitalize + "\n\n"
67
- puts opts
68
- exit 1
69
- end
70
- [options, files]
71
-
72
- end
73
-
74
- end
75
-
76
- class CMDRunner
77
-
78
- def self.run
79
- self.new.run
80
- end
81
-
82
- def run
83
- options, files = XRay::CMDOptions.parse ARGV
84
- @core_runner = XRay::Runner.new(options)
85
-
86
- unless files.empty?
87
- files.each do |file|
88
- check_file file, options
89
- end
90
- else
91
- method = options[:type] ? :"check_#{options[:type]}" : :check
92
- print @core_runner.send(method, options[:text].utf8!), options
93
- end
94
- end
95
-
96
- private
97
- def check_file( file, opt)
98
-
99
- if File.directory? file
100
- Find.find(file) do |f|
101
- check_file(f, opt) unless File.directory? f
102
- end
103
- return
104
- end
105
-
106
- if @core_runner.valid_file? file
107
- print @core_runner.check_file( file ), opt.merge( :file => file.to_s )
108
- end
109
- end
110
-
111
- def print( results, opt={} )
112
- print_results(
113
- results,
114
- opt.merge({
115
- :prefix => ' ' * 5,
116
- :source => @core_runner.source
117
- })
118
- )
119
- end
120
-
121
- def print_results( results, opt={} )
122
- IO.popen "chcp 65001" if ENV['OS'] =~ /windows/i
123
- print_class = case opt[:format]
124
- when :vim
125
- 'VimPrinter'
126
- when :nocolor
127
- 'NoColorPrinter'
128
- else
129
- 'ConsolePrinter'
130
- end
131
- XRay.register_printer XRay.const_get( print_class )
132
- XRay.printer.new( results, opt ).print
133
- end
134
-
135
- def print_type(format)
136
- format || 'base'
137
- end
138
-
139
- end
140
-
141
- end
142
-
143
- if $0 == __FILE__
144
- XRay::CMDRunner.run
145
- end
data/lib/context.rb DELETED
@@ -1,31 +0,0 @@
1
- module XRay
2
-
3
- module Context
4
-
5
- attr_accessor :scope
6
-
7
- def lib?
8
- scope == :lib
9
- end
10
-
11
- def page_level?
12
- scope == :page
13
- end
14
-
15
- def in_page?
16
- scope == :in_page
17
- end
18
-
19
- end
20
-
21
- class DefaultContext
22
-
23
- include Context
24
-
25
- def initialize
26
- @scope = :page
27
- end
28
- end
29
-
30
-
31
- end
data/lib/css/reader.rb DELETED
@@ -1,30 +0,0 @@
1
- require_relative '../encoding_error'
2
- require_relative '../helper/file_reader'
3
-
4
- module XRay
5
- module CSS
6
-
7
- class Reader
8
-
9
- include XRay::Helper
10
-
11
- def self.read( file, opt = {} )
12
- source, enc = FileReader::readfile(file)
13
- declare = get_encoding_declaration(file)
14
- if declare and enc != declare
15
- raise EncodingError.new
16
- end
17
- source
18
- end
19
-
20
- def self.get_encoding_declaration( file )
21
- begin
22
- File.open(file, &:readline)[/@charset\s+(['"])(.*?)\1/, 2]
23
- rescue
24
- end
25
- end
26
- end
27
-
28
- end
29
-
30
- end
@@ -1,48 +0,0 @@
1
- # encoding: utf-8
2
- require_relative '../../log_entry'
3
-
4
- module XRay
5
- module CSS
6
- module Rule
7
-
8
- class CompressionChecker
9
-
10
- def initialize( opt={} )
11
- @opt = opt.dup
12
- end
13
-
14
- def check_file( name )
15
- check_items([
16
- :has_minified_in_same_folder
17
- ], name)
18
- end
19
-
20
- def check_has_minified_in_same_folder( name )
21
- unless is_min_file?(name) or is_merge_file?(name) or File.exist?( name.sub(/\.css$/,'-min.css') )
22
- [LogEntry.new('发布上线的文件需要压缩,命名规则如a.js->a-min.js,且两者在同一目录下', :error)]
23
- end
24
- end
25
-
26
- protected
27
- def check_items( items, name )
28
- results = []
29
- items.each do |i|
30
- r = self.send(:"check_#{i}", name)
31
- results.concat r if Array === r
32
- end
33
- results
34
- end
35
-
36
- def is_merge_file?( name )
37
- name =~ /merge\.css$/
38
- end
39
-
40
- def is_min_file?( name )
41
- name =~ /min\.css$/
42
- end
43
-
44
- end
45
-
46
- end
47
- end
48
- end
@@ -1,45 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require_relative '../../rule'
4
- require_relative '../../context'
5
-
6
- module XRay
7
- module CSS
8
- module Rule
9
-
10
- class CheckListRule
11
-
12
- attr_reader :options
13
-
14
- include ::XRay::Rule, Context
15
-
16
- def initialize(options = {})
17
- @options = options
18
- end
19
-
20
- def visit_simple_selector(selector)
21
- check_css_selector selector
22
- end
23
-
24
- def visit_declaration(dec)
25
- check_declaration dec
26
- end
27
-
28
- def visit_ruleset(ruleset)
29
- check_css_ruleset ruleset
30
- end
31
-
32
- def visit_property(property)
33
- check_css_property property
34
- end
35
-
36
- def visit_value(value)
37
- check_css_value value
38
- end
39
-
40
-
41
- end
42
-
43
- end
44
- end
45
- end
@@ -1,50 +0,0 @@
1
- class CodeType
2
-
3
- class << self
4
-
5
- public
6
- def guess(text, filename=nil)
7
- if filename && !filename.empty?
8
- guess_by_name filename
9
- else
10
- guess_by_content text
11
- end
12
- end
13
-
14
- def guess_by_name( filename )
15
- case File.extname( filename )
16
- when /\.css$/i
17
- :css
18
- when /\.js$/i
19
- :js
20
- else
21
- :html #TODO: support more suffix
22
- end
23
- end
24
-
25
- def guess_by_content(text)
26
- return :html if is_html? text
27
- return :css if is_css? text
28
- :js #TODO: support more code syntaxes
29
- end
30
-
31
- def is_style_file?(filename)
32
- File.extname( filename ) =~ /(css|js|html?)$/i
33
- end
34
-
35
- def scope(filename)
36
- filename =~ /[\\\/]lib[\\\/]/ ? 'lib' : 'page'
37
- end
38
-
39
- private
40
- def is_html?(text)
41
- /^\s*</m =~ text
42
- end
43
-
44
- def is_css?(text)
45
- /^\s*@/m =~ text or /^\s*([-\*:\.#_\w]+\s*)+\{/ =~ text
46
- end
47
-
48
- end
49
-
50
- end
@@ -1,44 +0,0 @@
1
- class ColorString
2
-
3
- @@colors = %w(black red green yellow blue magenta cyan white)
4
-
5
- def self.colors
6
- @@colors
7
- end
8
-
9
- def initialize(str, color=nil, bg=nil)
10
- @str, @color, @bg = str, color, bg
11
- end
12
-
13
- def to_s
14
- return @str if @color.nil? and @bg.nil?
15
- s = []
16
- s << "3#{@color}" unless @color.nil?
17
- s << "4#{@bg}" unless @bg.nil?
18
- "\e[#{s.join(';')}m" << @str << "\e[0m"
19
- end
20
-
21
- def inspect
22
- "#<ColorString \"#{@str}\", color:#{@color}, bg:#{@bg}>"
23
- end
24
-
25
- String.public_instance_methods.each do |m|
26
- unless self.respond_to? m
27
- define_method(m) { |*arg| to_s.send( m, *arg ) }
28
- end
29
- end
30
-
31
- @@colors.each_with_index do |color, i|
32
- String.class_eval do
33
- define_method(color) { ColorString.new(self, i) }
34
- define_method(color << '_bg') { ColorString.new(self, nil, i) }
35
- end
36
-
37
- ColorString.class_eval do
38
- define_method(color) { @color = i; self }
39
- define_method(color << '_bg') { @bg = i; self }
40
- end
41
-
42
- end
43
- end
44
-
@@ -1,22 +0,0 @@
1
- require_relative 'strenc'
2
-
3
- module XRay
4
- module Helper
5
- module FileReader
6
- extend self
7
-
8
- # auto detect file encoding and read it.
9
- # return with an array containing string
10
- # and encoding
11
- def readfile(path, opt={})
12
- if File.readable?(path)
13
- bin = File.read(path).utf8!
14
- [bin, bin.former_enc ||'ascii-8bit' ]
15
- else
16
- raise ArgumentError.new("File is not readable!")
17
- end
18
- end
19
-
20
- end
21
- end
22
- end
data/lib/helper/strenc.rb DELETED
@@ -1,65 +0,0 @@
1
- # encoding: utf-8
2
-
3
- class String
4
-
5
- @@encs = %w(ascii utf-8 gb18030 gbk gb2312 cp936 big5)
6
-
7
- class << self
8
- def encs; @@encs; end
9
- def encs=(arr); @@encs = arr; end
10
- end
11
-
12
- if public_instance_methods.include? :encode!
13
- def try_convert to, from
14
- encode!( to, from ).force_encoding( to )
15
- valid_encoding?
16
- end
17
- else
18
- # for ruby 1.8
19
- require 'iconv'
20
- def try_convert to, from
21
- text = Iconv.new(to, from).iconv(self)
22
- replace(text) if self =~ /./
23
- end
24
- end
25
-
26
- attr_reader :former_enc
27
-
28
- def enc!(encoding)
29
-
30
- if respond_to? :force_encoding
31
- force_encoding encoding
32
- if valid_encoding?
33
- @former_enc = encoding
34
- return self
35
- end
36
- end
37
-
38
- @@encs.each do |c|
39
- begin
40
- if send :try_convert, encoding, c
41
- @former_enc = c
42
- break
43
- end
44
- rescue
45
- end
46
- end
47
- self
48
- end
49
-
50
- @@encs.each do |tar|
51
- mtd = tar.gsub(/-/, '') << "!"
52
- define_method mtd do
53
- enc! tar
54
- end
55
- public mtd
56
- end
57
-
58
- private :try_convert
59
-
60
- end
61
-
62
-
63
- if __FILE__ == $0
64
- puts File.read('test/fixtures/html/1-1.html').utf8!
65
- end
data/lib/js/expr/expr.rb DELETED
@@ -1,66 +0,0 @@
1
- require_relative 'primary'
2
- require_relative 'left_hand'
3
- require_relative 'operate'
4
-
5
-
6
- module XRay
7
- module JS
8
- module Expr
9
-
10
- module Expr
11
- include Primary, LeftHand, Operate
12
-
13
- def parse_expression
14
- log 'parse expression'
15
- parse_expr_with_operate :parse_expr_assignment, /,/
16
- end
17
-
18
- def parse_expr_assignment
19
- log 'parse expr assignment'
20
-
21
- expr = parse_expr_condition
22
-
23
- r = /=|\*=|\/=|%=|\+=|-=|<<=|>>=|>>>=|&=|\^=|\|=/
24
- if expr.left_hand? && check(r)
25
- op = scan r
26
- expr = create_element Expression, op.text, expr, parse_expr_assignment
27
- end
28
- expr
29
- end
30
-
31
- def parse_expr_condition
32
- log 'parse expr condition'
33
- expr = parse_expr_logical_or
34
- if check /\?/
35
- skip /\?/
36
- left = parse_expr_assignment
37
- skip /:/
38
- right = parse_expr_assignment
39
-
40
- expr = create_element ConditionExpression, expr, left, right
41
- end
42
- expr
43
- end
44
-
45
- protected
46
-
47
- def parse_expr_with_operate(left, pattern = nil, &block)
48
- block = block || lambda {
49
- if check pattern
50
- op = scan pattern
51
- [op.text, self.send(left)]
52
- end
53
- }
54
-
55
- expr = self.send left
56
- while (ret = block.call)
57
- expr = create_element Expression, ret[0], expr, ret[1]
58
- end
59
- expr
60
- end
61
-
62
- end
63
-
64
- end
65
- end
66
- end