Skip to content

Instantly share code, notes, and snippets.

@mbykovskyy
Created June 19, 2013 10:42
Show Gist options
  • Save mbykovskyy/5813389 to your computer and use it in GitHub Desktop.
Save mbykovskyy/5813389 to your computer and use it in GitHub Desktop.
A hacky, incomplete Microsoft Visual Studio Solution parser
require 'stringio'
module VSSLN
class Solution
def add_project project
@projects ||= {}
@projects[project.guid] = project
end
def remove_project project
if @projects
@projects.delete(project.guid)
@projects = nil if @projects.empty?
end
end
def each_project &block
if @projects
@projects.values.each do |project|
block.call project
end
end
end
def each_project_with_configuration_platform(name, &block)
each_project do |project|
if project.get_configuration_platform(name)
block.call(project)
end
end
end
def projects?
@projects != nil && !@projects.empty?
end
def get_project guid
@projects[guid] if @projects
end
def add_configuration_platform name, configuration_platform
@configuration_platforms ||= {}
@configuration_platforms[name] = configuration_platform
end
def remove_configuration_platform name
if @configuration_platforms
@configuration_platforms.delete(name)
@configuration_platforms = nil if @configuration_platforms.empty?
end
end
def each_configuration_platform &block
if @configuration_platforms
@configuration_platforms.each do |name, configuration_platform|
block.call name, configuration_platform
end
end
end
def configuration_platforms?
@configuration_platforms != nil && !@configuration_platforms.empty?
end
def configuration_platforms_include? name
@configuration_platforms.key? name
end
def project_configuration_platforms?
each_project do |project|
return true if project.configuration_platforms?
end
false
end
def add_property key, value
@properties ||= {}
@properties[key] = value
end
def remove_property name
if @properties
@properties.delete(name)
@properties = nil if @properties.empty?
end
end
def each_property &block
if @properties
@properties.each do |key, value|
block.call key, value
end
end
end
def properties?
@properties != nil && !@properties.empty?
end
def nested_projects?
each_project do |project|
return true if project.nested?
end
false
end
end
class Project
attr_accessor :type, :name, :path, :guid, :nested_under
def initialize type, name, path, guid
@type, @name, @path, @guid = type, name, path, guid
end
def nested?
@nested_under != nil
end
def add_dependency name, project
@dependencies ||= {}
@dependencies[name] = project
end
def remove_dependency name
if @dependencies
@dependencies.delete(name)
@dependencies = nil if @dependencies.empty?
end
end
def each_dependency &block
if @dependencies
@dependencies.each do |name, project|
block.call name, project
end
end
end
def dependencies?
@dependencies != nil && !@dependencies.empty?
end
def add_active_configuration_platform name, configuration_platform
add_configuration_platform(name, {:active => configuration_platform})
end
def add_build_configuration_platform name, configuration_platform
add_configuration_platform(name, {:build => configuration_platform})
end
def add_deploy_configuration_platform name, configuration_platform
add_configuration_platform(name, {:deploy => configuration_platform})
end
def remove_active_configuration_platform name
remove_configuration_platform(name, [:active])
end
def remove_build_configuration_platform name
remove_configuration_platform(name, [:build])
end
def remove_deploy_configuration_platform name
remove_configuration_platform(name, [:deploy])
end
def add_configuration_platform name, action_configuration_platform
@configuration_platforms ||= {}
@configuration_platforms[name] ||= {}
@configuration_platforms[name].merge!(action_configuration_platform)
end
def remove_configuration_platform(name, actions = nil)
if @configuration_platforms && @configuration_platforms[name]
if actions
actions.each do |action|
@configuration_platforms[name].delete(action)
end
@configuration_platforms.delete(name) if @configuration_platforms[name].empty?
else
@configuration_platforms.delete(name)
end
@configuration_platforms = nil if @configuration_platforms.empty?
end
end
def get_configuration_platform name
@configuration_platforms[name] if @configuration_platforms
end
def each_configuration_platform &block
if @configuration_platforms
@configuration_platforms.each do |name, action_configuration_platform|
block.call name, action_configuration_platform
end
end
end
def configuration_platforms?
@configuration_platforms != nil && !@configuration_platforms.empty?
end
end
class Document
HEX = "[0-9A-F]"
GUID = "\\{#{HEX}{8}-#{HEX}{4}-#{HEX}{4}-#{HEX}{4}-#{HEX}{12}\\}"
BOM = "\357\273\277"
VERSION_HEADER = /^Microsoft Visual Studio Solution File, Format Version (.*)$/
VERSION_COMMENT = /^# (Visual Studio .*)$/
CPP_PROJECT_GUID = "8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942"
VB_PROJECT_GUID = "F184B08F-C81C-45F6-A57F-5ABD9991F28F"
CSHARP_PROJECT_GUID = "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC"
WEB_PROJECT_GUID = "E24C65DC-7377-472b-9ABA-BC803B73C61A"
FOLDER_GUID = "2150E333-8FDC-42A3-9474-1A3956D46DE8"
PROJECT_TYPE_GUID = "\\{(#{CPP_PROJECT_GUID}|#{VB_PROJECT_GUID}|#{CSHARP_PROJECT_GUID}|#{WEB_PROJECT_GUID}|#{FOLDER_GUID})\\}"
PROJECT_SECTION = /^Project\("(#{PROJECT_TYPE_GUID})"\) = "(.*)", "(.*)", "(#{GUID})"$/
PROJECT_DEPENDENCIES_SECTION = /^\tProjectSection\(ProjectDependencies\) = postProject$/
PROJECT_DEPENDENCY = /^\t\t(#{GUID}) = (#{GUID})$/
END_PROJECT_DEPENDENCIES_SECTION = /^\tEndProjectSection$/
END_PROJECT_SECTION = /^EndProject$/
GLOBAL_SECTION = /^Global$/
CONFIGURATION_PLATFORM = "(\\S.*\\|\\S.*)"
SOLUTION_CONFIGURATION_PLATFORMS_SECTION = /^\tGlobalSection\(SolutionConfigurationPlatforms\) = preSolution$/
SOLUTION_CONFIGURATION_PLATFORM = /^\t\t#{CONFIGURATION_PLATFORM} = #{CONFIGURATION_PLATFORM}$/
END_SOLUTION_CONFIGURATION_PLATFORMS_SECTION = /^\tEndGlobalSection$/
PROJECT_CONFIGURATION_PLATFORMS_SECTION = /^\tGlobalSection\(ProjectConfigurationPlatforms\) = postSolution$/
PROJECT_CONFIGURATION_PLATFORM_ACTIVE = /^\t\t(#{GUID})\.#{CONFIGURATION_PLATFORM}\.ActiveCfg = #{CONFIGURATION_PLATFORM}$/
PROJECT_CONFIGURATION_PLATFORM_BUILD = /^\t\t(#{GUID})\.#{CONFIGURATION_PLATFORM}\.Build\.0 = #{CONFIGURATION_PLATFORM}$/
PROJECT_CONFIGURATION_PLATFORM_DEPLOY = /^\t\t(#{GUID})\.#{CONFIGURATION_PLATFORM}\.Deploy\.0 = #{CONFIGURATION_PLATFORM}$/
END_PROJECT_CONFIGURATION_PLATFORMS_SECTION = /^\tEndGlobalSection$/
SOLUTION_PROPERTIES_SECTION = /^\tGlobalSection\(SolutionProperties\) = preSolution$/
PROPERTY_VALUE = /^\t\t(\S+) = (.*)$/
END_SOLUTION_PROPERTIES_SECTION = /^\tEndGlobalSection$/
NESTED_PROJECTS_SECTION = /^\tGlobalSection\(NestedProjects\) = preSolution$/
NESTED_PROJECT = /^\t\t(#{GUID}) = (#{GUID})$/
END_NESTED_PROJECTS_SECTION = /^\tEndGlobalSection$/
END_GLOBAL_SECTION = /^EndGlobal$/
attr_accessor :version, :comment, :solution
def initialize content
@solution = Solution.new
parse(content)
end
def parse content
string_io = StringIO.new(content)
begin
while (line = string_io.readline)
if match = line.match(/#{BOM}/)
# Skip
elsif match = line.match(VERSION_HEADER)
@version = match[1]
elsif match = line.match(VERSION_COMMENT)
@comment = match[1]
elsif match = line.match(PROJECT_SECTION)
project = Project.new(match[1], match[3], match[4], match[5])
parse_project_section(project, string_io)
@solution.add_project(project)
elsif match = line.match(GLOBAL_SECTION)
parse_global_section(string_io)
else
raise "\"#{line}\" is unexpected"
end
end
rescue EOFError
# Reached the end of the stream
end
end
def to_s
string_io = StringIO.new
string_io << "#{BOM}\n"
string_io << "Microsoft Visual Studio Solution File, Format Version #{@version}\n"
string_io << "# #{@comment}\n"
# Add projects
@solution.each_project do |project|
string_io << "Project(\"#{project.type}\") = \"#{project.name}\", \"#{project.path}\", \"#{project.guid}\"\n"
# Add project dependencies
if project.dependencies?
string_io << "\tProjectSection(ProjectDependencies) = postProject\n"
project.each_dependency do |name, dependency|
string_io << "\t\t#{name} = #{dependency}\n"
end
string_io << "\tEndProjectSection\n"
end
string_io << "EndProject\n"
end
string_io << "Global\n"
# Add solution configuration platforms
if @solution.configuration_platforms?
string_io << "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n"
@solution.each_configuration_platform do |name, configuration_platform|
string_io << "\t\t#{name} = #{configuration_platform}\n"
end
string_io << "\tEndGlobalSection\n"
end
# Add project configuration platforms
if @solution.project_configuration_platforms?
string_io << "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n"
@solution.each_project do |project|
project.each_configuration_platform do |name, action_configuration_platform|
action_configuration_platform.each do |action, configuration_platform|
case action
when :active
action_str = "ActiveCfg"
when :build
action_str = "Build.0"
when :deploy
action_str = "Deploy.0"
else
raise "#{action} action is unknown"
end
string_io << "\t\t#{project.guid}.#{name}.#{action_str} = #{configuration_platform}\n"
end
end
end
string_io << "\tEndGlobalSection\n"
end
# Add solution properties
if @solution.properties?
string_io << "\tGlobalSection(SolutionProperties) = preSolution\n"
@solution.each_property do |key, value|
string_io << "\t\t#{key} = #{value}\n"
end
string_io << "\tEndGlobalSection\n"
end
# Add nested projects
if @solution.nested_projects?
string_io << "\tGlobalSection(NestedProjects) = preSolution\n"
@solution.each_project do |project|
if project.nested?
string_io << "\t\t#{project.guid} = #{project.nested_under}\n"
end
end
string_io << "\tEndGlobalSection\n"
end
string_io << "EndGlobal\n"
string_io.string
end
def each_project_with_configuration_platform(name, &block)
@solution.each_project_with_configuration_platform(name, &block)
end
private
def parse_project_section project, string_io
while (line = string_io.readline)
if match = line.match(PROJECT_DEPENDENCIES_SECTION)
parse_project_dependencies_section(project, string_io)
elsif match = line.match(END_PROJECT_SECTION)
break
else
raise "\"#{line}\" is unexpected"
end
end
end
def parse_project_dependencies_section project, string_io
while (line = string_io.readline)
if match = line.match(PROJECT_DEPENDENCY)
project.add_dependency(match[1], match[2])
elsif match = line.match(END_PROJECT_DEPENDENCIES_SECTION)
break
else
raise "\"#{line}\" is unexpected"
end
end
end
def parse_global_section string_io
while (line = string_io.readline)
if match = line.match(SOLUTION_CONFIGURATION_PLATFORMS_SECTION)
parse_solution_configuration_platforms_section(string_io)
elsif match = line.match(PROJECT_CONFIGURATION_PLATFORMS_SECTION)
parse_project_configuration_platforms_section(string_io)
elsif match = line.match(SOLUTION_PROPERTIES_SECTION)
parse_solution_properties_section(string_io)
elsif match = line.match(NESTED_PROJECTS_SECTION)
parse_nested_projects_section(string_io)
elsif match = line.match(END_GLOBAL_SECTION)
break
else
raise "\"#{line}\" is unexpected"
end
end
end
def parse_solution_configuration_platforms_section string_io
while (line = string_io.readline)
if match = line.match(SOLUTION_CONFIGURATION_PLATFORM)
@solution.add_configuration_platform(match[1], match[2])
elsif match = line.match(END_SOLUTION_CONFIGURATION_PLATFORMS_SECTION)
break
else
raise "\"#{line}\" is unexpected"
end
end
end
def parse_project_configuration_platforms_section string_io
while (line = string_io.readline)
if match = line.match(PROJECT_CONFIGURATION_PLATFORM_ACTIVE)
project = @solution.get_project(match[1])
raise "#{match[1]} project doesn't exist" if !project
project.add_active_configuration_platform(match[2], match[3])
elsif match = line.match(PROJECT_CONFIGURATION_PLATFORM_BUILD)
project = @solution.get_project(match[1])
raise "#{match[1]} project doesn't exist" if !project
project.add_build_configuration_platform(match[2], match[3])
elsif match = line.match(PROJECT_CONFIGURATION_PLATFORM_DEPLOY)
project = @solution.get_project(match[1])
raise "#{match[1]} project doesn't exist" if !project
project.add_deploy_configuration_platform(match[2], match[3])
elsif match = line.match(END_PROJECT_CONFIGURATION_PLATFORMS_SECTION)
break
else
raise "\"#{line}\" is unexpected"
end
end
end
def parse_solution_properties_section string_io
while (line = string_io.readline)
if match = line.match(PROPERTY_VALUE)
@solution.add_property(match[1], match[2])
elsif match = line.match(END_SOLUTION_PROPERTIES_SECTION)
break
else
raise "\"#{line}\" is unexpected"
end
end
end
def parse_nested_projects_section string_io
while (line = string_io.readline)
if match = line.match(NESTED_PROJECT)
project =@solution.get_project(match[1])
raise "#{match[1]} project doesn't exist" if !project
project.nested_under = match[2]
elsif match = line.match(END_NESTED_PROJECTS_SECTION)
break
else
raise "\"#{line}\" is unexpected"
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment