Last active
September 20, 2019 05:27
-
-
Save beccasaurus/4b94f57e259f1947bd420e5811261a0a to your computer and use it in GitHub Desktop.
.ini files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This is a simple, clean configuration file. | |
# | |
# No errors were intentionally added to this file. | |
# For error case checking, see: edgecases.ini. | |
# | |
# ⚠️ If you change any of these values the tests will _surely_ explode! 💥 | |
# | |
# Consult the README for tests on running the innie test suite. | |
[Parker] | |
breed = Pomeranian Dashund Mix | |
favorite toy = Duck | |
second favorite = Fox | |
favorite activity = pushing | |
[Lander] | |
breed = American Pitbull Terrier | |
favorite toy = Racquetball | |
second favorite = Kong | |
favorite activity = chewing | |
[Murdoch] | |
breed = Australian Shepherd Mix | |
favorite activity = licking | |
[Murphy] | |
breed = Golden Retriever | |
favorite toy = Rope | |
favorite activity = drooling |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Edge-cases! | |
# | |
# This file is meant to _test_ the parser. | |
# | |
# It should always be safe to add a new [section] to this file. | |
# We do not assert that the section names are _exactly_ equal to anything. | |
; | |
[ Hey, s’up? ] | |
not too much really = | |
; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
source "https://rubygems.org" | |
gemspec |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env ruby | |
require_relative "./innie" | |
puts ".innie version #{Innie::VERSION.gsub '0', 'o'}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Gem::Specification.new do |gem| | |
gem.name = "innie" | |
gem.version = "0.1.0" | |
gem.authors = ["Rebecca Taylor"] | |
gem.email = "bex@beccasaur.us" | |
gem.summary = "Manipulate INI files" | |
gem.description = "Manipulate INI files" | |
gem.license = "Apache-2.0" | |
gem.files = ["innie.rb"] | |
gem.require_path = "." | |
gem.bindir = "." | |
gem.executable = "innie" | |
gem.add_development_dependency "rake" | |
gem.add_development_dependency "rspec" | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright 2019 Google LLC | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# https://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
module Innie | |
VERSION = "0.1.0" | |
def self.load_file path | |
Innie::IniFile.new path | |
end | |
class IniFile | |
attr_reader :path | |
def initialize path = nil | |
@path = path | |
end | |
def section_names | |
ini_hash.keys | |
end | |
def section section_name | |
ini_hash[section_name] | |
end | |
alias [] section | |
def add_section section_name, properties | |
ini_hash[section_name] = properties | |
end | |
def remove_section section_name | |
ini_hash.delete section_name | |
end | |
def get section_name, property_name | |
ini_hash[section_name][property_name] | |
end | |
def set section_name, property_name, property_value | |
ini_hash[section_name][property_name] = property_value | |
end | |
def properties section_name | |
ini_hash[section_name].keys | |
end | |
def remove section_name, property_name = nil | |
if property_name | |
ini_hash[section_name].delete property_name | |
else | |
ini_hash.delete section_name | |
end | |
end | |
alias rm remove | |
def to_hash | |
ini_hash | |
end | |
alias to_h to_hash | |
def reload! | |
@ini_hash = IniParser.ini_to_hash path | |
self | |
end | |
# ! Sorry ! For now all comments and formatting is blown away 🌬 | |
def save! | |
ini_text = StringIO.new | |
section_names.each do |section_name| | |
ini_text.puts "[#{section_name}]" | |
section(section_name).each do |property_name, property_value| | |
ini_text.puts "#{property_name} = #{property_value}" | |
end | |
end | |
ini_text = ini_text.string | |
File.write path, ini_text | |
self | |
end | |
private | |
def ini_hash | |
reload! if @ini_hash.nil? | |
@ini_hash | |
end | |
end | |
class IniParser | |
EMPTY_LINE_PATTERN = /^\s*$/ | |
COMMENT_LINE_PATTERN = /^\s*[#;]/ | |
SECTION_NAME_PATTERN = /^\[(?<section_name>.*)\]$/ | |
PROPERTY_LINE_PATTERN = /^(?<property_name>[^=]+)=(?<property_value>.*)$/ | |
def self.ini_to_hash path | |
hash = {} | |
current_section_name = nil | |
File.read(path).each_line.with_index do |line, line_number| | |
next if line =~ EMPTY_LINE_PATTERN | |
next if line =~ COMMENT_LINE_PATTERN | |
if line =~ SECTION_NAME_PATTERN | |
current_section_name = $~[:section_name].strip | |
hash[current_section_name] = {} | |
next | |
end | |
if current_section_name.nil? | |
raise ".ini must begin with a section (ignoring whitespace/comments)" | |
end | |
if line =~ PROPERTY_LINE_PATTERN | |
property_name = $~[:property_name].strip | |
property_value = $~[:property_value].strip | |
hash[current_section_name][property_name] = property_value | |
next | |
end | |
raise "Unrecognized .ini line: [#{line_number}] #{line.inspect}" | |
end | |
hash | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright 2019 Google LLC | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# https://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
require "innie" | |
module Innie | |
module CLI | |
def self.run args = ARGV | |
end | |
class Command | |
end | |
module Commands | |
class ShowSection < Command | |
end | |
end | |
class Result | |
attr_accessor :status, :stdout, :stderr | |
def output | |
stdout + stderr | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright 2019 Google LLC | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# https://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
require "innie_cli" | |
require "rspec" | |
describe "CLI for configuration using .ini files" do | |
example "calling the innie CLI" do | |
expect(`innie --version`).to eq ".innie version o.1.o\n" | |
end | |
# We'll use the commands from elsewhere, we won't really use this CLI. | |
example "calling the innie CLI using the library" do | |
result = Innie::CLI.run %w[ innie --version ] | |
expect(result.output).to eq ".innie version o.1.o\n" | |
end | |
# $ innie section[s] /path/to/config.ini [list] | |
# | |
# Alternatively, you can export the INI_FILE environment variable with a | |
# value of the path to your local .ini file. The command will run in that | |
# context to read/write your .ini file. | |
# | |
# $ export INI_FILE=/path/to/config | |
# $ innie sections | |
# | |
example "list section names" | |
# $ innie details | |
example "list sections with content" | |
# $ innie section foo | |
example "show section (including name)" | |
# $ innie get foo bar | |
example "get property value" | |
# $ innie set foo bar baz | |
example "set property value" | |
# $ innie rm foo bar | |
example "remove property" | |
# $ innie rm foo | |
example "remove section" | |
# $ innie file[s] | |
example "list configuration files available [in its default directory]" | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright 2019 Google LLC | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# https://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
require "innie" | |
require "rspec" | |
require "tempfile" | |
describe "Reading and writing .ini files" do | |
def load_ini filename, &block | |
raise "load_ini requires a block to run with provided .ini" if block.nil? | |
fixture_ini = File.expand_path filename, __dir__ | |
ini_file = Tempfile.new "innie.test.ini" | |
ini_file.write File.read(fixture_ini) | |
ini_file.rewind | |
@ini = Innie.load_file(ini_file.path) | |
block.call | |
ini_file.close | |
ini_file.unlink | |
end | |
# Tests using the [dogs.ini] fixture file | |
context "dogs.ini" do | |
around {|example| load_ini("dogs.ini") { example.run }} | |
example "get section names" do | |
expect(@ini.section_names).to eq ["Parker", "Lander", "Murdoch", "Murphy"] | |
end | |
example "get section (as Hash)" do | |
parker = @ini["Parker"] | |
expect(parker.keys).to eq ["breed", "favorite toy", "second favorite", | |
"favorite activity"] | |
end | |
example "get entire ini as Hash" do | |
hash = @ini.to_hash | |
expect(hash.keys).to eq ["Parker", "Lander", "Murdoch", "Murphy"] | |
parker = hash["Parker"] | |
expect(parker.keys).to eq ["breed", "favorite toy", "second favorite", | |
"favorite activity"] | |
end | |
example "#get and #set and #remove #properties" do | |
# Change existing value with #set | |
expect(@ini.get "Parker", "breed").to eq "Pomeranian Dashund Mix" | |
@ini.set "Parker", "breed", "New value" | |
expect(@ini.get "Parker", "breed").to eq "New value" | |
# Add a new property with #set | |
expect(@ini.get "Parker", "favorite food").to be nil | |
@ini.set "Parker", "favorite food", "Cubes" | |
expect(@ini.get "Parker", "favorite food").to eq "Cubes" | |
# #remove properties | |
expect(@ini.properties "Parker").to include "breed" | |
expect(@ini.properties "Parker").to include "favorite food" | |
@ini.remove "Parker", "breed" | |
expect(@ini.properties "Parker").not_to include "breed" | |
expect(@ini.properties "Parker").to include "favorite food" | |
@ini.rm "Parker", "favorite food" | |
expect(@ini.properties "Parker").not_to include "breed" | |
expect(@ini.properties "Parker").not_to include "favorite food" | |
# #rm an entire section | |
expect(@ini.section_names).to include "Lander" | |
expect(@ini.section_names).to include "Parker" | |
@ini.rm "Lander" | |
expect(@ini.section_names).not_to include "Lander" | |
expect(@ini.section_names).to include "Parker" | |
end | |
example "change existing property value" do | |
@ini.reload! | |
expect(@ini["Parker"]["favorite toy"]).to eq "Duck" | |
# Confirm that we can change the values of @ini (Hash) | |
@ini["Parker"]["favorite toy"] = "Changed" | |
expect(@ini["Parker"]["favorite toy"]).to eq "Changed" | |
# Confirm that #reload blows away our changes | |
@ini.reload! | |
expect(@ini["Parker"]["favorite toy"]).to eq "Duck" | |
# Persist it! | |
@ini["Parker"]["favorite toy"] = "Changed" | |
@ini.save! | |
# Confirm that #save! didn't change the value | |
expect(@ini["Parker"]["favorite toy"]).to eq "Changed" | |
# Confirm that #reload loads our persisted changes | |
@ini.reload! | |
expect(@ini["Parker"]["favorite toy"]).to eq "Changed" | |
end | |
example "add new section" do | |
expect(@ini.section_names).to_not include "Cool new section" | |
@ini.add_section "Cool new section", "Foo" => "Bar", hi: "there" | |
expect(@ini.section_names).to include "Cool new section" | |
# If we reload it should disappear because add_section does not persist. | |
expect(@ini.reload!.section_names).not_to include "Cool new section" | |
@ini.add_section "Cool new section", "Foo" => "Bar", hi: "there" | |
# But if we save! first, we can reload! and the new section will be there. | |
expect(@ini.save!.reload!.section_names).to include "Cool new section" | |
expect(@ini["Cool new section"].keys).to eq ["Foo", "hi"] | |
# Check some of the contents of the actual .ini file | |
ini_text = File.read @ini.path | |
expect(ini_text).to include "[Cool new section]" | |
expect(ini_text).to include "Foo = Bar" | |
expect(ini_text).to include "hi = there" | |
end | |
example "delete section" do | |
expect(@ini.reload!.section_names).to include "Murphy" | |
@ini.remove_section "Murphy" | |
expect(@ini.section_names).not_to include "Cool new section" | |
# If we reload it should reappear because remove_section does not persist. | |
expect(@ini.reload!.section_names).to include "Murphy" | |
@ini.remove_section "Murphy" | |
# But if we save! first, we can reload! and the section will be gone. | |
expect(@ini.save!.reload!.section_names).not_to include "Murphy" | |
end | |
end | |
context "edgecases.ini" do | |
around {|example| load_ini("edgecases.ini") { example.run }} | |
example "Hey, s’up?" do | |
expect(@ini.section_names).to include "Hey, s’up?" | |
expect(@ini["Hey, s’up?"]).to eq({ "not too much really" => "" }) | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright 2019 Google LLC | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# https://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
begin | |
require "rspec/core/rake_task" | |
RSpec::Core::RakeTask.new(:spec) do |task| | |
task.pattern = '*_spec.rb' | |
end | |
rescue LoadError | |
end | |
task default: :spec |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment