Skip to content

Commit 071048c

Browse files
committed
Merge pull request #268 from schmidt4brains/feature/AddSourceContextToComment
Optionally show source context next to reference paths & line numbers
2 parents 232f436 + 171f910 commit 071048c

12 files changed

+418
-78
lines changed

Diff for: src/i18n.Domain.Tests/PathNormalizerTests.cs

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.IO;
3+
using i18n.Domain.Helpers;
4+
using Microsoft.VisualStudio.TestTools.UnitTesting;
5+
6+
namespace i18n.Domain.Tests
7+
{
8+
[TestClass]
9+
public class PathNormalizerTests
10+
{
11+
[TestMethod]
12+
[ExpectedException(typeof(ArgumentNullException))]
13+
public void MakeRelativePath_InvalidPath_Throws()
14+
{
15+
var anchorPath = string.Empty;
16+
PathNormalizer.MakeRelativePath(anchorPath, null);
17+
}
18+
19+
[TestMethod]
20+
public void MakeRelativePath_NullOrEmptyAnchorPath_ReturnsPath()
21+
{
22+
var path = @"C:\some\path\file.ext";
23+
24+
AssertNormalizedPath(null, path, path, "Null anchor path");
25+
AssertNormalizedPath(string.Empty, path, path, "Empty anchor path");
26+
}
27+
28+
private static void AssertNormalizedPath(string anchorPath, string path, string expected, string reason)
29+
{
30+
var actual = PathNormalizer.MakeRelativePath(anchorPath, path);
31+
32+
Assert.AreEqual(expected, actual, reason);
33+
}
34+
35+
[TestMethod]
36+
public void MakeRelativePath_SimpleRelativePath_ReturnsRelativePath()
37+
{
38+
var anchorPath = @"C:\Some\Path";
39+
var relativePath = @"Another\Nested\File.ext";
40+
var path = Path.Combine(anchorPath, relativePath);
41+
42+
AssertNormalizedPath(anchorPath, path, relativePath, "Simple relative path");
43+
}
44+
45+
[TestMethod]
46+
public void MakeRelativePath_NoCommonPortion_ReturnsPath()
47+
{
48+
var anchorPath = @"C:\Some\Path";
49+
var path = @"D:\Some\Other\Path";
50+
AssertNormalizedPath(anchorPath, path, path, "No common path portion");
51+
}
52+
53+
[TestMethod]
54+
public void MakeRelativePath_SomeCommonPorths_ReturnsRelativePath()
55+
{
56+
var commonPath = @"C:\Some\Common\Path";
57+
var anchorPath = Path.Combine(commonPath, @"Anchor\Path");
58+
var distinctPath = @"Distinct\Path\File.ext";
59+
var path = Path.Combine(commonPath, distinctPath);
60+
var relativePath = Path.Combine(@"..\..", distinctPath);
61+
AssertNormalizedPath(anchorPath, path, relativePath, "Some common path components");
62+
}
63+
}
64+
}

