I'm trying to write a function in C# that takes in a string containing typescript code and returns a string containing JavaScript code. Is there a library function for this?
You can use Process to invoke the compiler, specify --out file.js to a temporary folder and read the contents of the compiled file.
I made a little app to do that:
Usage
TypeScriptCompiler.Compile(#"C:\tmp\test.ts");
To get the JS string
string javascriptSource = File.ReadAllText(#"C:\tmp\test.js");
Full source with example and comments:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
try
{
// compiles a TS file
TypeScriptCompiler.Compile(#"C:\tmp\test.ts");
// if no errors were found, read the contents of the compile file
string javascriptSource = File.ReadAllText(#"C:\tmp\test.js");
}
catch (InvalidTypeScriptFileException ex)
{
// there was a compiler error, show the compiler output
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
}
public static class TypeScriptCompiler
{
// helper class to add parameters to the compiler
public class Options
{
private static Options #default;
public static Options Default
{
get
{
if (#default == null)
#default = new Options();
return #default;
}
}
public enum Version
{
ES5,
ES3,
}
public bool EmitComments { get; set; }
public bool GenerateDeclaration { get; set; }
public bool GenerateSourceMaps { get; set; }
public string OutPath { get; set; }
public Version TargetVersion { get; set; }
public Options() { }
public Options(bool emitComments = false
, bool generateDeclaration = false
, bool generateSourceMaps = false
, string outPath = null
, Version targetVersion = Version.ES5)
{
EmitComments = emitComments;
GenerateDeclaration = generateDeclaration;
GenerateSourceMaps = generateSourceMaps;
OutPath = outPath;
TargetVersion = targetVersion;
}
}
public static void Compile(string tsPath, Options options = null)
{
if (options == null)
options = Options.Default;
var d = new Dictionary<string,string>();
if (options.EmitComments)
d.Add("-c", null);
if (options.GenerateDeclaration)
d.Add("-d", null);
if (options.GenerateSourceMaps)
d.Add("--sourcemap", null);
if (!String.IsNullOrEmpty(options.OutPath))
d.Add("--out", options.OutPath);
d.Add("--target", options.TargetVersion.ToString());
// this will invoke `tsc` passing the TS path and other
// parameters defined in Options parameter
Process p = new Process();
ProcessStartInfo psi = new ProcessStartInfo("tsc", tsPath + " " + String.Join(" ", d.Select(o => o.Key + " " + o.Value)));
// run without showing console windows
psi.CreateNoWindow = true;
psi.UseShellExecute = false;
// redirects the compiler error output, so we can read
// and display errors if any
psi.RedirectStandardError = true;
p.StartInfo = psi;
p.Start();
// reads the error output
var msg = p.StandardError.ReadToEnd();
// make sure it finished executing before proceeding
p.WaitForExit();
// if there were errors, throw an exception
if (!String.IsNullOrEmpty(msg))
throw new InvalidTypeScriptFileException(msg);
}
}
public class InvalidTypeScriptFileException : Exception
{
public InvalidTypeScriptFileException() : base()
{
}
public InvalidTypeScriptFileException(string message) : base(message)
{
}
}
}
Perhaps you could use a JavaScript interpreter like JavaScriptDotNet to run the typescript compiler tsc.js from C#.
Something like:
string tscJs = File.ReadAllText("tsc.js");
using (var context = new JavascriptContext())
{
// Some trivial typescript:
var typescriptSource = "window.alert('hello world!');";
context.SetParameter("typescriptSource", typescriptSource);
context.SetParameter("result", "");
// Build some js to execute:
string script = tscJs + #"
result = TypeScript.compile(""typescriptSource"")";
// Execute the js
context.Run(script);
// Retrieve the result (which should be the compiled JS)
var js = context.GetParameter("result");
Assert.AreEqual(typescriptSource, js);
}
Obviously that code would need some serious work. If this did turn out to be feasible, I'd certainly be interested in the result.
You'd also probably want to modify tsc so that it could operate on strings in memory rather than requiring file IO.
The TypeScript compiler file officially runs on either node.js or Windows Script Host - it is written in TypeScript itself (and transpiled to JavaScript). It requires a script host that can access the file system.
So essentially, you can run TypeScript from any language as long as you can wrap it in a script engine that supports the file system operations required.
If you wanted to compile TypeScript to JavaScript purely in C#, you would end up writing a C# clone of the compiler.
Related
Need to replace a constant of another application with a new value using the dnlib library, so that when you run the application the new value will be displayed in place of the old (default)
Stub class:
StubDnlib namespace
{
using System;
public static class MyClass
{
public const string READ = "Console";
public const int MyConst = 10;
public static void InizializeTest()
{
Console.WriteLine($"Const: {READ} - default = Console");
Console.WriteLine($"Const: {MyConst} - default = 10");
}
}
}
Builder class:
using var module = ModuleDefMD.Load(Resources.StubDnlib); // StubDnlib - stub class (exe)
foreach (var type in module.GetTypes())
{
foreach (FieldDef field in type.Fields)
{
if (field.HasConstant && field.ElementType == ElementType.String)
{
field.Constant.Value = "newValue";
}
}
}
module.Write("Edited.exe");
The value of the constant changes, but if I run the application, it shows the old value and the constant breaks
I looked into DnSpy and looked at the values:
Console.WriteLine("Const: Console - default = Console");
public const string READ = "newValue";
Instead of "Const: {READ}" the value of "Console" was substituted, but not READ =(
I have added CommandLineParser library into my project and I have configure all the arguments which should be provided to my project for to support silent installation of the same.
An InstallOptions class is being created with some "Option" attributes for each of the required and non-required arguments to the same e.g. below
public class InstallOptions
{
[Option("path", Required = true, HelpText = "The installation path where you want the application installed.")]
public string InstallPath { get; set; }
[Option("dbname", Required = true, HelpText = "Database name.")]
public string DbName { get; set; }
[Option("dbserver", Required = true, HelpText = "Database server name or IP address.")]
public string DbServer { get; set; }
[HelpOption]
public string DisplayHelpOnParseError()
{
var help = new HelpText()
{
AddDashesToOption = true
};
var errors = "";
if (LastParserState.Errors.Any())
{
errors = help.RenderParsingErrorsText(this, 0);
}
//HACK fix where if help.addoptions is called more than once it truncates the output
if (_help == null)
{
help.AddOptions(this);
_help = help;
}
else
{
return String.IsNullOrEmpty(errors) ? _help : "ERROR(S):" + errors + _help;
}
return help;
}
}
From my program.cs file I want to debug I am running my project as below
public static void Main(string[] args)
{
args = new string[3];
args[0] = "--path C:\\Program files\MyProject";
args[1] = "--dbname myDBName";
args[2] = "--dbserver myDBServer";
var result = Parser.Default.ParseArguments(args, installOptions);
if (!result) throw new ArgumentException(installOptions.DisplayHelpOnParseError());
}
in the above code I all the time getting result = false and states throws below error message
--path required. The installation path where you want the application installed.
--dbname required. Database name.
--dbserver required. Database server name or IP address.
Please help me how to pass all 3 parameter to my project to test it is working correctly.
Thanks in advance
Arguments should be passed as below
I would like to store C# functions in DLL files and later call that function with parameters. So far i've been able to store the function in a DLL file with the following code:
var codeProvider = new CSharpCodeProvider();
var icc = codeProvider.CreateCompiler();
var parameters = new CompilerParameters();
parameters.GenerateExecutable = false;
parameters.OutputAssembly = "Sum.dll";
icc.CompileAssemblyFromSource(parameters, code);
The function in the DLL file (value of variable code above is):
public class Function : IFunc
{
public string ID
{
get { return ""Sum""; }
}
public string Name
{
get { return ""Sum""; }
}
public string Description
{
get { return ""Return the sum of the values specified in args""; }
}
public ResultSet Execute(params string[] args)
{
var sum = 0;
foreach(var arg in args)
{
var rslt = 0;
if(int.TryParse(arg, out rslt))
{
sum += rslt;
}
}
ResultSet rtn = new ResultSet();
rtn.Result = sum.ToString();
rtn.Type = ""int"";
return rtn;
}
}
I've used Assembly.LoadFile to load the DLL and used reflection to fetch the class containing the function. I also have 2 identical interface, one in my project and one in the DLL file:
public interface IFunc
{
string ID { get; }
string Name { get; }
string Description { get; }
string Execute(params string[] args);
}
To be able to call the function i use:
public static IFunc CreateSumFunction()
{
var dll = Assembly.LoadFile(#"...\Sum.dll");
var func = dll.GetType("Function"); // Class containing the function
var instance = Activator.CreateInstance(func);
return (IFunc)instance; // <--- CRASH
}
Part of the exception:
System.Windows.Markup.XamlParseException was unhandled
Message='The invocation of the constructor on type 'GenericCoder.MainWindow' that matches the specified binding constraints threw an exception.' Line number '3' and line position '9'.
Is there a way to resolve this, or maybe a complete new way of doing it?
Add your library to references of your project. Then you're able to use the functions without the need of reflections.
For MVC application custom listener does not create a log file when initializeData="CustomWeblog.txt" parameter is used, but initializeData="d:\CustomWeblog.txt" triggers file creation. What is the reason of such behaviour? Console application generates files for all types of listeners.
Custom class:
public class CustomTextWriterTraceListener : TextWriterTraceListener
{
public CustomTextWriterTraceListener(string fileName) : base(fileName)
}
Web.config (mvc application, web.config)
<trace autoflush="true" />
<sources>
<source name="Trace">
<listeners>
<add name="TextWriterListner"
type="System.Diagnostics.TextWriterTraceListener, WebTracing" initializeData="Weblog.txt"/>
<!-- the file is created -->
<add name="CustomTextWriterListner"
type="WebTracing.CustomTextWriterTraceListener, WebTracing" initializeData="CustomWeblog.txt"/>
<!-- the file is not created in MVC application ?! -->
<add name="CustomTextWriterListnerAbsolutePath"
type="WebTracing.CustomTextWriterTraceListener, WebTracing" initializeData="d:\CustomWeblog.txt"/>
<!-- the file is created -->
</listeners>
</source>
</sources>
Cutom listener does not create a log file.
Caller:
TraceSource obj = new TraceSource("Trace", SourceLevels.All);
obj.TraceEvent(TraceEventType.Critical,0,"This is a critical message");
I have tried to add some extra configuration: from this blog and this one. But there is no success. Should I provide a absolute path? Is there any workaround by creating a separate assembly for custom listener?
I was trying to create my own rolling text writer trace listener when I was encountering the same issue you described. Long story short, after all the running around here is what I came up with.
public class RollingTextWriterTraceListener : TextWriterTraceListener {
string fileName;
private static string[] _supportedAttributes = new string[]
{
"template", "Template",
"convertWriteToEvent", "ConvertWriteToEvent",
"addtoarchive","addToArchive","AddToArchive",
};
public RollingTextWriterTraceListener(string fileName)
: base() {
this.fileName = fileName;
}
/// <summary>
/// This makes sure that the writer exists to be written to.
/// </summary>
private void ensureWriter() {
//Resolve file name given. relative paths (if present) are resolved to full paths.
// Also allows for paths like this: initializeData="~/Logs/{ApplicationName}_{DateTime:yyyy-MM-dd}.log"
var logFileFullPath = ServerPathUtility.ResolvePhysicalPath(fileName);
var writer = base.Writer;
if (writer == null && createWriter(logFileFullPath)) {
writer = base.Writer;
}
if (!File.Exists(logFileFullPath)) {
if (writer != null) {
try {
writer.Flush();
writer.Close();
writer.Dispose();
} catch (ObjectDisposedException) { }
}
createWriter(logFileFullPath);
}
//Custom code to package the previous log file(s) into a zip file.
if (AddToArchive) {
TextFileArchiveHelper.Archive(logFileFullPath);
}
}
bool createWriter(string logFileFullPath) {
try {
logFileFullPath = ServerPathUtility.ResolveOrCreatePath(logFileFullPath);
var writer = new StreamWriter(logFileFullPath, true);
base.Writer = writer;
return true;
} catch (IOException) {
//locked as already in use
return false;
} catch (UnauthorizedAccessException) {
//ERROR_ACCESS_DENIED, mostly ACL issues
return false;
}
}
/// <summary>
/// Get the add to archive flag
/// </summary>
public bool AddToArchive {
get {
// Default behaviour is not to add to archive.
var addToArchive = false;
var key = Attributes.Keys.Cast<string>().
FirstOrDefault(s => string.Equals(s, "addtoarchive", StringComparison.InvariantCultureIgnoreCase));
if (!string.IsNullOrWhiteSpace(key)) {
bool.TryParse(Attributes[key], out addToArchive);
}
return addToArchive;
}
}
#region Overrides
/// <summary>
/// Allowed attributes for this trace listener.
/// </summary>
protected override string[] GetSupportedAttributes() {
return _supportedAttributes;
}
public override void Flush() {
ensureWriter();
base.Flush();
}
public override void Write(string message) {
ensureWriter();
base.Write(message);
}
public override void WriteLine(string message) {
ensureWriter();
base.WriteLine(message);
}
#endregion
}
UPDATE: Here is the utility class I wrote for resolving paths.
public static class ServerPathUtility {
public static string ResolveOrCreatePath(string pathToReplace) {
string rootedFileName = ResolvePhysicalPath(pathToReplace);
FileInfo fi = new FileInfo(rootedFileName);
try {
DirectoryInfo di = new DirectoryInfo(fi.DirectoryName);
if (!di.Exists) {
di.Create();
}
if (!fi.Exists) {
fi.CreateText().Close();
}
} catch {
// NO-OP
// TODO: Review what should be done here.
}
return fi.FullName;
}
public static string ResolvePhysicalPath(string pathToReplace) {
string rootedPath = ResolveFormat(pathToReplace);
if (rootedPath.StartsWith("~") || rootedPath.StartsWith("/")) {
rootedPath = System.Web.Hosting.HostingEnvironment.MapPath(rootedPath);
} else if (!Path.IsPathRooted(rootedPath)) {
rootedPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, rootedPath);
}
return rootedPath;
}
public static string ResolveFormat(string format) {
string result = format;
try {
result = ExpandApplicationVariables(format);
} catch (System.Security.SecurityException) {
// Log?
}
try {
string variables = Environment.ExpandEnvironmentVariables(result);
// If an Environment Variable is not found then remove any invalid tokens
Regex filter = new Regex("%(.*?)%", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
string filePath = filter.Replace(variables, "");
if (Path.GetDirectoryName(filePath) == null) {
filePath = Path.GetFileName(filePath);
}
result = filePath;
} catch (System.Security.SecurityException) {
// Log?
}
return result;
}
public static string ExpandApplicationVariables(string input) {
var filter = new Regex("{(.*?)}", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
var result = filter.Replace(input, evaluateMatch());
return result;
}
private static MatchEvaluator evaluateMatch() {
return match => {
var variableName = match.Value;
var value = GetApplicationVariable(variableName);
return value;
};
}
public static string GetApplicationVariable(string variable) {
string value = string.Empty;
variable = variable.Replace("{", "").Replace("}", "");
var parts = variable.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
variable = parts[0];
var parameter = string.Empty;
if (parts.Length > 1) {
parameter = string.Join("", parts.Skip(1));
}
Func<string, string> resolve = null;
value = VariableResolutionStrategies.TryGetValue(variable.ToUpperInvariant(), out resolve) && resolve != null
? resolve(parameter)
: string.Empty;
return value;
}
public static readonly IDictionary<string, Func<string, string>> VariableResolutionStrategies =
new Dictionary<string, Func<string, string>> {
{"MACHINENAME", p => Environment.MachineName },
{"APPDOMAIN", p => AppDomain.CurrentDomain.FriendlyName },
{"DATETIME", getDate},
{"DATE", getDate},
{"UTCDATETIME", getUtcDate},
{"UTCDATE", getUtcDate},
};
static string getDate(string format = "yyyy-MM-dd") {
var value = string.Empty;
if (string.IsNullOrWhiteSpace(format))
format = "yyyy-MM-dd";
value = DateTime.Now.ToString(format);
return value;
}
static string getUtcDate(string format = "yyyy-MM-dd") {
var value = string.Empty;
if (string.IsNullOrWhiteSpace(format))
format = "yyyy-MM-dd";
value = DateTime.Now.ToString(format);
return value;
}
}
So this utility class allows me to resolve relative paths and also customize formats. For example, if you looked at the code you would have seen that application name ApplicationName variable does not exist in this path
"~/Logs/{ApplicationName}_{DateTime:yyyy-MM-dd}.log"
I am able to configure that in the startup of the application along with any other variables I want to add like so
public partial class Startup {
public void Configuration(IAppBuilder app) {
//... Code removed for brevity
// Add APPLICATIONNAME name to path Utility
ServerPathUtility.VariableResolutionStrategies["APPLICATIONNAME"] = p => {
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
if (assembly != null)
return assembly.GetName().Name;
return string.Empty;
};
}
}
Ok, finally, I have switched investigation to the way listener paths are generated. What I have noticed on debugging is that source listeners list contain different paths.
System.Diagnostics.TextWriterTraceListener listener object has a full path generated;
WebTracing.CustomTextWriterTraceListener has only file name. There is no generated errors.
The different values as caused by the reason that custom listened swallowed UnauthorisedAccessException exception so that the application continues working without informing us about permissions issues.
But what is the place of storing Custom listener log files? Are they
generated or not?
The following link to the TextWriterTraceListener source code helped me to figure out the path. The following code:
//initializeData="CustomWeblog.txt", so fileName == "CustomWeblog.txt" here
string fullPath = Path.GetFullPath(fileName);
string dirPath = Path.GetDirectoryName(fullPath);
string fileNameOnly = Path.GetFileName(fullPath);
Actual storage path depends on Project > Properties > Web > Server: IIS Express:
c:\Program Files (x86)\IIS Express\CustomWeblog.txt
All the time I was debudding the MVC application (as an administrator: vs run as administrator) log files were correctly generated in that folder. When I am running VS without administrator permissions custom listeners do not create files at all.
As it was mentined above, I executed the source code listener and found that catch(UnauthorisedAccessException) { break; } is triggered on new StreamWriter(...) constructor call.
Why is access to the path denied? SO link
What are all the user accounts for IIS/ASP.NET and how do they differ? (In Practice section of the answer) SO link
Awesome video tutorial: Application pools in IIS on IIS and Application Pool configuration by Pragime Tech.
As another workaround you can declare the whole path in initializeData="d:\CustomWeblog.txt" attribute. But keep in mind that you have to have the proper permissions.
I have some Config files as part of my solution on Windows Mobile. I am porting the code to MonoForAndroid and MonoTouch, so I want to keep my code unchanged as much as possible.
When loading these xml files, on Windows Mobile works fine, in my last prototype it also worked on iOS, but the code does not work on MonForAndroid
I have these files
/solution folder
/My Documents/
/Business
App.Config
Settings.Config
I have these files build action set to Content and I can see that they are being copied to the /bin/Debug/ but When I try to read these files, I get the following exception:
System.IO.DirectoryNotFoundException
I see that there is a similar question in here, but they advised to use AndroidResources, which I do not want to do, there are many placed where these files are needed, so I do not want to change it in many places.
AndrodiResources, is out of the question, and if possible I would like to avoid using EmbededResources
ah and the way I am reading it, very straightforward xmDoc.Load(filePath) I also tried File.ReadAllText() I made sure that the filePath is correct, and I got the path generated using Path.Combine() to avoid any issues with the filePath/slashes
Here is how I construct my file path
var filePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase).Replace(FileURIPrefix, ""), "My Documents", "Business");
filePath = Path.Combine(filePath, "App.Config");
And I can see in the debugger that the filePath is correct
Thanks for the help in advance
After searching all around, I could not get the MonoDroid to load (or include) my files when their build action is set to Content.
I had to create an entity called FileHelper which is implemented differently on Android, I then use that FileHelper.ReadAllText(string filename);
I will put my implementation here, hoping that it would benefit somebody else.
Windows Mobile and iOS
public class FileHelper
{
public static string ReadAllText(string filePath)
{
var path = filePath.GetFullPath();
if (!File.Exists(path))
{
Logging.LogHandler.LogError("File " + path + " does not exists");
return string.Empty;
}
using (var reader = new StreamReader(filePath))
{
return reader.ReadToEnd();
}
}
}
Android version
public class FileHelper : BaseFileHelper
{
public static string ReadAllText(string filePath)
{
var entryAssemblyPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase).Replace("file:", ""), "MyExecutableAssemblyName.dll");
// This is because Assembly.GetEntryAssembly() returns null on Android... Booohhh
var assembly = Assembly.LoadFrom(entryAssemblyPath);
using (var stream = assembly.GetManifestResourceStream(filePath.GetFullPath()))
{
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
}
}
I had a shared code for Constants and an extention method for paths as below
Constants.cs
public static Class Constants
{
private static string _RootPath;
private static string _iOSRootPath;
private static string _AndroidResourcePath;
public static string RootPath
{
get
{
if (string.IsNullOrEmpty(_RootPath))
{
_RootPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase).Replace(FileURIPrefix, "") + "\\My Documents\\Business";
}
return _RootPath;
}
}
public static string iOSRootPath
{
get
{
if (!string.IsNullOrEmpty(_iOSRootPath))
{
_iOSRootPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase).Replace(FileURIPrefix, "").Replace("file:", ""), Path.Combine("My_Documents", "Business"));
}
return _iOSRootPath;
}
}
public static string AndroidResourcePath
{
get
{
if (string.IsNullOrEmpty(_AndroidResourcePath))
{
_AndroidResourcePath = "Leopard.Delivery.My_Documents.Business.";
}
return _AndroidResourcePath;
}
}
}
PathExtentions.cs
public static class PathExtensions
{
public static string GetFullPath(this string filePath)
{
if (Platform.IsAndroid) // platform is a class that I have to tell me which platfrom I am at :)
{
return Constants.AndroidResourcePath + filePath;
}
if (Platform.IsIOS)
{
return Path.Combine(Constants.iOSRootPath, filePath);
}
return Path.Combine(Constants.RootPath, filePath);
}
}
After setting this up, I am using my FileHelper just as easy as below
string configuratinContents = FileHelper.ReadAllText(configruationPath);
To whoever using this code, remember to set the build action to EmbededResources on Android, and to Content on iOS and Windows Mobile.