branch: externals/org
commit 526a7d1cc65409d5546b009f54fed28726a9457d
Author: Maximilian Kueffner <poverobuosodon...@gmail.com>
Commit: Maximilian Kueffner <poverobuosodon...@gmail.com>
lisp/ob-csharp.el: Include support for evaluating C# code blocks
* ob-csharp.el: implements the babel contract to expand and evaluate
C# code blocks in org-mode using the dotnet SDK.
* testing/lisp/test-ob-csharp.el: comprehensive tests for
ob-csharp.el.
* mk/default.mk (`csharp' to `BTEST_OB_LANGUAGES'): the new babel
language `csharp' is added to the list of `BTEST_OB_LANGUAGES'. It is
commented out because a .NET SDK is required to run respective tests.
* ORG-NEWS: describes the newly introduced C# code block feature.
This change aims to officially support C# code blocks using
~dotnet~ (over ~mono~) as the default compiler. C#-Code block
evaluation with this module happens in context of a temporary
~.csproj~ file allowing for idiomatic run- and compile time
dependencies like existing projects, assemblies or NuGet packages.
---
etc/ORG-NEWS | 4 +
lisp/ob-csharp.el | 299 ++++++++++++++++++++++++++++++++++++++
mk/default.mk | 1 +
testing/lisp/test-ob-csharp.el | 320 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 624 insertions(+)
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 1f7cb2cbc1..3f17e223e6 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -239,6 +239,10 @@ appropriate major mode is unavailable.
When editing Dot source blocks, Org now uses Graphviz Dot mode, if installed.
+*** C# code block support with =ob-csharp.el=
+
+Org now officially enables C# code block evaluation based on the .NET SDK.
+
** New and changed options
# Changes dealing with changing default values of customizations,
diff --git a/lisp/ob-csharp.el b/lisp/ob-csharp.el
new file mode 100644
index 0000000000..48dd7bfdc7
--- /dev/null
+++ b/lisp/ob-csharp.el
@@ -0,0 +1,299 @@
+;;; ob-csharp.el --- org-babel functions for csharp evaluation -*-
lexical-binding: t -*-
+
+;; Copyright (C) 2024-2025 Free Software Foundation, Inc.
+
+;; Author: Maximilian Kueffner
+;; Maintainer: Maximilian Kueffner <poverobuosodon...@gmail.com>
+;; Keywords: literate programming, reproducible research
+;; Homepage: https://orgmode.org
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Requirements:
+
+;; Some .NET runtime environment should be installed.
+;; The `dotnet' command should be available to the system's environment
+;; (PATH discoverable for example).
+
+;;; Code:
+(require 'ob)
+
+;; file extension for C#
+(add-to-list 'org-babel-tangle-lang-exts '("csharp" . "cs"))
+
+;; default header arguments for C#
+(defvar org-babel-default-header-args:csharp
+ '((main . ((no)))
+ (nugetconfig . :any)
+ (framework . :any)
+ (class . ((no nil :any)))
+ (references . :any)
+ (usings . :any)
+ (cmdline . :any))
+ "Csharp specific header arguments.")
+
+(defcustom org-babel-csharp-compiler "dotnet"
+ "The program to call for compiling a csharp project."
+ :group 'org-babel
+ :package-version '(Org. "9.8")
+ :type 'string)
+
+(defun org-babel-csharp--default-compile-command (dir-proj-sln bin-dir)
+ "Construct the default compilation command for C#.
+
+DIR-PROJ-SLN is either a directory containing a \".csproj\" or \".sln\" file
+or a full path to either of these.
+BIN-DIR is the directory for the compiled output."
+ (format "%s build --output %S %S"
+ org-babel-csharp-compiler bin-dir dir-proj-sln))
+
+(defun org-babel-csharp--default-restore-command (project-file)
+ "Construct the default restore command for C# projects.
+
+PROJECT-FILE is a path to a \".csproj\" file on which the restore command
+takes effect."
+ (format "%s restore %S" org-babel-csharp-compiler project-file))
+
+(defun org-babel-csharp--find-dotnet-version ()
+ "Get a list of dotnet major versions from a list of dotnet sdks."
+ (cl-delete-if #'(lambda (v) (= 0 v))
+ (delete-dups
+ (mapcar #'(lambda (n)
+ (let ((fr (string-match "^[0-9.]+\\." n))
+ (to (string-match "\\." n)))
+ (string-to-number (substring n fr to))))
+ (split-string
+ (shell-command-to-string
+ (format "%s --list-sdks" org-babel-csharp-compiler))
+ "\n")))))
+
+(defcustom org-babel-csharp-default-target-framework
+ (format "net%s.0"
+ (let ((net-sdks (org-babel-csharp--find-dotnet-version)))
+ (when net-sdks
+ (apply #'max net-sdks))))
+ "The desired target framework to use."
+ :group 'org-babel
+ :package-version '(Org. "9.8")
+ :type 'string)
+
+(defcustom org-babel-csharp-generate-compile-command
+ #'org-babel-csharp--default-compile-command
+ "A function creating the compile command.
+
+It must take two parameters intended for the target binary directory and
+a .sln file, .csproj file, or a base directory where either can be found."
+ :group 'org-babel
+ :package-version '(Org. "9.8")
+ :type 'function)
+
+(defcustom org-babel-csharp-generate-restore-command
+ #'org-babel-csharp--default-restore-command
+ "A function creating a project restore command.
+
+It must take one parameter defining the project to perform a restore on."
+ :group 'org-babel
+ :package-version '(Org. "9.8")
+ :type 'function)
+
+(defcustom org-babel-csharp-additional-project-flags nil
+ "Will be passed in the \"PropertyGroup\" defining the project.
+
+This is taken as-is. It should be a string in XML-format."
+ :group 'org-babel
+ :package-version '(Org. "9.8")
+ :type 'string)
+
+(defun org-babel-csharp--generate-project-file (refs framework)
+ "Generate the file content to be used in a csproj-file.
+
+REFS is a list of references. Check `org-babel-csharp--format-refs' for
+the allowed semantics.
+FRAMEWORK is the target framework."
+ (unless framework
+ (error "framework cannot be nil"))
+ (concat "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n "
+ (when refs
+ (org-babel-csharp--format-refs refs))
+ "\n\n <PropertyGroup>"
+ "\n <OutputType>Exe</OutputType>\n"
+ (format "\n <TargetFramework>%s</TargetFramework>" framework)
+ "\n <ImplicitUsings>enable</ImplicitUsings>"
+ "\n <Nullable>enable</Nullable>"
+ (when org-babel-csharp-additional-project-flags
+ (format "\n %s" org-babel-csharp-additional-project-flags))
+ "\n </PropertyGroup>"
+ "\n</Project>"))
+
+(defun org-babel-csharp--format-usings (usings)
+ "Format USINGS into a string suitable for inclusion in a C# source file.
+
+USINGS should be a list of strings, each representing a using directive.
+Returns a string with each using directive on a new line."
+ (mapconcat
+ (lambda (u)
+ (unless (stringp u) (error "Usings must be of type string."))
+ (format "using %s;" u))
+ usings "\n"))
+
+(defun org-babel-expand-body:csharp (body params)
+ "Expand a block of C# code in BODY according to PARAMS.
+
+See `org-babel-default-header-args:csharp' for available parameters."
+ (let* ((main-p (not (string= (cdr (assq :main params)) "no")))
+ (class (pcase (alist-get :class params)
+ ("no" nil)
+ (`nil "Program")
+ (_ (alist-get :class params))))
+ (namespace "org.babel.autogen")
+ (usings (alist-get :usings params)))
+ (with-temp-buffer
+ (when (alist-get :prologue params)
+ (insert (alist-get :prologue params) "\n"))
+ (insert "namespace " namespace ";\n")
+ (when usings
+ (insert (format "\n%s\n" (org-babel-csharp--format-usings usings))))
+ (when class
+ (insert "\nclass " class "\n{\n"))
+ (when main-p
+ (insert "static void Main(string[] args)\n{\n"))
+ (insert (if (alist-get :var params)
+ (mapconcat #'identity (org-babel-variable-assignments:csharp
params) "\n")
+ "")
+ "\n")
+ (insert body)
+ (when main-p
+ (insert "\n}"))
+ (when class
+ (insert "\n}"))
+ (when (alist-get :epilogue params)
+ (insert "\n" (alist-get :epilogue params)))
+ (buffer-string))))
+
+(defun org-babel-csharp--format-refs (refs)
+ "Format REFS into a string suitable for inclusion in a .csproj file.
+
+REFS should be a list of strings or cons cells, each representing a reference.
+If an entry is a cons cell, the car denotes the reference name and
+the cdr is the version.
+
+Returns a formatted string representing the references, categorized into
+project reference, assembly reference, and package reference.
+Reference types are distinguished by their file extension.
+'.csproj' is interpreted as a project reference,
+'.dll' as an assembly reference.
+When a version is present, it will be treated as a package reference."
+ (let ((projectref)
+ (assemblyref)
+ (systemref))
+ (dolist (ref refs)
+ (let* ((version (if (consp ref)
+ (cdr ref)
+ nil))
+ (ref-string (if (consp ref)
+ (car ref)
+ ref))
+ (full-ref (if version
+ (file-truename (car ref))
+ (file-truename ref))))
+ (cond
+ ((string= "csproj" (file-name-extension full-ref))
+ (setf projectref
+ (concat projectref
+ (format "\n <ProjectReference Include=\"%s\" />"
+ full-ref))))
+ ((string= "dll" (file-name-extension full-ref))
+ (setf assemblyref
+ (concat assemblyref
+ (format "\n <Reference Include=%S>\n
<HintPath>%s</HintPath>\n </Reference>"
+ (file-name-base full-ref) full-ref))))
+ (t (setf systemref
+ (concat systemref
+ (format "\n <PackageReference Include=%s />"
+ (if version
+ (format "%S Version=%S" ref-string
version)
+ (format "%S" ref-string)))))))))
+ (format "%s\n\n %s\n\n %s"
+ (if projectref
+ (format "<ItemGroup>%s\n </ItemGroup>" projectref)
+ "")
+ (if assemblyref
+ (format "<ItemGroup>%s\n </ItemGroup>" assemblyref)
+ "")
+ (if systemref
+ (format "<ItemGroup>%s\n </ItemGroup>" systemref)
+ ""))))
+
+(defun org-babel-execute:csharp (body params)
+ "Execute a block of Csharp code with org-babel.
+This function is called by `org-babel-execute-src-block'"
+ (let* ((full-body (org-babel-expand-body:csharp body params))
+ (base-dir (make-temp-name (file-name-concat
org-babel-temporary-directory "obcs")))
+ (project-name (file-name-base base-dir))
+ (bin-dir (file-name-concat base-dir "bin"))
+ (framework (or (alist-get :framework params)
org-babel-csharp-default-target-framework))
+ (program-file (file-name-concat base-dir "Program.cs"))
+ (project-file (file-name-concat base-dir (concat project-name
".csproj")))
+ (nuget-file (alist-get :nugetconfig params))
+ (cmdline (alist-get :cmdline params))
+ (cmdline (if cmdline cmdline ""))
+ (restore-cmd (funcall org-babel-csharp-generate-restore-command
project-file))
+ (compile-cmd (funcall org-babel-csharp-generate-compile-command
+ (file-truename project-file)
+ (file-truename bin-dir)))
+ (run-cmd (format "%S %S" (file-truename (file-name-concat bin-dir
project-name)) cmdline)))
+ (unless (org-babel-csharp--find-dotnet-version)
+ (error "Could not find a .NET SDK for compiling."))
+ (unless (file-exists-p base-dir)
+ (make-directory base-dir))
+ (with-temp-file program-file
+ (insert full-body))
+ (with-temp-file project-file
+ (insert
+ (let ((refs (alist-get :references params)))
+ (org-babel-csharp--generate-project-file refs framework))))
+ (when (and nuget-file (file-exists-p (file-truename nuget-file)))
+ (copy-file nuget-file (file-name-concat base-dir (file-name-nondirectory
(file-truename nuget-file)))))
+ ;; nuget restore
+ (org-babel-eval restore-cmd "")
+ (let ((compile-result (org-babel-eval compile-cmd "")))
+ (when (string-match ": error" compile-result)
+ (org-babel-eval-error-notify 1 compile-result)))
+ (let ((results (org-babel-eval run-cmd "")))
+ (when results
+ (setq results (org-remove-indentation results))
+ ;; results
+ (org-babel-reassemble-table
+ (org-babel-result-cond (cdr (assq :result-params params))
+ results
+ (let ((tmp-file (org-babel-temp-file "c-")))
+ (with-temp-file tmp-file (insert results))
+ (org-babel-import-elisp-from-file tmp-file)))
+ (org-babel-pick-name
+ (cdr (assq :colname-names params)) (cdr (assq :colnames params)))
+ (org-babel-pick-name
+ (cdr (assq :rowname-names params)) (cdr (assq :rownames params))))))))
+
+(defun org-babel-variable-assignments:csharp (params)
+ "Return a list of C# variable assignments from header arguments."
+ (mapcar
+ #'(lambda (pair) (format "var %s = %S;" (car pair) (cdr pair)))
+ (org-babel--get-vars params)))
+
+(provide 'ob-csharp)
+;;; ob-csharp.el ends here
diff --git a/mk/default.mk b/mk/default.mk
index c5ea1ba01d..9ecda3c10a 100644
--- a/mk/default.mk
+++ b/mk/default.mk
@@ -56,6 +56,7 @@ BTEST_POST =
BTEST_OB_LANGUAGES = awk C fortran maxima lilypond octave perl python java
sqlite eshell calc
# R # requires ESS to be installed and
configured
# ruby # requires inf-ruby to be installed and
configured
+ # csharp # requires a .NET SDK to be installed
# extra packages to require for testing
BTEST_EXTRA =
# ess-site # load ESS for R tests
diff --git a/testing/lisp/test-ob-csharp.el b/testing/lisp/test-ob-csharp.el
new file mode 100644
index 0000000000..afcfae38d4
--- /dev/null
+++ b/testing/lisp/test-ob-csharp.el
@@ -0,0 +1,320 @@
+;;; test-ob-csharp.el --- Tests for ob-csharp -*- lexical-binding: t;
-*-
+
+;; Copyright (C) 2025 Maximilian Kueffner
+
+;; Author: Maximilian Kueffner <poverobuosodon...@gmail.com>
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(unless (featurep 'ob-csharp)
+ (signal 'missing-test-dependency '("Support for C# code blocks")))
+
+(require 'ob-core)
+
+(org-test-for-executable org-babel-csharp-compiler)
+
+(ert-deftest test-ob-csharp/customized-compile-command-used ()
+ "User specified compile command is used."
+ (let* ((custom-fun (lambda (p b) (format "custom-compiler %s %s" p b)))
+ (project "/tmp/placeholder/dummy.csproj")
+ (binary "/tmp/placeholder/bin")
+ (default-command (funcall org-babel-csharp-generate-compile-command
project binary))
+ (cmd-backup org-babel-csharp-generate-compile-command))
+ (unwind-protect
+ (progn
+ (setq org-babel-csharp-generate-compile-command custom-fun)
+ (should-not (string=
+ default-command
+ (funcall org-babel-csharp-generate-compile-command
project binary)))
+ (should (string= (funcall custom-fun project binary)
+ (funcall org-babel-csharp-generate-compile-command
project binary))))
+ (setq org-babel-csharp-generate-compile-command cmd-backup))))
+
+(ert-deftest test-ob-csharp/customized-restore-command-used ()
+ "User specified compile command is used."
+ (let* ((custom-fun (lambda (p) (format "custom-restore %s" p)))
+ (project "/tmp/placeholder/dummy.csproj")
+ (default-command (funcall org-babel-csharp-generate-restore-command
project))
+ (cmd-backup org-babel-csharp-generate-restore-command))
+ (unwind-protect
+ (progn
+ (setq org-babel-csharp-generate-restore-command custom-fun)
+ (should-not (string=
+ default-command
+ (funcall org-babel-csharp-generate-restore-command
project)))
+ (should (string= (funcall custom-fun project)
+ (funcall org-babel-csharp-generate-restore-command
project))))
+ (setq org-babel-csharp-generate-restore-command cmd-backup))))
+
+(ert-deftest test-ob-csharp/generate-project-file ()
+ "Test intended parameterization of the project file generator."
+ (should (stringp (org-babel-csharp--generate-project-file nil "net6.0")))
+ (should (stringp (org-babel-csharp--generate-project-file '("a-ref")
"net6.0")))
+ (should (stringp (org-babel-csharp--generate-project-file '("a-ref" "b-ref")
"net6.0")))
+ (should-error (org-babel-csharp--generate-project-file nil nil))
+ (should-error (org-babel-csharp--generate-project-file '(nil) "net6.0"))
+ (should-error (org-babel-csharp--generate-project-file "a-ref" "net6.0")))
+
+(ert-deftest test-ob-csharp/format-usings ()
+ "Test intended parameterization of the C# using formatter."
+ (should (string=
+ "using namespaceA;\nusing namesaceB;"
+ (org-babel-csharp--format-usings '("namespaceA" "namesaceB"))))
+ (should (string=
+ ""
+ (org-babel-csharp--format-usings nil)))
+ (should-error (org-babel-csharp--format-usings '("namespaceA" nil
"namesaceB")))
+ (should-error (org-babel-csharp--format-usings "singleUsing")))
+
+(ert-deftest test-ob-csharp/extension-based-reference-types ()
+ "Test if the references as supplied to the \"references\" header-arg are
correctly parsed."
+ (let ((nuget-inp '(("Nugetref" . "0.0.1")))
+ (assembly-inp '("assembly.dll"))
+ (project-inp '("project.csropj")))
+ (should (string= (org-babel-csharp--format-refs nuget-inp) "\n\n \n\n
<ItemGroup>\n <PackageReference Include=\"Nugetref\" Version=\"0.0.1\" />\n
</ItemGroup>"))
+ (should (string-search "\n\n <ItemGroup>\n <Reference
Include=\"assembly\">\n <HintPath>"
+ (org-babel-csharp--format-refs assembly-inp)))
+ (should (string= (org-babel-csharp--format-refs project-inp) "\n\n \n\n
<ItemGroup>\n <PackageReference Include=\"project.csropj\" />\n
</ItemGroup>"))
+ (should-error (org-babel-csharp--format-refs "not-a-list"))))
+(ert-deftest test-ob-csharp/custom-class-name-header-argument ()
+ "The generated class name matches the provided string or defaults to
\"Program\"."
+ (let ((cs-block (org-test-with-temp-text
+ "#+begin_src csharp :class \"MyAwesomeClass\"
+ Console.WriteLine(\"ok\");
+#+end_src"
+ (org-babel-expand-src-block))))
+ (should (string-search "class MyAwesomeClass" cs-block))
+ (should-not (string-search "class Program" cs-block))))
+
+(ert-deftest test-ob-csharp/custom-main-function-wrapping ()
+ "Lax main function wrapping works."
+ (let* ((dummy-block "#+begin_src csharp %s \nvar a = 1 + 1;\n#+end_src")
+ (disable-main-quote (org-test-with-temp-text
+ (format dummy-block ":main \"no\"")
+ (org-babel-expand-src-block)))
+ (disable-main-plain (org-test-with-temp-text
+ (format dummy-block ":main no")
+ (org-babel-expand-src-block)))
+ (main-implicit (org-test-with-temp-text
+ (format dummy-block "")
+ (org-babel-expand-src-block)))
+ (main-explicit (org-test-with-temp-text
+ (format dummy-block ":main anything")
+ (org-babel-expand-src-block)))
+ (str-after-break (lambda (s) (substring s (+ 1 (string-search "\n"
s)) -1))))
+ (should (equal (funcall str-after-break disable-main-plain) (funcall
str-after-break disable-main-quote)))
+ (should (equal (funcall str-after-break main-implicit) (funcall
str-after-break main-explicit)))
+ (should-not (string-search "static void Main" disable-main-quote))
+ (should-not (string-search "static void Main" disable-main-plain))
+ (should (string-search "static void Main" main-implicit))
+ (should (string-search "static void Main" main-explicit))))
+
+(ert-deftest test-ob-csharp/int-from-var ()
+ "Test of an integer variable."
+ (org-test-with-temp-text "#+begin_src csharp :var i=42 :results silent
+ Console.WriteLine(i);
+#+end_src"
+ (should (= 42 (org-babel-execute-src-block)))))
+
+(ert-deftest test-ob-csharp/float-from-var ()
+ "Test of a float variable."
+ (org-test-with-temp-text "#+begin_src csharp :var f=3.14 :results silent
+ Console.WriteLine(f);
+#+end_src"
+ (should (= 3.14 (org-babel-execute-src-block)))))
+
+(ert-deftest test-ob-csharp/string-from-var ()
+ "Test of a string variable."
+ (org-test-with-temp-text "#+begin_src csharp :var s=\"pi\" :results
silent
+ Console.WriteLine(s);
+#+end_src"
+ (should (string= "pi" (org-babel-execute-src-block)))))
+
+(ert-deftest test-ob-csharp/outputs-list ()
+ "Test list output."
+ (org-test-with-temp-text "#+begin_src csharp :results raw list silent
+ Console.WriteLine(\"Item 1\");
+ Console.WriteLine(\"Item 2\");
+ Console.WriteLine(\"Item 3\");
+ Console.WriteLine(\"Item 4\");
+#+end_src"
+ (should (equal "Item 1\nItem 2\nItem 3\nItem 4\n"
(org-babel-execute-src-block)))))
+
+(ert-deftest test-ob-csharp/commandline-input ()
+ "Test command line input."
+ (org-test-with-temp-text "#+begin_src csharp :cmdline 3 :usings '(\"System\"
\"System.Text\") :results silent
+ int argInt = 0;
+ Int32.TryParse(args[0], out argInt);
+
+ Console.WriteLine(argInt * 14);
+#+end_src"
+ (should (= 42 (org-babel-execute-src-block)))))
+
+(ert-deftest test-ob-csharp/custom-class-and-main ()
+ "Test custom class with custom main function."
+ (org-test-with-temp-text "#+begin_src csharp :class no :main no :results
silent
+ internal class ClassA
+ {
+ public ClassA(int i)
+ {
+ this.AnInt = i;
+ }
+
+ public int AnInt { get; set; }
+ }
+
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ ClassA daInstance = new(123);
+
+ Console.WriteLine(daInstance.AnInt);
+ }
+ }
+#+end_src"
+ (should (= 123 (org-babel-execute-src-block)))))
+
+(ert-deftest test-ob-csharp/tabular-format-output ()
+ "Test for tabular output format."
+ (org-test-with-temp-text "#+begin_src csharp :results table silent
+ Console.WriteLine($\"In, questo, mondo, una, cosa\");
+ Console.WriteLine($\"si, perde, una, si, trova\");
+#+end_src"
+ (should (equal '(("In" "questo" "mondo" "una" "cosa")
+ ("si" "perde" "una" "si" "trova"))
+ (org-babel-execute-src-block)))))
+
+(ert-deftest test-ob-csharp/nuget-reference ()
+ "Test with nuget reference."
+ (org-test-with-temp-text "#+begin_src csharp :references
'((\"Newtonsoft.Json\" . \"13.0.3\")) :usings '(\"System\" \"Newtonsoft.Json\")
:main no :project \"json-test\" :results verbatim silent
+ public class DTO
+ {
+ public int TheInt { get; set; }
+ public string TheString { get; set; }
+ }
+
+ static void Main(string[] args)
+ {
+ DTO myDto = new() { TheInt = 12, TheString = \"ok\" };
+
+ string json = JsonConvert.SerializeObject(myDto, Formatting.Indented);
+ Console.WriteLine($\"{json}\");
+ }
+#+end_src"
+ (should (string= "{\n \"TheInt\": 12,\n \"TheString\": \"ok\"\n}\n"
+ (org-babel-execute-src-block)))))
+
+(ert-deftest test-ob-csharp/respects-custom-nuget-config ()
+ "Check that the provided NuGet.config is taken into account when evaluating
a source block."
+ (let ((nugetconf (make-temp-file "nuget")))
+ (unwind-protect
+ (progn
+ (with-temp-buffer
+ (insert "<?xml version=\"1.0\" encoding=\"utf-8\"?>
+ <configuration>
+ <packageSources>
+ <clear />
+ <add key=\"local\" value=\"./local_packages\" />
+ </packageSources>
+ </configuration>")
+ (write-file nugetconf))
+ (org-test-with-temp-text (format "#+begin_src csharp :references
'((\"Newtonsoft.Json\" . \"13.0.3\")) :usings '(\"Newtonsoft.Json.Linq\")
:nugetconfig %S :results raw
+ var js = JObject.Parse(\"{\\\"TheInt\\\": 12, \\\"TheString\\\":
\\\"ok\\\"}\");
+ Console.Write(js);
+ ,#+end_src" nugetconf)
+ (should-error (org-babel-execute-src-block))))
+ (delete-file nugetconf))))
+
+(ert-deftest test-ob-csharp/additional-project-flags-fails-with-invalid-syntax
()
+ "Compilation fails when the `org-babel-csharp-additional-project-flags' is
not xml formatted."
+ (unwind-protect
+ (progn
+ (setq org-babel-csharp-additional-project-flags "somegarbage/>")
+ (org-test-with-temp-text "#+begin_src csharp
+ Console.WriteLine(\"ok\");
+#+end_src"
+ (should (eq nil (org-babel-execute-src-block)))))
+ (setq org-babel-csharp-additional-project-flags nil)))
+
+(ert-deftest test-ob-csharp/additional-project-flags-executes-with-xml-syntax
()
+ "Compilation succeeds when the `org-babel-csharp-additional-project-flags'
is xml formatted."
+ (unwind-protect
+ (progn
+ (setq org-babel-csharp-additional-project-flags
"<LangVersion>latest</LangVersion>")
+ (org-test-with-temp-text "#+begin_src csharp
+ Console.WriteLine(\"ok\");
+#+end_src"
+ (should (string= "ok"
+ (org-babel-execute-src-block)))))
+ (setq org-babel-csharp-additional-project-flags nil)))
+
+(ert-deftest test-ob-csharp/prologue-and-epilouge-expanded ()
+ "Check if prologue and epilogue are written plain to start and end of the
expanded block."
+ (org-test-with-temp-text "#+begin_src csharp :prologue \"// File header\"
:epilogue \"// file ends here\"
+ Console.WriteLine(\"ok\");
+#+end_src"
+ (let ((block-expand (org-babel-expand-src-block)))
+ (should (string= (substring block-expand 0 14) "// File header"))
+ (should (string= (substring block-expand -17) "// file ends here")))))
+
+(ert-deftest test-ob-csharp/invalid-additional-project-flags-fail ()
+ "An invalid setting in `org-babel-csharp-additional-project-flags' fails."
+ (let ((block "#+begin_src csharp\nConsole.WriteLine(1);\n#+end_src")
+ (invalid-flags "<UserCustomPoperty>This is an invalid xml string
(property not closed)"))
+ (unwind-protect
+ (progn
+ (setq org-babel-csharp-additional-project-flags invalid-flags)
+ (should-not (org-test-with-temp-text block
(org-babel-execute-src-block))))
+ (setq org-babel-csharp-additional-project-flags nil))))
+
+(ert-deftest test-ob-csharp/valid-additional-project-flags-are-respected ()
+ "A valid setting of `org-babel-csharp-additional-project-flags' is respected
code block compilation."
+ (let ((block "#+begin_src csharp\nConsole.WriteLine(1);\n#+end_src")
+ (valid-flags "<Configuration>Release</Configuration>"))
+ (unwind-protect
+ (progn
+ (setq org-babel-csharp-additional-project-flags valid-flags)
+ (should (= 1 (org-test-with-temp-text block
(org-babel-execute-src-block)))))
+ (setq org-babel-csharp-additional-project-flags nil))))
+
+;; requires at least 2 dotnet frameworks installed
+(ert-deftest test-ob-csharp/framework-header-is-configurable ()
+ "Check for additional framework header arguments."
+ (skip-when (< (length (org-babel-csharp--find-dotnet-version)) 2))
+ (let* ((src-result (lambda (v) (org-test-with-temp-text
+ (format "#+begin_src csharp :framework
\"net%s.0\"
+ Console.WriteLine(\"ok\");
+#+end_src" v)
+ (org-babel-execute-src-block))))
+ (res-first (funcall src-result (car
(org-babel-csharp--find-dotnet-version))))
+ (res-second (funcall src-result (cadr
(org-babel-csharp--find-dotnet-version)))))
+ (should (string= res-first "ok"))
+ (should (string= res-second "ok"))
+ (should (string= res-first res-second))
+ (should (eq nil (funcall src-result "nonexisting")))))
+
+(ert-deftest test-ob-csharp/runtime-error-without-valid-dotnet-sdk ()
+ "Unless there is a valid dotnet SDK found, evaluating a csharp block fails."
+ (cl-letf (((symbol-function 'org-babel-csharp--find-dotnet-version)
#'ignore))
+ (org-test-with-temp-text "#+begin_src csharp
+ Console.WriteLine(\"hi\");
+#+end_src"
+ (should-error (org-babel-execute-src-block)))))
+
+
+(provide 'test-ob-csharp)
+;;; test-ob-csharp.el ends here