Diff for: src/i18n.Domain.Tests/ReferenceContextTests.cs

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using i18n.Domain.Entities;
2+
using i18n.Domain.Helpers;
3+
using Microsoft.VisualStudio.TestTools.UnitTesting;
4+
5+
namespace i18n.Domain.Tests
6+
{
7+
[TestClass]
8+
public class ReferenceContextTests
9+
{
10+
private const string Path = @"C:\Some\Path\File.ext";
11+
private const string Content =
12+
// 0 1 2 3 4 5
13+
// 0123456789 0 12345678901 2 345678901234567890 1 234567890123456789
14+
"The quick\r\n brown fox\r\n jumps over the lazy \r\ndog";
15+
16+
[TestMethod]
17+
public void Create_PositionTooSmall_ReturnsFirstLine()
18+
{
19+
AssertExpectedContext(-10, CreateExpected("The quick", 1), "Position too small");
20+
}
21+
22+
[TestMethod]
23+
public void Create_WithOutOfRangePosition_ReturnsLastLineWithNoContext()
24+
{
25+
AssertExpectedContext(Content.Length + 10, CreateExpected(string.Empty, 4), "Position too big");
26+
}
27+
28+
[TestMethod]
29+
public void Create_PositionInFirstLine_ReturnsFirstLine()
30+
{
31+
AssertExpectedContext(2, CreateExpected("The quick", 1), "Position in first line");
32+
}
33+
34+
[TestMethod]
35+
public void Create_PositionInThirdLine_ReturnsThirdLine()
36+
{
37+
AssertExpectedContext(23, CreateExpected("jumps over the lazy", 3), "Position in 3rd line");
38+
}
39+
40+
[TestMethod]
41+
public void Create_PositionInLastLine_ReturnsLastLine()
42+
{
43+
AssertExpectedContext(46, CreateExpected("dog", 4), "Position in last line");
44+
}
45+
46+
private static ReferenceContext CreateExpected(string context, int lineNumber)
47+
{
48+
return new ReferenceContext
49+
{
50+
Path = Path,
51+
LineNumber = lineNumber,
52+
Context = context
53+
};
54+
}
55+
56+
private static void AssertExpectedContext(int position, ReferenceContext expected, string reason)
57+
{
58+
var actual = ReferenceContext.Create(Path, Content, position);
59+
60+
Assert.AreEqual(expected.Path, actual.Path, "Path: " + reason);
61+
Assert.AreEqual(expected.LineNumber, actual.LineNumber, "LineNumber: " + reason);
62+
Assert.AreEqual(expected.Context, actual.Context, "Context:" + reason);
63+
}
64+
}
65+
}

Diff for: src/i18n.Domain.Tests/i18n.Domain.Tests.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@
5050
</Choose>
5151
<ItemGroup>
5252
<Compile Include="NuggetTests.cs" />
53+
<Compile Include="PathNormalizerTests.cs" />
5354
<Compile Include="Properties\AssemblyInfo.cs" />
5455
<Compile Include="NuggetParserTests.cs" />
56+
<Compile Include="ReferenceContextTests.cs" />
5557
</ItemGroup>
5658
<ItemGroup>
5759
<ProjectReference Include="..\i18n.Domain\i18n.Domain.csproj">

Diff for: src/i18n.Domain/Concrete/FileNuggetFinder.cs

+13-18
Original file line numberDiff line numberDiff line change
@@ -102,19 +102,18 @@ public IDictionary<string, TemplateItem> ParseAll()
102102

