Created
June 19, 2013 10:42
-
-
Save mbykovskyy/5813389 to your computer and use it in GitHub Desktop.
A hacky, incomplete Microsoft Visual Studio Solution parser
This file contains hidden or 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
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 && [email protected]? | |
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 && [email protected]? | |
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 && [email protected]? | |
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 [email protected]_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