Here is my unfinished code:
#When convert button is pressed
File.rename("*.osz", "*.zip$")
dialog.directory(
def extract_zip(file, destination) FileUtils.mkdir_p(destination)
file_path = "./convert_temp/*.zip"
destination = "./convert_temp/osz/"
extract_zip(file_path, destination)
until File.exists?( ".osu$" ) == false do
File.rename("./convert_temp/osz/*.osu$", "*.txt$")
File.foreach(filename) do |file|
file_string = File.read('./convert_temp/osz/*.txt$')
if file_string.include?('Mode: 1')
puts 'Yes'
else
puts 'No'
end
end
end
Robocop giving the following syntax error:
unexpected token $end (Using Ruby 2.2 parser; configure using `TargetRubyVersion` parameter, under `AllCops`)
Actually, Rubocop is not even able to parse the file, because it has syntax errors.
The error message syntax error: unexpected token $end means that the ruby parser was parsing along happily, but then it suddenly encountered an $end, which is the parser's way to say "the end of the file". It was expecting more code, but instead it found the end of the file.
This is what your code looks like with proper indentation:
#When convert button is pressed
File.rename("*.osz", "*.zip$")
dialog.directory(
def extract_zip(file, destination) FileUtils.mkdir_p(destination)
file_path = "./convert_temp/*.zip"
destination = "./convert_temp/osz/"
extract_zip(file_path, destination)
until File.exists?( ".osu$" ) == false do
File.rename("./convert_temp/osz/*.osu$", "*.txt$")
File.foreach(filename) do |file|
file_string = File.read('./convert_temp/osz/*.txt$')
if file_string.include?('Mode: 1')
puts 'Yes'
else
puts 'No'
end
end
end
Using this kind of indentation makes it easy to see that there are some missing ends/parentheses, because the last line is left hanging in the air instead of closing back to the left edge where it started from.
Additional notes:
dialog.directory(
def extract_zip(file, destination) FileUtils.mkdir_p(destination)
It's very unconventional to define a new method inside a method call. File.open(def hello_world(..)) Doesn't make a lot of sense.
until File.exists?( ".osu$" ) == false do
Are you using $ as a way to indicate "filename ends in .osu"? If yes, it does not work like that. This would look for a file that has .osu$ as name.
File.foreach(filename) do |file|
The file parameter is not used in the block that follows, you use file_string.
file_string = File.read('./convert_temp/osz/*.txt$')
You can't read multiple files at once like that. Also, File.foreach above would read the file line by line, so here you are trying to read it again, inside the loop that is reading it already.
I would like to validate my file.
Should parse the file line by line (and not read the whole file into memory).
Should exit as soon as it passes the test.
Should consider the file valid if any of the vendor_codes match the provided vendor_code.
Some code:
error = "WRONG VENDOR DUMMY. This is #{account.vendor_code}"
CSV.foreach(file, options) do |row|
if row[:vendor].to_s == account.vendor_code.to_s
error = false
break
else
next
end
end
raise(error) if error
is there a more elegant way to do this?
I think this approach is better:
valid = CSV.foreach(file, options).any? do |row|
row[:vendor].to_s == account.vendor_code.to_s
end
raise("WRONG VENDOR DUMMY. This is #{account.vendor_code}") unless valid
I'm relatively new to ruby, moving into it from languages like Python, and C# so please forgive me if this is an obvious bug I'm missing.
The Issue
I was writing code for a tool to help myself and other developers generate .editorconfig files as a way to learn Ruby. I use rspec as my testing framework and wrote the following tests:
module EditorConfigGenerator
RSpec.describe EditorConfigGenerator::FileGenerator do
configs_sensible_defaults = [EditorConfigGenerator::EditorConfig.new({root: true, indent_style: 'space', indent_size: 2,
end_of_line: 'lf', charset: 'utf-8',
trim_trailing_whitespace: true, insert_final_newline: true})]
file_generator = EditorConfigGenerator::FileGenerator.new(configs_sensible_defaults)
context "A collection of one EditorConfig object is provided" do
it "displays a preview of the file output" do
# Newlines are automatically inserted into ruby multi-line strings
output = "root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
"
# String.delete(' ') to remove any potential formatting discrepancies
# which don't affect the test of the content.
expect(file_generator.preview_output.delete(' ')).to eq output.delete(' ')
end
it "creates a .editorconfig file in the correct location" do
dot_editorconfig = file_generator.generate_config_file('test_output/.editorconfig')
expect(dot_editorconfig.class).to eq File
expect(File.exist? "test_output/.editorconfig").to eq true
end
end
context "A collection of multiple EditorConfig objects is provided" do
it "displays a preview of the file output" do
configs = configs_sensible_defaults.push(EditorConfigGenerator::EditorConfig.new({file_type: '*.{js,py}', indent_size: 4}))
file_generator = EditorConfigGenerator::FileGenerator.new(configs)
output = "root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{js,py}]
indent_size = 4"
expect(file_generator.preview_output.delete(' ')).to eq output.delete(' ')
end
end
end
end
When I ran these tests the first few times, they worked seemingly fine. I then enabled the following recommended lines in my spec_helper.rb file
config.profile_examples = 10
config.order = :random
kernel.srand config.seed
I ran the tests a few more times, all of them passing until - seemingly randomly - I obtained the following failure output with seed 31166:
1) EditorConfigGenerator::FileGenerator A collection of one EditorConfig object is provided displays a preview of the file output
Failure/Error: expect(file_generator.preview_output.delete(' ')).to eq output.delete(' ')
expected: "root=true\n[*]\nindent_style=space\nindent_size=2\nend_of_line=lf\ncharset=utf-8\ntrim_trailing_whitespace=true\ninsert_final_newline=true\n"
got: "root=true\n[*]\nindent_style=space\nindent_size=2\nend_of_line=lf\ncharset=utf-8\ntrim_trailing_whitespace=true\ninsert_final_newline=true\n[*.{js,py}]\nindent_size=4"
(compared using ==)
Diff:
## -6,4 +6,6 ##
charset=utf-8
trim_trailing_whitespace=true
insert_final_newline=true
+[*.{js,py}]
+indent_size=4
# ./spec/file_generator_spec.rb:23:in `block (3 levels) in <module:EditorConfigGenerator>'
With other seeds I tried, it worked, but with seed 31166 it failed.
The Fix
The output made me feel like it was contextual, so therefore I tried to look for a bug in my implementation. I didn't find one, so I thought it may have been an issue with the way I defined the shared variable configs_sensible_defaults in the spec.
I decided to change the code to use before(:each) to assign an instance variable before each test (i.e. resetting the data properly) and it seemed to fix it.
Here's the fixed spec/file_generator_spec.rb
module EditorConfigGenerator
RSpec.describe EditorConfigGenerator::FileGenerator do
before(:each) do
#configs_sensible_defaults = [EditorConfigGenerator::EditorConfig.new({root: true, indent_style: 'space', indent_size: 2, end_of_line: 'lf', charset: 'utf-8', trim_trailing_whitespace: true, insert_final_newline: true})]
#file_generator = EditorConfigGenerator::FileGenerator.new(#configs_sensible_defaults)
end
context "A collection of one EditorConfig object is provided" do
it "displays a preview of the file output" do
# Newlines are automatically inserted into ruby multi-line strings
output = "root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
"
# String.delete(' ') to remove any potential formatting discrepancies
# which don't affect the test of the content.
expect(#file_generator.preview_output.delete(' ')).to eq output.delete(' ')
end
it "creates a .editorconfig file in the correct location" do
dot_editorconfig = #file_generator.generate_config_file('test_output/.editorconfig')
expect(dot_editorconfig.class).to eq File
expect(File.exist? "test_output/.editorconfig").to eq true
end
end
context "A collection of multiple EditorConfig objects is provided" do
it "displays a preview of the file output" do
configs = #configs_sensible_defaults.clone.push(EditorConfigGenerator::EditorConfig.new({file_type: '*.{js,py}', indent_size: 4}))
#file_generator = EditorConfigGenerator::FileGenerator.new(configs)
output = "root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{js,py}]
indent_size = 4"
expect(#file_generator.preview_output.delete(' ')).to eq output.delete(' ')
end
end
end
end
Here is a diff output of both files (view on Github):
RSpec.describe EditorConfigGenerator::FileGenerator do
configs_sensible_defaults = [EditorConfigGenerator::EditorConfig.new({root: true, indent_style: 'space', indent_size: 2,
end_of_line: 'lf', charset: 'utf-8',
trim_trailing_whitespace: true, insert_final_newline: true})]
file_generator = EditorConfigGenerator::FileGenerator.new(configs_sensible_defaults)
before(:each) do
#configs_sensible_defaults = [EditorConfigGenerator::EditorConfig.new({root: true, indent_style: 'space', indent_size: 2, end_of_line: 'lf', charset: 'utf-8', trim_trailing_whitespace: true, insert_final_newline: true})]
#file_generator = EditorConfigGenerator::FileGenerator.new(#configs_sensible_defaults)
end
context "A collection of one EditorConfig object is provided" do
it "displays a preview of the file output" do
# file_generator_spec.rb:22 # module EditorConfigGenerator
"
# String.delete(' ') to remove any potential formatting discrepancies
# which don't affect the test of the content.
expect(file_generator.preview_output.delete(' ')).to eq output.delete(' ')
expect(#file_generator.preview_output.delete(' ')).to eq output.delete(' ')
end
it "creates a .editorconfig file in the correct location" do
dot_editorconfig = file_generator.generate_config_file('test_output/.editorconfig')
dot_editorconfig = #file_generator.generate_config_file('test_output/.editorconfig')
expect(dot_editorconfig.class).to eq File
expect(File.exist? "test_output/.editorconfig").to eq true
end
end
context "A collection of multiple EditorConfig objects is provided" do
Finally, for context, here are the relevant file_generator.rb and editor_config.rb files
module EditorConfigGenerator
class FileGenerator
def initialize(configs)
#configs = configs
end
def preview_output
output = ""
#configs.each do |config|
if output.include? "root="
output << config.to_s_without_root
next
end
output << config.to_s
end
return output.rstrip if #configs.size > 1
output
end
def generate_config_file(location='.editorconfig')
File.delete(location) if File.exist? location
file = File.new(location, "w")
file.print(preview_output)
file.close
file
end
end
end
module EditorConfigGenerator
class EditorConfig
attr_reader :root, :indent_style, :indent_size,
:end_of_line, :charset, :trim_trailing_whitespace,
:insert_final_newline, :file_type
def initialize(options)
#root = nil
#file_type = :*
transform_options(options)
set_options(options)
end
def set_options(options)
#root = options[:root] unless options[:root].nil?
#indent_style = options[:indent_style] unless options[:indent_style].nil?
#indent_size = options[:indent_size] unless options[:indent_size].nil?
#end_of_line = options[:end_of_line] unless options[:end_of_line].nil?
#charset = options[:charset] unless options[:charset].nil?
#trim_trailing_whitespace = options[:trim_trailing_whitespace] unless options[:trim_trailing_whitespace].nil?
#insert_final_newline = options[:insert_final_newline] unless options[:insert_final_newline].nil?
#file_type = options[:file_type] unless options[:file_type].nil?
end
def transform_options(options)
options[:root] = true if options[:root] == 'y'
options[:root] = false if options[:root] == 'n'
options[:trim_trailing_whitespace] = true if options[:trim_trailing_whitespace] == 'y'
options[:trim_trailing_whitespace] = false if options[:trim_trailing_whitespace] == 'n'
options[:insert_final_newline] = true if options[:insert_final_newline] == 'y'
options[:insert_final_newline] = false if options[:insert_final_newline] == 'n'
end
def to_s
config_string = ""
config_string << "root = #{#root.to_s}\n" unless #root.nil?
config_string << "[#{#file_type}]\n"
config_string << "indent_style = #{#indent_style}\n" unless #indent_style.nil?
config_string << "indent_size = #{#indent_size}\n" unless #indent_size.nil?
config_string << "end_of_line = #{#end_of_line}\n" unless #end_of_line.nil?
config_string << "charset = #{#charset}\n" unless #charset.nil?
config_string << "trim_trailing_whitespace = #{#trim_trailing_whitespace.to_s}\n" unless #trim_trailing_whitespace.nil?
config_string << "insert_final_newline = #{#insert_final_newline.to_s}\n" unless #insert_final_newline.nil?
config_string
end
def to_s_without_root
lines = to_s.lines
lines.delete_at 0
lines.join
end
def to_s_without_line(line_identifier)
lines = to_s.lines
index = lines.index(line_identifier)
lines.delete_at index
lines.join
end
end
end
The Question
Could someone explain exactly why the change fixed the issue? Did the change fix the issue?
I believe it's to do with the collection in configs_sensible_defaults being mutated and not being reset correctly after each run, which would explain why a certain seed would trigger a failure (as maybe under that seed test 2 would run before 1).
The reason it was working sometimes and not other times is because one of your tests (in the second context) reassigned file_generator. The test in the first context was using the file_generator from the shared outer scope. So long as that test ran first, it worked as expected. Then the second test in the second scope ran, reassigned file_generator, and also passed.
Since the tests ran in random order, whenever they ran in the order they are in the file, everything was passing. When the second test ran first, it overwrote file_generator, the second test still passed, but the first test ran with the overwritten value.
You can use a before block to configure things for each test, as you did, but you should only configure common, shared values in a before block. Anything that's going to be different for each context or each test should be initialized closer to the scope in which it is used.
In your examples above, I wouldn't use any before block, at least not in the outer scope. Both #config_sensible_defaults and #file_generator are different in each context, and should not be shared at all. If you want to group a bunch of tests together in a context with the same defaults and generator, you can put a before block inside the context block, to initialize things correctly for each context.
I have a Ruby app which uses readline with command completion.
After the first string (the command) was typed, I would like to be able to complete its arguments. The arguments list should be based on the chosen command.
Does someone have a quick example?
These are the commands:
COMMANDS = [
'collect', 'watch'
].sort
COLLECT = [
'stuff', 'otherstuff'
].sort
comp = proc do |s|
COMMANDS.grep( /^#{Regexp.escape(s)}/ )
end
Readline.completion_proc = comp
Each time I press TAB, the proc block is executed and a command from the COMMANDS array is matched.
After one of the commands was fully matched I would like to start searching for the argument only in the COLLECT array.
Since your question popped up first every time I looked for something like this I want to share my code for any one else.
#!/usr/bin/env ruby
require 'readline'
module Shell
PROMPT = "shell> "
module InputCompletor
CORE_WORDS = %w[ clear help show exit export]
SHOW_ARGS = %w[ list user ]
EXPORT_ARGS = %w[ file ]
COMPLETION_PROC = proc { |input|
case input
when /^(show|export) (.*)/
command = $1
receiver = $2
DISPATCH_TABLE[$1].call($2)
when /^(h|s|c|e.*)/
receiver = $1
CORE_WORDS.grep(/^#{Regexp.quote(receiver)}/)
when /^\s*$/
puts
CORE_WORDS.map{|d| print "#{d}\t"}
puts
print PROMPT
end
}
def self.show(receiver)
if SHOW_ARGS.grep(/^#{Regexp.quote(receiver)}/).length > 1
SHOW_ARGS.grep(/^#{Regexp.quote(receiver)}/)
elsif SHOW_ARGS.grep(/^#{Regexp.quote(receiver)}/).length == 1
"show #{SHOW_ARGS.grep(/^#{Regexp.quote(receiver)}/).join}"
end
end
def self.export(receiver)
if EXPORT_ARGS.grep(/^#{Regexp.quote(receiver)}/).length > 1
EXPORT_ARGS.grep(/^#{Regexp.quote(receiver)}/)
elsif EXPORT_ARGS.grep(/^#{Regexp.quote(receiver)}/).length == 1
"export #{EXPORT_ARGS.grep(/^#{Regexp.quote(receiver)}/).join}"
end
end
DISPATCH_TABLE = {'show' => lambda {|x| show(x)} ,
'export' => lambda {|x| export(x)}}
end
class CLI
Readline.completion_append_character = ' '
Readline.completer_word_break_characters = "\x00"
Readline.completion_proc = Shell::InputCompletor::COMPLETION_PROC
def initialize
while line = Readline.readline("#{PROMPT}",true)
Readline::HISTORY.pop if /^\s*$/ =~ line
begin
if Readline::HISTORY[-2] == line
Readline::HISTORY.pop
end
rescue IndexError
end
cmd = line.chomp
case cmd
when /^clear/
system('clear')
when /^help/
puts 'no help here'
when /show list/
puts 'nothing to show'
when /^show\s$/
puts 'missing args'
when /export file/
puts 'nothing to export'
when /^export\s$/
puts 'missing args'
when /^exit/
exit
end
end
end
end
end
Shell::CLI.new
After thinking a while, the solution was very simple:
comp = proc do |s|
if Readline.line_buffer =~ /^.* /
COLLECT.grep( /^#{Regexp.escape(s)}/ )
else
COMMANDS.grep( /^#{Regexp.escape(s)}/ )
end
end
Now I just need to turn it into something more flexible/usable.
I have this Ruby block:
status = ''
build.parse do |entry|
puts "parsing an item"
puts entry.title
if entry.title =~ /FAILURE/ then
puts "failure"
status = "FAILURE"
else
status = "SUCCESS"
end
puts status
break entry if status == "FAILURE"
end
For some unknown reason to me I can't seem to break out of it? I realise the block is a little weird it's semi-copied from here:
http://macruby.labs.oreilly.com/ch03.html#_xml_parsing
Honestly my Ruby is poor but I'm trying to write a little mac app that involves some RSS parsing.
The regex matches and status gets set to "FAILURE" but it doesn't break out the block/loop. Am I doing something obviously wrong?
Cheers,
Adam
you dont need the 'then' in your if block
#entries = ["this is a FAILURE", "this is a success"]
status = ''
#entries.each do |entry|
if entry =~ /FAILURE/
puts "failure"
status = "failure"
else
status = "success"
end
puts "status == #{status}"
break if status == "failure"
end
as a side note, it would be more idiomatic to write this as:
status = #entries.any?{|e| e =~ /FAILURE/} ? 'failure' : 'succes'
when you are dealing with enumerable objects, like arrays it is nice to use the tools built into Ruby.
http://apidock.com/ruby/Enumerable/any%3F
Try break if status == "FAILURE"