rsutphin-cf_case_check 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/History.txt +3 -0
- data/Manifest.txt +26 -0
- data/README.txt +75 -0
- data/Rakefile +39 -0
- data/bin/cf_case_check +27 -0
- data/lib/case_check/coldfusion_source.rb +92 -0
- data/lib/case_check/commands.rb +103 -0
- data/lib/case_check/configuration.rb +46 -0
- data/lib/case_check/core-ext.rb +51 -0
- data/lib/case_check/reference.rb +63 -0
- data/lib/case_check/references/cfc.rb +47 -0
- data/lib/case_check/references/cfinclude.rb +20 -0
- data/lib/case_check/references/cfmodule.rb +48 -0
- data/lib/case_check/references/custom_tag.rb +50 -0
- data/lib/case_check.rb +60 -0
- data/spec/coldfusion_source_spec.rb +161 -0
- data/spec/commands_spec.rb +103 -0
- data/spec/configuration_spec.rb +42 -0
- data/spec/core_ext_spec.rb +72 -0
- data/spec/reference_spec.rb +52 -0
- data/spec/references/cfc_spec.rb +62 -0
- data/spec/references/cfinclude_spec.rb +66 -0
- data/spec/references/cfmodule_spec.rb +139 -0
- data/spec/references/custom_tag_spec.rb +143 -0
- data/spec/spec_helper.rb +37 -0
- metadata +100 -0
data/.gitignore
ADDED
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
.gitignore
|
2
|
+
History.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
bin/cf_case_check
|
7
|
+
lib/case_check.rb
|
8
|
+
lib/case_check/coldfusion_source.rb
|
9
|
+
lib/case_check/commands.rb
|
10
|
+
lib/case_check/configuration.rb
|
11
|
+
lib/case_check/core-ext.rb
|
12
|
+
lib/case_check/reference.rb
|
13
|
+
lib/case_check/references/cfc.rb
|
14
|
+
lib/case_check/references/cfinclude.rb
|
15
|
+
lib/case_check/references/cfmodule.rb
|
16
|
+
lib/case_check/references/custom_tag.rb
|
17
|
+
spec/coldfusion_source_spec.rb
|
18
|
+
spec/commands_spec.rb
|
19
|
+
spec/configuration_spec.rb
|
20
|
+
spec/core_ext_spec.rb
|
21
|
+
spec/reference_spec.rb
|
22
|
+
spec/references/cfc_spec.rb
|
23
|
+
spec/references/cfinclude_spec.rb
|
24
|
+
spec/references/cfmodule_spec.rb
|
25
|
+
spec/references/custom_tag_spec.rb
|
26
|
+
spec/spec_helper.rb
|
data/README.txt
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
`cf_case_check`
|
2
|
+
===============
|
3
|
+
http://github.com/rsutphin/cf_case_check
|
4
|
+
|
5
|
+
Description
|
6
|
+
-----------
|
7
|
+
|
8
|
+
`cf_case_check` is a utility which walks a ColdFusion application's source and
|
9
|
+
determines which references to other files will not work with a case-sensitive
|
10
|
+
filesystem. Its intended audience is developers/sysadmins who are migrating
|
11
|
+
a CF application from Windows hosting to Linux or another UNIX.
|
12
|
+
|
13
|
+
`cf_case_check` was developed at the [Northwestern University Biomedical
|
14
|
+
Informatics Center][NUBIC].
|
15
|
+
|
16
|
+
[NUBIC]: http://www.nucats.northwestern.edu/centers/nubic/index.html
|
17
|
+
|
18
|
+
Features
|
19
|
+
--------
|
20
|
+
|
21
|
+
* Resolves references of the following types:
|
22
|
+
- `CF_`-style custom tags
|
23
|
+
- `cfinclude`
|
24
|
+
- `cfmodule` (both `template` and `name`)
|
25
|
+
- `createObject` (for CFCs only)
|
26
|
+
* Prints report to stdout
|
27
|
+
* Allows for designation of custom tag & CFC search paths outside the
|
28
|
+
application root
|
29
|
+
|
30
|
+
Synopsis
|
31
|
+
--------
|
32
|
+
|
33
|
+
myapp$ cf_case_check
|
34
|
+
|
35
|
+
For command-line options, do:
|
36
|
+
|
37
|
+
$ cf_case_check --help
|
38
|
+
|
39
|
+
Requirements
|
40
|
+
------------
|
41
|
+
|
42
|
+
* Ruby 1.8.6 or later (may work with earlier, but not tested)
|
43
|
+
|
44
|
+
Install
|
45
|
+
-------
|
46
|
+
|
47
|
+
Follow the GitHub rubygems [setup directions](http://gems.github.com/), then
|
48
|
+
|
49
|
+
$ sudo gem install rsutphin-cf_case_check
|
50
|
+
|
51
|
+
License
|
52
|
+
-------
|
53
|
+
|
54
|
+
(The MIT License)
|
55
|
+
|
56
|
+
Copyright (c) 2008 Rhett Sutphin
|
57
|
+
|
58
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
59
|
+
a copy of this software and associated documentation files (the
|
60
|
+
'Software'), to deal in the Software without restriction, including
|
61
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
62
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
63
|
+
permit persons to whom the Software is furnished to do so, subject to
|
64
|
+
the following conditions:
|
65
|
+
|
66
|
+
The above copyright notice and this permission notice shall be
|
67
|
+
included in all copies or substantial portions of the Software.
|
68
|
+
|
69
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
70
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
71
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
72
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
73
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
74
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
75
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Look in the tasks/setup.rb file for the various options that can be
|
2
|
+
# configured in this Rakefile. The .rake files in the tasks directory
|
3
|
+
# are where the options are used.
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'bones'
|
7
|
+
Bones.setup
|
8
|
+
rescue LoadError
|
9
|
+
load 'tasks/setup.rb'
|
10
|
+
end
|
11
|
+
|
12
|
+
ensure_in_path 'lib'
|
13
|
+
require 'case_check'
|
14
|
+
|
15
|
+
task :default => 'spec:run'
|
16
|
+
task :install => 'gem:install'
|
17
|
+
|
18
|
+
PROJ.name = 'cf_case_check'
|
19
|
+
PROJ.authors = 'Rhett Sutphin'
|
20
|
+
PROJ.email = 'rhett@detailedbalance.net'
|
21
|
+
PROJ.url = 'http://github.com/rsutphin/cf_case_check'
|
22
|
+
PROJ.version = CaseCheck::VERSION
|
23
|
+
# PROJ.rubyforge.name = 'cf_case_check'
|
24
|
+
PROJ.description = "A utility which walks a ColdFusion application's source and determines which includes, custom tags, etc, will not work with a case-sensitive filesystem"
|
25
|
+
PROJ.exclude << "gem$" << "gemspec$"
|
26
|
+
|
27
|
+
PROJ.ruby_opts = [] # There are a bunch of warnings in rspec, so setting -w isn't useful
|
28
|
+
PROJ.spec.opts << '--color'
|
29
|
+
PROJ.rcov.opts << '--exclude /Library'
|
30
|
+
|
31
|
+
PROJ.gem.dependencies << 'activesupport'
|
32
|
+
|
33
|
+
desc 'Regenerate the gemspec for github'
|
34
|
+
task :'gem:spec' => 'gem:prereqs' do
|
35
|
+
PROJ.gem._spec.files = PROJ.gem._spec.files.reject { |f| f =~ /^tasks/ }
|
36
|
+
File.open("#{PROJ.name}.gemspec", 'w') do |gemspec|
|
37
|
+
gemspec.puts PROJ.gem._spec.to_ruby
|
38
|
+
end
|
39
|
+
end
|
data/bin/cf_case_check
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.expand_path(
|
4
|
+
File.join(File.dirname(__FILE__), %w[.. lib case_check]))
|
5
|
+
|
6
|
+
def print_report(checker, out=$stdout)
|
7
|
+
CaseCheck.status_stream.puts "#{checker.reference_count} references"
|
8
|
+
checker.sources.each do |s|
|
9
|
+
out.puts s.src.filename
|
10
|
+
s.internal_references.each do |ir|
|
11
|
+
out.puts " #{bullet(ir)} #{ir.message}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def bullet(ir)
|
17
|
+
case ir.resolution
|
18
|
+
when :exact
|
19
|
+
'-'
|
20
|
+
when :case_insensitive
|
21
|
+
'+'
|
22
|
+
else
|
23
|
+
'*'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
print_report CaseCheck::Checker.new(CaseCheck::Params.new(ARGV))
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'enumerator'
|
2
|
+
|
3
|
+
module CaseCheck
|
4
|
+
|
5
|
+
class ColdfusionSource
|
6
|
+
attr_accessor :internal_references, :content, :filename
|
7
|
+
|
8
|
+
def self.create(filename)
|
9
|
+
f = File.expand_path(filename)
|
10
|
+
new(f, File.read(f))
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(filename, content = nil)
|
14
|
+
@filename = filename
|
15
|
+
self.content = content
|
16
|
+
end
|
17
|
+
|
18
|
+
def analyze
|
19
|
+
[CustomTag, Cfmodule, Cfinclude, Cfc].each do |reftype|
|
20
|
+
internal_references.concat reftype.search(self)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def internal_references
|
25
|
+
@internal_references ||= []
|
26
|
+
end
|
27
|
+
|
28
|
+
def inexact_internal_references
|
29
|
+
internal_references.reject { |ir| ir.resolution == :exact }
|
30
|
+
end
|
31
|
+
|
32
|
+
# returns the line number (1-based) on which the given character index lies
|
33
|
+
def line_of(i)
|
34
|
+
return nil if i >= content.size
|
35
|
+
char_ct = 0
|
36
|
+
l = 0
|
37
|
+
while char_ct <= i
|
38
|
+
char_ct += lines[l].size
|
39
|
+
l += 1
|
40
|
+
end
|
41
|
+
l
|
42
|
+
end
|
43
|
+
|
44
|
+
def content=(c)
|
45
|
+
@lines = nil
|
46
|
+
@content = c
|
47
|
+
end
|
48
|
+
|
49
|
+
def lines
|
50
|
+
return @lines if @lines
|
51
|
+
@lines = []
|
52
|
+
content.split(/(\r\n|\r|\n)/).each_slice(2) { |line_and_br| @lines << line_and_br.join('') }
|
53
|
+
@lines
|
54
|
+
end
|
55
|
+
|
56
|
+
# Scans the content for the given RE, yielding the MatchData for each match
|
57
|
+
# and the line number on which it occurred
|
58
|
+
def scan(re, &block)
|
59
|
+
results = []
|
60
|
+
char_offset = 0
|
61
|
+
re.scan(content) do |md|
|
62
|
+
results << (yield [md, line_of(char_offset + md.begin(0))])
|
63
|
+
|
64
|
+
char_offset += md[0].size + md.pre_match.size
|
65
|
+
remaining = md.post_match
|
66
|
+
end
|
67
|
+
results
|
68
|
+
end
|
69
|
+
|
70
|
+
# Scans the content for opening and/or self-closing tags with the given
|
71
|
+
# name. Yields the full text of the tag, a parsed representation of the
|
72
|
+
# attributes, and the line number back to the provided block.
|
73
|
+
def scan_for_tag(tag, &block)
|
74
|
+
scan(/<#{tag}(.*?)>/mi) do |md, l|
|
75
|
+
attributes = %w(' ").collect do |q|
|
76
|
+
/(\w+)\s*=\s*#{q}([^#{q}]*?)#{q}/.scan(md[1].gsub(%r(/$), ''))
|
77
|
+
end.flatten.inject({}) do |attrs, amd|
|
78
|
+
attrs[normalize_attribute_key(amd[1])] = amd[2]
|
79
|
+
attrs
|
80
|
+
end
|
81
|
+
yield md[0], attributes, l
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def normalize_attribute_key(key)
|
88
|
+
key.downcase.to_sym
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module CaseCheck
|
5
|
+
|
6
|
+
class Params
|
7
|
+
def initialize(argv, name='cf_case_check')
|
8
|
+
@options = OpenStruct.new
|
9
|
+
|
10
|
+
opts = OptionParser.new do |opts|
|
11
|
+
opts.banner = "Usage: #{name} [options]"
|
12
|
+
|
13
|
+
opts.on("-d", "--dir DIRECTORY",
|
14
|
+
"The application root from which to search. Defaults to the current directory.") do |p|
|
15
|
+
@options.directory = p
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on("-c", "--config CONFIGYAML",
|
19
|
+
"The configuration file which includes the directories to search for custom tags and CFCs.") do |p|
|
20
|
+
@options.configfile = p
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on("-v", "--verbose", "Show all references, including the ones which can be resolved exactly.") do |v|
|
24
|
+
@options.verbose = true
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
28
|
+
CaseCheck.status_stream.puts opts
|
29
|
+
CaseCheck.exit
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on_tail("--version", "Show version") do
|
33
|
+
CaseCheck.status_stream.puts "#{name} #{CaseCheck.version}"
|
34
|
+
CaseCheck.exit
|
35
|
+
end
|
36
|
+
end.parse!(argv)
|
37
|
+
|
38
|
+
read_config!
|
39
|
+
end
|
40
|
+
|
41
|
+
def directory
|
42
|
+
@options.directory || '.'
|
43
|
+
end
|
44
|
+
|
45
|
+
def configuration_file
|
46
|
+
@options.configfile || File.join(directory, "cf_case_check.yml")
|
47
|
+
end
|
48
|
+
|
49
|
+
def verbose?
|
50
|
+
@options.verbose
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def read_config!
|
56
|
+
@configuration =
|
57
|
+
if File.exist?(configuration_file)
|
58
|
+
Configuration.new(configuration_file)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Checker
|
64
|
+
def initialize(params)
|
65
|
+
@params = params
|
66
|
+
CaseCheck.status_stream.print "Reading source files "
|
67
|
+
@sources = Dir["#{params.directory}/**/*.cf[mc]"].collect do |f|
|
68
|
+
CaseCheck.status_stream.print '.'
|
69
|
+
ColdfusionSource.create(f)
|
70
|
+
end
|
71
|
+
CaseCheck.status_stream.puts
|
72
|
+
CaseCheck.status_stream.print "Analyzing "
|
73
|
+
@sources.each do |s|
|
74
|
+
CaseCheck.status_stream.print '.'
|
75
|
+
s.analyze
|
76
|
+
end
|
77
|
+
CaseCheck.status_stream.puts "\n"
|
78
|
+
end
|
79
|
+
|
80
|
+
def sources
|
81
|
+
@sources.reject { |src| @params.verbose? ? src.internal_references.empty? : src.inexact_internal_references.empty? }.
|
82
|
+
collect { |src| FilteredSource.new(src, @params.verbose?) }
|
83
|
+
end
|
84
|
+
|
85
|
+
def reference_count
|
86
|
+
sources.inject(0) { |c, s| c + s.internal_references.size }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class FilteredSource
|
91
|
+
attr_reader :src
|
92
|
+
|
93
|
+
def initialize(source, include_exact_matches)
|
94
|
+
@src = source
|
95
|
+
@include_exact = include_exact_matches
|
96
|
+
end
|
97
|
+
|
98
|
+
def internal_references
|
99
|
+
@include_exact ? src.internal_references : src.inexact_internal_references
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module CaseCheck
|
5
|
+
|
6
|
+
class Configuration
|
7
|
+
def initialize(filename)
|
8
|
+
@filename = filename
|
9
|
+
@doc = YAML.load_file(@filename)
|
10
|
+
apply
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](k)
|
14
|
+
@doc[k]
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def apply
|
20
|
+
read_custom_tag_dirs
|
21
|
+
read_cfc_dirs
|
22
|
+
end
|
23
|
+
|
24
|
+
def read_custom_tag_dirs
|
25
|
+
CustomTag.directories = absolutize_directories(@doc['custom_tag_directories'] || [])
|
26
|
+
end
|
27
|
+
|
28
|
+
def read_cfc_dirs
|
29
|
+
Cfc.directories = absolutize_directories(@doc['cfc_directories'] || [])
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def absolutize_directories(dirs)
|
35
|
+
dirs.to_a.collect { |d|
|
36
|
+
p = Pathname.new(d)
|
37
|
+
if p.absolute?
|
38
|
+
p
|
39
|
+
else
|
40
|
+
Pathname.new(File.dirname(@filename)) + p
|
41
|
+
end
|
42
|
+
}.collect { |p| p.to_s }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Extend File with case-insensitive utility functions
|
2
|
+
class File
|
3
|
+
# Determines if the given filename maps exactly to an existing file, even
|
4
|
+
# if the underlying filesystem is case-insensitive
|
5
|
+
def self.exists_exactly?(name, base=nil)
|
6
|
+
first, rest = File.expand_path(name, '/')[1, name.size].split('/', 2)
|
7
|
+
base ||= ''
|
8
|
+
actual_files = Dir[File.join(base, '*')]
|
9
|
+
candidate = File.join(base, first)
|
10
|
+
actual_files.include?(candidate) && (!rest || self.exists_exactly?(rest, candidate))
|
11
|
+
end
|
12
|
+
|
13
|
+
# Finds the true filename for the file which can be accessed as "name"
|
14
|
+
# case-insensitively
|
15
|
+
def self.case_insensitive_canonical_name(name, base=nil)
|
16
|
+
first, rest = File.expand_path(name, '/')[1, name.size].split('/', 2)
|
17
|
+
base ||= ''
|
18
|
+
actual_files = Dir[File.join(base, '*')].collect { |fn| fn[(base.length + 1) .. -1] }
|
19
|
+
match =
|
20
|
+
if actual_files.include?(first)
|
21
|
+
first
|
22
|
+
else
|
23
|
+
actual_files.detect { |fn| fn.downcase == first.downcase }
|
24
|
+
end
|
25
|
+
if rest && match
|
26
|
+
case_insensitive_canonical_name(rest, File.join(base, match))
|
27
|
+
elsif match
|
28
|
+
File.join(base, match)
|
29
|
+
else
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Regexp
|
36
|
+
# Like String#scan, except that it returns an array of MatchData instead of strings
|
37
|
+
# Works incrementally (returning nil) if given a block
|
38
|
+
def scan(s)
|
39
|
+
data = []
|
40
|
+
remaining = s
|
41
|
+
while md = self.match(remaining)
|
42
|
+
if block_given?
|
43
|
+
yield md
|
44
|
+
else
|
45
|
+
data << md
|
46
|
+
end
|
47
|
+
remaining = md.post_match
|
48
|
+
end
|
49
|
+
block_given? ? nil : data
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Models of various sorts of file references within CF code
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'activesupport'
|
5
|
+
|
6
|
+
module CaseCheck
|
7
|
+
|
8
|
+
# base class
|
9
|
+
class Reference < Struct.new(:source, :text, :line)
|
10
|
+
# abstract methods
|
11
|
+
# - expected_path
|
12
|
+
# returns the exact relative path to which this reference refers
|
13
|
+
# - resolved_to
|
14
|
+
# returns the absolute file to which this reference seems to point, if one could be found
|
15
|
+
|
16
|
+
# Returns :exact, :case_insensitive, or nil depending on whether
|
17
|
+
# the reference could be resolved on a case_sensitive FS,
|
18
|
+
# only on a case_insensitive FS, or not at all
|
19
|
+
def resolution
|
20
|
+
return nil unless resolved_to
|
21
|
+
case_sensitive_match? ? :exact : :case_insensitive
|
22
|
+
end
|
23
|
+
|
24
|
+
def message
|
25
|
+
start =
|
26
|
+
case resolution
|
27
|
+
when :exact
|
28
|
+
"Exactly resolved"
|
29
|
+
when :case_insensitive
|
30
|
+
"Case-insensitively resolved"
|
31
|
+
else
|
32
|
+
"Unresolved"
|
33
|
+
end
|
34
|
+
msg = "#{start} #{type_name} on line #{line}"
|
35
|
+
if resolution
|
36
|
+
"#{msg} from #{text} to #{resolved_to}"
|
37
|
+
else
|
38
|
+
"#{msg}: #{text}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def type_name
|
43
|
+
self.class.name.split('::').last.underscore.gsub('_', ' ')
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def case_sensitive_match?
|
49
|
+
resolved_to.ends_with?(expected_path_tail)
|
50
|
+
end
|
51
|
+
|
52
|
+
def expected_path_tail
|
53
|
+
expected_path.split('/').reject { |pe| pe == '.' }.join('/').gsub(%r((\.\./)+), '')
|
54
|
+
end
|
55
|
+
|
56
|
+
def resolve_in(dir)
|
57
|
+
File.case_insensitive_canonical_name(File.expand_path(expected_path, dir))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end # module CaseCheck
|
62
|
+
|
63
|
+
CaseCheck.require_all_libs_relative_to(__FILE__, 'references')
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module CaseCheck
|
2
|
+
|
3
|
+
# Reference as createObject("component", '...')
|
4
|
+
class Cfc < Reference
|
5
|
+
attr_reader :expected_path, :resolved_to
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_writer :directories
|
9
|
+
|
10
|
+
def directories
|
11
|
+
@directories ||= []
|
12
|
+
end
|
13
|
+
|
14
|
+
def search(source)
|
15
|
+
source.scan(/createObject\((.*?)\)/mi) do |match, l|
|
16
|
+
args = match[1].split(/\s*,\s*/).collect { |a| a.gsub(/['"]/, '') }
|
17
|
+
unless args.size == 2 && args.first =~ /component/i
|
18
|
+
$stderr.puts "Non-CFC call on line #{l} of #{source.filename}: #{match[0]}"
|
19
|
+
end
|
20
|
+
new(source, args.last, l)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(source, text, line_number)
|
26
|
+
super
|
27
|
+
@expected_path = text.gsub('.', '/') + ".cfc"
|
28
|
+
@resolved_to = self.class.directories.inject(nil) do |resolved, dir|
|
29
|
+
resolved || resolve_in(dir)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def case_sensitive_match?
|
36
|
+
return true if super
|
37
|
+
# According to the CF docs:
|
38
|
+
# "On UNIX systems, ColdFusion searches first for a file with a name
|
39
|
+
# that matches the specified component name, but is all lowercase.
|
40
|
+
# If it does not find the file, it looks for a filename that matches
|
41
|
+
# the component name exactly, with the identical character casing."
|
42
|
+
resolved_to.ends_with?(expected_path_tail.downcase)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module CaseCheck
|
2
|
+
|
3
|
+
# Reference as <cfinclude template=
|
4
|
+
class Cfinclude < Reference
|
5
|
+
attr_accessor :expected_path, :resolved_to
|
6
|
+
|
7
|
+
def self.search(source)
|
8
|
+
source.scan_for_tag('cfinclude') do |text, attributes, line_number|
|
9
|
+
new(source, attributes[:template], line_number)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(source, text, line)
|
14
|
+
super
|
15
|
+
@expected_path = text
|
16
|
+
@resolved_to = resolve_in(File.dirname(source.filename))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module CaseCheck
|
2
|
+
|
3
|
+
# Reference as <cfmodule name= or <cfmodule template=
|
4
|
+
class Cfmodule < Reference
|
5
|
+
attr_reader :expected_path, :resolved_to
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def search(source)
|
9
|
+
source.scan_for_tag('cfmodule') do |text, attributes, line_number|
|
10
|
+
if attributes.keys.include?(:name)
|
11
|
+
Name.new(source, attributes[:name], line_number)
|
12
|
+
elsif attributes.keys.include?(:template)
|
13
|
+
Template.new(source, attributes[:template], line_number)
|
14
|
+
else
|
15
|
+
$stderr.puts "Neither name nor template for cfmodule on line #{line_number} of #{source.filename}"
|
16
|
+
end
|
17
|
+
end.compact
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Name < Cfmodule
|
22
|
+
def initialize(source, text, line)
|
23
|
+
super
|
24
|
+
@expected_path = text.gsub('.', '/') + ".cfm"
|
25
|
+
@resolved_to = CustomTag.directories.inject(nil) do |resolved, dir|
|
26
|
+
resolved || resolve_in(dir)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def type_name
|
31
|
+
"cfmodule with name"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Template < Cfmodule
|
36
|
+
def initialize(source, text, line)
|
37
|
+
super
|
38
|
+
@expected_path = text
|
39
|
+
@resolved_to = resolve_in(File.dirname(source.filename))
|
40
|
+
end
|
41
|
+
|
42
|
+
def type_name
|
43
|
+
"cfmodule with template"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|