Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added netcore 3, net standart 2 support, improve performance #60

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 26 additions & 26 deletions UAParser.ConsoleApp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
namespace UAParser.ConsoleApp
{
using System;
using System.Linq;
using System;
using System.Linq;

static class Program
{
static void Main(string[] args)
static class Program
{
if (args.Any(arg => arg == "-?" || arg == "-h" || arg == "--help"))
{
Help();
return;
}
static void Main(string[] args)
{
if (args.Any(arg => arg == "-?" || arg == "-h" || arg == "--help"))
{
Help();
return;
}

var uaParser = Parser.GetDefault();
string uaString;
while ((uaString = Console.In.ReadLine()) != null)
{
uaString = uaString.Trim();
if (uaString.Length == 0)
continue;
var c = uaParser.Parse(uaString);
Console.WriteLine("Agent : {0}", c.UA);
Console.WriteLine("OS : {0}", c.OS);
Console.WriteLine("Device: {0}", c.Device);
}
}
var uaParser = Parser.GetDefault();
string uaString;
while ((uaString = Console.In.ReadLine()) != null)
{
uaString = uaString.Trim();
if (uaString.Length == 0)
continue;
var c = uaParser.Parse(uaString);
Console.WriteLine("Agent : {0}", c.UA);
Console.WriteLine("OS : {0}", c.OS);
Console.WriteLine("Device: {0}", c.Device);
}
}

static void Help()
{
static void Help()
{
Console.WriteLine(@"UAParser
Copyright 2015 " + "S\u00f8ren Enem\u00e6rke" + @"
https://github.com/tobie/ua-parser

This application accepts user agent strings (one per line) from standard
input, parses them and then emits the identified agent, operating system and
device for each string.");
}
}
}
}
2 changes: 1 addition & 1 deletion UAParser.ConsoleApp/UAParser.ConsoleApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>
10 changes: 10 additions & 0 deletions UAParser.Tests.Benchmark/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using BenchmarkDotNet.Running;

namespace UAParser.Tests.Benchmark
{
class Program
{
static void Main(string[] args)
=> BenchmarkRunner.Run<UAParserTests>();
}
}
22 changes: 22 additions & 0 deletions UAParser.Tests.Benchmark/UAParser.Tests.Benchmark.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\PublicKey.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\UAParser\UAParser.csproj" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="..\uap-core\regexes.yaml" Link="Regexes\regexes.yaml" />
</ItemGroup>

</Project>
54 changes: 54 additions & 0 deletions UAParser.Tests.Benchmark/UAParserTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using BenchmarkDotNet.Attributes;
using System;
using System.IO;
using System.Text;

namespace UAParser.Tests.Benchmark
{
[MemoryDiagnoser]
public class UAParserTests
{
private static readonly string _yamlString = GetTestResources("UAParser.Tests.Benchmark.Regexes.regexes.yaml");
private static readonly Parser _parser = Parser.GetDefault();

[Params(
"Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.1.1; G8231 Build/41.2.A.0.219; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/69.0.3497.105 Mobile/15E148 Safari/605.1")]
public string UserAgentString { get; set; }

[Benchmark]
public ClientInfo UAParseTest()
{
return _parser.Parse(UserAgentString);
}

[Benchmark]
public bool ReadYaml()
{
var yamlParser = new MinimalYamlParser(_yamlString);

return yamlParser != null;
}

[Benchmark]
public Parser CreateParser()
{
return Parser.GetDefault();
}

internal static string GetTestResources(string name)
{
using (var s = typeof(UAParserTests).Assembly.GetManifestResourceStream(name))
{
if (s == null)
throw new InvalidOperationException("Could not locate an embedded test resource with name: " + name);
using (var sr = new StreamReader(s, Encoding.UTF8))
{
return sr.ReadToEnd();
}
}
}
}
}
2 changes: 1 addition & 1 deletion UAParser.Tests/ParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public void can_get_parser_from_input()
public void can_utilize_regex_timeouts()
{
string yamlContent = this.GetTestResources("UAParser.Tests.Regexes.backtracking.yaml");
Parser parser = Parser.FromYaml(yamlContent, new ParserOptions()
Parser parser = Parser.FromYaml(yamlContent, new ParserOptions
{
MatchTimeOut = TimeSpan.FromSeconds(1),
});
Expand Down
7 changes: 3 additions & 4 deletions UAParser.Tests/TestResourceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,12 @@ private static void RunTestCases<TTestCase>(List<TTestCase> testCases) where TTe
sb.AppendLine($"test case {(i + 1)}: {ex.Message}");
}
}

Assert.True(0 == sb.Length, "Failed tests: " + Environment.NewLine + sb);
}

