Parsing a .cfg file using Ruby? - ruby

I have a .cfg file with the following data:
*.*.key_val = {
key1= "value1";
key2 = "value2";
key3 = "value3";
};
I want to read this file and store the key value pairs in a hash #var[key][val].
How it can be done?

THIS cfg you may parse in such way:
read file using File#read
convert text into 2-dimentional array using String#scan and regex
convert array into hash using Hash[]
text = File.read('your.cfg')
# => "*.*.key_val = {\n key1= \"value1\";\n key2 = \"value2\";\n key3 = \"value3\";\n};"
data = text.scan(/(\S+)\s*=\s*"([^"]+)/)
# => [["key1", "value1"], ["key2", "value2"], ["key3", "value3"]]
#var = Hash[data]
# => {"key1"=>"value1", "key2"=>"value2", "key3"=>"value3"}
Or just:
#var = Hash[File.read('your.cfg').scan(/(\S+)\s*=\s*"([^"]+)/)]

I'd strongly recommend transferring the configuration to something like YAML. It's made to be easy to understand, flexible, universally implemented, well documented, both as a standard and as a part of the core library, and easy to understand. (Yes, I said it twice on purpose.)
My YAML files load into Ruby as a Hash when I do something like:
require 'yaml'
config = YAML.load_file('/path/to/config/file')
I'll create the initial template for the configuration file in Ruby, as a Hash, then serialize it and write it to disk. That way I know what's on the disk is exactly the way YAML wants it to be and helps me avoid that "it won't load because either the data is wrong or the code is wrong" quandary.
# A simple round-trip (load and dump) of an object.
require 'yaml'
test_obj = {
'foo' => 'bar',
'one_two_three' => [1, 2, 3],
'hash' => {'another' => 'hash'}
} #=> {"foo"=>"bar", "one_two_three"=>[1, 2, 3], "hash"=>{"another"=>"hash"}}
File.open('./config.yaml', 'w') { |fo| fo.puts YAML::dump( test_obj ) } #=> nil
ruby_obj = YAML::load_file( './config.yaml' ) #=> {"foo"=>"bar", "one_two_three"=>[1, 2, 3], "hash"=>{"another"=>"hash"}}
ruby_obj == test_obj #=> true
require 'pp'
pp ruby_obj
{"foo"=>"bar", "one_two_three"=>[1, 2, 3], "hash"=>{"another"=>"hash"}}
pp test_obj
{"foo"=>"bar", "one_two_three"=>[1, 2, 3], "hash"=>{"another"=>"hash"}}

You should try out the 'parseconfig' gem: https://rubygems.org/gems/parseconfig/
gem install parseconfig
Here's a sample how to use this gem:
require 'rubygems'
require 'parseconfig'
my_config = ParseConfig.new('your_file.cfg')
puts my_config.get_value('key_val')
Good luck and have fun learning Ruby. :)
EDIT
As Glenux said this is only for simple configuration files. I'll check if I can find anything else.
EDIT 2
I can't find a gem or something to parse a cfg file like in your example. I guess your only option is to write a parser yourself (like Nakilon did) or use something like YAML instead. Good luck anyway. :)

