// MonoGame - Copyright (C) The MonoGame Team // This file is subject to the terms and conditions defined in // file 'LICENSE.txt', which is part of this source code package. using System; using System.Text.RegularExpressions; namespace MonoGame.Tools.Pipeline { public enum OutputState { Initialized, BuildBegin, Cleaning, Skipping, BuildAsset, BuildError, BuildWarning, BuildErrorContinue, BuildEnd, BuildTime, BuildTerminated, Unknown } public class OutputParser { internal OutputState State { get; private set; } internal String Filename { get; private set; } internal String ErrorMessage { get; private set; } internal String BuildBeginTime { get; private set; } internal String BuildInfo { get; private set; } internal String BuildElapsedTime { get; private set; } Regex _reBuildBegin = new Regex(@"^(Build started)\W+(?.*?)\r?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); Regex _reCleaning = new Regex(@"^(Cleaning)\W(?.*?)\r?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); Regex _reSkipping = new Regex(@"^(Skipping)\W(?.*?)\r?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); Regex _reBuildAsset = new Regex(@"^(?([a-zA-Z]:)?/.+?)\r?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); Regex _reBuildError = new Regex(@"^(?([a-zA-Z]:)?/.+?)\W*?:\W*?error\W*?:\W*(?.*?)\r?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); Regex _reFileErrorWithLineNum = new Regex(@"^(?.+?)(\((?[0-9]+),(?[0-9]+)(-(?[0-9]+))?\))?:\W*?(error)\W*(?[A-Z][0-9]+):\W*(?.*?)\r?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); Regex _reFileWarningWithLineNum = new Regex(@"^(?.+?)(\((?[0-9]+),(?[0-9]+)(-(?[0-9]+))?\))?:\W*?(warning)\W*(?[A-Z][0-9]+):\W*(?.*?)\r?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); Regex _reFileError = new Regex(@"^(?([a-zA-Z]:)?/.+?)\W*?: (?.*?)\r?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); Regex _reBuildEnd = new Regex(@"^(Build)\W+(?.*?)\r?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); Regex _reBuildTime = new Regex(@"^(Time elapsed)\W+(?.*?)\.\r?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); public OutputParser() { Reset(); } internal void Reset() { State = OutputState.Initialized; Filename = null; BuildBeginTime = null; BuildInfo = null; BuildElapsedTime = null; ErrorMessage = null; } internal void Parse(string text) { ParseLine(text); } private void ParseLine(string line) { /* * Line <-- BuildBegin * Line <-- BuildEnd * Line <-- BuildTime * Line <-- Cleaning * Line <-- Skipping * Line <-- BuildError (BuildErrorContinue)+ * Line <-- BuildAsset * BuildBegin <-- "Build" "started" buildBeginTime * Cleaning <-- "Cleaning" filenane * Skipping <-- "Skipping" filenane * BuildAsset <-- filename * BuildError <-- filename ':' "Error" ':' errorMessage * BuildErrorContinue <-- errorMessage * BuildEnd <-- "Build" buildInfo * BuildTime <-- "Time" "elapsed" buildElapsedTime */ var prevState = State; var prevFilename = Filename; State = OutputState.Unknown; Filename = null; BuildBeginTime = null; BuildInfo = null; BuildElapsedTime = null; ErrorMessage = null; if (line == "Build terminated!") { State = OutputState.BuildTerminated; } else if (_reBuildBegin.IsMatch(line)) { State = OutputState.BuildBegin; var m = _reBuildBegin.Match(line); BuildBeginTime = m.Groups["buildBeginTime"].Value; } else if (_reBuildEnd.IsMatch(line)) { State = OutputState.BuildEnd; var m = _reBuildEnd.Match(line); BuildInfo = m.Groups["buildInfo"].Value; } else if (_reBuildTime.IsMatch(line)) { State = OutputState.BuildTime; var m = _reBuildTime.Match(line); BuildElapsedTime = m.Groups["buildElapsedTime"].Value; } else if (_reCleaning.IsMatch(line)) { State = OutputState.Cleaning; var m = _reCleaning.Match(line); Filename = m.Groups["filename"].Value; } else if (_reSkipping.IsMatch(line)) { State = OutputState.Skipping; var m = _reSkipping.Match(line); Filename = m.Groups["filename"].Value; } else if (_reBuildError.IsMatch(line)) { State = OutputState.BuildError; var m = _reBuildError.Match(line); Filename = m.Groups["filename"].Value; ErrorMessage = m.Groups["errorMessage"].Value; } else if (_reFileErrorWithLineNum.IsMatch(line)) { State = OutputState.BuildError; var m = _reFileErrorWithLineNum.Match(line); var lineNum = m.Groups["line"]; var columnBegin = m.Groups["column"]; var columnEnd = m.Groups["columnEnd"]; var column = columnBegin.Value; if(columnEnd.Success) column += "-" + columnEnd.Value; var errorCode = m.Groups["errorCode"]; Filename = m.Groups["filename"].Value.Replace("\\\\","/").Replace("\\", "/"); ErrorMessage = string.Format("{0} ({1},{2}): {3}", errorCode, lineNum, column, m.Groups["errorMessage"].Value); } else if (_reFileWarningWithLineNum.IsMatch(line)) { State = OutputState.BuildWarning; var m = _reFileWarningWithLineNum.Match(line); var lineNum = m.Groups["line"]; var columnBegin = m.Groups["column"]; var columnEnd = m.Groups["columnEnd"]; var column = columnBegin.Value; if(columnEnd.Success) column += "-" + columnEnd.Value; var errorCode = m.Groups["warningCode"]; Filename = m.Groups["filename"].Value.Replace("\\\\", "/").Replace("\\", "/"); ErrorMessage = string.Format("{0} ({1},{2}): {3}", errorCode, lineNum, column, m.Groups["warningMessage"].Value); } else if (_reFileError.IsMatch(line)) { State = OutputState.BuildError; var m = _reFileError.Match(line); Filename = m.Groups["filename"].Value; ErrorMessage = m.Groups["errorMessage"].Value; } else if (_reBuildAsset.IsMatch(line)) { State = OutputState.BuildAsset; var m = _reBuildAsset.Match(line); Filename = m.Groups["filename"].Value; } else if (prevState == OutputState.BuildError || prevState == OutputState.BuildErrorContinue) { State = OutputState.BuildErrorContinue; Filename = prevFilename; ErrorMessage = line.TrimEnd(); } else { State = OutputState.Unknown; } } } }