public List<TTestCase> GetTestCases<TTestCase>(
string resourceName,
string yamlNodeName,
Func<Dictionary<string, string>, TTestCase> testCaseFunction)
public List<TTestCase> GetTestCases<TTestCase>(string resourceName, string yamlNodeName,
Func<Dictionary<string, string>, TTestCase> testCaseFunction)
{
string yamlContent = this.GetTestResources(resourceName);
YamlStream yaml = new YamlStream();
Expand Down
10 changes: 8 additions & 2 deletions UAParser.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.12
# Visual Studio Version 16
VisualStudioVersion = 16.0.29806.167
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UAParser.ConsoleApp", "UAParser.ConsoleApp\UAParser.ConsoleApp.csproj", "{280CF383-6A6E-487B-9D25-281C98F3A7C8}"
EndProject
Expand All @@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UAParser", "UAParser\UAPars
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UAParser.Tests", "UAParser.Tests\UAParser.Tests.csproj", "{754416BE-F962-4898-832A-739B0EF6C3CC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UAParser.Tests.Benchmark", "UAParser.Tests.Benchmark\UAParser.Tests.Benchmark.csproj", "{ECFBC2D2-1E86-4121-B001-9AE522E7F9FD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -43,6 +45,10 @@ Global
{754416BE-F962-4898-832A-739B0EF6C3CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{754416BE-F962-4898-832A-739B0EF6C3CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{754416BE-F962-4898-832A-739B0EF6C3CC}.Release|Any CPU.Build.0 = Release|Any CPU
{ECFBC2D2-1E86-4121-B001-9AE522E7F9FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ECFBC2D2-1E86-4121-B001-9AE522E7F9FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ECFBC2D2-1E86-4121-B001-9AE522E7F9FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ECFBC2D2-1E86-4121-B001-9AE522E7F9FD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
129 changes: 129 additions & 0 deletions UAParser/Abstraction/AbstractParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace UAParser.Abstraction
{
internal abstract class AbstractParser<TOut, TTemplate> : IParser<TOut>
where TOut : Part
where TTemplate : Template
{
protected static readonly string[] _allReplacementTokens = new string[]
{ "$1","$2","$3","$4","$5","$6","$7","$8","$91" };

private readonly ParserOptions _options;

protected AbstractParser(IEnumerable<Dictionary<string, string>> maps, ParserOptions options, TOut defaultValue)
{
_options = options;
DefaultValue = defaultValue;
Templates = maps.Select(InitializeTemplate).ToList();
}

protected List<TTemplate> Templates { get; }

protected TOut DefaultValue { get; }

public virtual TOut Parse(string input)
{
foreach (var template in Templates)
{
var device = Matcher(input, template);
if (device != default)
return device;
}

return DefaultValue;
}

protected abstract TOut Matcher(string input, TTemplate template);

protected virtual Match Match(string input, Regex regex)
{
#if REGEX_MATCHTIMEOUT
try
{
return regex.Match(input);
}
catch (RegexMatchTimeoutException)
{
// we'll simply swallow this exception and return the default (non-matched)
return default;
}
#else
return regex.Match(input);
#endif
}

protected abstract TTemplate InitializeTemplate(IDictionary<string, string> map);

protected Regex Regex(IDictionary<string, string> indexer, string key, string regexFlag = null)
{
var pattern = indexer.Find("regex");
if (pattern == null)
throw new Exception($"{key} is missing regular expression specification.");

// Some expressions in the regex.yaml file causes parsing errors
// in .NET such as the \_ token so need to alter them before
// proceeding.

if (pattern.IndexOf(@"\_", StringComparison.Ordinal) >= 0)
pattern = pattern.Replace(@"\_", "_");

//Singleline: User agent strings do not contain newline characters. RegexOptions.Singleline improves performance.
//CultureInvariant: The interpretation of a user agent never depends on the current locale.
var options = RegexOptions.Singleline | RegexOptions.CultureInvariant;

if ("i".Equals(regexFlag))
{
options |= RegexOptions.IgnoreCase;
}

#if REGEX_COMPILATION
if (_options.UseCompiledRegex)
{
options |= RegexOptions.Compiled;
}
#endif

#if REGEX_MATCHTIMEOUT

return new Regex(pattern, options, _options.MatchTimeOut);
#else
return new Regex(pattern, options);
#endif
}

protected string Replace(Match m, IEnumerator<int> num, string replacement)
{
return replacement != null
? Select(_ => replacement, m, num)
: Select(x => x, m, num);
}

protected string Replace(Match m, IEnumerator<int> num, string replacement, string token)
{
return replacement != null && replacement.Contains(token)
? Select(s => s != null ? replacement.ReplaceFirstOccurence(token, s) : replacement, m, num)
: Replace(m, num, replacement);
}

protected string Select(Func<string, string> selector, Match m, IEnumerator<int> num)
{
if (!num.MoveNext()) throw new InvalidOperationException();
var groups = m.Groups; Group group;
return selector(
num.Current <= groups.Count && (group = groups[num.Current]).Success
? group.Value.Trim()
: null);
}

protected IEnumerator<T> Generate<T>(T initial, Func<T, T> next)
{
for (var state = initial; ; state = next(state))
yield return state;
// ReSharper disable once FunctionNeverReturns
}
}
}
7 changes: 7 additions & 0 deletions UAParser/Abstraction/IParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace UAParser.Abstraction
{
internal interface IParser<out T>
{
T Parse(string input);
}
}
31 changes: 31 additions & 0 deletions UAParser/Abstraction/IUAParserOutput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace UAParser.Abstraction
{
/// <summary>
/// Representing the parse results. Structure of this class aligns with the
/// ua-parser-output WebIDL structure defined in this document: https://github.com/ua-parser/uap-core/blob/master/docs/specification.md
/// </summary>
public interface IUAParserOutput
{
/// <summary>
/// The user agent string, the input for the UAParser
/// </summary>
string String { get; }

/// <summary>
/// The OS parsed from the user agent string
/// </summary>
// ReSharper disable once InconsistentNaming
OS OS { get; }

/// <summary>
/// The Device parsed from the user agent string
/// </summary>
Device Device { get; }

// ReSharper disable once InconsistentNaming
/// <summary>
/// The User Agent parsed from the user agent string
/// </summary>
UserAgent UA { get; }
}
}
Loading