我有一个奇怪的行为问题,涉及以下代码:
ignore_list = %w(boot boot_as_service required test)
info_file = ''
file_list = []
Dir.chdir(__dir__){
Dir.glob('**/*.rb') do | file|
t = file.split('/').last.split('.')
f_name = t.first
f_type = t.last
# puts [f_name,f_type].inspect
next unless f_type == 'rb'
next if ignore_list.include?(f_name)
info_file = file.gsub('/','\\') and next if f_name == 'info'
file_list << file.gsub('/','\\')
end
}
file_list.unshift(info_file)
然后我依次要求file_list
中的每个文件:
file_list.each do |file|
puts "loading #{File.basename(file)}"
require __dir__.gsub('/','\\') + "\\#{file.gsub('lib\\','')}"
end
当我运行代码并打印出数组时,我得到:
["gg-web-opsboard\\info.rb", "gg-web-opsboard\\data\\data.rb", "gg-web-opsboard\\helpers\\helpers.rb", "gg-web-opsboard\\main.rb", "gg-web-opsboard\\routing\\a
ctions.rb", "test.rb"]
像我期望的那样,当我将我的应用程序作为gem安装,并尝试运行启动它的命令时,我得到了这个:
<class:GG_Web_OpsBoard>': uninitialized constant Sinatra::GG_Web_OpsBoard::Actions (NameError)
并且加载的文件是:
Starting Sinatra app on 0.0.0.0:3000
booting...
loading info.rb
loading data.rb
loading helpers.rb
loading main.rb
所以我的问题是,actions.rb
被忽略的原因是什么?
答案 0 :(得分:1)
您可以采取一些措施来简化代码。
从您的代码开始:
ignore_list = %w(boot boot_as_service required test)
info_file = ''
file_list = []
Dir.chdir(__dir__){
Dir.glob('**/*.rb') do | file|
t = file.split('/').last.split('.')
f_name = t.first
f_type = t.last
# puts [f_name,f_type].inspect
next unless f_type == 'rb'
next if ignore_list.include?(f_name)
info_file = file.gsub('/','\\') and next if f_name == 'info'
file_list << file.gsub('/','\\')
end
}
file_list.unshift(info_file)
首先,Ruby并不关心Windows上的文件名是否使用反斜杠或转义斜杠。这来自the IO documentation:
如果可能,Ruby将在不同的操作系统约定之间转换路径名。例如,在Windows系统上,文件名为&#34; /gumby/ruby/test.rb"将打开为#34; \ gumby \ ruby \ test.rb&#34;。
换句话说,省去转换为路径中反斜杠的麻烦,让Ruby处理它。这减少了行
info_file = file.gsub('/','\\') and next if f_name == 'info'
file_list << file.gsub('/','\\')
到
info_file = file and next if f_name == 'info'
file_list << file
这些行中的第一行可以更具可读性。将and next
与尾随if
条件一起使用是尝试将代码缩减为单行,但这不应成为目标,因为它不会提高性能或可维护性。相反,我会使用:
if f_name == 'info'
info_file = file
next
end
该行
next unless f_type == 'rb'
可以完全删除,因为
Dir.glob('**/*.rb')
只返回.rb
作为其扩展名的文件。
让我们看一下你用
做什么ignore_list = %w(boot boot_as_service required test)
它是一个数组,您想使用include?
检查文件是否在该列表中。每次检查时,必须走这个数组,这可能会变得很昂贵,无论是数组的大小增加还是被检查的文件数量增加。相反,使用Set,它也有include?
方法,但比数组快得多,因为Set基于哈希,所以它是随机访问,而不是顺序访问。另外,因为ignore_list
表现得像一个常数,我会把它作为一个:
include 'set'
IGNORE_LIST = %w(boot boot_as_service required test).to_set
看着
t = file.split('/').last.split('.')
f_name = t.first
f_type = t.last
next if IGNORE_LIST.include?(f_name)
当你只是试图从文件中获取基本名称和扩展名时,它会非常复杂。我这样做:
f_type = File.extname(file)
f_name = File.basename(file, f_type)
next if IGNORE_LIST.include?(f_name)
前两行是这样做的:
f_type = File.extname(file) # => ".rb"
f_name = File.basename(file, f_type) # => "file"
f_type
现在有一个领先.
并不重要,因为我们已经知道测试f_type == 'rb'
并非必要。相反,basename
可以使用它来删除extname
返回的扩展名,保留文件名减去扩展名。
这使代码看起来像:
include 'set'
IGNORE_LIST = %w(boot boot_as_service required test).to_set
info_file = ''
file_list = []
Dir.chdir(__dir__) do
Dir.glob('**/*.rb') do | file|
f_type = File.extname(file)
f_name = File.basename(file, f_type)
next if IGNORE_LIST.include?(f_name)
if f_name == 'info'
info_file = file
next
end
file_list << file
end
end
file_list.unshift(info_file)
这是一个相当合理的复杂性降低,并且我认为代码更易读,更易于维护。但是,有一些关于Ruby的东西以及它如何让我们像处理有用的集合一样对待数组。考虑一下:
IGNORE_LIST = %w(boot boot_as_service required test).map{ |s| "#{ s }.rb" }
files = %w[a.rb boot.rb boot_as_service.rb q.rb required.rb s.rb test.rb]
如果files
是Dir.glob('**/*.rb')
的输出,并且IGNORE_LIST
透明地包含了扩展名,那么我们可以轻松找到目录中存在哪些不被忽略:
files - IGNORE_LIST # => ["a.rb", "q.rb", "s.rb"]
知道了,代码简化为:
IGNORE_LIST = %w(boot boot_as_service required test info).map{ |s| "#{ s }.rb" }
file_list = []
Dir.chdir(__dir__) do
file_list = Dir.glob('**/*.rb') - IGNORE_LIST
end
file_list << 'info.rb'
唯一的问题是名称Dir.glob
返回路径,因此必须适应。创建一个哈希,其中键是文件的基本名称会有所帮助。 group_by
让事情变得简单:
files = %w[/foo/a.rb /foo/b.rb /bar/a.rb /bar/b.rb]
files_by_basenames = files.group_by{ |f| File.basename(f) }
files_by_basenames # => {"a.rb"=>["/foo/a.rb", "/bar/a.rb"], "b.rb"=>["/foo/b.rb", "/bar/b.rb"]}
我们可以轻松抓住钥匙:
files_by_basenames.keys # => ["a.rb", "b.rb"]
这使我们回到可以测试块中所需文件的位置,因为所有期望的完整路径名文件都作为数组分组在值中。这里有一些代码要考虑:
files = %w[/foo/a.rb /foo/b.rb /foo/boot.rb /bar/a.rb /bar/b.rb /bar/test.rb]
files_by_basenames = files.group_by{ |f| File.basename(f) }
files_by_basenames # => {"a.rb"=>["/foo/a.rb", "/bar/a.rb"], "b.rb"=>["/foo/b.rb", "/bar/b.rb"], "boot.rb"=>["/foo/boot.rb"], "test.rb"=>["/bar/test.rb"]}
files_by_basenames.keys # => ["a.rb", "b.rb", "boot.rb", "test.rb"]
good_files = files_by_basenames.keys - %w[boot.rb test.rb] # => ["a.rb", "b.rb"]
files_by_basenames.values_at(*good_files) # => [["/foo/a.rb", "/bar/a.rb"], ["/foo/b.rb", "/bar/b.rb"]]
files_by_basenames.values_at(*good_files).flatten # => ["/foo/a.rb", "/bar/a.rb", "/foo/b.rb", "/bar/b.rb"]
将代码重新组合在一起:
IGNORE_LIST = %w(boot boot_as_service required test info).map{ |s| "#{ s }.rb" }
file_list = []
Dir.chdir(__dir__) do
files_by_basenames = Dir.glob('**/*.rb').group_by{ |f| File.basename(f) }
good_filenames = files_by_basenames.keys - IGNORE_LIST
file_list = files_by_basenames.values_at(*good_filenames).flatten
end
file_list << 'info.rb'
因为我不知道您的层次结构会看到哪些路径,所以很难说出在&#39; info.rb&#39前面应该有哪些路径名称;,但即便如此,也可以通过以下方式快速找到:
IGNORE_LIST = %w(boot boot_as_service required test).map{ |s| "#{ s }.rb" }
file_list = []
Dir.chdir(__dir__) do
files_by_basenames = Dir.glob('**/*.rb').group_by{ |f| File.basename(f) }
good_filenames = files_by_basenames.keys - IGNORE_LIST
info_files = files_by_basenames.delete('info.rb')
file_list = files_by_basenames.values_at(*good_filenames).flatten
end
file_list += info_files
&#34;信息&#34;已从IGNORE_LIST
移除,因此我们可以将其捕获到info_files
,然后在事后添加它们。
那是未经测试的,但看起来是正确的。
答案 1 :(得分:0)
我刚刚意识到问题是什么,它根本没有跳过actions.rb
,它只是没有达到它,我只需要确保最后加载main.rb
即可。
我可以通过将info_files
更改为哈希然后执行以下操作来完成此操作:
file_list.unshift(ordered_files[:info]) << ordered_files[:main]