lib/dotenv/parser.rb in dotenv-2.0.0.beta vs lib/dotenv/parser.rb in dotenv-2.0.0
- old
+ new
@@ -1,15 +1,17 @@
-require 'dotenv/substitutions/variable'
-if RUBY_VERSION > '1.8.7'
- require 'dotenv/substitutions/command'
-end
+require "dotenv/substitutions/variable"
+require "dotenv/substitutions/command" if RUBY_VERSION > "1.8.7"
module Dotenv
class FormatError < SyntaxError; end
+ # This class enables parsing of a string for key value pairs to be returned
+ # and stored in the Environment. It allows for variable substitutions and
+ # exporting of variables.
class Parser
- @@substitutions = Substitutions.constants.map { |const| Substitutions.const_get(const) }
+ @substitutions =
+ Substitutions.constants.map { |const| Substitutions.const_get(const) }
LINE = /
\A
(?:export\s+)? # optional export
([\w\.]+) # key
@@ -23,46 +25,69 @@
)? # value end
(?:\s*\#.*)? # optional comment
\z
/x
- def self.call(string)
- new(string).call
+ class << self
+ attr_reader :substitutions
+
+ def call(string)
+ new(string).call
+ end
end
def initialize(string)
@string = string
+ @hash = {}
end
def call
- @string.split("\n").inject({}) do |hash, line|
- if match = line.match(LINE)
- key, value = match.captures
+ @string.split("\n").each do |line|
+ parse_line(line)
+ end
+ @hash
+ end
- value ||= ''
- # Remove surrounding quotes
- value = value.strip.sub(/\A(['"])(.*)\1\z/, '\2')
+ private
- if $1 == '"'
- value = value.gsub('\n', "\n")
- # Unescape all characters except $ so variables can be escaped properly
- value = value.gsub(/\\([^$])/, '\1')
- end
+ def parse_line(line)
+ if (match = line.match(LINE))
+ key, value = match.captures
+ @hash[key] = parse_value(value || "")
+ elsif line.split.first == "export"
+ if variable_not_set?(line)
+ fail FormatError, "Line #{line.inspect} has an unset variable"
+ end
+ elsif line !~ /\A\s*(?:#.*)?\z/ # not comment or blank line
+ fail FormatError, "Line #{line.inspect} doesn't match format"
+ end
+ end
- if $1 != "'"
- @@substitutions.each do |proc|
- value = proc.call(value, hash)
- end
- end
+ def parse_value(value)
+ # Remove surrounding quotes
+ value = value.strip.sub(/\A(['"])(.*)\1\z/, '\2')
- hash[key] = value
- elsif line.split.first == 'export'
- # looks like you want to export after declaration, I guess that is ok
- raise FormatError, "Line #{line.inspect} has a variable that is not set" unless line.split[1..-1].all?{ |var| hash.member?(var) }
- elsif line !~ /\A\s*(?:#.*)?\z/ # not comment or blank line
- raise FormatError, "Line #{line.inspect} doesn't match format"
+ if Regexp.last_match(1) == '"'
+ value = unescape_characters(expand_newlines(value))
+ end
+
+ if Regexp.last_match(1) != "'"
+ self.class.substitutions.each do |proc|
+ value = proc.call(value, @hash)
end
- hash
end
+ value
+ end
+
+ def unescape_characters(value)
+ value.gsub(/\\([^$])/, '\1')
+ end
+
+ def expand_newlines(value)
+ value.gsub('\n', "\n")
+ end
+
+ def variable_not_set?(line)
+ !line.split[1..-1].all? { |var| @hash.member?(var) }
end
end
end