103103
private void ParseFile(string projectDirectory, string filePath, ConcurrentDictionary<string, TemplateItem> templateItems)
104104
{
105-
var referencePath = (projectDirectory != null) && filePath.StartsWith(projectDirectory, StringComparison.OrdinalIgnoreCase)
106-
? filePath.Substring(projectDirectory.Length + 1)
107-
: filePath;
105+
var referencePath = PathNormalizer.MakeRelativePath(projectDirectory, filePath);
108106

109107
DebugHelpers.WriteLine("FileNuggetFinder.ParseFile -- {0}", filePath);
110108
// Lookup any/all nuggets in the file and for each add a new template item.
111109
using (var fs = File.OpenText(filePath))
112110
{
113111
_nuggetParser.ParseString(fs.ReadToEnd(), delegate(string nuggetString, int pos, Nugget nugget, string i_entity)
114112
{
113+
var referenceContext = ReferenceContext.Create(referencePath, i_entity, pos);
114+
115115
AddNewTemplateItem(
116-
referencePath,
117-
i_entity.LineFromPos(pos),
116+
referenceContext,
118117
nugget,
119118
templateItems);
120119
// Done.
@@ -123,13 +122,11 @@ private void ParseFile(string projectDirectory, string filePath, ConcurrentDicti
123122
}
124123
}
125124

126-
private void AddNewTemplateItem(
127-
string filePath,
128-
int lineNumber,
125+
private void AddNewTemplateItem(
126+
ReferenceContext referenceContext,
129127
Nugget nugget,
130128
ConcurrentDictionary<string, TemplateItem> templateItems)
131-
{
132-
string reference = filePath + ":" + lineNumber.ToString();
129+
{
133130
string msgid = nugget.MsgId.Replace("\r\n", "\n").Replace("\r", "\\n");
134131
// NB: In memory msgids are normalized so that LFs are converted to "\n" char sequence.
135132
string key = TemplateItem.KeyFromMsgidAndComment(msgid, nugget.Comment, _settings.MessageContextEnabledFromComment);
@@ -143,9 +140,7 @@ private void AddNewTemplateItem(
143140
item.MsgKey = key;
144141
item.MsgId = msgid;
145142

146-
tmpList = new List<string>();
147-
tmpList.Add(reference);
148-
item.References = tmpList;
143+
item.References = new List<ReferenceContext> {referenceContext};
149144

150145
if (nugget.Comment.IsSet()) {
151146
tmpList = new List<string>();
@@ -156,11 +151,11 @@ private void AddNewTemplateItem(
156151
return item;
157152
},
158153
// Update routine.
159-
(k, v) => {
160-
161-
tmpList = v.References.ToList();
162-
tmpList.Add(reference);
163-
v.References = tmpList;
154+
(k, v) =>
155+
{
156+
var newReferences = new List<ReferenceContext>(v.References.ToList());
157+
newReferences.Add(referenceContext);
158+
v.References = newReferences;
164159

165160
if (nugget.Comment.IsSet()) {
166161
tmpList = v.Comments != null ? v.Comments.ToList() : new List<string>();

Diff for: src/i18n.Domain/Concrete/POTranslationRepository.cs

+11-10
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ public void SaveTranslation(Translation translation)
224224
foreach (var reference in item.References)
225225
{
226226
hasReferences = true;
227-
stream.WriteLine("#: " + reference);
227+
stream.WriteLine("#: " + reference.ToComment());
228228
}
229229
}
230230

@@ -320,7 +320,7 @@ public void SaveTemplate(IDictionary<string, TemplateItem> items)
320320

321321
foreach (var reference in item.References)
322322
{
323-
stream.WriteLine("#: " + reference);
323+
stream.WriteLine("#: " + reference.ToComment());
324324
}
325325

326326
if (_settings.MessageContextEnabledFromComment
@@ -383,10 +383,10 @@ private Translation ParseTranslationFile(string langtag)
383383
bool itemStarted = false;
384384
while ((line = fs.ReadLine()) != null)
385385
{
386-
List<string> extractedComments = new List<string>();
387-
List<string> translatorComments = new List<string>();
388-
List<string> flags = new List<string>();
389-
List<string> references = new List<string>();
386+
var extractedComments = new List<string>();
387+
var translatorComments = new List<string>();
388+
var flags = new List<string>();
389+
var references = new List<ReferenceContext>();
390390

391391
//read all comments, flags and other descriptive items for this string
392392
//if we have #~ its a historical/log entry but it is the messageID/message so we skip this do/while
@@ -401,7 +401,7 @@ private Translation ParseTranslationFile(string langtag)
401401
extractedComments.Add(line.Substring(2).Trim());
402402
break;
403403
case ':': //references
404-
references.Add(line.Substring(2).Trim());
404+
references.Add(ReferenceContext.Parse(line.Substring(2).Trim()));
405405
break;
406406
case ',': //flags
407407
flags.Add(line.Substring(2).Trim());
@@ -435,9 +435,10 @@ private Translation ParseTranslationFile(string langtag)
435435
// Update routine.
436436
(k, v) => {
437437
v.References = v.References.Append(item.References);
438-
v.ExtractedComments = v.ExtractedComments.Append(item.References);
439-
v.TranslatorComments = v.TranslatorComments.Append(item.References);
440-
v.Flags = v.Flags.Append(item.References);
438+
var referencesAsComments = item.References.Select(r => r.ToComment()).ToList();
439+
v.ExtractedComments = v.ExtractedComments.Append(referencesAsComments);
440+
v.TranslatorComments = v.TranslatorComments.Append(referencesAsComments);
441+
v.Flags = v.Flags.Append(referencesAsComments);
441442
return v;
442443
});
443444
}

Diff for: src/i18n.Domain/Entities/ReferenceContext.cs

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System;
2+
using System.Text.RegularExpressions;
3+
4+
namespace i18n.Domain.Entities
5+
{
6+
public class ReferenceContext
7+
{
8+
public static bool ShowSourceContext { get; set; }
9+
10+
public string Path { get; set; }
11+
public int LineNumber { get; set; }
12+
public string Context { get; set; }
13+
14+
public static ReferenceContext Create(string path, string content, int position)
15+
{
16+
var lineNumber = 1;
17+
var lineStartPosition = 0;
18+
var context = string.Empty;
19+
20+
for (var i = 0; i < content.Length; i++)
21+
{
22+
if (content[i] == '\n')
23+
{
24+
lineNumber++;
25+
lineStartPosition = i;
26+
}
27+
28+
if (i < position) continue;
29+
30+
var lineEndPosition = content.IndexOf("\n", i, StringComparison.Ordinal);
31+
32+
if (lineEndPosition < 0)
33+
{
34+
lineEndPosition = content.Length;
35+
}
36+
37+
context = content.Substring(lineStartPosition, lineEndPosition - lineStartPosition);
38+
break;
39+
}
40+
41+
return new ReferenceContext
42+
{
43+
Path = path,
44+
LineNumber = lineNumber,
45+
Context = context.Trim()
46+
};
47+
}
48+
49+
private static readonly Regex ReferenceRegex = new Regex(@"\s*(?<path>.*):(?<lineNumber>\d+)(?<context>.*)$", RegexOptions.Compiled);
50+
51+
public static ReferenceContext Parse(string line)
52+
{
53+
var match = ReferenceRegex.Match(line);
54+
55+
if (match.Success)
56+
{
57+
return new ReferenceContext
58+
{
59+
Path = match.Groups["path"].Value.Trim(),
60+
LineNumber = Convert.ToInt32(match.Groups["lineNumber"].Value),
61+
Context = match.Groups["context"].Value.Trim()
62+
};
63+
}
64+
65+
return new ReferenceContext
66+
{
67+
Path = line
68+
};
69+
}
70+
71+
public string ToComment()
72+
{
73+
var comment = Path + ":" + LineNumber;
74+
75+
if (ShowSourceContext)
76+
{
77+
comment += " " + Context;
78+
}
79+
80+
return comment;
81+
}
82+
}
83+
}

Diff for: src/i18n.Domain/Entities/TemplateItem.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class TemplateItem
1515
{
1616
public string MsgKey;
1717
public string MsgId;
18-
public IEnumerable<string> References { get; set; }
18+
public IEnumerable<ReferenceContext> References { get; set; }
1919
public IEnumerable<string> Comments { get; set; }
2020

2121
public override string ToString()

Diff for: src/i18n.Domain/Entities/TranslationItem.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class TranslationItem
2020
public string MsgKey { get; set; }
2121
public string MsgId { get; set; }
2222
public string Message { get; set; }
23-
public IEnumerable<string> References { get; set; }
23+
public IEnumerable<ReferenceContext> References { get; set; }
2424
public IEnumerable<string> ExtractedComments { get; set; }
2525
public IEnumerable<string> TranslatorComments { get; set; }
2626
public IEnumerable<string> Flags { get; set; }

0 commit comments

Comments
 (0)