123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293 |
- 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) }
- LINE = /
- \A
- (?:export\s+)? # optional export
- ([\w\.]+) # key
- (?:\s*=\s*|:\s+?) # separator
- ( # optional value begin
- '(?:\'|[^'])*' # single quoted value
- | # or
- "(?:\"|[^"])*" # double quoted value
- | # or
- [^#\n]+ # unquoted value
- )? # value end
- (?:\s*\#.*)? # optional comment
- \z
- /x
- 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").each do |line|
- parse_line(line)
- end
- @hash
- end
- private
- 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
- def parse_value(value)
- # Remove surrounding quotes
- value = value.strip.sub(/\A(['"])(.*)\1\z/, '\2')
- 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
- 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
|