The parseconfig class is only intended for simple configuration files !
It accepts files of the format "param = value" (cf http://www.5dollarwhitebox.org/drupal/projects#rb-parseconfig ) but it will not parse the *.*.key_val = { and } thing.
Is the configuration file yours or generated/used by a third-party software? If it is yours, it may be wiser to use an other configuration file format (JSON, Ini, YAML, etc).

Wanted to mention this ruby library that can help you transfer configuration between JSON, YAML or Windows Ini file formats:
https://github.com/kigster/dupervisor
The use case is to move the configuration to YAML, but be able to generate whatever the format is needed by the software – in the case of this gem – it's supervisord's INI file format.

Related

How to save an array of objects into a file in ruby?

I have an array of objects, where the objects are instances of a class. I would like to save this array into a file in such format that I could read the file back to an array and the objects and its' instance variable values would be as they were before saving. Does someone know how this could be achieved?
The class instance objects that I would like to save to a file are fairly complex containing tens of instance variables that are often other class instance variables themselves.
WHAT I HAVE TRIED:
According to this post I tried the following:
TRIAL1:
Save file:
require 'pp'
$stdout = File.open('path/to/file.txt', 'w')
pp myArray
Load file:
require 'rubygems'
require 'json'
buffer = File.open('path/to/file.txt', 'r').read
myArray = JSON.parse(buffer)
but I got a JSON::ParserError
TRIAL2:
Save file
serialized_array = Marshal.dump(myArray)
File.open('./myArray.txt', 'w') {|f| f.write(serialized_array) }
received Encoding::UndefinedConversionError
TRIAL1 doesn't work because pp "prints arguments in pretty form" and that's not necessarily JSON.
TRIAL2 probably isn't working because Marshal produces binary data (not text) and you're not working with your file in binary mode, that could lead to encoding and EOL problems. Besides, Marshal isn't a great format for persistence since the format is tied to the version of Ruby you're using.
A modification of TRIAL1 to write JSON is probably the best solution these days:
require 'json'
File.open('path/to/file.json', 'w') { |f| JSON.dump(myArray, f) }
Finally managed to find a solution that worked!
dump = Marshal.dump(myArray)
File.write('./myarray', myArray, mode: 'r+b')
dump = File.read('./myarray')
user = Marshal.restore(dump)
Marshall was able to do the trick after changing the encoding to binary mode

Create a Ruby Hash out of an xml string with the 'ox' gem

I am currently trying to create a hash out of an xml documen, with the help of the ox gem
Input xml:
<?xml version="1.0"?>
<expense>
<payee>starbucks</payee>
<amount>5.75</amount>
<date>2017-06-10</date>
</expense>
with the following ruby/ox code:
doc = Ox.parse(xml)
plist = doc.root.nodes
I get the following output:
=> [#<Ox::Element:0x00007f80d985a668 #value="payee", #attributes={}, #nodes=["starbucks"]>, #<Ox::Element:0x00007f80d9839198 #value="amount", #attributes={}, #nodes=["5.75"]>, #<Ox::Element:0x00007f80d9028788 #value="date", #attributes={}, #nodes=["2017-06-10"]>]
The output I want is a hash in the format:
{'payee' => 'Starbucks',
'amount' => 5.75,
'date' => '2017-06-10'}
to save in my sqllite database. How can I transform the objects array into a hash like above.
Any help is highly appreciated.
The docs suggest you can use the following:
require 'ox'
xml = %{
<top name="sample">
<middle name="second">
<bottom name="third">Rock bottom</bottom>
</middle>
</top>
}
puts Ox.load(xml, mode: :hash)
puts Ox.load(xml, mode: :hash_no_attrs)
#{:top=>[{:name=>"sample"}, {:middle=>[{:name=>"second"}, {:bottom=>[{:name=>"third"}, "Rock bottom"]}]}]}
#{:top=>{:middle=>{:bottom=>"Rock bottom"}}}
I'm not sure that's exactly what you're looking for though.
Otherwise, it really depends on the methods available on the Ox::Element instances in the array.
From the docs, it looks like there are two handy methods here: you can use [] and text.
Therefore, I'd use reduce to coerce the array into the hash format you're looking for, using something like the following:
ox_nodes = [#<Ox::Element:0x00007f80d985a668 #value="payee", #attributes={}, #nodes=["starbucks"]>, #<Ox::Element:0x00007f80d9839198 #value="amount", #attributes={}, #nodes=["5.75"]>, #<Ox::Element:0x00007f80d9028788 #value="date", #attributes={}, #nodes=["2017-06-10"]>]
ox_nodes.reduce({}) do |hash, node|
hash[node['#value']] = node.text
hash
end
I'm not sure whether node['#value'] will work, so you might need to experiment with that - otherwise perhaps node.instance_variable_get('#value') would do it.
node.text does the following, which sounds about right:
Returns the first String in the elements nodes array or nil if there is no String node.
N.B. I prefer to tidy the reduce block a little using tap, something like the following:
ox_nodes.reduce({}) do |hash, node|
hash.tap { |h| h[node['#value']] = node.text }
end
Hope that helps - let me know how you get on!
I found the answer to the question in my last comment by myself:
def create_xml(expense)
Ox.default_options=({:with_xml => false})
doc = Ox::Document.new(:version => '1.0')
expense.each do |key, value|
e = Ox::Element.new(key)
e << value
doc << e
end
Ox.dump(doc)
end
The next question would be how can i transform the value of the amount key from a string to an integer befopre saving it to the database

Inspecting a hash

When I want to debug the following hash, it returns try2test2.
dictionary = {
"test" => 2,
"try" => 2
}
puts dictionary
# => try2test2
Are there any other ways to do it so that it will give you the full list like {'test': 2, 'try': 2}?
As V. Melnychuk mentioned, JSON is a good option, just remember to import the "json" module first:
require "json"
dictionary.to_json
in general, you can retreive a readable string version of an object by calling
inspect on it:
dictionary.inspect
finally, there is a "pp" module to pretty-print variable (pretty much like the pprint module in python):
require "pp"
pp dictionary
Hope it helps !
Try to convert object to JSON
dictionary.to_json
You could also do p dictionary which sends inspect by default:
dictionary = {
"test" => 2,
"try" => 2
}
p dictionary # => {"test"=>2, "try"=>2}

Convert CSV file into array of hashes

I have a csv file, some hockey stats, for example:
09.09.2008,1,HC Vitkovice Steel,BK Mlada Boleslav,1:0 (PP)
09.09.2008,1,HC Lasselsberger Plzen,RI OKNA ZLIN,6:2
09.09.2008,1,HC Litvinov,HC Sparta Praha,3:5
I want to save them in an array of hashes. I don't have any headers and I would like to add keys to each value like "time" => "09.09.2008" and so on. Each line should by accessible like arr[i], each value by for example arr[i]["time"]. I prefer CSV class rather than FasterCSV or split. Can you show the way or redirect to some thread where a similar problem was solved?
Just pass headers: true
CSV.foreach(data_file, headers: true) do |row|
puts row.inspect # hash
end
From there, you can manipulate the hash however you like.
(Tested with Ruby 2.0, but I think this has worked for quite a while.)
Edit
You say you don't have any headers - could you add a header line to the beginning of the file contents after reading them?
You can use the Ruby CSV parser to parse it, and then use Hash[ keys.zip(values) ] to make it a hash.
Example:
test = '''
09.09.2008,1,HC Vitkovice Steel,BK Mlada Boleslav,1:0 (PP)
09.09.2008,1,HC Lasselsberger Plzen,RI OKNA ZLIN,6:2
09.09.2008,1,HC Litvinov,HC Sparta Praha,3:5
'''.strip
keys = ['time', etc... ]
CSV.parse(test).map {|a| Hash[ keys.zip(a) ] }
This is a fantastic post by Josh Nichols which explains how to do what you're asking.
To summarize, here his code:
csv = CSV.new(body, :headers => true, :header_converters => :symbol, :converters => [:all, :blank_to_nil])
csv.to_a.map {|row| row.to_hash }
=> [{:year=>1997, :make=>"Ford", :model=>"E350", :description=>"ac, abs, moon", :price=>3000.0}, {:year=>1999, :make=>"Chevy", :model=>"Venture \"Extended Edition\"", :description=>nil, :price=>4900.0}, {:year=>1999, :make=>"Chevy", :model=>"Venture \"Extended Edition, Very Large\"", :description=>nil, :price=>5000.0}, {:year=>1996, :make=>"Jeep", :model=>"Grand Cherokee", :description=>"MUST SELL!\nair, moon roof, loaded", :price=>4799.0}]
So, you could save the body of your CSV file into a string called body.
body = "09.09.2008,1,HC Vitkovice Steel,BK Mlada Boleslav,1:0 (PP)
09.09.2008,1,HC Lasselsberger Plzen,RI OKNA ZLIN,6:2
09.09.2008,1,HC Litvinov,HC Sparta Praha,3:5"
And then run his code as listed above on it.
A little shorter solution
Parse string:
CSV.parse(content, headers: :first_row).map(&:to_h)
Parse file:
CSV.open(filename, headers: :first_row).map(&:to_h)
Slight variation on Nathan Long's answer
data_file = './sheet.csv'
data = CSV.foreach(data_file, headers: true).map(&:to_h)
Now data is an array of hashes to do your bidding with!
The headers option to the CSV module accepts an array of strings to be used as the headers, when they're not present as the first row in the CSV content.
CSV.parse(content, headers: %w(time number team_1 team_2 score))
This will generate an enumerable of hashes using the given headers as keys.
You can try the following gem also
require 'csv_hasher'
arr_of_hashes = CSVHasher.hashify('/path/to/csv/file')
The keys of the returned hashes will be the header values of the csv file.
If you want to pass your own keys then
keys = [:key1, :key2, ... ]
arr_of_hashers = CSVHasher.hashify('/path/to/csv/file', { keys: keys })
I guess this is the shortest version:
keys = ["time", ...]
CSV.parse(content, headers: keys).map(&:to_h)
you could also use the SmarterCSV gem,
which returns data from CSV files as Ruby hashes by default.
It has a lot of features, including processing the data in chunks, which is very benefitial for huge data files.
require 'smarter_csv'
options = {} # see GitHub README
data = SmarterCSV.process(your_file_name, options)

how to store a Ruby array into a file?

How to store a Ruby array into a file?
I am not sure what exactly you want, but, to serialize an array, write it to a file and read back, you can use this:
fruits = %w{mango banana apple guava}
=> ["mango", "banana", "apple", "guava"]
serialized_array = Marshal.dump(fruits)
=> "\004\b[\t\"\nmango\"\vbanana\"\napple\"\nguava"
File.open('/tmp/fruits_file.txt', 'w') {|f| f.write(serialized_array) }
=> 33
# read the file back
fruits = Marshal.load File.read('/tmp/fruits_file.txt')
=> ["mango", "banana", "apple", "guava"]
There are other alternatives you can explore, like json and YAML.
To just dump the array to a file in the standard [a,b,c] format:
require 'pp'
$stdout = File.open('path/to/file.txt', 'w')
pp myArray
That might not be so helpful, perhaps you might want to read it back? In that case you could use json. Install using rubygems with gem install json.
require 'rubygems'
require 'json'
$stdout = File.open('path/to/file.txt', 'w')
puts myArray.to_json
Read it back:
require 'rubygems'
require 'json'
buffer = File.open('path/to/file.txt', 'r').read
myArray = JSON.parse(buffer)
There are multiple ways to dump an array to disk. You need to decide if you want to serialize in a binary format or in a text format.
For binary serialization you can look at Marshal
For text format you can use json, yaml, xml (with rexml, builder, ... ) , ...
Some standard options for serializing data in Ruby:
Marshal
YAML
JSON (built-in as of 1.9, various gems available as well)
(There are other, arguably better/faster implementations of YAML and JSON, but I'm linking to built-ins for a start.)
In practice, I seem to see YAML most often, but that may not be indicative of anything real.
Here's a quick yaml example
config = {"rank" => "Admiral", "name"=>"Akbar",
"wallet_value" => 9, "bills" => [5,1,1,2]}
open('store.yml', 'w') {|f| YAML.dump(config, f)}
loaded = open('store.yml') {|f| YAML.load(f) }
p loaded
# => {"name"=>"Akbar", "wallet_value"=>9, \
# "bills"=>[5, 1, 1, 2], "rank"=>"Admiral"}
Example: write text_area to a file where text_area is an array of strings.
File.open('output.txt', 'w') { |f| text_area.each { |line| f << line } }
Don't forget to do error checking on file operations :)
Afaik.. files contain lines not arrays. When you read the files, the data can then be stored in an array or other data structures. I am anxious to know if there is another way.

Categories

Resources