ProcessResult Apply(IEnumerable<SnippetGroup> availableSnippets, TextWriter writer, IndexReader reader)
        {
            var snippets = availableSnippets.ToList();
            var missingSnippets = new List<MissingSnippet>();
            var usedSnippets = new List<SnippetGroup>();
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                string key;
                if (!keyReader(line, out key))
                {
                    writer.WriteLine(line);
                    continue;
                }
                writer.WriteLine("<!-- snippet: {0} -->", key);

                var snippetGroup = snippets.FirstOrDefault(x => x.Key == key);
                if (snippetGroup == null)
                {
                    var missingSnippet = new MissingSnippet(key: key, line: reader.Index);
                    missingSnippets.Add(missingSnippet);
                    var message = $"** Could not find key '{key}' **";
                    writer.WriteLine(message);
                    continue;
                }

                AppendGroup(snippetGroup, writer);
                if (usedSnippets.All(x => x.Key != snippetGroup.Key))
                {
                    usedSnippets.Add(snippetGroup);
                }
            }
            return new ProcessResult(missingSnippets: missingSnippets, usedSnippets: usedSnippets);
        }
 /// <summary>
 /// Read <see cref="ReadSnippet"/> from a <see cref="TextReader"/>.
 /// </summary>
 /// <param name="textReader">The <see cref="TextReader"/> to read from.</param>
 /// <param name="source">Used to infer the version. Usually this will be the path to a file or a url.</param>
 public ReadSnippets FromReader(TextReader textReader, string source = null)
 {
     Guard.AgainstNull(textReader, "textReader");
     using (var reader = new IndexReader(textReader))
     {
         return GetSnippetsFromFile(reader, source);
     }
 }
 ReadSnippets ProcessFile(string file)
 {
     using (var textReader = File.OpenText(file))
     using (var stringReader = new IndexReader(textReader))
     {
         return GetSnippetsFromFile(stringReader, file);
     }
 }
 /// <summary>
 /// Apply <paramref name="snippets"/> to <paramref name="textReader"/>.
 /// </summary>
 public ProcessResult Apply(IEnumerable<SnippetGroup> snippets, TextReader textReader, TextWriter writer)
 {
     Guard.AgainstNull(snippets, "snippets");
     Guard.AgainstNull(textReader, "textReader");
     Guard.AgainstNull(writer, "writer");
     using (var reader = new IndexReader(textReader))
     {
         return Apply(snippets, writer, reader);
     }
 }
        ReadSnippets GetSnippetsFromFile(IndexReader stringReader, string file)
        {
            var errors = new List<ReadSnippetError>();
            var snippets = new List<ReadSnippet>();
            var language = GetLanguageFromFile(file);
            var loopState = new LoopState();
            while (true)
            {
                var line = stringReader.ReadLine();
                if (line == null)
                {
                    if (loopState.IsInSnippet)
                    {
                        errors.Add(new ReadSnippetError(
                            message: "Snippet was not closed",
                            file: file,
                            line: loopState.StartLine.Value + 1,
                            key: loopState.CurrentKey, 
                            version: null));
                    }
                    break;
                }

                var trimmedLine = line.Trim().Replace("  ", " ").ToLowerInvariant();
                if (loopState.IsInSnippet)
                {
                    if (!loopState.EndFunc(trimmedLine))
                    {
                        loopState.SnippetLines.Add(line);
                        continue;
                    }

                    TryAddSnippet(stringReader, file, loopState, language, errors, snippets);
                    loopState.Reset();
                    continue;
                }
                IsStart(stringReader, trimmedLine, loopState);
            }
            return new ReadSnippets(snippets, errors);
        }
 void TryAddSnippet(IndexReader stringReader, string file, LoopState loopState, string language, List<ReadSnippetError> errors, List<ReadSnippet> snippets)
 {
     VersionRange parsedVersion;
     var startRow = loopState.StartLine.Value + 1;
     
     if (!TryParseVersion(file, loopState, out parsedVersion))
     {
         errors.Add(new ReadSnippetError(
                                     message : "Could not extract version",
                                     file : file,
                                     line : startRow,
                                     key : loopState.CurrentKey,
                                     version:null));
         return;
     }
     var value = ConvertLinesToValue(loopState.SnippetLines);
     if (value.IndexOfAny(invalidCharacters) > -1)
     {
         var joinedInvalidChars = "'" + string.Join("', '", invalidCharacters) + "'";
         errors.Add(new ReadSnippetError(
             message: $"Snippet contains invalid characters ({joinedInvalidChars}). This was probably caused by you copying code from MS Word or Outlook. Dont do that.",
             file: file,
             line: startRow,
             key: loopState.CurrentKey,
             version: parsedVersion));
     }
     
     var snippet = new ReadSnippet(
                       startLine : startRow,
                       endLine : stringReader.Index,
                       key : loopState.CurrentKey.ToLowerInvariant(),
                       version : parsedVersion,
                       value : value,
                       file : file,
                       language: language.ToLowerInvariant());
     snippets.Add(snippet);
 }
 static void IsStart(IndexReader stringReader, string trimmedLine, LoopState loopState)
 {
     string version;
     string currentKey;
     if (IsStartCode(trimmedLine, out currentKey, out version))
     {
         loopState.EndFunc = IsEndCode;
         loopState.CurrentKey = currentKey;
         loopState.IsInSnippet = true;
         loopState.Version = version;
         loopState.StartLine = stringReader.Index;
         loopState.SnippetLines = new List<string>();
         return;
     }
     if (IsStartRegion(trimmedLine, out currentKey, out version))
     {
         loopState.EndFunc = IsEndRegion;
         loopState.CurrentKey = currentKey;
         loopState.IsInSnippet = true;
         loopState.Version = version;
         loopState.StartLine = stringReader.Index;
         loopState.SnippetLines = new List<string>();
     }
 }