diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 97e2716..ec4d367 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -17,7 +17,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
- dotnet-version: 6.x
+ dotnet-version: 8.x
- name: Restore dependencies
run: dotnet restore
- name: Build
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c4733b3..c9cbb6c 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -14,7 +14,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
- dotnet-version: 6.x
+ dotnet-version: 8.x
- name: Verify commit exists in origin/master
run: |
diff --git a/src/TopDownProteomics.Benchmarks/TopDownProteomics.Benchmarks.csproj b/src/TopDownProteomics.Benchmarks/TopDownProteomics.Benchmarks.csproj
index d1873b5..cadb98a 100644
--- a/src/TopDownProteomics.Benchmarks/TopDownProteomics.Benchmarks.csproj
+++ b/src/TopDownProteomics.Benchmarks/TopDownProteomics.Benchmarks.csproj
@@ -3,7 +3,7 @@
Exe
TopDownProteomics.Benchmarks
TopDownProteomics.Benchmarks
- net6.0
+ net8.0
Consortium for Top-Down Proteomics
diff --git a/src/TopDownProteomics/ProForma/ProFormaParser.cs b/src/TopDownProteomics/ProForma/ProFormaParser.cs
index d1e2209..c8594b8 100644
--- a/src/TopDownProteomics/ProForma/ProFormaParser.cs
+++ b/src/TopDownProteomics/ProForma/ProFormaParser.cs
@@ -10,10 +10,26 @@ namespace TopDownProteomics.ProForma
///
public class ProFormaParser
{
+ private readonly Func _residueValidator;
+
///
/// Initializes a new instance of the class.
///
- public ProFormaParser() { }
+ public ProFormaParser() : this(char.IsUpper) { }
+
+ ///
+ /// Initializes a new instance of the class with a residue validator.
+ ///
+ /// Determines whether a character is a valid residue.
+ /// is .
+ ///
+ /// For workflows that create a proteoform group or chemical hash, use a matching
+ /// with the custom residue validator.
+ ///
+ public ProFormaParser(Func residueValidator)
+ {
+ _residueValidator = residueValidator ?? throw new ArgumentNullException(nameof(residueValidator));
+ }
#if !NETSTANDARD2_1
///
@@ -218,8 +234,8 @@ public ProFormaTerm ParseString(ReadOnlySpan proFormaString)
else
{
// Validate amino acid character
- if (!char.IsUpper(current))
- throw new ProFormaParseException($"{current} is not an upper case letter.");
+ if (!_residueValidator(current))
+ throw new ProFormaParseException($"{current} is not a valid residue.");
// Reset the range as soon as we see an amino acid
if (endRange.HasValue)
@@ -263,9 +279,12 @@ private void HandleGlobalModification(ref IDictionary?
targets = new List();
for (int k = atSymbolIndex + 1; k < tagText.Length; k++)
{
- if (char.IsUpper(tagText[k]))
+ if (tagText[k] == ',')
+ continue;
+
+ if (_residueValidator(tagText[k]))
targets.Add(tagText[k]);
- else if (tagText[k] != ',')
+ else
throw new ProFormaParseException($"Unexpected character {tagText[k]} in global modification target list.");
}
}
diff --git a/tests/TopDownProteomics.Tests/ProForma/ProFormaParserTests.cs b/tests/TopDownProteomics.Tests/ProForma/ProFormaParserTests.cs
index ffddd5c..5939d52 100644
--- a/tests/TopDownProteomics.Tests/ProForma/ProFormaParserTests.cs
+++ b/tests/TopDownProteomics.Tests/ProForma/ProFormaParserTests.cs
@@ -15,6 +15,7 @@ public void InvalidProFormaStrings()
{
Assert.Throws(() => _parser.ParseString((Span)null));
Assert.Throws(() => _parser.ParseString(string.Empty));
+ Assert.Throws(() => new ProFormaParser(null!));
}
[Test]
@@ -27,6 +28,24 @@ public void NoModifications()
Assert.IsNull(term.Tags);
}
+ [Test]
+ public void ResidueValidatorControlsSequenceAndGlobalModificationTargets()
+ {
+ var parser = new ProFormaParser(residue => residue == 'A' || residue == '*');
+
+ var term = parser.ParseString("<[Oxidation]@*,A>A*");
+
+ Assert.AreEqual("A*", term.Sequence);
+ CollectionAssert.AreEquivalent(new[] { '*', 'A' }, term.GlobalModifications.Single().TargetAminoAcids);
+ var exception = Assert.Throws(() => parser.ParseString("AB"));
+ Assert.AreEqual("B is not a valid residue.", exception.Message);
+ Assert.Throws(() => _parser.ParseString("A*"));
+
+ var permissiveParser = new ProFormaParser(_ => true);
+ term = permissiveParser.ParseString("<[Oxidation]@A,C>A");
+ CollectionAssert.AreEquivalent(new[] { 'A', 'C' }, term.GlobalModifications.Single().TargetAminoAcids);
+ }
+
[Test]
[TestCase("PRO[info:test]TEOFORM", "PROTEOFORM", "test")]
[TestCase("PRO[info:test[nested]]TEOFORM", "PROTEOFORM", "test[nested]")]
diff --git a/tests/TopDownProteomics.Tests/TopDownProteomics.Tests.csproj b/tests/TopDownProteomics.Tests/TopDownProteomics.Tests.csproj
index 10b857b..a1d941d 100644
--- a/tests/TopDownProteomics.Tests/TopDownProteomics.Tests.csproj
+++ b/tests/TopDownProteomics.Tests/TopDownProteomics.Tests.csproj
@@ -1,6 +1,6 @@
- net6.0;net472
+ net8.0;net472
TopDownProteomics.Tests
Consortium for Top-Down Proteomics
latest