summaryrefslogtreecommitdiff
path: root/lib/rubygems/request_set/lockfile
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems/request_set/lockfile')
-rw-r--r--lib/rubygems/request_set/lockfile/parser.rb334
-rw-r--r--lib/rubygems/request_set/lockfile/tokenizer.rb108
2 files changed, 442 insertions, 0 deletions
diff --git a/lib/rubygems/request_set/lockfile/parser.rb b/lib/rubygems/request_set/lockfile/parser.rb
new file mode 100644
index 0000000000..7778b7ae17
--- /dev/null
+++ b/lib/rubygems/request_set/lockfile/parser.rb
@@ -0,0 +1,334 @@
+class Gem::RequestSet::Lockfile::Parser
+ ###
+ # Parses lockfiles
+
+ def initialize tokenizer, set, platforms, filename = nil
+ @tokens = tokenizer
+ @filename = filename
+ @set = set
+ @platforms = platforms
+ end
+
+ def parse
+ until @tokens.empty? do
+ type, data, column, line = get
+
+ case type
+ when :section then
+ @tokens.skip :newline
+
+ case data
+ when 'DEPENDENCIES' then
+ parse_DEPENDENCIES
+ when 'GIT' then
+ parse_GIT
+ when 'GEM' then
+ parse_GEM
+ when 'PATH' then
+ parse_PATH
+ when 'PLATFORMS' then
+ parse_PLATFORMS
+ else
+ type, = get until @tokens.empty? or peek.first == :section
+ end
+ else
+ raise "BUG: unhandled token #{type} (#{data.inspect}) at line #{line} column #{column}"
+ end
+ end
+ end
+
+ ##
+ # Gets the next token for a Lockfile
+
+ def get expected_types = nil, expected_value = nil # :nodoc:
+ current_token = @tokens.shift
+
+ type, value, column, line = current_token
+
+ if expected_types and not Array(expected_types).include? type then
+ unget current_token
+
+ message = "unexpected token [#{type.inspect}, #{value.inspect}], " +
+ "expected #{expected_types.inspect}"
+
+ raise Gem::RequestSet::Lockfile::ParseError.new message, column, line, @filename
+ end
+
+ if expected_value and expected_value != value then
+ unget current_token
+
+ message = "unexpected token [#{type.inspect}, #{value.inspect}], " +
+ "expected [#{expected_types.inspect}, " +
+ "#{expected_value.inspect}]"
+
+ raise Gem::RequestSet::Lockfile::ParseError.new message, column, line, @filename
+ end
+
+ current_token
+ end
+
+ def parse_DEPENDENCIES # :nodoc:
+ while not @tokens.empty? and :text == peek.first do
+ _, name, = get :text
+
+ requirements = []
+
+ case peek[0]
+ when :bang then
+ get :bang
+
+ requirements << pinned_requirement(name)
+ when :l_paren then
+ get :l_paren
+
+ loop do
+ _, op, = get :requirement
+ _, version, = get :text
+
+ requirements << "#{op} #{version}"
+
+ break unless peek[0] == :comma
+
+ get :comma
+ end
+
+ get :r_paren
+
+ if peek[0] == :bang then
+ requirements.clear
+ requirements << pinned_requirement(name)
+
+ get :bang
+ end
+ end
+
+ @set.gem name, *requirements
+
+ skip :newline
+ end
+ end
+
+ def parse_GEM # :nodoc:
+ sources = []
+
+ while [:entry, 'remote'] == peek.first(2) do
+ get :entry, 'remote'
+ _, data, = get :text
+ skip :newline
+
+ sources << Gem::Source.new(data)
+ end
+
+ sources << Gem::Source.new(Gem::DEFAULT_HOST) if sources.empty?
+
+ get :entry, 'specs'
+
+ skip :newline
+
+ set = Gem::Resolver::LockSet.new sources
+ last_specs = nil
+
+ while not @tokens.empty? and :text == peek.first do
+ _, name, column, = get :text
+
+ case peek[0]
+ when :newline then
+ last_specs.each do |spec|
+ spec.add_dependency Gem::Dependency.new name if column == 6
+ end
+ when :l_paren then
+ get :l_paren
+
+ type, data, = get [:text, :requirement]
+
+ if type == :text and column == 4 then
+ version, platform = data.split '-', 2
+
+ platform =
+ platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY
+
+ last_specs = set.add name, version, platform
+ else
+ dependency = parse_dependency name, data
+
+ last_specs.each do |spec|
+ spec.add_dependency dependency
+ end
+ end
+
+ get :r_paren
+ else
+ raise "BUG: unknown token #{peek}"
+ end
+
+ skip :newline
+ end
+
+ @set.sets << set
+ end
+
+ def parse_GIT # :nodoc:
+ get :entry, 'remote'
+ _, repository, = get :text
+
+ skip :newline
+
+ get :entry, 'revision'
+ _, revision, = get :text
+
+ skip :newline
+
+ type, value = peek.first 2
+ if type == :entry and %w[branch ref tag].include? value then
+ get
+ get :text
+
+ skip :newline
+ end
+
+ get :entry, 'specs'
+
+ skip :newline
+
+ set = Gem::Resolver::GitSet.new
+ set.root_dir = @set.install_dir
+
+ last_spec = nil
+
+ while not @tokens.empty? and :text == peek.first do
+ _, name, column, = get :text
+
+ case peek[0]
+ when :newline then
+ last_spec.add_dependency Gem::Dependency.new name if column == 6
+ when :l_paren then
+ get :l_paren
+
+ type, data, = get [:text, :requirement]
+
+ if type == :text and column == 4 then
+ last_spec = set.add_git_spec name, data, repository, revision, true
+ else
+ dependency = parse_dependency name, data
+
+ last_spec.add_dependency dependency
+ end
+
+ get :r_paren
+ else
+ raise "BUG: unknown token #{peek}"
+ end
+
+ skip :newline
+ end
+
+ @set.sets << set
+ end
+
+ def parse_PATH # :nodoc:
+ get :entry, 'remote'
+ _, directory, = get :text
+
+ skip :newline
+
+ get :entry, 'specs'
+
+ skip :newline
+
+ set = Gem::Resolver::VendorSet.new
+ last_spec = nil
+
+ while not @tokens.empty? and :text == peek.first do
+ _, name, column, = get :text
+
+ case peek[0]
+ when :newline then
+ last_spec.add_dependency Gem::Dependency.new name if column == 6
+ when :l_paren then
+ get :l_paren
+
+ type, data, = get [:text, :requirement]
+
+ if type == :text and column == 4 then
+ last_spec = set.add_vendor_gem name, directory
+ else
+ dependency = parse_dependency name, data
+
+ last_spec.dependencies << dependency
+ end
+
+ get :r_paren
+ else
+ raise "BUG: unknown token #{peek}"
+ end
+
+ skip :newline
+ end
+
+ @set.sets << set
+ end
+
+ def parse_PLATFORMS # :nodoc:
+ while not @tokens.empty? and :text == peek.first do
+ _, name, = get :text
+
+ @platforms << name
+
+ skip :newline
+ end
+ end
+
+ ##
+ # Parses the requirements following the dependency +name+ and the +op+ for
+ # the first token of the requirements and returns a Gem::Dependency object.
+
+ def parse_dependency name, op # :nodoc:
+ return Gem::Dependency.new name, op unless peek[0] == :text
+
+ _, version, = get :text
+
+ requirements = ["#{op} #{version}"]
+
+ while peek[0] == :comma do
+ get :comma
+ _, op, = get :requirement
+ _, version, = get :text
+
+ requirements << "#{op} #{version}"
+ end
+
+ Gem::Dependency.new name, requirements
+ end
+
+ private
+
+ def skip type # :nodoc:
+ @tokens.skip type
+ end
+
+ ##
+ # Peeks at the next token for Lockfile
+
+ def peek # :nodoc:
+ @tokens.peek
+ end
+
+ def pinned_requirement name # :nodoc:
+ spec = @set.sets.select { |set|
+ Gem::Resolver::GitSet === set or
+ Gem::Resolver::VendorSet === set
+ }.map { |set|
+ set.specs[name]
+ }.compact.first
+
+ spec.version
+ end
+
+ ##
+ # Ungets the last token retrieved by #get
+
+ def unget token # :nodoc:
+ @tokens.unshift token
+ end
+end
+
diff --git a/lib/rubygems/request_set/lockfile/tokenizer.rb b/lib/rubygems/request_set/lockfile/tokenizer.rb
new file mode 100644
index 0000000000..73c9a834bb
--- /dev/null
+++ b/lib/rubygems/request_set/lockfile/tokenizer.rb
@@ -0,0 +1,108 @@
+require 'strscan'
+require 'rubygems/request_set/lockfile/parser'
+
+class Gem::RequestSet::Lockfile::Tokenizer
+ def self.from_file file
+ new File.read(file), file
+ end
+
+ def initialize input, filename = nil, line = 0, pos = 0
+ @line = line
+ @line_pos = pos
+ @tokens = []
+ @filename = filename
+ tokenize input
+ end
+
+ def make_parser set, platforms
+ Gem::RequestSet::Lockfile::Parser.new self, set, platforms, @filename
+ end
+
+ def to_a
+ @tokens
+ end
+
+ def skip type
+ @tokens.shift while not @tokens.empty? and peek.first == type
+ end
+
+ ##
+ # Calculates the column (by byte) and the line of the current token based on
+ # +byte_offset+.
+
+ def token_pos byte_offset # :nodoc:
+ [byte_offset - @line_pos, @line]
+ end
+
+ def empty?
+ @tokens.empty?
+ end
+
+ def unshift token
+ @tokens.unshift token
+ end
+
+ def next_token
+ @tokens.shift
+ end
+ alias :shift :next_token
+
+ def peek
+ @tokens.first || [:EOF]
+ end
+
+ private
+
+ def tokenize input
+ s = StringScanner.new input
+
+ until s.eos? do
+ pos = s.pos
+
+ pos = s.pos if leading_whitespace = s.scan(/ +/)
+
+ if s.scan(/[<|=>]{7}/) then
+ message = "your #{@filename} contains merge conflict markers"
+ column, line = token_pos pos
+
+ raise Gem::RequestSet::Lockfile::ParseError.new message, column, line, @filename
+ end
+
+ @tokens <<
+ case
+ when s.scan(/\r?\n/) then
+ token = [:newline, nil, *token_pos(pos)]
+ @line_pos = s.pos
+ @line += 1
+ token
+ when s.scan(/[A-Z]+/) then
+ if leading_whitespace then
+ text = s.matched
+ text += s.scan(/[^\s)]*/).to_s # in case of no match
+ [:text, text, *token_pos(pos)]
+ else
+ [:section, s.matched, *token_pos(pos)]
+ end
+ when s.scan(/([a-z]+):\s/) then
+ s.pos -= 1 # rewind for possible newline
+ [:entry, s[1], *token_pos(pos)]
+ when s.scan(/\(/) then
+ [:l_paren, nil, *token_pos(pos)]
+ when s.scan(/\)/) then
+ [:r_paren, nil, *token_pos(pos)]
+ when s.scan(/<=|>=|=|~>|<|>|!=/) then
+ [:requirement, s.matched, *token_pos(pos)]
+ when s.scan(/,/) then
+ [:comma, nil, *token_pos(pos)]
+ when s.scan(/!/) then
+ [:bang, nil, *token_pos(pos)]
+ when s.scan(/[^\s),!]*/) then
+ [:text, s.matched, *token_pos(pos)]
+ else
+ raise "BUG: can't create token for: #{s.string[s.pos..-1].inspect}"
+ end
+ end
+
+ @tokens
+ end
+end