#!/usr/bin/env ruby
#
# Visual Studio .NET 2003 の vcproj 生成ツール
# Satofumi KAMIMURA
# $Id: vcprogen.rb 1191 2009-07-30 05:52:49Z satofumi $

# 置換処理
def replace(template, key, value, add = false)

  # 検索パターンの生成
  path = key.split('/') - [""]
  name = path.pop
  parent = path.pop

  reg_str = '(<'
  path.each { |tag|
    reg_str += tag + '.+?<'
  }
  reg_str += parent + '.+?' + name + '=")(.*?)(")'

  left = template
  template = ''
  pattern = Regexp.compile(reg_str, Regexp::MULTILINE)
  while left =~ pattern
    template += $` + $1 + ((add) ? $2 : '') + value + $3
    left = $'
  end

  return template + left
end


# ファイルの行毎に置換処理
def replaceFromFile(template, config_file)
  open(config_file) { |io|
    while line = io.gets
      tags = line.chomp.split(/\s/, 2)
      if tags.size() >= 2
        if tags[0].downcase == "true"
          add_tags = tags[1].split(/\s/, 2)
          template = replace(template, add_tags[0], add_tags[1], true)
        else
          template = replace(template, tags[0], tags[1])
        end
      end
    end
    return template
  } rescue STDERR.puts "replaceFromFile: Warning: #$!"
end

# Makefile の指定タグに指定されているファイルを展開


# include パスを追加
def addIncludes(includes, file)
  includes.push(File.dirname(file))
end


# １行分の行の取り出し
def parseLine(line, io, dir_path)
  while line[-1,1] == '\\'
    line = line.chop + io.gets
  end
  files = line.split
  files.map! { |file| dir_path + '/' + file }
  return files.join(' ')
end


# Makefile.am を展開する
def extendMakefile(file, keyword = 'SOURCES')
  pattern = Regexp.compile('^\w+?_' + keyword + '\s+=')
  open(file) { |io|
    while line = io.gets
      line.strip!
      if pattern =~ line
        return parseLine($', io, File.dirname(file))
      end
    end
  } rescue STDERR.puts "extendMakefile: Warning: #$!"
  return ""
end


# コードに含めるファイル一覧を生成
def createFileList(sources, headers, includes, files, mask)
  files.split(/\s/).each { |file|
    if File.basename(file) == "Makefile.am"
      createFileList(sources, headers, includes, extendMakefile(file), mask)
      next
    end
    ext = File.extname(file)[1..-1]
    if !mask.split(/[,\s]/).index(ext)
      next
    end

    addIncludes(includes, file)
    if ext == "h" || ext == "hh"
      headers.push(file)
    else
      sources.push(file)
    end
  }
end


# ファイルタグの生成
def createFilesXML(list, offset)
  tags = ''
  list.each { |file|
    tags +=
    offset + '<File RelativePath="' + file.gsub('/', '\\') + "\"></File>\n"
  }
  return tags
end


# ソースファイルを挿入
def insertSources(template, sources)

  tags = createFilesXML(sources, "\t\t\t")
  if tags.empty?
    return
  end
  pattern = /(<Filter\s+Name="Source files".+?\}">)(.+?<\/Filter>)/m
  if template =~ pattern
    template.sub!(pattern, $1 + "\n" + tags + $2)
  end
end


# リソースファイルを挿入
def insertResources(template, resources)

  tags = createFilesXML(resources, "\t\t\t")
  if tags.empty?
    return
  end
  pattern = /(<Filter\s+Name="Resource files".+?\}">)(.+?<\/Filter>)/m
  if template =~ pattern
    template.sub!(pattern, $1 + "\n" + tags + $2)
  end
end


# ヘッダファイルを挿入
def insertHeaders(template, headers)

  tags = createFilesXML(headers, "\t\t\t")
  if tags.empty?
    return
  end
  pattern = /<Filter\s+Name="Header files".+?\}">^.+?<\/Filter>/m
  if template =~ pattern
    template.sub!(pattern, $1 + "\n" + tags + $2)
  end
end


# include/ の生成
def createIncludes(template, includes, files)
  targets = []
  files.split(/\s/).each { |file|
    if File.basename(file) == "Makefile.am"
      targets += extendMakefile(file, "HEADERS").split(' ')
    else
      includes << file
    end
  }

  if ! targets.empty?
    offset = "\t\t\t\t"
    cmd = 'del /q include;' + "\n" + offset + 'mkdir include;' + "\n"
    targets.each { |file|
      cmd += offset + 'copy ' + file.gsub('/', '\\') + ' include\;' + "\n"
    }
    return replace(template, '/Configuration/Tool/CommandLine', cmd.chomp)
  else
    return template
  end
end


# 追加のインクルードパスを挿入
def insertIncludes(template, includes)

  replace = '.\; ' + includes.uniq.join('; ').gsub('/', '\\') + ';'
  path = "/Configuration/Tool/AdditionalIncludeDirectories"
  return replace(template, path, replace)
end


# lib 生成用テンプレート
lib_template = <<-EOB
<?xml version="1.0" encoding="shift_jis"?>
<VisualStudioProject
	ProjectType="Visual C++"
	Version="7.10"
	Name="lib_template"
	ProjectGUID="{E35DE899-8736-4A8E-A604-821469165AF1}"
	RootNamespace="lib_template"
	Keyword="ManagedCProj">
	<Platforms>
		<Platform
			Name="Win32"/>
	</Platforms>
	<Configurations>
		<Configuration
			Name="Debug|Win32"
			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
			IntermediateDirectory="$(ConfigurationName)"
			ConfigurationType="4"
			InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
			CharacterSet="2"
			ManagedExtensions="0">
			<Tool
				Name="VCCLCompilerTool"
				Optimization="0"
				AdditionalIncludeDirectories=";"
				PreprocessorDefinitions="WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS"
				MinimalRebuild="TRUE"
				RuntimeLibrary="3"
				RuntimeTypeInfo="TRUE"
				WarningLevel="3"
				DebugInformationFormat="4"/>
			<Tool
				Name="VCCustomBuildTool"/>
			<Tool
				Name="VCLibrarianTool"
				AdditionalDependencies=";"
				AdditionalLibraryDirectories=";"/>
			<Tool
				Name="VCMIDLTool"/>
			<Tool
				Name="VCPostBuildEventTool"/>
			<Tool
				Name="VCPreBuildEventTool"
				CommandLine=";"/>
			<Tool
				Name="VCPreLinkEventTool"/>
			<Tool
				Name="VCResourceCompilerTool"/>
			<Tool
				Name="VCWebServiceProxyGeneratorTool"/>
			<Tool
				Name="VCXMLDataGeneratorTool"/>
			<Tool
				Name="VCManagedWrapperGeneratorTool"/>
			<Tool
				Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
		</Configuration>
		<Configuration
			Name="Release|Win32"
			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
			IntermediateDirectory="$(ConfigurationName)"
			ConfigurationType="4"
			InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
			CharacterSet="2"
			ManagedExtensions="0">
			<Tool
				Name="VCCLCompilerTool"
				AdditionalIncludeDirectories=";"
				PreprocessorDefinitions="WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS"
				MinimalRebuild="TRUE"
				RuntimeLibrary="2"
				RuntimeTypeInfo="TRUE"
				WarningLevel="3"
				DebugInformationFormat="3"/>
			<Tool
				Name="VCCustomBuildTool"/>
			<Tool
				Name="VCLibrarianTool"
				AdditionalDependencies=";"
				AdditionalLibraryDirectories=";"/>
			<Tool
				Name="VCMIDLTool"/>
			<Tool
				Name="VCPostBuildEventTool"/>
			<Tool
				Name="VCPreBuildEventTool"
				CommandLine=";"/>
			<Tool
				Name="VCPreLinkEventTool"/>
			<Tool
				Name="VCResourceCompilerTool"/>
			<Tool
				Name="VCWebServiceProxyGeneratorTool"/>
			<Tool
				Name="VCXMLDataGeneratorTool"/>
			<Tool
				Name="VCWebDeploymentTool"/>
			<Tool
				Name="VCManagedWrapperGeneratorTool"/>
			<Tool
				Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
		</Configuration>
	</Configurations>
	<References>
	</References>
	<Files>
		<Filter
			Name="Source files"
			Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx"
			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}">
		</Filter>
		<Filter
			Name="Header files"
			Filter="h;hpp;hxx;hm;inl;inc;xsd"
			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}">
		</Filter>
		<Filter
			Name="Resource files"
			Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx"
			UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}">
		</Filter>
	</Files>
	<Globals>
	</Globals>
</VisualStudioProject>
EOB


# exe 生成用テンプレート
exe_template = <<-EOB
<?xml version="1.0" encoding="shift_jis"?>
<VisualStudioProject
	ProjectType="Visual C++"
	Version="7.10"
	Name="exe_template"
	ProjectGUID="{BA46C739-97CD-4F68-9F3D-ECF9759134CE}"
	RootNamespace="exe_template"
	Keyword="ManagedCProj">
	<Platforms>
		<Platform
			Name="Win32"/>
	</Platforms>
	<Configurations>
		<Configuration
			Name="Debug|Win32"
			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
			IntermediateDirectory="$(ConfigurationName)"
			ConfigurationType="1"
			CharacterSet="1"
			>
			<Tool
				Name="VCPreBuildEventTool"
			/>
			<Tool
				Name="VCCustomBuildTool"
			/>
			<Tool
				Name="VCXMLDataGeneratorTool"
			/>
			<Tool
				Name="VCWebServiceProxyGeneratorTool"
			/>
			<Tool
				Name="VCMIDLTool"
			/>
			<Tool
				Name="VCCLCompilerTool"
				Optimization="0"
				AdditionalIncludeDirectories=";"
				PreprocessorDefinitions="WIN32;_DEBUG"
				MinimalRebuild="TRUE"
				RuntimeLibrary="1"
				UsePrecompiledHeader="0"
				WarningLevel="3"
				Detect64BitPortabilityProblems="true"
				DebugInformationFormat="4"/>
			<Tool
				Name="VCCustomBuildTool"/>
			<Tool
				Name="VCLinkerTool"
				AdditionalDependencies=""
				OutputFile="$(OutDir)\\$(ProjectName).exe"
				LinkIncremental="2"
				AdditionalLibraryDirectories=""
				IgnoreDefaultLibraryNames=""
				SuppressStartupBanner="TRUE"
				GenerateDebugInformation="TRUE"
				SubSystem="1"
				TargetMachine="1"
			/>
			<Tool
				Name="VCMIDLTool"/>
			<Tool
				Name="VCPostBuildEventTool"/>
			<Tool
				Name="VCPreBuildEventTool"
				CommandLine=";"/>
			<Tool
				Name="VCPreLinkEventTool"/>
			<Tool
				Name="VCResourceCompilerTool"/>
			<Tool
				Name="VCWebServiceProxyGeneratorTool"/>
			<Tool
				Name="VCXMLDataGeneratorTool"/>
			<Tool
				Name="VCWebDeploymentTool"/>
			<Tool
				Name="VCManagedWrapperGeneratorTool"/>
			<Tool
				Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
		</Configuration>
		<Configuration
			Name="Release|Win32"
			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
			IntermediateDirectory="$(ConfigurationName)"
			ConfigurationType="1"
			CharacterSet="1"
			WholeProgramOptimization="1"
			>
			<Tool
				Name="VCCLCompilerTool"
				AdditionalIncludeDirectories=";"
				PreprocessorDefinitions="WIN32;NDEBUG"
				MinimalRebuild="TRUE"
				RuntimeLibrary="0"
				UsePrecompiledHeader="0"
				WarningLevel="3"
				Detect64BitPortabilityProblems="true"
				DebugInformationFormat="3"/>
			<Tool
				Name="VCCustomBuildTool"/>
			<Tool
				Name="VCLinkerTool"
				AdditionalDependencies=""
				OutputFile="$(OutDir)\\$(ProjectName).exe"
				LinkIncremental="1"
				AdditionalLibraryDirectories=""
				IgnoreDefaultLibraryNames=""
				GenerateDebugInformation="TRUE"
				SubSystem="1"/>
			<Tool
				Name="VCMIDLTool"/>
			<Tool
				Name="VCPostBuildEventTool"/>
			<Tool
				Name="VCPreBuildEventTool"
				CommandLine=";"/>
			<Tool
				Name="VCPreLinkEventTool"/>
			<Tool
				Name="VCResourceCompilerTool"/>
			<Tool
				Name="VCWebServiceProxyGeneratorTool"/>
			<Tool
				Name="VCXMLDataGeneratorTool"/>
			<Tool
				Name="VCWebDeploymentTool"/>
			<Tool
				Name="VCManagedWrapperGeneratorTool"/>
			<Tool
				Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
		</Configuration>
	</Configurations>
	<References>
	</References>
	<Files>
		<Filter
			Name="Source files"
			Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx"
			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}">
		</Filter>
		<Filter
			Name="Header files"
			Filter="h;hpp;hxx;hm;inl;inc;xsd"
			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}">
		</Filter>
		<Filter
			Name="Resource files"
			Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx"
			UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}">
		</Filter>
	</Files>
	<Globals>
	</Globals>
</VisualStudioProject>
EOB


def stripNoneDependencies(template)

  template = template.gsub(/AdditionalDependencies=\";\"/, "AdditionalDependencies=\"\"")
  template = template.gsub(/AdditionalLibraryDirectories=\";\"/, "AdditionalLibraryDirectories=\"\"")

  template = template.gsub(/CommandLine=\";\"/, "")

  return template
end


# 使い方を表示して終了
def printUsage()
  print \
  "usage:\n" \
  "\t% ruby " + __FILE__ + " [options] <config.txt>\n" \
  "\n" \
  "options: \n" \
  "--name <project name>           specify project name\n" \
  "--type <exe, lib>               specify project output type\n" \
  "--mask \"cpp,c,h\"                specify handled file type\n" \
  "--sources \"<Makefile.am list>\"  to create sources list\n" \
  "--headers \"<Makefile.am list>\"  to create include folder\n" \
  "--codes   \"<Makefile.am list>\"  means sourcesb and headers option\n" \
  "\n"
  exit
end


# メイン処理
if ARGV.empty?
  printUsage()
end

# オプションの判定
pre_arg = nil
proj_name = nil
proj_type = nil
proj_mask = "c,cpp,h"
proj_sources = nil
proj_resources = nil
proj_headers = nil
files = []
ARGV.reverse.each { |arg|
  case arg
  when /--name/
    proj_name = pre_arg.split('.')[0]
    files.shift

  when /--type/
    if pre_arg == "exe" || pre_arg == "lib"
      proj_type = pre_arg
    end
    files.shift

  when /--mask/
    proj_mask = pre_arg
    files.shift

  when /--sources/
    proj_sources = pre_arg
    files.shift

  when /--resources/
    proj_resources = pre_arg
    files.shift

  when /--headers/
    proj_headers = pre_arg
    files.shift

  when /--codes/
    proj_sources = pre_arg
    proj_headers = pre_arg
    files.shift

  else
    files.unshift(arg)
  end
  pre_arg = arg
}
config_file = files.shift
if proj_name == nil
  print "name is not specified!\n"
  printUsage()
end
if proj_type == nil
  print "type is not specified!\n"
  printUsage()
end
template = (proj_type == "exe") ? exe_template : lib_template


# プロジェクト名の置換
template = replace(template, "/VisualStudioProject/Name", proj_name)
template = replace(template, "/VisualStudioProject/RootNamespace", proj_name)


# コンフィグファイル指定の要素を置換
if config_file
  template = replaceFromFile(template, config_file)
end

sources = []
resources = []
headers = []
includes = []


# include folder の生成
if proj_headers
  template = createIncludes(template, includes, proj_headers)
end


# resources の調整
if proj_resources
  resources = proj_resources.split
end


# source files, include path の調整
if proj_sources
  createFileList(sources, headers, includes, proj_sources, proj_mask)
end


# sources, headers, includes の反映
insertSources(template, sources)
insertHeaders(template, headers)
insertResources(template, resources)
template = insertIncludes(template, includes)


# Additional*=";" だとエラーになるのを修正
# CommandLine=";" のときは、出力しないように修正
template = stripNoneDependencies(template);


# プロジェクトファイルの生成
open(proj_name + '.vcproj', 'w') { |io|
  io.print template
}
