feat: add lisp playground.
This commit is contained in:
parent
001aad5ed9
commit
fb95d8d8f4
5 changed files with 200 additions and 0 deletions
101
Jellyfin.Plugin.SmartPlaylist/Api/LispPlaygroundController.cs
Normal file
101
Jellyfin.Plugin.SmartPlaylist/Api/LispPlaygroundController.cs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
using System.Net.Mime;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using MediaBrowser.Common.Api;
|
||||||
|
|
||||||
|
using Jellyfin.Plugin.SmartPlaylist.Lisp;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.SmartPlaylist.Api {
|
||||||
|
[Serializable]
|
||||||
|
public class ProgramOutputDto {
|
||||||
|
public string Output { get; set; }
|
||||||
|
public string FinalExpression { get; set; }
|
||||||
|
public string Traceback { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Authorize(Policy = Policies.RequiresElevation)]
|
||||||
|
[Route("LispPlayground")]
|
||||||
|
[Produces(MediaTypeNames.Application.Json)]
|
||||||
|
public class LispPlaygroundController : ControllerBase {
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public LispPlaygroundController(
|
||||||
|
ILogger<LispPlaygroundController> logger
|
||||||
|
) {
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Executor SetupExecutor(StringBuilder sb) {
|
||||||
|
var env = new DefaultEnvironment();
|
||||||
|
var executor = new Executor(env);
|
||||||
|
executor.builtins["logd"] = (x) => {
|
||||||
|
_logger.LogDebug(((Lisp.String)x.First()).Value(), x.Skip(1).ToArray());
|
||||||
|
return Lisp.Boolean.TRUE;
|
||||||
|
};
|
||||||
|
executor.builtins["logi"] = (x) => {
|
||||||
|
_logger.LogInformation(((Lisp.String)x.First()).Value(), x.Skip(1).ToArray());
|
||||||
|
return Lisp.Boolean.TRUE;
|
||||||
|
};
|
||||||
|
executor.builtins["logw"] = (x) => {
|
||||||
|
_logger.LogWarning(((Lisp.String)x.First()).Value(), x.Skip(1).ToArray());
|
||||||
|
return Lisp.Boolean.TRUE;
|
||||||
|
};
|
||||||
|
executor.builtins["loge"] = (x) => {
|
||||||
|
_logger.LogError(((Lisp.String)x.First()).Value(), x.Skip(1).ToArray());
|
||||||
|
return Lisp.Boolean.TRUE;
|
||||||
|
};
|
||||||
|
executor.builtins["print"] = (x) => {
|
||||||
|
sb.Append(string.Join(" ", x.Select((i) => {
|
||||||
|
if (i is Lisp.String i_s) {
|
||||||
|
return i_s.Value();
|
||||||
|
}
|
||||||
|
return i.ToString();
|
||||||
|
})));
|
||||||
|
return Lisp.Boolean.TRUE;
|
||||||
|
};
|
||||||
|
executor.builtins["print"] = (x) => {
|
||||||
|
sb.Append(string.Join(" ", x.Select((i) => {
|
||||||
|
if (i is Lisp.String i_s) {
|
||||||
|
return i_s.Value();
|
||||||
|
}
|
||||||
|
return i.ToString();
|
||||||
|
})));
|
||||||
|
sb.Append("\n");
|
||||||
|
return Lisp.Boolean.TRUE;
|
||||||
|
};
|
||||||
|
if (Plugin.Instance is not null) {
|
||||||
|
executor.eval(Plugin.Instance.Configuration.InitialProgram);
|
||||||
|
} else {
|
||||||
|
throw new ApplicationException("Plugin Instance is not yet initialized");
|
||||||
|
}
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public async Task<ActionResult<ProgramOutputDto>> SetPlaylist() {
|
||||||
|
try {
|
||||||
|
string program;
|
||||||
|
using (StreamReader reader = new StreamReader(Request.Body)) {
|
||||||
|
program = await reader.ReadToEndAsync();
|
||||||
|
}
|
||||||
|
StringBuilder output = new StringBuilder();
|
||||||
|
var e = SetupExecutor(output);
|
||||||
|
var r = e.eval(program).ToString();
|
||||||
|
return Ok(new ProgramOutputDto() {
|
||||||
|
FinalExpression = r,
|
||||||
|
Output = output.ToString(),
|
||||||
|
});
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return Ok(new ProgramOutputDto() {
|
||||||
|
Traceback = ex.ToString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
Jellyfin.Plugin.SmartPlaylist/Pages/lispPlayground.html
Normal file
42
Jellyfin.Plugin.SmartPlaylist/Pages/lispPlayground.html
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>LispPlayground</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="LispPlaygroundConfigPage" data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox" data-controller="__plugin/lispPlayground.js">
|
||||||
|
<div data-role="content">
|
||||||
|
<div class="content-primary">
|
||||||
|
<form id="LispPlaygroundConfigForm">
|
||||||
|
<div class="inputContainer">
|
||||||
|
<label class="inputLabel inputLabelUnfocused" for="LispPlaygroundEditProgram">Program</label>
|
||||||
|
<div class="fieldDescription">A program which should return <code>t</code> or <code>nil</code> to include or exclude the provided <code>item</code>.</div>
|
||||||
|
<textarea id="LispPlaygroundEditProgram" class="emby-input smartcollection-monospace" name="Program" rows="16" cols="120"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="inputContainer">
|
||||||
|
<label class="inputLabel inputLabelUnfocused" for="LispPlaygroundOutput">Output</label>
|
||||||
|
<div class="fieldDescription">The output of the program.</div>
|
||||||
|
<textarea id="LispPlaygroundOutput" class="emby-input smartcollection-monospace" name="Output" rows="16" cols="120"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="inputContainer">
|
||||||
|
<label class="inputLabel inputLabelUnfocused" for="LispPlaygroundReturn">Final expression</label>
|
||||||
|
<div class="fieldDescription">The final expression the program has been reduced to.</div>
|
||||||
|
<textarea id="LispPlaygroundReturn" class="emby-input smartcollection-monospace" name="Output" rows="1" cols="120"></textarea>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button is="emby-button" type="submit" class="raised button-submit block emby-button">
|
||||||
|
<span>Run</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.smartcollection-monospace {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
43
Jellyfin.Plugin.SmartPlaylist/Pages/lispPlayground.js
Normal file
43
Jellyfin.Plugin.SmartPlaylist/Pages/lispPlayground.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
|
||||||
|
function fillForm(o) {
|
||||||
|
const output = document.querySelector('#LispPlaygroundOutput');
|
||||||
|
const return_ = document.querySelector('#LispPlaygroundReturn');
|
||||||
|
output.value = (o.hasOwnProperty("Output")) ? o.Output : o.Traceback;
|
||||||
|
return_.value = (o.hasOwnProperty("FinalExpression")) ? o.FinalExpression : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiClient.runLispProgram = function (program) {
|
||||||
|
const url = ApiClient.getUrl('LispPlayground');
|
||||||
|
return this.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: url,
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'text/plain; charset=UTF-8',
|
||||||
|
data: program,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function initial_load() {
|
||||||
|
Dashboard.showLoadingMsg();
|
||||||
|
Dashboard.hideLoadingMsg();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector('#LispPlaygroundConfigPage')
|
||||||
|
.addEventListener('viewshow', function() {
|
||||||
|
initial_load();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('#LispPlaygroundConfigPage')
|
||||||
|
.addEventListener('pageshow', function() {
|
||||||
|
initial_load();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('#LispPlaygroundConfigForm')
|
||||||
|
.addEventListener('submit', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
Dashboard.showLoadingMsg();
|
||||||
|
const editProgram = document.querySelector('#LispPlaygroundEditProgram');
|
||||||
|
ApiClient.runLispProgram(editProgram.value).then(function (r) {
|
||||||
|
fillForm(r);
|
||||||
|
Dashboard.hideLoadingMsg();
|
||||||
|
});
|
||||||
|
});
|
|
@ -53,6 +53,16 @@ namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
Name = "smartCollections.js",
|
Name = "smartCollections.js",
|
||||||
EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Pages.smartCollections.js", GetType().Namespace),
|
EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Pages.smartCollections.js", GetType().Namespace),
|
||||||
},
|
},
|
||||||
|
new PluginPageInfo {
|
||||||
|
Name = "Lisp Playground",
|
||||||
|
DisplayName = "Lisp Playground",
|
||||||
|
EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Pages.lispPlayground.html", GetType().Namespace),
|
||||||
|
EnableInMainMenu = true,
|
||||||
|
},
|
||||||
|
new PluginPageInfo {
|
||||||
|
Name = "lispPlayground.js",
|
||||||
|
EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Pages.lispPlayground.js", GetType().Namespace),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,12 +21,16 @@
|
||||||
<None Remove="Pages\smartPlaylists.js"/>
|
<None Remove="Pages\smartPlaylists.js"/>
|
||||||
<None Remove="Pages\smartCollections.html"/>
|
<None Remove="Pages\smartCollections.html"/>
|
||||||
<None Remove="Pages\smartCollections.js"/>
|
<None Remove="Pages\smartCollections.js"/>
|
||||||
|
<None Remove="Pages\lispPlayground.html"/>
|
||||||
|
<None Remove="Pages\lispPlayground.js"/>
|
||||||
<EmbeddedResource Include="Configuration\configPage.html"/>
|
<EmbeddedResource Include="Configuration\configPage.html"/>
|
||||||
<EmbeddedResource Include="Configuration\configPage.js"/>
|
<EmbeddedResource Include="Configuration\configPage.js"/>
|
||||||
<EmbeddedResource Include="Pages\smartPlaylists.html"/>
|
<EmbeddedResource Include="Pages\smartPlaylists.html"/>
|
||||||
<EmbeddedResource Include="Pages\smartPlaylists.js"/>
|
<EmbeddedResource Include="Pages\smartPlaylists.js"/>
|
||||||
<EmbeddedResource Include="Pages\smartCollections.html"/>
|
<EmbeddedResource Include="Pages\smartCollections.html"/>
|
||||||
<EmbeddedResource Include="Pages\smartCollections.js"/>
|
<EmbeddedResource Include="Pages\smartCollections.js"/>
|
||||||
|
<EmbeddedResource Include="Pages\lispPlayground.html"/>
|
||||||
|
<EmbeddedResource Include="Pages\lispPlayground.js"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
Loading…
Add table
Reference in a new issue