PageRenderTime 27ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/src/OpenWrap/ProjectModel/Drivers.File/SolutionFile.cs

http://github.com/openrasta/openwrap
C# | 417 lines | 362 code | 52 blank | 3 comment | 48 complexity | 8bb2f73a90a1dde39ded3c96c78c2ab8 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. using System.Xml;
  8. using System.Xml.Linq;
  9. using OpenFileSystem.IO;
  10. using OpenWrap.IO;
  11. using OpenWrap.Runtime;
  12. namespace OpenWrap.ProjectModel.Drivers.File
  13. {
  14. public class SolutionFile : ISolution
  15. {
  16. const string GUID_REGEX = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}";
  17. const string NS_MSBUILD = "http://schemas.microsoft.com/developer/msbuild/2003";
  18. const string QUOTE = "\"";
  19. const string WS = "\\s*";
  20. readonly List<object> _content;
  21. readonly IFile _file;
  22. public SolutionFile(IFile inputFile, Version vsVersion)
  23. {
  24. _file = inputFile;
  25. _content = new List<object>
  26. {
  27. string.Empty,
  28. new SolutionFormatVersion(vsVersion),
  29. new SolutionVisualStudioVersion(SolutionConstants.Editions[vsVersion])
  30. };
  31. }
  32. SolutionFile(IFile inputFile)
  33. {
  34. _file = inputFile;
  35. _content = new List<object>();
  36. ParseContent();
  37. }
  38. public IEnumerable<IProject> AllProjects
  39. {
  40. get { return _content.OfType<IProject>(); }
  41. }
  42. public Version Version
  43. {
  44. get { return _content.OfType<SolutionFormatVersion>().Select(x => x.Version).FirstOrDefault(); }
  45. }
  46. public bool OpenWrapAddInEnabled
  47. {
  48. get
  49. {
  50. string progId = Version.Major == 10 ? SolutionConstants.ADD_IN_PROGID_2010 : SolutionConstants.ADD_IN_PROGID_2008;
  51. var global = _content.OfType<Global>().FirstOrDefault();
  52. if (global == null) return false;
  53. var addinSection = global.Sections.OfType<ExtensibilityAddInsGlobalSection>().FirstOrDefault();
  54. if (addinSection == null) return false;
  55. return addinSection.AddIns.Any(x => x.ProgId == progId);
  56. }
  57. set
  58. {
  59. string progId = Version.Major == 10 ? SolutionConstants.ADD_IN_PROGID_2010 : SolutionConstants.ADD_IN_PROGID_2008;
  60. if (value == false && !OpenWrapAddInEnabled) return;
  61. if (value == false)
  62. {
  63. _content.OfType<Global>().First().Sections.OfType<ExtensibilityAddInsGlobalSection>().First().AddIns.RemoveAll(x => x.ProgId == progId);
  64. return;
  65. }
  66. var global = _content.OfType<Global>().FirstOrDefault();
  67. if (global == null) _content.Add(global = new Global());
  68. var addinSection = global.Sections.OfType<ExtensibilityAddInsGlobalSection>().FirstOrDefault();
  69. if (addinSection == null) global.Sections.Add(addinSection = new ExtensibilityAddInsGlobalSection());
  70. addinSection.AddIns.Add(new AddIn(progId, true, SolutionConstants.ADD_IN_NAME, SolutionConstants.ADD_IN_DESCRIPTION));
  71. }
  72. }
  73. public static SolutionFile Parse(IFile inputFile)
  74. {
  75. if (!inputFile.Exists) throw new FileNotFoundException("Solution file not found.", inputFile.Path);
  76. return new SolutionFile(inputFile);
  77. }
  78. public static string Quoted(string input)
  79. {
  80. return QUOTE + input + QUOTE;
  81. }
  82. public IProject AddProject(IFile projectFile)
  83. {
  84. using (var projectStream = projectFile.OpenRead())
  85. {
  86. var xmlDoc = XDocument.Load(new XmlTextReader(projectStream));
  87. var projectGuid = (from propertyGroup in xmlDoc.Descendants(XName.Get("PropertyGroup", NS_MSBUILD))
  88. from guid in propertyGroup.Descendants(XName.Get("ProjectGuid", NS_MSBUILD))
  89. select guid.Value).First();
  90. var projectTypeGuid = GetFromFileName(projectFile);
  91. var newProject = new SolutionProjectReference
  92. {
  93. Guid = new Guid(projectGuid),
  94. Name = projectFile.NameWithoutExtension,
  95. Path = projectFile.Path.MakeRelative(_file.Parent.Path),
  96. Type = projectTypeGuid
  97. };
  98. var indexToInsert = _content.OfType<SolutionProjectReference>().Any()
  99. ? _content.IndexOf(_content.OfType<SolutionProjectReference>().Last()) + 1
  100. : _content.Count;
  101. _content.Insert(indexToInsert, newProject);
  102. return newProject;
  103. }
  104. }
  105. public void Save()
  106. {
  107. using (var writer = new StreamWriter(_file.OpenWrite(), Encoding.UTF8))
  108. foreach (var section in _content)
  109. writer.WriteLine(section.ToString());
  110. }
  111. static string Group(string name, string val)
  112. {
  113. return string.Format("(?<{0}>{1})", name, val);
  114. }
  115. static string QuotedGuid(string captureGroup)
  116. {
  117. return Quoted("{" + Group(captureGroup, GUID_REGEX) + "}");
  118. }
  119. static string Text(string input)
  120. {
  121. var output = Regex.Escape(input);
  122. if (input == output) throw new ArgumentException("input does not need encoding");
  123. return output;
  124. }
  125. Guid GetFromFileName(IFile projectFile)
  126. {
  127. if (projectFile.Extension == ".csproj") return ProjectConstants.VisualCSharp;
  128. if (projectFile.Extension == ".vbproj") return ProjectConstants.VisualBasic;
  129. throw new ArgumentException("The project is not a known type.", "projectFile");
  130. }
  131. void ParseContent()
  132. {
  133. var lines = _file.ReadString().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
  134. for (int i = 0; i < lines.Length; i++)
  135. {
  136. _content.Add(
  137. SolutionFormatVersion.TryParse(lines, ref i) ??
  138. SolutionVisualStudioVersion.TryParse(lines, ref i) ??
  139. SolutionProjectReference.TryParse(_file.FileSystem, lines, ref i) ??
  140. Global.TryParse(lines, ref i) ??
  141. (object)lines[i]
  142. );
  143. }
  144. }
  145. class Global
  146. {
  147. public List<GlobalSection> Sections = new List<GlobalSection>();
  148. public static Global TryParse(string[] lines, ref int index)
  149. {
  150. if (lines[index] != "Global") return null;
  151. var global = new Global();
  152. for(index++; index < lines.Length; index++)
  153. {
  154. if (lines[index] == "EndGlobal") break;
  155. var section = GlobalSection.TryParse(lines, ref index);
  156. if (section == null) continue;
  157. global.Sections.Add(section);
  158. }
  159. return global;
  160. }
  161. public override string ToString()
  162. {
  163. return "Global\r\n" +
  164. Sections.Select(x => x.ToString()).JoinString("\r\n") + "\r\n"
  165. + "EndGlobal";
  166. }
  167. }
  168. class GlobalSection
  169. {
  170. public virtual string Name { get; set; }
  171. public virtual GlobalSectionInitialization Type { get; set; }
  172. static Regex _sectionParser = new Regex(Text("\tGlobalSection(") + Group("name", ".*") + Text(") = ") + Group("type", ".*"));
  173. protected const string EndInstruction = "\tEndGlobalSection";
  174. protected GlobalSection(string name, GlobalSectionInitialization type, List<string> sectionLines)
  175. {
  176. Name = name;
  177. Type = type;
  178. Lines = sectionLines;
  179. }
  180. public static GlobalSection TryParse(string[] lines, ref int index)
  181. {
  182. var match = _sectionParser.Match(lines[index]);
  183. if (match.Success == false) return null;
  184. var type = match.Groups["type"].Value;
  185. var name = match.Groups["name"].Value;
  186. var sectionLines = new List<string>();
  187. for(index++; index < lines.Length; index++)
  188. {
  189. if (lines[index] == EndInstruction) break;
  190. sectionLines.Add(lines[index]);
  191. }
  192. var globalSectionInitialization = (GlobalSectionInitialization)Enum.Parse(typeof(GlobalSectionInitialization), type, true);
  193. var section = name == "ExtensibilityAddIns"
  194. ? new ExtensibilityAddInsGlobalSection(name, globalSectionInitialization, sectionLines)
  195. : new GlobalSection(name, globalSectionInitialization, sectionLines);
  196. return section;
  197. }
  198. public virtual IEnumerable<string> Lines { get; private set; }
  199. public override string ToString()
  200. {
  201. return BeginInstruction + "\r\n" + Lines.JoinString("\r\n") + "\r\n" + EndInstruction;
  202. }
  203. protected string BeginInstruction
  204. {
  205. get { return string.Format("\tGlobalSection({0}) = {1}", Name, Type.ToString()); }
  206. }
  207. }
  208. class ExtensibilityAddInsGlobalSection : GlobalSection
  209. {
  210. static Regex _addin = new Regex(WS + Group("progid", "\\S+") + WS + "=" + WS +
  211. Group("connected", "(0|1)") + ";" +
  212. Group("description", "[^;]") + ";" +
  213. Group("name", ".*"));
  214. public ExtensibilityAddInsGlobalSection()
  215. : base("ExtensibilityAddIns", GlobalSectionInitialization.postSolution, new List<string>())
  216. {
  217. AddIns = new List<AddIn>();
  218. }
  219. public ExtensibilityAddInsGlobalSection(string name, GlobalSectionInitialization type, List<string> sectionLines)
  220. : base(name,type,sectionLines)
  221. {
  222. AddIns = new List<AddIn>();
  223. foreach (var line in sectionLines)
  224. {
  225. var match = _addin.Match(line);
  226. if (match.Success == false) throw new InvalidOperationException("Cannot parse addin section");
  227. AddIns.Add(new AddIn(match.Groups["progid"].Value, match.Groups["connected"].Value == "1", match.Groups["name"].Value, match.Groups["description"].Value));
  228. }
  229. }
  230. public override IEnumerable<string> Lines
  231. {
  232. get
  233. {
  234. return AddIns.Select(x => x.ToString());
  235. }
  236. }
  237. public List<AddIn> AddIns { get; set; }
  238. }
  239. class AddIn
  240. {
  241. public string ProgId { get; set; }
  242. public bool Connected { get; set; }
  243. public string Name { get; set; }
  244. public string Description { get; set; }
  245. public AddIn(string progId, bool connected, string name, string description)
  246. {
  247. ProgId = progId;
  248. Connected = connected;
  249. Name = name;
  250. Description = description;
  251. }
  252. public override string ToString()
  253. {
  254. return string.Format("\t\t{0} = {1};{2};{3}", ProgId, Connected ? "1" : "0", Description, Name);
  255. }
  256. }
  257. // ReSharper disable InconsistentNaming
  258. enum GlobalSectionInitialization
  259. {
  260. preSolution,
  261. postSolution
  262. }
  263. // ReSharper restore InconsistentNaming
  264. // TODO: Make public class once fully implemented
  265. class SolutionProjectReference : IProject
  266. {
  267. public Guid Guid;
  268. public string Name;
  269. public string Path;
  270. public Guid Type;
  271. static readonly Regex _projectRegex = new Regex(
  272. Text("Project(") + QuotedGuid("projectTypeGuid") + Text(")") +
  273. WS + "=" + WS +
  274. Quoted(Group("name", ".*")) +
  275. WS + "," + WS +
  276. Quoted(Group("path", ".*")) +
  277. WS + "," + WS +
  278. QuotedGuid("projectGuid"));
  279. readonly List<string> _lines = new List<string>();
  280. public static SolutionProjectReference TryParse(IFileSystem fileSystem, string[] lines, ref int index)
  281. {
  282. var match = _projectRegex.Match(lines[index]);
  283. if (match.Success == false) return null;
  284. var project = new SolutionProjectReference
  285. {
  286. Name = match.Groups["name"].Value,
  287. Path = match.Groups["path"].Value,
  288. Type = new Guid(match.Groups["projectTypeGuid"].Value),
  289. Guid = new Guid(match.Groups["projectGuid"].Value),
  290. File = fileSystem.GetFile(match.Groups["path"].Value)
  291. };
  292. for (index++; index < lines.Length; index++)
  293. {
  294. if (lines[index] == "EndProject") break;
  295. project._lines.Add(lines[index]);
  296. }
  297. return project;
  298. }
  299. public IFile File { get; private set; }
  300. public override string ToString()
  301. {
  302. return string.Format("Project(\"{{{0}}}\") = \"{1}\", \"{2}\", \"{{{3}}}\"\r\n{4}EndProject",
  303. Type.ToString().ToUpperInvariant(),
  304. Name,
  305. Path,
  306. Guid.ToString().ToUpperInvariant(),
  307. _lines.Aggregate(string.Empty, (input,line)=>input+line+"\r\n"));
  308. }
  309. public TargetFramework TargetFramework
  310. {
  311. get { throw new NotImplementedException(); }
  312. }
  313. public string TargetPlatform
  314. {
  315. get { throw new NotImplementedException(); }
  316. }
  317. public bool OpenWrapEnabled
  318. {
  319. get { return MSBuildProject.OpenWrapEnabled(Path); }
  320. }
  321. public string RootNamespace
  322. {
  323. get { throw new NotImplementedException(); }
  324. set { throw new NotImplementedException(); }
  325. }
  326. }
  327. class SolutionFormatVersion
  328. {
  329. public readonly Version Version;
  330. static readonly Regex _solutionId = new Regex(@"Microsoft Visual Studio Solution File, Format Version (?<version>\d+\.\d+)");
  331. public SolutionFormatVersion(Version version)
  332. {
  333. Version = version;
  334. }
  335. public static SolutionFormatVersion TryParse(string[] lines, ref int index)
  336. {
  337. var match = _solutionId.Match(lines[index]);
  338. if (match.Success == false) return null;
  339. return new SolutionFormatVersion(match.Groups["version"].Value.ToVersion());
  340. }
  341. public override string ToString()
  342. {
  343. return string.Format("Microsoft Visual Studio Solution File, Format Version {0}.{1:00}", Version.Major, Version.Minor);
  344. }
  345. }
  346. class SolutionVisualStudioVersion
  347. {
  348. static readonly Regex _parser = new Regex(@"# Visual Studio (?<version>\S+)");
  349. readonly string Version;
  350. public SolutionVisualStudioVersion(string version)
  351. {
  352. Version = version;
  353. }
  354. public static SolutionVisualStudioVersion TryParse(string[] lines, ref int index)
  355. {
  356. var match = _parser.Match(lines[index]);
  357. return match.Success ? new SolutionVisualStudioVersion("Visual Studio " + match.Groups["version"].Value) : null;
  358. }
  359. public override string ToString()
  360. {
  361. return "# " + Version;
  362. }
  363. }
  364. }
  365. }