From 4ad24ad5ef8dc93a4c92cb73e2b355b9a0d315cf Mon Sep 17 00:00:00 2001 From: potoo0 <1415615232@qq.com> Date: Wed, 1 Jun 2022 16:50:31 +0800 Subject: [PATCH 01/13] update dependences version --- LICENSE | 4 +- build.gradle | 177 ++++-------- buildSrc/build.gradle | 13 - .../gradle/DeclaredModuleInfo.groovy | 7 - .../gradle/InventoryReportRenderer.groovy | 91 ------ .../NewInventoryHtmlReportRenderer.groovy | 267 ------------------ .../resources/license-report.template.css | 151 ---------- .../resources/license-report.template.html | 71 ----- gradle/wrapper/gradle-wrapper.properties | 2 +- .../io/github/spencerpark/ijava/IJava.java | 2 +- .../github/spencerpark/ijava/JavaKernel.java | 4 +- .../ijava/execution/CodeEvaluator.java | 2 +- .../ijava/execution/CodeEvaluatorBuilder.java | 18 +- .../ijava/execution/CompilationException.java | 2 +- .../EvaluationInterruptedException.java | 23 ++ .../execution/EvaluationTimeoutException.java | 2 +- .../execution/IJavaExecutionControl.java | 2 +- .../IJavaExecutionControlProvider.java | 2 +- .../execution/IncompleteSourceException.java | 2 +- .../execution/LazyInputStreamDelegate.java | 2 +- .../execution/LazyOutputStreamDelegate.java | 2 +- .../execution/MagicsSourceTransformer.java | 2 +- .../ijava/magics/ClasspathMagics.java | 28 +- .../ijava/magics/MavenResolver.java | 2 +- .../dependencies/CommonRepositories.java | 23 ++ .../ijava/magics/dependencies/Maven.java | 26 +- .../ijava/magics/dependencies/MavenToIvy.java | 23 ++ .../spencerpark/ijava/runtime/Display.java | 23 ++ .../spencerpark/ijava/runtime/Kernel.java | 23 ++ .../spencerpark/ijava/runtime/Magics.java | 23 ++ .../spencerpark/ijava/utils/FileUtils.java | 63 +++++ src/main/resources/ijava-jshell-init.jshell | 2 +- .../github/spencerpark/ijava/TestUtils.java | 57 ++++ 33 files changed, 398 insertions(+), 743 deletions(-) delete mode 100644 buildSrc/build.gradle delete mode 100644 buildSrc/src/main/groovy/io/github/spencerpark/gradle/DeclaredModuleInfo.groovy delete mode 100644 buildSrc/src/main/groovy/io/github/spencerpark/gradle/InventoryReportRenderer.groovy delete mode 100644 buildSrc/src/main/groovy/io/github/spencerpark/gradle/NewInventoryHtmlReportRenderer.groovy delete mode 100644 buildSrc/src/main/resources/license-report.template.css delete mode 100644 buildSrc/src/main/resources/license-report.template.html create mode 100644 src/main/java/io/github/spencerpark/ijava/utils/FileUtils.java create mode 100644 src/test/java/io/github/spencerpark/ijava/TestUtils.java diff --git a/LICENSE b/LICENSE index 4404db1..ae0ff2b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018 Spencer Park +Copyright (c) ${year} ${author} Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/build.gradle b/build.gradle index 2c05250..4ce2c48 100644 --- a/build.gradle +++ b/build.gradle @@ -1,153 +1,96 @@ -plugins { - id 'java' - id 'maven-publish' - id('com.github.hierynomus.license') version '0.14.0' - id('io.github.spencerpark.jupyter-kernel-installer') version '2.1.0' - id('com.github.jk1.dependency-license-report') -} - +import com.github.jk1.license.filter.LicenseBundleNormalizer +import com.github.jk1.license.render.InventoryHtmlReportRenderer +import com.github.jk1.license.render.JsonReportRenderer import org.apache.tools.ant.filters.ReplaceTokens -import com.github.jk1.license.render.* -import com.github.jk1.license.filter.* -import io.github.spencerpark.gradle.* -group = 'io.github.spencerpark' -version = '1.3.0' - -wrapper { - gradleVersion = '4.8.1' - distributionType = Wrapper.DistributionType.ALL -} - -// Add the license header to source files -license { - header = file('LICENSE') - exclude '**/*.json' - mapping { - // Use a regular multiline comment rather than a javadoc comment - java = 'SLASHSTAR_STYLE' - } -} -build.dependsOn 'licenseFormat' -// Configures the license report generated for the dependencies. -licenseReport { - excludeGroups = [] - renderers = [ - // Generate a pretty HTML report that groups dependencies by their license. - new NewInventoryHtmlReportRenderer('dependencies.html'), - // TODO make sure ci verifies that all licenses are know to be allowed to redistribute before publishing - new JsonReportRenderer('dependencies.json') - ] - - // Group same licenses despite names being slightly different (ex. Apache 2.0 vs Apache version 2) - filters = [new LicenseBundleNormalizer()] - - configurations = ['compile'] -} - -compileJava { - sourceCompatibility = 1.9 - targetCompatibility = 1.9 +plugins { + id 'java' + // id 'maven-publish' // use johnrengelman.shadow instead + id 'com.github.hierynomus.license' version '0.16.1' + id "com.github.jk1.dependency-license-report" version "2.1" + id 'com.github.johnrengelman.shadow' version '7.1.2' +// id 'io.github.spencerpark.jupyter-kernel-installer' version '2.1.0' } -configurations { - shade - // transitive true to make sure that the dependencies of shade dependencies also get shaded - // into the jar - shade.transitive = true - compile.extendsFrom(shade) -} +group = 'io.github.spencerpark' +version = '1.3.1' repositories { + mavenLocal() mavenCentral() maven { url = 'https://oss.sonatype.org/content/repositories/snapshots/' } - mavenLocal() } dependencies { - shade group: 'io.github.spencerpark', name: 'jupyter-jvm-basekernel', version: '2.3.0' - - shade group: 'org.apache.ivy', name: 'ivy', version: '2.5.0-rc1' - //shade group: 'org.apache.maven', name: 'maven-settings-builder', version: '3.6.0' - shade group: 'org.apache.maven', name: 'maven-model-builder', version: '3.6.0' + implementation group: 'io.github.spencerpark', name: 'jupyter-jvm-basekernel', version: '2.3.0' + implementation group: 'org.apache.ivy', name: 'ivy', version: '2.5.0' + implementation group: 'org.apache.maven', name: 'maven-model-builder', version: '3.8.5' - testCompile group: 'junit', name: 'junit', version: '4.12' + testImplementation group: 'junit', name: 'junit', version: '4.13.2' } -jar { - //Include all shaded dependencies in the jar - from configurations.shade - .collect { it.isDirectory() ? it : zipTree(it) } - - manifest { - attributes('Main-class': 'io.github.spencerpark.ijava.IJava') - } +// Add the license header to source files +license { + header = file('LICENSE') + include "**/*.java" + exclude "**/Test*.java" + mapping java: 'SLASHSTAR_STYLE' + ext.year = Calendar.getInstance().get(Calendar.YEAR) } +licenseMain.dependsOn 'licenseFormat' +// replace @symbol@ in properties processResources { def tokens = [ 'version': project.version, 'project': project.name ] inputs.properties(tokens) - filter ReplaceTokens, tokens: tokens + filter tokens: tokens, ReplaceTokens } -publishing { - publications { - mavenJava(MavenPublication) { - from components.java - } +jar { + manifest { + attributes 'Main-class': 'io.github.spencerpark.ijava.IJava' } } -jupyter { - kernelName = 'java' - kernelDisplayName = 'Java' - kernelLanguage = 'java' - kernelInterruptMode = 'message' +// shadow Jar config +shadowJar { + // inherit from the manifest of the standard jar task + // manifest { + // attributes 'Main-class': 'io.github.spencerpark.ijava.IJava' + // } - kernelParameters { - list('classpath', 'IJAVA_CLASSPATH') { - separator = PATH_SEPARATOR - description = '''A file path separator delimited list of classpath entries that should be available to the user code. **Important:** no matter what OS, this should use forward slash "/" as the file separator. Also each path may actually be a simple glob.''' - } - - list('comp-opts', 'IJAVA_COMPILER_OPTS') { - separator = ' ' - description = '''A space delimited list of command line options that would be passed to the `javac` command when compiling a project. For example `-parameters` to enable retaining parameter names for reflection.''' - } - - list('startup-scripts-path', 'IJAVA_STARTUP_SCRIPTS_PATH') { - separator = PATH_SEPARATOR - description = '''A file path seperator delimited list of `.jshell` scripts to run on startup. This includes ijava-jshell-init.jshell and ijava-display-init.jshell. **Important:** no matter what OS, this should use forward slash "/" as the file separator. Also each path may actually be a simple glob.''' - } - - string('startup-script', 'IJAVA_STARTUP_SCRIPT') { - description = '''A block of java code to run when the kernel starts up. This may be something like `import my.utils;` to setup some default imports or even `void sleep(long time) { try {Thread.sleep(time); } catch (InterruptedException e) { throw new RuntimeException(e); }}` to declare a default utility method to use in the notebook.''' - } - - string('timeout', 'IJAVA_TIMEOUT') { - aliases NO_TIMEOUT: '-1' - description = '''A duration specifying a timeout (in milliseconds by default) for a _single top level statement_. If less than `1` then there is no timeout. If desired a time may be specified with a `TimeUnit` may be given following the duration number (ex `"30 SECONDS"`).''' - } + // copy build.gradle to shadowed jar + from("./") { + include 'build.gradle' } } -installKernel { - kernelInstallPath = commandLineSpecifiedPath(userInstallPath) -} - -zipKernel { - installers { - with 'python' - } +// publish +//publishing { +// publications { +// shadow(MavenPublication) { publication -> +// project.shadow.component(publication) +// } +// } +// repositories { +// maven { +// url "http://repo.myorg.com" +// } +// } +//} + +// create license report +licenseReport { + renderers = [ + new InventoryHtmlReportRenderer('license-report.html'), + new JsonReportRenderer('license-report.json') + ] - from(generateLicenseReport.outputFolder) { - into 'dependency-licenses' - } + filters = [new LicenseBundleNormalizer()] } -zipKernel.dependsOn 'generateLicenseReport' \ No newline at end of file diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle deleted file mode 100644 index bb112f9..0000000 --- a/buildSrc/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -plugins { - id 'groovy' -} - -repositories { - maven { - url = 'https://plugins.gradle.org/m2/' - } -} - -dependencies { - compile group: 'com.github.jk1.dependency-license-report', name: 'com.github.jk1.dependency-license-report.gradle.plugin', version: '1.1' -} \ No newline at end of file diff --git a/buildSrc/src/main/groovy/io/github/spencerpark/gradle/DeclaredModuleInfo.groovy b/buildSrc/src/main/groovy/io/github/spencerpark/gradle/DeclaredModuleInfo.groovy deleted file mode 100644 index 4216c1a..0000000 --- a/buildSrc/src/main/groovy/io/github/spencerpark/gradle/DeclaredModuleInfo.groovy +++ /dev/null @@ -1,7 +0,0 @@ -package io.github.spencerpark.gradle - -class DeclaredModuleInfo { - String projectUrl - String license - String licenseUrl -} diff --git a/buildSrc/src/main/groovy/io/github/spencerpark/gradle/InventoryReportRenderer.groovy b/buildSrc/src/main/groovy/io/github/spencerpark/gradle/InventoryReportRenderer.groovy deleted file mode 100644 index a15d2bb..0000000 --- a/buildSrc/src/main/groovy/io/github/spencerpark/gradle/InventoryReportRenderer.groovy +++ /dev/null @@ -1,91 +0,0 @@ -package io.github.spencerpark.gradle - -import com.github.jk1.license.* -import groovy.json.JsonParserType -import groovy.json.JsonSlurper - -abstract class InventoryReportRenderer { - /** - * Collect declared from a JSON object with maven coordinates as keys and {@link DeclaredModuleInfo} - * as values - * @param declarations the stream to parse the json object from - * @return the collected declarations - */ - static Map parseDeclarations(InputStream declarations) { - def rawDeclarations = new JsonSlurper() - .setType(JsonParserType.LAX) - .parse(declarations) - - assert rawDeclarations instanceof Map: "Declaration spec must be a json object" - - return rawDeclarations.collectEntries([:]) { coords, spec -> - assert spec instanceof Map: "Declaration spec for $coords is not an object" - - DeclaredModuleInfo info = new DeclaredModuleInfo() - spec.forEach { String key, val -> - assert val instanceof String: "Declaration spec for $coords::$key must be a string" - assert info.hasProperty(key), "Declaration spec for $coords has unknown key $key" - - info[key] = val - } - assert info.projectUrl: "Declaration missing required key: projectUrl" - assert info.license: "Declaration missing required key: license" - assert info.licenseUrl: "Declaration missing required key: licenseUrl" - - return [(coords): info] - } - } - - static Map> collectModulesByLicenseName(ProjectData data, Map declared) { - Map> modulesByLicense = [:] - - def addModule = { String licenseName, ModuleData module -> - String coords = module.with { "$group:$name:$version" } - - if (licenseName == "Unknown" && declared.containsKey(coords)) - licenseName = declared[coords].license - - modulesByLicense.compute(licenseName) { k, modules -> - return (modules ?: []) << module - } - } - - data.allDependencies.each { module -> - if (module.poms.isEmpty()) { - addModule(module.licenseFiles.isEmpty() ? "Unknown" : "Embedded", module) - return - } - - PomData pom = module.poms.first() - if (pom.licenses.isEmpty()) { - addModule(module.licenseFiles.isEmpty() ? "Unknown" : "Embedded", module) - } else { - pom.licenses.each { License license -> - addModule(license.name, module) - } - } - } - - return modulesByLicense - } - - // imported modules are things declared as dependencies but not actually included via gradle means. For - // example a javascript dependency. - static Map>> collectModulesByLicenseFromImported(ProjectData data) { - Map>> externalByModulesByLicense = [:] - - data.importedModules.each { ImportedModuleBundle module -> - Map> modulesByLicense = [:] - - module.modules.each { ImportedModuleData moduleData -> - modulesByLicense.compute(moduleData.license) { k, modules -> - return (modules ?: []) << moduleData - } - } - - externalByModulesByLicense[module.name] = modulesByLicense - } - - return externalByModulesByLicense - } -} diff --git a/buildSrc/src/main/groovy/io/github/spencerpark/gradle/NewInventoryHtmlReportRenderer.groovy b/buildSrc/src/main/groovy/io/github/spencerpark/gradle/NewInventoryHtmlReportRenderer.groovy deleted file mode 100644 index 5182943..0000000 --- a/buildSrc/src/main/groovy/io/github/spencerpark/gradle/NewInventoryHtmlReportRenderer.groovy +++ /dev/null @@ -1,267 +0,0 @@ -package io.github.spencerpark.gradle - -import com.github.jk1.license.* -import com.github.jk1.license.render.ReportRenderer -import groovy.text.SimpleTemplateEngine -import groovy.text.StreamingTemplateEngine - -class NewInventoryHtmlReportRenderer implements ReportRenderer { - private final String name - private final String fileName - private final Map declared - private final Map colors = [ - accent : '#F37726', - primary : 'white', - accentBg : '#616262', - primaryBg: '#989798', - darkText : '#4E4E4E', - lightText: '#e8e5e5', - ] - - private Writer output - private int counter - - NewInventoryHtmlReportRenderer(String fileName = 'index.html', String name = null, File declarationsFileName = null, Map colors = [:]) { - this.name = name - this.fileName = fileName - - if (declarationsFileName) - declared = InventoryReportRenderer.parseDeclarations(declarationsFileName.newInputStream()) - else - declared = [:] - - this.colors.putAll(colors) - } - - @Override - void render(ProjectData data) { - this.counter = 0 - - def project = data.project - def name = project.name - LicenseReportExtension config = project.licenseReport - def outFile = new File(config.outputDir, fileName) - - def stylesheet = NewInventoryHtmlReportRenderer.class.getResourceAsStream("/license-report.template.css").withReader { - new SimpleTemplateEngine() - .createTemplate(it) - .make(this.colors) - .writeTo(new StringWriter()) - .toString() - } - def template = NewInventoryHtmlReportRenderer.class.getResourceAsStream("/license-report.template.html").withReader { - new StreamingTemplateEngine().createTemplate(it) - } - - def binding = [ - stylesheet : stylesheet, - name : name, - project : project, - inventory : InventoryReportRenderer.collectModulesByLicenseName(data, declared), - externalInventories : InventoryReportRenderer.collectModulesByLicenseFromImported(data), - serializeHref : { String... values -> - values.findAll { it != null }.collect { it.replaceAll(/\s/, '_') }.join('_') - }, - printDependency : this.&printDependency, - printImportedDependency: this.&printImportedDependency, - ] - - outFile.withWriter { - template.make(binding).writeTo(it) - } - } - - void tag(Map attrs = [:], String name, def children) { - output << "<$name ${attrs.collect { k, v -> "$k=\"$v\"" }.join(" ")}>\n" - if (children.respondsTo("call")) - children.call() - else - text(children) - output << "\n" - } - - void div(Map attrs = [:], def children) { - tag(attrs, "div", children) - } - - void p(Map attrs = [:], def children) { - tag(attrs, "p", children) - } - - void a(Map attrs = [:], def children) { - tag(attrs, "a", children) - } - - void strong(Map attrs = [:], def children) { - tag(attrs, "strong", children) - } - - void ul(Map attrs = [:], def children) { - tag(attrs, "ul", children) - } - - void li(Map attrs = [:], def children) { - tag(attrs, "li", children) - } - - void text(def contents) { - if (contents != null) - output << String.valueOf(contents) - } - - void renderDependencyProperty(String label, def children) { - div(class: "dependency-prop") { - tag("label") { text(label) } - div(class: "dependency-value", children) - } - } - - void renderDependencyTitle(ModuleData data) { - p(class: "title") { - strong(class: "index", "${++counter}.") - - if (data.group) { - strong("Group: ") - text(data.group) - } - - if (data.name) { - strong("Name: ") - text(data.name) - } - - if (data.version) { - strong("Version: ") - text(data.version) - } - } - } - - void renderDependencyTitle(ImportedModuleData data) { - p(class: "title") { - strong(class: "index", ++counter) - - if (data.name) { - strong("Name: ") - text(data.name) - } - - if (data.version) { - strong("Version: ") - text(data.version) - } - } - } - - void renderDependencyProjectUrl(ModuleData data) { - String coords = data.with { "$group:$name:$version" } - - ManifestData manifest = data.manifests.isEmpty() ? null : data.manifests.first() - PomData pomData = data.poms.isEmpty() ? null : data.poms.first() - - if (manifest?.url && pomData?.projectUrl && manifest.url == pomData.projectUrl) { - renderDependencyProperty("Project URL") { - a(href: manifest.url, { text(manifest.url) }) - } - } else if (manifest?.url || pomData?.projectUrl) { - if (manifest?.url) { - renderDependencyProperty("Manifest Project URL") { - a(href: manifest.url, { text(manifest.url) }) - } - } - - if (pomData?.projectUrl) { - renderDependencyProperty("POM Project URL") { - a(href: pomData.projectUrl, { text(pomData.projectUrl) }) - } - } - } else if (declared.containsKey(coords)) { - renderDependencyProperty("Project URL") { - a(href: declared[coords].projectUrl, { text(declared[coords].projectUrl) }) - } - } - } - - void renderReferencedLicenses(ModuleData data) { - String coords = data.with { "$group:$name:$version" } - - ManifestData manifest = data.manifests.isEmpty() ? null : data.manifests.first() - PomData pomData = data.poms.isEmpty() ? null : data.poms.first() - - if (manifest?.license || pomData?.licenses) { - if (manifest?.license) { - if (manifest.license.startsWith("http")) { - renderDependencyProperty("Manifest license URL") { - a(href: manifest.license, { text(manifest.license) }) - } - } else if (manifest.hasPackagedLicense) { - renderDependencyProperty("Packaged License File") { - a(href: manifest.license, { text(manifest.url) }) - } - } else { - renderDependencyProperty("Manifest License") { - text("${manifest.license} (Not Packaged)") - } - } - } - - if (pomData?.licenses) { - pomData.licenses.each { License license -> - if (license.url) { - renderDependencyProperty("POM License") { - text("${license.name} - ") - if (license.url.startsWith("http")) - a(href: license.url, { text(license.url) }) - else - text(license.url) - } - } else { - renderDependencyProperty("POM License", { text(license.name) }) - } - } - } - } else if (declared.containsKey(coords)) { - renderDependencyProperty("License URL") { - a(href: declared[coords].licenseUrl, { text(declared[coords].license) }) - } - } - } - - void renderIncludedLicenses(ModuleData data) { - if (!data.licenseFiles.isEmpty() && !data.licenseFiles.first().fileDetails.isEmpty()) { - renderDependencyProperty("Embedded license files") { - ul { - data.licenseFiles.first().fileDetails.each { - def file = it.file - li { - a(href: file, { text(file) }) - } - } - } - } - } - } - - private void printDependency(Writer out, ModuleData data) { - this.output = out - div(class: "dependency") { - renderDependencyTitle(data) - renderDependencyProjectUrl(data) - renderReferencedLicenses(data) - renderIncludedLicenses(data) - } - } - - private printImportedDependency(Writer out, ImportedModuleData data) { - this.output = out - div(class: "dependency") { - renderDependencyTitle(data) - renderDependencyProperty("Project URL") { - a(href: data.projectUrl, { text(data.projectUrl) }) - } - renderDependencyProperty("License URL") { - a(href: data.licenseUrl, { text(data.license) }) - } - } - } -} diff --git a/buildSrc/src/main/resources/license-report.template.css b/buildSrc/src/main/resources/license-report.template.css deleted file mode 100644 index d6bbcdb..0000000 --- a/buildSrc/src/main/resources/license-report.template.css +++ /dev/null @@ -1,151 +0,0 @@ -@media print { - .inventory { - display: none; - } - - .content { - position: static !important; - } -} - -html, body, section { - height: 100%; -} - -body { - font-family: sans-serif; - line-height: 125%; - margin: 0; - background: $primaryBg; -} - -.header { - /* #22aa44 */ - background: $accent; - color: $darkText; - padding: 2em 1em 1em 1em; -} - -.header h1 { - font-size: 16pt; - margin: 0.5em 0; -} - -.header h2 { - font-size: 10pt; - margin: 0; -} - -.container { -} - -.inventory { - background: $accentBg; - color: $lightText; - padding: 0; - position: fixed; - left: 0; - top: 0; - height: 100%; - width: 25%; - overflow: auto; -} - -.inventory ul { - margin: 0; - padding: 0; -} - -.inventory li { - list-style: none; - padding: 0; - margin: 0; -} - -.inventory li a { - width: 100%; - box-sizing: border-box; - color: $lightText; - text-decoration: none; - display: flex; - flex-direction: row; - padding: 0.938em 0.750em; -} - -.inventory li a:hover { - background: rgba(50, 50, 50, 0.5); - color: white; -} - -.inventory .section-heading { - background: rgba(50, 50, 50, 0.25); - padding-left: 0.5em; - margin: 0; - padding-top: 1em; - padding-bottom: 1em; -} - -.license .license-name { - flex-grow: 1; -} - -.license .badge { - background: $accent; - padding: 0.625em 0.938em; - border-radius: 1.250em; - color: $darkText; - display: inline-table; -} - -.content { - padding: 0 1rem; - position: absolute; - top: 0; - bottom: 0; - left: 25%; - width: 75%; - box-sizing: border-box; -} - -.content h1 { - color: $darkText; - background-color: $accent; - padding: 0.67em; - margin: 0 -1rem; -} - -.dependency { - background: white; - padding: 1em; - margin-bottom: 1em; -} - -.dependency:hover { - box-shadow: dimgrey 0.2em 0.2em 0.2em 0em; -} - -.dependency .index { - font-size: larger; - font-weight: lighter; -} - -.dependency-prop { - padding: 0.3em; -} - -.dependency-prop:hover { - background: rgba(50, 50, 50, 0.25); -} - -.dependency-prop label { - font-weight: bold; -} - -.dependency-value { - padding-left: 1em; -} - -.dependency-value ul { - margin-top: 0; - margin-bottom: 0; -} diff --git a/buildSrc/src/main/resources/license-report.template.html b/buildSrc/src/main/resources/license-report.template.html deleted file mode 100644 index 1a714d2..0000000 --- a/buildSrc/src/main/resources/license-report.template.html +++ /dev/null @@ -1,71 +0,0 @@ - - - Dependency License Report for $name - - - -
- - -
-
-

${project.name} ${!"unspecified".equals(project.version) ? project.version : ""}

-

Dependency License Report

-

- ${new Date().format("yyyy-MM-dd HH:mm:ss z")} -

-
- -

${name}

- - - <% externalInventories.each { name, modules -> %> -

${name}

- - <% } %> -
- - -
-

${name}

- <% inventory.keySet().sort().each { license -> %> - -

${license}

- <% inventory[license].sort({ a, b -> a.group <=> b.group }).each { data -> - printDependency.call(out, data) - } %> - <% } %> - - <% externalInventories.keySet().sort().each { String name -> %> -

${name}

- <% externalInventories[name].each { String license, dependencies -> %> - - <% dependencies.each { importedData -> - printImportedDependency.call(out, importedData) - } %> - <% } %> - <% } %> -
-
- - \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0c28a15..44ff29f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/src/main/java/io/github/spencerpark/ijava/IJava.java b/src/main/java/io/github/spencerpark/ijava/IJava.java index 05dcc91..ef47218 100644 --- a/src/main/java/io/github/spencerpark/ijava/IJava.java +++ b/src/main/java/io/github/spencerpark/ijava/IJava.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2018 Spencer Park + * Copyright (c) 2022 ${author} * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/io/github/spencerpark/ijava/JavaKernel.java b/src/main/java/io/github/spencerpark/ijava/JavaKernel.java index b61d3af..1c4638c 100644 --- a/src/main/java/io/github/spencerpark/ijava/JavaKernel.java +++ b/src/main/java/io/github/spencerpark/ijava/JavaKernel.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2018 Spencer Park + * Copyright (c) 2022 ${author} * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -30,8 +30,8 @@ import io.github.spencerpark.jupyter.kernel.LanguageInfo; import io.github.spencerpark.jupyter.kernel.ReplacementOptions; import io.github.spencerpark.jupyter.kernel.display.DisplayData; -import io.github.spencerpark.jupyter.kernel.magic.registry.Magics; import io.github.spencerpark.jupyter.kernel.magic.common.Load; +import io.github.spencerpark.jupyter.kernel.magic.registry.Magics; import io.github.spencerpark.jupyter.kernel.util.CharPredicate; import io.github.spencerpark.jupyter.kernel.util.StringStyler; import io.github.spencerpark.jupyter.kernel.util.TextColor; diff --git a/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluator.java b/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluator.java index 4583b31..490a272 100644 --- a/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluator.java +++ b/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluator.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2018 Spencer Park + * Copyright (c) 2022 ${author} * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluatorBuilder.java b/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluatorBuilder.java index 0ff23e7..71f7358 100644 --- a/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluatorBuilder.java +++ b/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluatorBuilder.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2018 Spencer Park + * Copyright (c) 2022 ${author} * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,6 +23,7 @@ */ package io.github.spencerpark.ijava.execution; +import io.github.spencerpark.ijava.utils.FileUtils; import io.github.spencerpark.jupyter.kernel.util.GlobFinder; import jdk.jshell.JShell; @@ -43,13 +44,13 @@ public class CodeEvaluatorBuilder { private static final OutputStream STDERR = new LazyOutputStreamDelegate(() -> System.err); private static final InputStream STDIN = new LazyInputStreamDelegate(() -> System.in); - private String timeout; private final List classpath; private final List compilerOpts; + private final List startupScripts; + private String timeout; private PrintStream out; private PrintStream err; private InputStream in; - private List startupScripts; public CodeEvaluatorBuilder() { this.classpath = new LinkedList<>(); @@ -147,6 +148,15 @@ public CodeEvaluatorBuilder startupScript(InputStream scriptStream) { } public CodeEvaluatorBuilder startupScriptFiles(String paths) { + // todo debug + try { + String glob1 = "glob:*"; + String path1 = "."; + System.out.println("------------- startup: " + FileUtils.listMatchedFilePath(glob1, path1)); + } catch (IOException e) { + e.printStackTrace(); + } + if (paths == null) return this; if (BLANK.matcher(paths).matches()) return this; @@ -173,7 +183,7 @@ public CodeEvaluatorBuilder startupScriptFile(Path path) { return this; try { - String script = new String(Files.readAllBytes(path), "UTF-8"); + String script = Files.readString(path); this.startupScripts.add(script); } catch (IOException e) { throw new RuntimeException(String.format("IOException while loading startup script for '%s': %s", path, e.getMessage()), e); diff --git a/src/main/java/io/github/spencerpark/ijava/execution/CompilationException.java b/src/main/java/io/github/spencerpark/ijava/execution/CompilationException.java index 33777f4..c1d353f 100644 --- a/src/main/java/io/github/spencerpark/ijava/execution/CompilationException.java +++ b/src/main/java/io/github/spencerpark/ijava/execution/CompilationException.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2018 Spencer Park + * Copyright (c) 2022 ${author} * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/io/github/spencerpark/ijava/execution/EvaluationInterruptedException.java b/src/main/java/io/github/spencerpark/ijava/execution/EvaluationInterruptedException.java index a1382fa..78ffa11 100644 --- a/src/main/java/io/github/spencerpark/ijava/execution/EvaluationInterruptedException.java +++ b/src/main/java/io/github/spencerpark/ijava/execution/EvaluationInterruptedException.java @@ -1,3 +1,26 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 ${author} + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package io.github.spencerpark.ijava.execution; public class EvaluationInterruptedException extends Exception { diff --git a/src/main/java/io/github/spencerpark/ijava/execution/EvaluationTimeoutException.java b/src/main/java/io/github/spencerpark/ijava/execution/EvaluationTimeoutException.java index 9765e31..1ff05e0 100644 --- a/src/main/java/io/github/spencerpark/ijava/execution/EvaluationTimeoutException.java +++ b/src/main/java/io/github/spencerpark/ijava/execution/EvaluationTimeoutException.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2018 Spencer Park + * Copyright (c) 2022 ${author} * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/io/github/spencerpark/ijava/execution/IJavaExecutionControl.java b/src/main/java/io/github/spencerpark/ijava/execution/IJavaExecutionControl.java index 5f536f8..e82e2f0 100644 --- a/src/main/java/io/github/spencerpark/ijava/execution/IJavaExecutionControl.java +++ b/src/main/java/io/github/spencerpark/ijava/execution/IJavaExecutionControl.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2018 Spencer Park + * Copyright (c) 2022 ${author} * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/io/github/spencerpark/ijava/execution/IJavaExecutionControlProvider.java b/src/main/java/io/github/spencerpark/ijava/execution/IJavaExecutionControlProvider.java index ac12319..7e9aa74 100644 --- a/src/main/java/io/github/spencerpark/ijava/execution/IJavaExecutionControlProvider.java +++ b/src/main/java/io/github/spencerpark/ijava/execution/IJavaExecutionControlProvider.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2018 Spencer Park + * Copyright (c) 2022 ${author} * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/io/github/spencerpark/ijava/execution/IncompleteSourceException.java b/src/main/java/io/github/spencerpark/ijava/execution/IncompleteSourceException.java index 263b7b0..79861da 100644 --- a/src/main/java/io/github/spencerpark/ijava/execution/IncompleteSourceException.java +++ b/src/main/java/io/github/spencerpark/ijava/execution/IncompleteSourceException.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2018 Spencer Park + * Copyright (c) 2022 ${author} * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/io/github/spencerpark/ijava/execution/LazyInputStreamDelegate.java b/src/main/java/io/github/spencerpark/ijava/execution/LazyInputStreamDelegate.java index 8f270e2..bbb3767 100644 --- a/src/main/java/io/github/spencerpark/ijava/execution/LazyInputStreamDelegate.java +++ b/src/main/java/io/github/spencerpark/ijava/execution/LazyInputStreamDelegate.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2018 Spencer Park + * Copyright (c) 2022 ${author} * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/io/github/spencerpark/ijava/execution/LazyOutputStreamDelegate.java b/src/main/java/io/github/spencerpark/ijava/execution/LazyOutputStreamDelegate.java index 535540c..ca488e0 100644 --- a/src/main/java/io/github/spencerpark/ijava/execution/LazyOutputStreamDelegate.java +++ b/src/main/java/io/github/spencerpark/ijava/execution/LazyOutputStreamDelegate.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2018 Spencer Park + * Copyright (c) 2022 ${author} * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/io/github/spencerpark/ijava/execution/MagicsSourceTransformer.java b/src/main/java/io/github/spencerpark/ijava/execution/MagicsSourceTransformer.java index 5694641..7f1d644 100644 --- a/src/main/java/io/github/spencerpark/ijava/execution/MagicsSourceTransformer.java +++ b/src/main/java/io/github/spencerpark/ijava/execution/MagicsSourceTransformer.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2018 Spencer Park + * Copyright (c) 2022 ${author} * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/io/github/spencerpark/ijava/magics/ClasspathMagics.java b/src/main/java/io/github/spencerpark/ijava/magics/ClasspathMagics.java index 3f6ddb8..796c10e 100644 --- a/src/main/java/io/github/spencerpark/ijava/magics/ClasspathMagics.java +++ b/src/main/java/io/github/spencerpark/ijava/magics/ClasspathMagics.java @@ -1,3 +1,26 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 ${author} + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package io.github.spencerpark.ijava.magics; import io.github.spencerpark.jupyter.kernel.magic.registry.LineMagic; @@ -6,7 +29,6 @@ import java.io.IOException; import java.util.List; import java.util.function.Consumer; -import java.util.stream.Collectors; import java.util.stream.StreamSupport; public class ClasspathMagics { @@ -28,7 +50,7 @@ public List jars(List args) { } }) .map(p -> p.toAbsolutePath().toString()) - .collect(Collectors.toList()); + .toList(); jars.forEach(this.addToClasspath); @@ -47,7 +69,7 @@ public List classpath(List args) { } }) .map(p -> p.toAbsolutePath().toString()) - .collect(Collectors.toList()); + .toList(); paths.forEach(this.addToClasspath); diff --git a/src/main/java/io/github/spencerpark/ijava/magics/MavenResolver.java b/src/main/java/io/github/spencerpark/ijava/magics/MavenResolver.java index b0014bc..fa32d67 100644 --- a/src/main/java/io/github/spencerpark/ijava/magics/MavenResolver.java +++ b/src/main/java/io/github/spencerpark/ijava/magics/MavenResolver.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2018 Spencer Park + * Copyright (c) 2022 ${author} * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/io/github/spencerpark/ijava/magics/dependencies/CommonRepositories.java b/src/main/java/io/github/spencerpark/ijava/magics/dependencies/CommonRepositories.java index 52b3138..3973676 100644 --- a/src/main/java/io/github/spencerpark/ijava/magics/dependencies/CommonRepositories.java +++ b/src/main/java/io/github/spencerpark/ijava/magics/dependencies/CommonRepositories.java @@ -1,3 +1,26 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 ${author} + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package io.github.spencerpark.ijava.magics.dependencies; import org.apache.ivy.plugins.resolver.DependencyResolver; diff --git a/src/main/java/io/github/spencerpark/ijava/magics/dependencies/Maven.java b/src/main/java/io/github/spencerpark/ijava/magics/dependencies/Maven.java index 2e3fa23..5f321b8 100644 --- a/src/main/java/io/github/spencerpark/ijava/magics/dependencies/Maven.java +++ b/src/main/java/io/github/spencerpark/ijava/magics/dependencies/Maven.java @@ -1,3 +1,26 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 ${author} + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package io.github.spencerpark.ijava.magics.dependencies; import org.apache.maven.building.StringSource; @@ -93,8 +116,7 @@ private Path getUserHomePath() { private Path getGlobalHomePath() { String envM2Home = this.getEnv("M2_HOME"); - return envM2Home != null - ? Paths.get(envM2Home).toAbsolutePath() : null; + return envM2Home != null ? Paths.get(envM2Home).toAbsolutePath() : null; } private Path getUserSettingsPath() { diff --git a/src/main/java/io/github/spencerpark/ijava/magics/dependencies/MavenToIvy.java b/src/main/java/io/github/spencerpark/ijava/magics/dependencies/MavenToIvy.java index f8238a7..5db6631 100644 --- a/src/main/java/io/github/spencerpark/ijava/magics/dependencies/MavenToIvy.java +++ b/src/main/java/io/github/spencerpark/ijava/magics/dependencies/MavenToIvy.java @@ -1,3 +1,26 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 ${author} + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package io.github.spencerpark.ijava.magics.dependencies; import org.apache.ivy.plugins.resolver.ChainResolver; diff --git a/src/main/java/io/github/spencerpark/ijava/runtime/Display.java b/src/main/java/io/github/spencerpark/ijava/runtime/Display.java index 84f572e..885674a 100644 --- a/src/main/java/io/github/spencerpark/ijava/runtime/Display.java +++ b/src/main/java/io/github/spencerpark/ijava/runtime/Display.java @@ -1,3 +1,26 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 ${author} + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package io.github.spencerpark.ijava.runtime; import io.github.spencerpark.ijava.JavaKernel; diff --git a/src/main/java/io/github/spencerpark/ijava/runtime/Kernel.java b/src/main/java/io/github/spencerpark/ijava/runtime/Kernel.java index 595d585..ab36ee1 100644 --- a/src/main/java/io/github/spencerpark/ijava/runtime/Kernel.java +++ b/src/main/java/io/github/spencerpark/ijava/runtime/Kernel.java @@ -1,3 +1,26 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 ${author} + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package io.github.spencerpark.ijava.runtime; import io.github.spencerpark.ijava.IJava; diff --git a/src/main/java/io/github/spencerpark/ijava/runtime/Magics.java b/src/main/java/io/github/spencerpark/ijava/runtime/Magics.java index 384d7ae..c2f9dea 100644 --- a/src/main/java/io/github/spencerpark/ijava/runtime/Magics.java +++ b/src/main/java/io/github/spencerpark/ijava/runtime/Magics.java @@ -1,3 +1,26 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 ${author} + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package io.github.spencerpark.ijava.runtime; import io.github.spencerpark.ijava.IJava; diff --git a/src/main/java/io/github/spencerpark/ijava/utils/FileUtils.java b/src/main/java/io/github/spencerpark/ijava/utils/FileUtils.java new file mode 100644 index 0000000..32abfda --- /dev/null +++ b/src/main/java/io/github/spencerpark/ijava/utils/FileUtils.java @@ -0,0 +1,63 @@ +package io.github.spencerpark.ijava.utils; + +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.*; + +public class FileUtils { + private FileUtils() { + // hide + } + + public static Map readXmlElementText(String filePath, Collection elementNames) throws FileNotFoundException, XMLStreamException { + Map result = new HashMap<>(); + XMLInputFactory xmlInputFactory = XMLInputFactory.newDefaultFactory(); + XMLEventReader reader = xmlInputFactory.createXMLEventReader(new FileInputStream(filePath)); + + while (reader.hasNext()) { + XMLEvent nextEvent = reader.nextEvent(); + + if (nextEvent.isStartElement()) { + StartElement startElement = nextEvent.asStartElement(); + String elementName = startElement.getName().getLocalPart(); + if (elementNames.contains(elementName)) { + // if startElement, then next to textData + nextEvent = reader.nextEvent(); + + result.put(elementName, nextEvent.asCharacters().getData()); + } + } + } + + return result; + } + + public static Collection listMatchedFilePath(String glob, String location) throws IOException { + // String startFolder = new File(FileUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getParentFile().getPath(); + final List matchedPath = new ArrayList<>(); + final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher(glob); + Files.walkFileTree(Path.of(location), new HashSet<>(2), 5, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { + if (pathMatcher.matches(path) && Files.isReadable(path)) { + matchedPath.add(path); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + return FileVisitResult.CONTINUE; + } + }); + return matchedPath; + } +} diff --git a/src/main/resources/ijava-jshell-init.jshell b/src/main/resources/ijava-jshell-init.jshell index 9e944c1..8ee7787 100644 --- a/src/main/resources/ijava-jshell-init.jshell +++ b/src/main/resources/ijava-jshell-init.jshell @@ -12,4 +12,4 @@ import static io.github.spencerpark.ijava.runtime.Magics.*; public void printf(String format, Object... args) { System.out.printf(format, args); -} \ No newline at end of file +} diff --git a/src/test/java/io/github/spencerpark/ijava/TestUtils.java b/src/test/java/io/github/spencerpark/ijava/TestUtils.java new file mode 100644 index 0000000..b4cb081 --- /dev/null +++ b/src/test/java/io/github/spencerpark/ijava/TestUtils.java @@ -0,0 +1,57 @@ +package io.github.spencerpark.ijava; + +import io.github.spencerpark.ijava.utils.FileUtils; +import org.junit.Assert; +import org.junit.Test; + +import javax.xml.stream.XMLStreamException; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.*; + +public class TestUtils { + public static List match(String glob, String location) throws IOException { + final List matchedPath = new ArrayList<>(); + final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher(glob); + Files.walkFileTree(Path.of(location), new HashSet<>(2), 5, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { + if (pathMatcher.matches(path) && Files.isReadable(path)) { + matchedPath.add(path); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + return FileVisitResult.CONTINUE; + } + }); + return matchedPath; + } + + @Test + public void testFileGlob() throws IOException { + String glob = "glob:**/*.zip"; + String path = "E:/flink-platform-dev"; + //path = "."; + //glob = "glob:*"; + System.out.println(File.pathSeparator); + //List match = match(glob, path); + //Assert.assertNotNull(match); + } + + @Test + public void testReadXml() throws XMLStreamException, FileNotFoundException { + String filePath = "D:\\Maven\\apache-maven-3.6.3\\conf\\settings.xml"; + Set elementNames = Collections.singleton("localRepository"); + Map elementTextData = FileUtils.readXmlElementText(filePath, elementNames); + Assert.assertNotNull(elementTextData); + for (String elementName : elementNames) { + Assert.assertEquals(elementTextData.get(elementName), "D:\\Maven\\repository"); + } + } +} From 5a8a0f4d7bfcc628c73c5401bd21b036cb615fc6 Mon Sep 17 00:00:00 2001 From: potoo0 <1415615232@qq.com> Date: Wed, 1 Jun 2022 16:51:37 +0800 Subject: [PATCH 02/13] 1. try some new features in jdk17; 2. print result with variable name. --- README.md | 122 ++++++++++++------ build.gradle | 1 + docs/img/print-with-var-name.png | Bin 0 -> 21337 bytes .../github/spencerpark/ijava/JavaKernel.java | 112 ++++++++-------- .../ijava/execution/CodeEvaluator.java | 48 +++---- .../ijava/execution/CodeEvaluatorBuilder.java | 2 +- .../ijava/magics/MavenResolver.java | 60 ++++----- .../ijava/magics/PrinterMagics.java | 44 +++++++ .../dependencies/CommonRepositories.java | 4 +- .../ijava/magics/dependencies/Maven.java | 66 +++------- .../spencerpark/ijava/utils/FileUtils.java | 27 +++- src/main/resources/print.jshell | 74 +++++++++++ .../github/spencerpark/ijava/TestUtils.java | 2 +- 13 files changed, 349 insertions(+), 213 deletions(-) create mode 100644 docs/img/print-with-var-name.png create mode 100644 src/main/java/io/github/spencerpark/ijava/magics/PrinterMagics.java create mode 100644 src/main/resources/print.jshell diff --git a/README.md b/README.md index 528a278..960ac25 100644 --- a/README.md +++ b/README.md @@ -32,45 +32,57 @@ Clicking on the [![badge](https://img.shields.io/badge/launch-binder-E66581.svg? Currently the kernel supports -* Code execution. - ![output](docs/img/output.png) -* Autocompletion (`TAB` in Jupyter notebook). - ![autocompletion](docs/img/autocompletion.png) -* Code inspection (`Shift-TAB` up to 4 times in Jupyter notebook). - ![code-inspection](docs/img/code-inspection.png) -* Colored, friendly, error message displays. - ![compilation-error](docs/img/compilation-error.png) - ![incomplete-src-error](docs/img/incomplete-src-error.png) - ![runtime-error](docs/img/runtime-error.png) -* Add maven dependencies at runtime (See also [magics.md](docs/magics.md) and [Try the example ![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/SpencerPark/ijava-binder/master?urlpath=lab/tree/home/jovyan/3rdPartyDependency.ipynb)). - ![maven-pom-dep](docs/img/maven-pom-dep.png) -* Display rich output (See also [display.md](docs/display.md) and [maven magic](docs/magics.md#addmavendependencies)). Chart library in the demo photo is [XChart](https://github.com/knowm/XChart) with the sample code taken from their README. ([Try the example ![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/SpencerPark/ijava-binder/master?urlpath=lab/tree/home/jovyan/3rdPartyDependency.ipynb)) - ![display-img](docs/img/display-img.png) -* `eval` function. (See also [kernel.md](docs/kernel.md)) **Note: the signature is `Object eval(String) throws Exception`.** This evaluates the expression (a cell) in the user scope and returns the actual evaluation result instead of a serialized one. - ![eval](docs/img/eval.png) -* Configurable evaluation timeout - ![timeout](docs/img/timeout.png) +* Code execution. + ![output](docs/img/output.png) +* Autocompletion (`TAB` in Jupyter notebook). + ![autocompletion](docs/img/autocompletion.png) +* Code inspection (`Shift-TAB` up to 4 times in Jupyter notebook). + ![code-inspection](docs/img/code-inspection.png) +* Colored, friendly, error message displays. + ![compilation-error](docs/img/compilation-error.png) + ![incomplete-src-error](docs/img/incomplete-src-error.png) + ![runtime-error](docs/img/runtime-error.png) +* Add maven dependencies at runtime (See also [magics.md](docs/magics.md) + and [Try the example ![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/SpencerPark/ijava-binder/master?urlpath=lab/tree/home/jovyan/3rdPartyDependency.ipynb)) + . + ![maven-pom-dep](docs/img/maven-pom-dep.png) +* Display rich output (See also [display.md](docs/display.md) and [maven magic](docs/magics.md#addmavendependencies)). + Chart library in the demo photo is [XChart](https://github.com/knowm/XChart) with the sample code taken from their + README. ([Try the example ![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/SpencerPark/ijava-binder/master?urlpath=lab/tree/home/jovyan/3rdPartyDependency.ipynb)) + ![display-img](docs/img/display-img.png) +* `eval` function. (See also [kernel.md](docs/kernel.md)) **Note: the signature + is `Object eval(String) throws Exception`.** This evaluates the expression (a cell) in the user scope and returns the + actual evaluation result instead of a serialized one. + ![eval](docs/img/eval.png) +* Configurable evaluation timeout + ![timeout](docs/img/timeout.png) +* Print with variable name or source + ![timeout](docs/img/print-with-var-name.png) ### Requirements -1. [Java JDK >= 9](http://www.oracle.com/technetwork/java/javase/downloads/index.html). **Not the JRE**. Java 12 is the current release and should be considered if selecting a version but if a java 9, 10, or 11 build is installed, everything _should_ still be working fine. +1. ~~[Java JDK >= 9](http://www.oracle.com/technetwork/java/javase/downloads/index.html). **Not the JRE**. Java 12 is + the current release and should be considered if selecting a version but if a java 9, 10, or 11 build is installed, + everything _should_ still be working + fine.~~[Java JDK >= 17](http://www.oracle.com/technetwork/java/javase/downloads/index.html). **Not the JRE**. - 1. Ensure that the `java` command is in the PATH and is using version 9. For example: - ```bash - > java -version - java version "9" - Java(TM) SE Runtime Environment (build 9+181) - Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode) - ``` + 1. Ensure that the `java` command is in the PATH and is using version 9. For example: + ```bash + > java -version + java version "17.0.2" 2022-01-18 LTS + Java(TM) SE Runtime Environment (build 17.0.2+8-LTS-86) + Java HotSpot(TM) 64-Bit Server VM (build 17.0.2+8-LTS-86, mixed mode, sharing) + ``` - 2. Next ensure that `java` is in a location where the jdk was installed and not just the jre. Use the `java --list-modules` command to do this. The list should contain `jdk.jshell`. + 2. Next ensure that `java` is in a location where the jdk was installed and not just the jre. Use + the `java --list-modules` command to do this. The list should contain `jdk.jshell`. - * On *nix `java --list-modules | grep "jdk.jshell"` - * On windows `java --list-modules | findstr "jdk.jshell"` + * On *nix `java --list-modules | grep "jdk.jshell"` + * On windows `java --list-modules | findstr "jdk.jshell"` - Both should output `jdk.jshell@` followed by your java version. + Both should output `jdk.jshell@` followed by your java version. - If the kernel cannot start with an error along the lines of + If the kernel cannot start with an error along the lines of ```text Exception in thread "main" java.lang.NoClassDefFoundError: jdk/jshell/JShellException ... @@ -117,23 +129,32 @@ Get the latest _release_ of the software with no compilation needed. See [Instal Get the latest version of the kernel but possibly run into some issues with installing. This is also the route to take if you wish to contribute to the kernel. -1. Download the project. +1. Download the project. ```bash > git clone https://github.com/SpencerPark/IJava.git > cd IJava/ ``` -2. Build and install the kernel. - - On *nix `./gradlew installKernel` - - On windows `gradlew installKernel` +2. Build the kernel. + + On *nix `./gradlew build` - See all available options for configuring the install path with `gradlew -q help --task installKernel`. Pass the `--default`, `--user`, `--sys-prefix`, `--prefix`, `--path`, or `--legacy` options to change the install location. Also use the `--param` flag (repeatedly) to set (or add) parameter values with the parameter names (not environment variable) specified in the configuration section below. For example `--param classpath:/my/classpath/root` to append to the classpath list. + On windows `gradlew build` + + See all available options for configuring the install path with `gradlew -q help --task installKernel`. Pass + the `--default`, `--user`, `--sys-prefix`, `--prefix`, `--path`, or `--legacy` options to change the install + location. Also use the `--param` flag (repeatedly) to set (or add) parameter values with the parameter names (not + environment variable) specified in the configuration section below. For + example `--param classpath:/my/classpath/root` to append to the classpath list. + +3. install the kernel: plz follow *#Install pre-built binary* ### Configuring -Configuring the kernel can be done via environment variables. These can be set on the system or inside the `kernel.json`. The configuration can be done at install time, which may be repeated as often as desired. The parameters are listed with `python3 install.py -h` as well as below in the list of options. Configuration done via the installer (or `gradlew installKernel --param ...:...`) should use the names in the _Parameter name_ column. +Configuring the kernel can be done via environment variables. These can be set on the system or inside the `kernel.json` +. The configuration can be done at install time, which may be repeated as often as desired. The parameters are listed +with `python3 install.py -h` as well as below in the list of options. Configuration done via the installer ( +or `gradlew installKernel --param ...:...`) should use the names in the _Parameter name_ column. #### List of options @@ -178,8 +199,8 @@ For example to enable assertions, set a limit on the heap size to `128m`. ```diff { -- "argv": [ "java", "-jar", "{connection_file}"], -+ "argv": [ "java", "-ea", "-Xmx128m", "-jar", "{connection_file}"], +- "argv": [ "java", "-jar", "path_to_ijava_jar", "{connection_file}"], ++ "argv": [ "java", "-ea", "-Xmx128m", "-jar", "path_to_ijava_jar", "{connection_file}"], "display_name": "Java", "language": "java", "interrupt_mode": "message", @@ -188,16 +209,31 @@ For example to enable assertions, set a limit on the heap size to `128m`. } ``` +For debug, argv example: + +``` +[ + "java", + "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", + "-jar", + "path_to_ijava_jar", + "{connection_file}" +] +``` + ### Run -This is where the documentation diverges, each environment has it's own way of selecting a kernel. To test from command line with Jupyter's console application run: +This is where the documentation diverges, each environment has it's own way of selecting a kernel. To test from command +line with [Jupyter's console](https://github.com/jupyter/jupyter_console) (`pip install jupyter-console` to install) +application run: ```bash jupyter console --kernel=java ``` Then at the prompt try: -```java + +``` In [1]: String helloWorld = "Hello world!" In [2]: helloWorld diff --git a/build.gradle b/build.gradle index 4ce2c48..b56536c 100644 --- a/build.gradle +++ b/build.gradle @@ -70,6 +70,7 @@ shadowJar { include 'build.gradle' } } +build.dependsOn 'shadowJar' // publish //publishing { diff --git a/docs/img/print-with-var-name.png b/docs/img/print-with-var-name.png new file mode 100644 index 0000000000000000000000000000000000000000..1a6f7abf4ce63edb0ddc7ec70765d5f8549105f4 GIT binary patch literal 21337 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 Vfq;L50n1YWiMV*a%JN$BzW^xiv&{ei literal 0 HcmV?d00001 diff --git a/src/main/java/io/github/spencerpark/ijava/JavaKernel.java b/src/main/java/io/github/spencerpark/ijava/JavaKernel.java index 1c4638c..a74d28d 100644 --- a/src/main/java/io/github/spencerpark/ijava/JavaKernel.java +++ b/src/main/java/io/github/spencerpark/ijava/JavaKernel.java @@ -26,6 +26,7 @@ import io.github.spencerpark.ijava.execution.*; import io.github.spencerpark.ijava.magics.ClasspathMagics; import io.github.spencerpark.ijava.magics.MavenResolver; +import io.github.spencerpark.ijava.magics.PrinterMagics; import io.github.spencerpark.jupyter.kernel.BaseKernel; import io.github.spencerpark.jupyter.kernel.LanguageInfo; import io.github.spencerpark.jupyter.kernel.ReplacementOptions; @@ -41,6 +42,7 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; public class JavaKernel extends BaseKernel { @@ -76,7 +78,20 @@ public static String maybeCompleteCodeSignifier() { private final StringStyler errorStyler; + public static boolean printWithVarName = true; + // jupyter support ANSI_escape_code, java ansi code demo: https://stackoverflow.com/a/5762502 + private static String varNamePattern = "\u001B[36m%s\u001B[0m: "; + private Long snippetId = 0L; + public JavaKernel() { + // todo for debug + //try { + // System.out.println("------------- sleep start -------------"); + // Thread.sleep(10 * 1000); + // System.out.println("------------- sleep end -------------"); + //} catch (InterruptedException e) { + // e.printStackTrace(); + //} this.evaluator = new CodeEvaluatorBuilder() .addClasspathFromString(System.getenv(IJava.CLASSPATH_KEY)) .compilerOptsFromString(System.getenv(IJava.COMPILER_OPTS_KEY)) @@ -95,6 +110,7 @@ public JavaKernel() { this.magics.registerMagics(this.mavenResolver); this.magics.registerMagics(new ClasspathMagics(this::addToClasspath)); this.magics.registerMagics(new Load(List.of(".jsh", ".jshell", ".java", ".ijava"), this::eval)); + this.magics.registerMagics(new PrinterMagics()); this.languageInfo = new LanguageInfo.Builder("Java") .version(Runtime.version().toString()) @@ -103,7 +119,7 @@ public JavaKernel() { .pygments("java") .codemirror("java") .build(); - this.banner = String.format("Java %s :: IJava kernel %s \nProtocol v%s implementation by %s %s", + this.banner = String.format("Java %s :: IJava kernel %s %nProtocol v%s implementation by %s %s", Runtime.version().toString(), IJava.VERSION, Header.PROTOCOL_VERISON, @@ -154,24 +170,21 @@ public List getHelpLinks() { @Override public List formatError(Exception e) { - List fmt = new LinkedList<>(); - if (e instanceof CompilationException) { - return formatCompilationException((CompilationException) e); - } else if (e instanceof IncompleteSourceException) { - return formatIncompleteSourceException((IncompleteSourceException) e); - } else if (e instanceof EvalException) { - return formatEvalException((EvalException) e); - } else if (e instanceof UnresolvedReferenceException) { - return formatUnresolvedReferenceException(((UnresolvedReferenceException) e)); - } else if (e instanceof EvaluationTimeoutException) { - return formatEvaluationTimeoutException((EvaluationTimeoutException) e); - } else if (e instanceof EvaluationInterruptedException) { - return formatEvaluationInterruptedException((EvaluationInterruptedException) e); + if (e instanceof CompilationException compilationexception) { + return formatCompilationException(compilationexception); + } else if (e instanceof IncompleteSourceException incompleteSourceException) { + return formatIncompleteSourceException(incompleteSourceException); + } else if (e instanceof EvalException evalException) { + return formatEvalException(evalException); + } else if (e instanceof UnresolvedReferenceException unresolvedReferenceException) { + return formatUnresolvedReferenceException(unresolvedReferenceException); + } else if (e instanceof EvaluationTimeoutException evaluationTimeoutException) { + return formatEvaluationTimeoutException(evaluationTimeoutException); + } else if (e instanceof EvaluationInterruptedException evaluationInterruptedException) { + return formatEvaluationInterruptedException(evaluationInterruptedException); } else { - fmt.addAll(super.formatError(e)); + return new LinkedList<>(super.formatError(e)); } - - return fmt; } private List formatCompilationException(CompilationException e) { @@ -196,15 +209,8 @@ private List formatCompilationException(CompilationException e) { fmt.add(""); // Add a blank line }); - if (snippet instanceof DeclarationSnippet) { - List unresolvedDependencies = this.evaluator.getShell().unresolvedDependencies((DeclarationSnippet) snippet) - .collect(Collectors.toList()); - if (!unresolvedDependencies.isEmpty()) { - fmt.addAll(this.errorStyler.primaryLines(snippet.source())); - fmt.add(this.errorStyler.secondary("Unresolved dependencies:")); - unresolvedDependencies.forEach(dep -> - fmt.add(this.errorStyler.secondary(" - " + dep))); - } + if (snippet instanceof DeclarationSnippet declarationSnippet) { + formatUnresolvedDep(declarationSnippet, fmt); } return fmt; @@ -223,7 +229,6 @@ private List formatIncompleteSourceException(IncompleteSourceException e private List formatEvalException(EvalException e) { List fmt = new ArrayList<>(); - String evalExceptionClassName = EvalException.class.getName(); String actualExceptionName = e.getExceptionClassName(); super.formatError(e).stream() @@ -235,19 +240,17 @@ private List formatEvalException(EvalException e) { private List formatUnresolvedReferenceException(UnresolvedReferenceException e) { List fmt = new ArrayList<>(); + formatUnresolvedDep(e.getSnippet(), fmt); + return fmt; + } - DeclarationSnippet snippet = e.getSnippet(); - - List unresolvedDependencies = this.evaluator.getShell().unresolvedDependencies(snippet) - .collect(Collectors.toList()); + private void formatUnresolvedDep(DeclarationSnippet declarationSnippet, final List fmt) { + List unresolvedDependencies = this.evaluator.getShell().unresolvedDependencies(declarationSnippet).toList(); if (!unresolvedDependencies.isEmpty()) { - fmt.addAll(this.errorStyler.primaryLines(snippet.source())); + fmt.addAll(this.errorStyler.primaryLines(declarationSnippet.source())); fmt.add(this.errorStyler.secondary("Unresolved dependencies:")); - unresolvedDependencies.forEach(dep -> - fmt.add(this.errorStyler.secondary(" - " + dep))); + unresolvedDependencies.forEach(dep -> fmt.add(this.errorStyler.secondary(" - " + dep))); } - - return fmt; } private List formatEvaluationTimeoutException(EvaluationTimeoutException e) { @@ -280,12 +283,24 @@ public Object evalRaw(String expr) throws Exception { public DisplayData eval(String expr) throws Exception { Object result = this.evalRaw(expr); - if (result != null) - return result instanceof DisplayData - ? (DisplayData) result - : this.getRenderer().render(result); + // last snippet is ExpressSnippet or VarSnippet -> getSource().replaceAll("\s+", "") + if (result == null) return null; + if (result instanceof DisplayData displayData) return displayData; + + if (printWithVarName) { + Optional lastSnippet = this.evaluator.getShell().snippets().skip(snippetId).reduce((first, second) -> second); + if (lastSnippet.isPresent()) { + Snippet snippet = lastSnippet.get(); + if (snippet instanceof ExpressionSnippet || snippet instanceof VarSnippet) { + snippetId = snippet.id().matches("\\d+") ? (Long.parseLong(snippet.id()) - 1) : (snippetId + 1); + String sourceStr = snippet.source().replaceAll("\\s+", ""); + if (sourceStr.length() > 32) sourceStr = sourceStr.substring(0, 32) + "..."; + return this.getRenderer().render(String.format(varNamePattern, sourceStr) + result); + } + } + } - return null; + return this.getRenderer().render(result); } @Override @@ -314,8 +329,7 @@ public DisplayData inspect(String code, int at, boolean extraDetail) { if (javadoc != null) formatted += '\n' + javadoc; return formatted; - }).collect(Collectors.joining("\n\n") - ) + }).collect(Collectors.joining("\n\n")) ); fmtDocs.putHTML( @@ -328,8 +342,7 @@ public DisplayData inspect(String code, int at, boolean extraDetail) { if (javadoc != null) formatted += "
" + javadoc; return formatted; - }).collect(Collectors.joining("

") - ) + }).collect(Collectors.joining("

")) ); return fmtDocs; @@ -341,15 +354,12 @@ public ReplacementOptions complete(String code, int at) { List suggestions = this.evaluator.getShell().sourceCodeAnalysis().completionSuggestions(code, at, replaceStart); if (suggestions == null || suggestions.isEmpty()) return null; + // .sorted((s1, s2) -> s1.matchesType() ? s2.matchesType() ? 0 : -1 : s2.matchesType() ? 1 : 0) List options = suggestions.stream() - .sorted((s1, s2) -> - s1.matchesType() - ? s2.matchesType() ? 0 : -1 - : s2.matchesType() ? 1 : 0 - ) + .sorted((s1, s2) -> (s1.matchesType() ? 0 : 1) + (s2.matchesType() ? 0 : -1)) .map(SourceCodeAnalysis.Suggestion::continuation) .distinct() - .collect(Collectors.toList()); + .toList(); return new ReplacementOptions(options, replaceStart[0], at); } diff --git a/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluator.java b/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluator.java index 490a272..c5695c4 100644 --- a/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluator.java +++ b/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluator.java @@ -92,16 +92,11 @@ protected Object evalSingle(String code) throws Exception { ? executionControl.takeResult(key) : event.value(); - switch (subKind) { - case VAR_VALUE_SUBKIND: - case OTHER_EXPRESSION_SUBKIND: - case TEMP_VAR_EXPRESSION_SUBKIND: - result = NO_MAGIC_RETURN.equals(value) ? null : value; - break; - default: - result = null; - break; - } + result = switch (subKind) { + case VAR_VALUE_SUBKIND, OTHER_EXPRESSION_SUBKIND, TEMP_VAR_EXPRESSION_SUBKIND -> + NO_MAGIC_RETURN.equals(value) ? null : value; + default -> null; + }; } for (SnippetEvent event : events) { @@ -111,12 +106,11 @@ protected Object evalSingle(String code) throws Exception { if (e != null) { if (e instanceof EvalException) { switch (((EvalException) e).getExceptionClassName()) { - case IJavaExecutionControl.EXECUTION_TIMEOUT_NAME: - throw new EvaluationTimeoutException(executionControl.getTimeoutDuration(), executionControl.getTimeoutUnit(), code.trim()); - case IJavaExecutionControl.EXECUTION_INTERRUPTED_NAME: - throw new EvaluationInterruptedException(code.trim()); - default: - throw e; + case IJavaExecutionControl.EXECUTION_TIMEOUT_NAME -> + throw new EvaluationTimeoutException(executionControl.getTimeoutDuration(), executionControl.getTimeoutUnit(), code.trim()); + case IJavaExecutionControl.EXECUTION_INTERRUPTED_NAME -> + throw new EvaluationInterruptedException(code.trim()); + default -> throw e; } } @@ -201,23 +195,19 @@ public String isComplete(String code) { while (info.completeness().isComplete()) info = analyzeCompletion(info.remaining()); - switch (info.completeness()) { - case UNKNOWN: + return switch (info.completeness()) { + case UNKNOWN -> // Unknown means "bad code" and the only way to see if is complete is // to execute it. - return JavaKernel.invalidCodeSignifier(); - case COMPLETE: - case COMPLETE_WITH_SEMI: - case EMPTY: - return JavaKernel.completeCodeSignifier(); - case CONSIDERED_INCOMPLETE: - case DEFINITELY_INCOMPLETE: + JavaKernel.invalidCodeSignifier(); + case COMPLETE, COMPLETE_WITH_SEMI, EMPTY -> JavaKernel.completeCodeSignifier(); + case CONSIDERED_INCOMPLETE, DEFINITELY_INCOMPLETE -> // Compute the indent of the last line and match it - return this.computeIndentation(info.remaining()); - default: + this.computeIndentation(info.remaining()); + default -> // For completeness, return an "I don't know" if we somehow get down here - return JavaKernel.maybeCompleteCodeSignifier(); - } + JavaKernel.maybeCompleteCodeSignifier(); + }; } public void interrupt() { diff --git a/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluatorBuilder.java b/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluatorBuilder.java index 71f7358..27bf340 100644 --- a/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluatorBuilder.java +++ b/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluatorBuilder.java @@ -153,7 +153,7 @@ public CodeEvaluatorBuilder startupScriptFiles(String paths) { String glob1 = "glob:*"; String path1 = "."; System.out.println("------------- startup: " + FileUtils.listMatchedFilePath(glob1, path1)); - } catch (IOException e) { + } catch (Exception e) { e.printStackTrace(); } diff --git a/src/main/java/io/github/spencerpark/ijava/magics/MavenResolver.java b/src/main/java/io/github/spencerpark/ijava/magics/MavenResolver.java index fa32d67..097fc61 100644 --- a/src/main/java/io/github/spencerpark/ijava/magics/MavenResolver.java +++ b/src/main/java/io/github/spencerpark/ijava/magics/MavenResolver.java @@ -60,7 +60,7 @@ import java.io.*; import java.net.MalformedURLException; import java.net.URL; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.text.ParseException; import java.util.*; @@ -252,7 +252,7 @@ public List resolveMavenDependency(String canonical, Set repos, in return Arrays.stream(resolved.getAllArtifactsReports()) .filter(a -> JAR_TYPE.equalsIgnoreCase(a.getType())) .map(ArtifactDownloadReport::getLocalFile) - .collect(Collectors.toList()); + .toList(); } private File convertPomToIvy(Ivy ivy, File pomFile) throws IOException, ParseException { @@ -268,7 +268,7 @@ private File convertPomToIvy(Ivy ivy, File pomFile) throws IOException, ParseExc parser.toIvyFile(pomUrl.openStream(), new URLResource(pomUrl), tempIvyFile, pomModule); MessageLogger logger = ivy.getLoggerEngine(); - logger.info(new String(Files.readAllBytes(tempIvyFile.toPath()), Charset.forName("utf8"))); + logger.info(Files.readString(tempIvyFile.toPath(), StandardCharsets.UTF_8)); return tempIvyFile; } @@ -298,20 +298,20 @@ private List resolveFromIvyFile(Ivy ivy, File ivyFile, List scopes return Arrays.stream(resolved.getAllArtifactsReports()) .map(ArtifactDownloadReport::getLocalFile) - .collect(Collectors.toList()); + .toList(); } private String solidifyPartialPOM(String rawIn) throws ParserConfigurationException, IOException, SAXException, TransformerException { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilderFactory factory = DocumentBuilderFactory.newDefaultInstance(); factory.setValidating(false); DocumentBuilder builder = factory.newDocumentBuilder(); // Wrap in a dummy tag to allow fragments InputStream inStream = new SequenceInputStream(Collections.enumeration(Arrays.asList( - new ByteArrayInputStream("".getBytes(Charset.forName("utf-8"))), - new ByteArrayInputStream(rawIn.getBytes(Charset.forName("utf-8"))), - new ByteArrayInputStream("".getBytes(Charset.forName("utf-8"))) + new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8)), + new ByteArrayInputStream(rawIn.getBytes(StandardCharsets.UTF_8)), + new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8)) ))); Document doc = builder.parse(inStream); @@ -339,43 +339,37 @@ private String solidifyPartialPOM(String rawIn) throws ParserConfigurationExcept Node child = rootChildren.item(i); switch (child.getNodeName()) { - case "modelVersion": + case "modelVersion" -> { setModelVersion = true; this.appendChildInNewDoc(child, fixed, project); - break; - case "groupId": + } + case "groupId" -> { setGroupId = true; this.appendChildInNewDoc(child, fixed, project); - break; - case "artifactId": + } + case "artifactId" -> { setArtifactId = true; this.appendChildInNewDoc(child, fixed, project); - break; - case "version": + } + case "version" -> { setVersion = true; this.appendChildInNewDoc(child, fixed, project); - break; - case "dependency": - this.appendChildInNewDoc(child, fixed, dependencies); - break; - case "repository": - this.appendChildInNewDoc(child, fixed, repositories); - break; - case "dependencies": + } + case "dependency" -> this.appendChildInNewDoc(child, fixed, dependencies); + case "repository" -> this.appendChildInNewDoc(child, fixed, repositories); + case "dependencies" -> { // Add all dependencies to the collecting tag NodeList dependencyChildren = child.getChildNodes(); for (int j = 0; j < dependencyChildren.getLength(); j++) this.appendChildInNewDoc(dependencyChildren.item(j), fixed, dependencies); - break; - case "repositories": + } + case "repositories" -> { // Add all repositories to the collecting tag NodeList repositoryChildren = child.getChildNodes(); for (int j = 0; j < repositoryChildren.getLength(); j++) this.appendChildInNewDoc(repositoryChildren.item(j), fixed, repositories); - break; - default: - this.appendChildInNewDoc(child, fixed, project); - break; + } + default -> this.appendChildInNewDoc(child, fixed, project); } } @@ -408,8 +402,8 @@ private void appendChildInNewDoc(Node oldNode, Document doc, Node newParent) { newParent.appendChild(newNode); } - private String writeDOM(Source src) throws TransformerException, UnsupportedEncodingException { - Transformer idTransformer = TransformerFactory.newInstance().newTransformer(); + private String writeDOM(Source src) throws TransformerException { + Transformer idTransformer = TransformerFactory.newDefaultInstance().newTransformer(); idTransformer.setOutputProperty(OutputKeys.INDENT, "yes"); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -417,7 +411,7 @@ private String writeDOM(Source src) throws TransformerException, UnsupportedEnco idTransformer.transform(src, dest); - return out.toString("utf-8"); + return out.toString(StandardCharsets.UTF_8); } public void addJarsToClasspath(Iterable jars) { @@ -474,7 +468,7 @@ public void loadFromPOM(List args, String body) throws Exception { tempPomPath.deleteOnExit(); String rawPom = this.solidifyPartialPOM(body); - Files.write(tempPomPath.toPath(), rawPom.getBytes(Charset.forName("utf-8"))); + Files.writeString(tempPomPath.toPath(), rawPom); List loadArgs = new ArrayList<>(args.size() + 1); loadArgs.add(tempPomPath.getAbsolutePath()); diff --git a/src/main/java/io/github/spencerpark/ijava/magics/PrinterMagics.java b/src/main/java/io/github/spencerpark/ijava/magics/PrinterMagics.java new file mode 100644 index 0000000..980f57d --- /dev/null +++ b/src/main/java/io/github/spencerpark/ijava/magics/PrinterMagics.java @@ -0,0 +1,44 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 ${author} + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.spencerpark.ijava.magics; + +import io.github.spencerpark.ijava.JavaKernel; +import io.github.spencerpark.jupyter.kernel.magic.registry.LineMagic; + +import java.util.List; + +public class PrinterMagics { + + @LineMagic + public void printWithName(List args) { + if (args == null || args.isEmpty()) { + JavaKernel.printWithVarName = !JavaKernel.printWithVarName; + System.out.println("printWithVarName=" + JavaKernel.printWithVarName + ", -h for help."); + return; + } + if (args.get(0).equals("-h") || args.get(0).equals("--help")) { + System.out.println("run %printWithName to switch"); + } + } +} diff --git a/src/main/java/io/github/spencerpark/ijava/magics/dependencies/CommonRepositories.java b/src/main/java/io/github/spencerpark/ijava/magics/dependencies/CommonRepositories.java index 3973676..c239c4b 100644 --- a/src/main/java/io/github/spencerpark/ijava/magics/dependencies/CommonRepositories.java +++ b/src/main/java/io/github/spencerpark/ijava/magics/dependencies/CommonRepositories.java @@ -25,8 +25,8 @@ import org.apache.ivy.plugins.resolver.DependencyResolver; import org.apache.ivy.plugins.resolver.IBiblioResolver; -import org.xml.sax.SAXException; +import javax.xml.stream.XMLStreamException; import java.io.IOException; import java.nio.file.Path; @@ -68,7 +68,7 @@ public static DependencyResolver mavenLocal() { localRepoPath = Maven.getInstance().getConfiguredLocalRepositoryPath(); } catch (IOException e) { throw new RuntimeException("Error reading maven settings. " + e.getLocalizedMessage(), e); - } catch (SAXException e) { + } catch (XMLStreamException e) { throw new RuntimeException("Error parsing maven settings. " + e.getLocalizedMessage(), e); } diff --git a/src/main/java/io/github/spencerpark/ijava/magics/dependencies/Maven.java b/src/main/java/io/github/spencerpark/ijava/magics/dependencies/Maven.java index 5f321b8..946defc 100644 --- a/src/main/java/io/github/spencerpark/ijava/magics/dependencies/Maven.java +++ b/src/main/java/io/github/spencerpark/ijava/magics/dependencies/Maven.java @@ -23,26 +23,20 @@ */ package io.github.spencerpark.ijava.magics.dependencies; +import io.github.spencerpark.ijava.utils.FileUtils; import org.apache.maven.building.StringSource; import org.apache.maven.model.building.*; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; + +import javax.xml.stream.XMLStreamException; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.Map; import java.util.Properties; -import java.util.function.Function; +import java.util.function.UnaryOperator; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -132,44 +126,18 @@ private Path getDefaultLocalRepoPath() { return this.getUserHomePath().resolve("repository"); } - private Path readConfiguredLocalRepositoryPath(Path settingsXmlPath) throws IOException, SAXException { - if (!Files.isRegularFile(settingsXmlPath)) - return null; - - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setValidating(false); - - DocumentBuilder builder; - try { - builder = factory.newDocumentBuilder(); - } catch (ParserConfigurationException e) { - // We are configuring the factory, the configuration will be fine... - e.printStackTrace(); - return null; - } - - try (InputStream in = Files.newInputStream(settingsXmlPath)) { - Document settingsDoc = builder.parse(in); - NodeList settings = settingsDoc.getElementsByTagName("settings"); - if (settings.getLength() == 0) - return null; - - for (int i = 0; i < settings.getLength(); i++) { - Node setting = settings.item(i); - switch (setting.getNodeName()) { - case "localRepository": - String localRepository = setting.getTextContent(); - localRepository = this.replaceMavenVars(localRepository); - return Paths.get(localRepository); - } - } - } + private Path readConfiguredLocalRepositoryPath(Path settingsXmlPath) throws IOException, XMLStreamException { + if (!Files.isRegularFile(settingsXmlPath)) return null; - return null; + String localRepositoryName = "localRepository"; + String localRepositoryVal = FileUtils.readXmlElementText(settingsXmlPath, Collections.singleton(localRepositoryName)) + .get(localRepositoryName); + if (localRepositoryVal == null) return null; + return Paths.get(this.replaceMavenVars(localRepositoryVal)); } // TODO just use the effective settings - public Path getConfiguredLocalRepositoryPath() throws IOException, SAXException { + public Path getConfiguredLocalRepositoryPath() throws IOException, XMLStreamException { Path userSettingsXmlPath = this.getUserSettingsPath(); Path path = this.readConfiguredLocalRepositoryPath(userSettingsXmlPath); @@ -199,18 +167,14 @@ public Path getConfiguredLocalRepositoryPath() throws IOException, SAXException }*/ public ModelBuildingResult readEffectiveModel(CharSequence pom) throws ModelBuildingException { - return this.readEffectiveModel(req -> - req.setModelSource((ModelSource) new StringSource(pom)) - ); + return this.readEffectiveModel(req -> req.setModelSource((ModelSource) new StringSource(pom))); } public ModelBuildingResult readEffectiveModel(File pom) throws ModelBuildingException { - return this.readEffectiveModel(req -> - req.setPomFile(pom) - ); + return this.readEffectiveModel(req -> req.setPomFile(pom)); } - private ModelBuildingResult readEffectiveModel(Function configuration) throws ModelBuildingException { + private ModelBuildingResult readEffectiveModel(UnaryOperator configuration) throws ModelBuildingException { DefaultModelBuilder modelBuilder = new DefaultModelBuilderFactory().newInstance(); ModelBuildingRequest request = new DefaultModelBuildingRequest(); diff --git a/src/main/java/io/github/spencerpark/ijava/utils/FileUtils.java b/src/main/java/io/github/spencerpark/ijava/utils/FileUtils.java index 32abfda..d1d6177 100644 --- a/src/main/java/io/github/spencerpark/ijava/utils/FileUtils.java +++ b/src/main/java/io/github/spencerpark/ijava/utils/FileUtils.java @@ -1,3 +1,26 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 ${author} + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package io.github.spencerpark.ijava.utils; import javax.xml.stream.XMLEventReader; @@ -17,10 +40,10 @@ private FileUtils() { // hide } - public static Map readXmlElementText(String filePath, Collection elementNames) throws FileNotFoundException, XMLStreamException { + public static Map readXmlElementText(Path filePath, Collection elementNames) throws FileNotFoundException, XMLStreamException { Map result = new HashMap<>(); XMLInputFactory xmlInputFactory = XMLInputFactory.newDefaultFactory(); - XMLEventReader reader = xmlInputFactory.createXMLEventReader(new FileInputStream(filePath)); + XMLEventReader reader = xmlInputFactory.createXMLEventReader(new FileInputStream(filePath.toFile())); while (reader.hasNext()) { XMLEvent nextEvent = reader.nextEvent(); diff --git a/src/main/resources/print.jshell b/src/main/resources/print.jshell new file mode 100644 index 0000000..a8e7047 --- /dev/null +++ b/src/main/resources/print.jshell @@ -0,0 +1,74 @@ +import io.github.spencerpark.ijava.IJava; +import io.github.spencerpark.ijava.JavaKernel; +import io.github.spencerpark.ijava.execution.CodeEvaluator; +import jdk.jshell.JShell; +import jdk.jshell.Snippet; + +import java.lang.reflect.Field; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +System.out.println("\n----- printer init -----\n"); + + +public class Printer { + private static final Pattern VAR_IDX_PATTERN = Pattern.compile("(?i)\\$JShell\\$(\\d+)"); + private static final Pattern CONTENT_PATTERN = Pattern.compile(".*print\\((\\w+)\\)"); + + private static int varIdx = 0; + private static String prefix = "printer|"; + // java ansi code demo: https://stackoverflow.com/a/5762502 + private static final String varNameStylePattern = "\u001B[36m%s\u001B[0m"; + + private static JShell jshell; + + static { + try { + JavaKernel kernel = IJava.getKernelInstance(); + Field field = kernel.getClass().getDeclaredField("evaluator"); + field.setAccessible(true); + CodeEvaluator evaluator = (CodeEvaluator) field.get(kernel); + jshell = evaluator.getShell(); + } catch (Exception e) { + System.out.println(">>> Printer Get JShell instance error: " + e.getMessage()); + } + } + + public static void print(Object obj) { + String varName = null; + + if (jshell != null) { + StackTraceElement[] strace = new Exception().getStackTrace(); + // call from an outer *print* wrapper function, so the strace index is 1 + 1; + // if call Printer.print directly, the strace index is 1. + Matcher idMatcher = VAR_IDX_PATTERN.matcher(strace[1 + 1].getClassName()); + String id = idMatcher.find() ? idMatcher.group(1) : null; + if (id != null) { + Optional snippetOptional = jshell.snippets() + .skip(Long.parseLong(id) - 1) + .filter(o -> o.id().equals(id)).findFirst(); + if (snippetOptional.isPresent()) { + Snippet snippet = snippetOptional.get(); + Matcher contentMatcher = CONTENT_PATTERN.matcher(snippet.source()); + varName = contentMatcher.find() ? contentMatcher.group(1) : null; + } + } + } + if (varName == null) { + varName = "var-" + varIdx; + varIdx++; + } + System.out.printf("%s %s | %s%n", prefix, String.format(varNameStylePattern, varName), obj); + } + + public static JShell getJshell() { + return jshell; + } +} + + +public void print(Object arg) { + Printer.print(arg); +} diff --git a/src/test/java/io/github/spencerpark/ijava/TestUtils.java b/src/test/java/io/github/spencerpark/ijava/TestUtils.java index b4cb081..930aaf7 100644 --- a/src/test/java/io/github/spencerpark/ijava/TestUtils.java +++ b/src/test/java/io/github/spencerpark/ijava/TestUtils.java @@ -46,7 +46,7 @@ public void testFileGlob() throws IOException { @Test public void testReadXml() throws XMLStreamException, FileNotFoundException { - String filePath = "D:\\Maven\\apache-maven-3.6.3\\conf\\settings.xml"; + Path filePath = Path.of("D:\\Maven\\apache-maven-3.6.3\\conf\\settings.xml"); Set elementNames = Collections.singleton("localRepository"); Map elementTextData = FileUtils.readXmlElementText(filePath, elementNames); Assert.assertNotNull(elementTextData); From 8a5ec06602c44dad2b611231eb61c8b1997b5023 Mon Sep 17 00:00:00 2001 From: potoo0 <1415615232@qq.com> Date: Wed, 1 Jun 2022 19:54:12 +0800 Subject: [PATCH 03/13] pack into zip --- build.gradle | 26 +++++ src/main/resources/install.py | 198 +++++++++++++++++++++++++++++++++ src/main/resources/kernel.json | 12 ++ 3 files changed, 236 insertions(+) create mode 100644 src/main/resources/install.py create mode 100644 src/main/resources/kernel.json diff --git a/build.gradle b/build.gradle index b56536c..1b0b038 100644 --- a/build.gradle +++ b/build.gradle @@ -95,3 +95,29 @@ licenseReport { filters = [new LicenseBundleNormalizer()] } + +// pack up +tasks.register('packDist', Zip) { + from(layout.buildDirectory.dir("resources/main")) { + include "install.py" + } + + from(layout.buildDirectory.dir("libs")) { + include "*-all.jar" + into "java" + } + + from(layout.buildDirectory.dir("resources/main")) { + include "kernel.json" + into "java" + } + + from(layout.buildDirectory.dir("reports/dependency-license")) { + into "java/dependency-license" + } + + dependsOn build +} + +// execute task after build +build.finalizedBy(packDist) diff --git a/src/main/resources/install.py b/src/main/resources/install.py new file mode 100644 index 0000000..9c38219 --- /dev/null +++ b/src/main/resources/install.py @@ -0,0 +1,198 @@ +import argparse +import json +import os +import sys + +from jupyter_client.kernelspec import KernelSpecManager + + +ALIASES = { + "IJAVA_CLASSPATH": {}, + "IJAVA_COMPILER_OPTS": {}, + "IJAVA_STARTUP_SCRIPTS_PATH": {}, + "IJAVA_STARTUP_SCRIPT": {}, + "IJAVA_TIMEOUT": { + "NO_TIMEOUT": "-1", + }, +} + +NAME_MAP = { + "classpath": "IJAVA_CLASSPATH", + "comp-opts": "IJAVA_COMPILER_OPTS", + "startup-scripts-path": "IJAVA_STARTUP_SCRIPTS_PATH", + "startup-script": "IJAVA_STARTUP_SCRIPT", + "timeout": "IJAVA_TIMEOUT", +} + + +def type_assertion(name, type_fn): + env = NAME_MAP[name] + aliases = ALIASES.get(env, {}) + + def checker(value): + alias = aliases.get(value, value) + type_fn(alias) + return alias + setattr(checker, '__name__', getattr(type_fn, '__name__', 'type_fn')) + return checker + + +class EnvVar(argparse.Action): + def __init__(self, option_strings, dest, aliases=None, name_map=None, list_sep=None, **kwargs): + super(EnvVar, self).__init__(option_strings, dest, **kwargs) + + if aliases is None: + aliases = {} + if name_map is None: + name_map = {} + + self.aliases = aliases + self.name_map = name_map + self.list_sep = list_sep + + for name in self.option_strings: + if name.lstrip('-') not in name_map: + raise ValueError( + 'Name "%s" is not mapped to an environment variable' % name.lstrip('-')) + + def __call__(self, parser, namespace, value, option_string=None): + if option_string is None: + raise ValueError('option_string is required') + + env = getattr(namespace, self.dest, None) + if env is None: + env = {} + + name = option_string.lstrip('-') + env_var = self.name_map[name] + + if self.list_sep: + old = env.get(env_var) + value = old + self.list_sep + \ + str(value) if old is not None else str(value) + + env[env_var] = value + + setattr(namespace, self.dest, env) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Install the java kernel.') + + install_location = parser.add_mutually_exclusive_group() + install_location.add_argument( + '--user', + help='Install to the per-user kernel registry.', + action='store_true' + ) + install_location.add_argument( + '--sys-prefix', + help="Install to Python's sys.prefix. Useful in conda/virtual environments.", + action='store_true' + ) + install_location.add_argument( + '--prefix', + help=''' + Specify a prefix to install to, e.g. an env. + The kernelspec will be installed in PREFIX/share/jupyter/kernels/ + ''', + default='' + ) + + parser.add_argument( + '--replace', + help='Replace any existing kernel spec with this name.', + action='store_true' + ) + + parser.add_argument( + "--classpath", + dest="env", + action=EnvVar, + aliases=ALIASES, + name_map=NAME_MAP, + help="A file path separator delimited list of classpath entries that should be available to the user code. **Important:** no matter what OS, this should use forward slash \"/\" as the file separator. Also each path may actually be a simple glob.", + type=type_assertion("classpath", str), + list_sep=os.pathsep, + ) + parser.add_argument( + "--comp-opts", + dest="env", + action=EnvVar, + aliases=ALIASES, + name_map=NAME_MAP, + help="A space delimited list of command line options that would be passed to the `javac` command when compiling a project. For example `-parameters` to enable retaining parameter names for reflection.", + type=type_assertion("comp-opts", str), + list_sep=" ", + ) + parser.add_argument( + "--startup-scripts-path", + dest="env", + action=EnvVar, + aliases=ALIASES, + name_map=NAME_MAP, + help="A file path seperator delimited list of `.jshell` scripts to run on startup. This includes ijava-jshell-init.jshell and ijava-display-init.jshell. **Important:** no matter what OS, this should use forward slash \"/\" as the file separator. Also each path may actually be a simple glob.", + type=type_assertion("startup-scripts-path", str), + list_sep=os.pathsep, + ) + parser.add_argument( + "--startup-script", + dest="env", + action=EnvVar, + aliases=ALIASES, + name_map=NAME_MAP, + help="A block of java code to run when the kernel starts up. This may be something like `import my.utils;` to setup some default imports or even `void sleep(long time) { try {Thread.sleep(time); } catch (InterruptedException e) { throw new RuntimeException(e); }}` to declare a default utility method to use in the notebook.", + type=type_assertion("startup-script", str), + ) + parser.add_argument( + "--timeout", + dest="env", + action=EnvVar, + aliases=ALIASES, + name_map=NAME_MAP, + help="A duration specifying a timeout (in milliseconds by default) for a _single top level statement_. If less than `1` then there is no timeout. If desired a time may be specified with a `TimeUnit` may be given following the duration number (ex `\"30 SECONDS\"`).", + type=type_assertion("timeout", str), + ) + + args = parser.parse_args() + + if not hasattr(args, "env") or getattr(args, "env") is None: + setattr(args, "env", {}) + + # Install the kernel + install_dest = KernelSpecManager().install_kernel_spec( + os.path.join(os.path.dirname(os.path.abspath(__file__)), 'java'), + kernel_name='java', + user=args.user, + prefix=sys.prefix if args.sys_prefix else args.prefix, + replace=args.replace + ) + + # Connect the self referencing token left in the kernel.json to point to it's install location. + + # Prepare the token replacement string which should be properly escaped for use in a JSON string + # The [1:-1] trims the first and last " json.dumps adds for strings. + install_dest_json_fragment = json.dumps(install_dest)[1:-1] + + # Prepare the paths to the installed kernel.json and the one bundled with this installer. + local_kernel_json_path = os.path.join(os.path.dirname( + os.path.abspath(__file__)), 'java', 'kernel.json') + installed_kernel_json_path = os.path.join(install_dest, 'kernel.json') + + # Replace the @KERNEL_INSTALL_DIRECTORY@ token with the path to where the kernel was installed + # in the installed kernel.json from the local template. + with open(local_kernel_json_path, 'r') as template_kernel_json_file: + template_kernel_json_contents = template_kernel_json_file.read() + kernel_json_contents = template_kernel_json_contents.replace( + '@KERNEL_INSTALL_DIRECTORY@', + install_dest_json_fragment + ) + kernel_json_json_contents = json.loads(kernel_json_contents) + kernel_env = kernel_json_json_contents.setdefault('env', {}) + for k, v in args.env.items(): + kernel_env[k] = v + with open(installed_kernel_json_path, 'w') as installed_kernel_json_file: + json.dump(kernel_json_json_contents, + installed_kernel_json_file, indent=4, sort_keys=True) + + print('Installed java kernel into "%s"' % install_dest) diff --git a/src/main/resources/kernel.json b/src/main/resources/kernel.json new file mode 100644 index 0000000..08da2bc --- /dev/null +++ b/src/main/resources/kernel.json @@ -0,0 +1,12 @@ +{ + "argv": [ + "java", + "-jar", + "@KERNEL_INSTALL_DIRECTORY@/@project@-@version@-all.jar", + "{connection_file}" + ], + "display_name": "Java", + "env": {}, + "interrupt_mode": "message", + "language": "java" +} From 625cbfb5947019e9131fad14a064a0808c04ef46 Mon Sep 17 00:00:00 2001 From: potoo0 <1415615232@qq.com> Date: Thu, 2 Jun 2022 17:33:25 +0800 Subject: [PATCH 04/13] 1. add `list` lineMagic and `time` cellMagic; 2. load print.jshell by default. --- README.md | 32 +++++-- UPGRADE.md | 11 +++ build.gradle | 3 +- docs/img/cell-magic-time.png | Bin 0 -> 65742 bytes docs/img/line-magic-list.png | Bin 0 -> 19449 bytes docs/img/print-func-line-magic.png | Bin 0 -> 26277 bytes docs/img/print-with-var-name.png | Bin 21337 -> 25947 bytes .../io/github/spencerpark/ijava/IJava.java | 1 + .../github/spencerpark/ijava/JavaKernel.java | 27 +++--- .../ijava/execution/CodeEvaluatorBuilder.java | 17 ++-- .../spencerpark/ijava/magics/MagicsTool.java | 78 +++++++++++++++++ .../ijava/magics/TimeItMagics.java | 82 ++++++++++++++++++ src/main/resources/print.jshell | 63 ++++++++++++-- .../github/spencerpark/ijava/TestUtils.java | 54 ++++-------- 14 files changed, 295 insertions(+), 73 deletions(-) create mode 100644 UPGRADE.md create mode 100644 docs/img/cell-magic-time.png create mode 100644 docs/img/line-magic-list.png create mode 100644 docs/img/print-func-line-magic.png create mode 100644 src/main/java/io/github/spencerpark/ijava/magics/MagicsTool.java create mode 100644 src/main/java/io/github/spencerpark/ijava/magics/TimeItMagics.java diff --git a/README.md b/README.md index 960ac25..b16564f 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,36 @@ # IJava -[![badge](https://img.shields.io/badge/launch-binder-E66581.svg?logo=data:image/png;base64,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)](https://mybinder.org/v2/gh/SpencerPark/ijava-binder/master) [![badge](https://img.shields.io/badge/launch-binder%20lab-579ACA.svg?logo=data:image/png;base64,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)](https://mybinder.org/v2/gh/SpencerPark/ijava-binder/master?urlpath=lab) +Fork from [SpencerPark](https://github.com/SpencerPark)/[IJava](https://github.com/SpencerPark/IJava), but with some new +features and magics: -![display-img](docs/img/display-img.png) +* Upgrade to jdk 17 and gradle 7.3.3 +* Print with variable name or source + ![timeout](docs/img/print-with-var-name.png) +* add `print` function and `printerPrefix` line magic + ![timeout](docs/img/print-func-line-magic.png) +* add `list` line magic + ![timeout](docs/img/line-magic-list.png) +* add `time` cell magic + ![timeout](docs/img/cell-magic-time.png) + +[//]: # ([![badge](https://img.shields.io/badge/launch-binder-E66581.svg?logo=data:image/png;base64,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)](https://mybinder.org/v2/gh/SpencerPark/ijava-binder/master) [![badge](https://img.shields.io/badge/launch-binder%20lab-579ACA.svg?logo=data:image/png;base64,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)](https://mybinder.org/v2/gh/SpencerPark/ijava-binder/master?urlpath=lab)) -A [Jupyter](http://jupyter.org/) kernel for executing Java code. The kernel executes code via the new [JShell tool](https://docs.oracle.com/javase/9/jshell/introduction-jshell.htm). Some of the additional commands should be supported as needed via a syntax similar to the ipython magics. +[//]: # (![display-img](docs/img/display-img.png)) -The kernel is fully functional. Check out the [list of features](#features) further down in the README. Any requests for new ones or prioritizing current requests are welcomed in the [issues](https://github.com/SpencerPark/IJava/issues) along with bug requests, installation help, or other questions. +A [Jupyter](http://jupyter.org/) kernel for executing Java code. The kernel executes code via the +new [JShell tool](https://docs.oracle.com/javase/9/jshell/introduction-jshell.htm). Some of the additional commands +should be supported as needed via a syntax similar to the ipython magics. -If you are interested in building your own kernel that runs on the JVM check out the related project that this kernel is build on, [jupyter-jvm-basekernel](https://github.com/SpencerPark/jupyter-jvm-basekernel). +The kernel is fully functional. Check out the [list of features](#features) further down in the README. Any requests for +new ones or prioritizing current requests are welcomed in the [issues](https://github.com/SpencerPark/IJava/issues) +along with bug requests, installation help, or other questions. + +If you are interested in building your own kernel that runs on the JVM check out the related project that this kernel is +build on, [jupyter-jvm-basekernel](https://github.com/SpencerPark/jupyter-jvm-basekernel). ### Contents -* [Try online](#try-online) +* [Try online](#try-online) * [Features](#features) * [Requirements](#requirements) * [Installing](#installing) @@ -56,8 +74,6 @@ Currently the kernel supports ![eval](docs/img/eval.png) * Configurable evaluation timeout ![timeout](docs/img/timeout.png) -* Print with variable name or source - ![timeout](docs/img/print-with-var-name.png) ### Requirements diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 0000000..d1979d5 --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,11 @@ +Upgrade Note: + +* Upgrade to jdk 17 and gradle 7.3.3 +* Print with variable name or source + ![timeout](docs/img/print-with-var-name.png) +* add `print` function and `printerPrefix` line magic + ![timeout](docs/img/print-func-line-magic.png) +* add `list` line magic + ![timeout](docs/img/line-magic-list.png) +* add `time` cell magic + ![timeout](docs/img/cell-magic-time.png) diff --git a/build.gradle b/build.gradle index 1b0b038..a57ee9c 100644 --- a/build.gradle +++ b/build.gradle @@ -116,8 +116,9 @@ tasks.register('packDist', Zip) { into "java/dependency-license" } + dependsOn generateLicenseReport dependsOn build } // execute task after build -build.finalizedBy(packDist) +//build.finalizedBy(packDist) diff --git a/docs/img/cell-magic-time.png b/docs/img/cell-magic-time.png new file mode 100644 index 0000000000000000000000000000000000000000..363fbe0559a7dc571f5f441634c343634be36b29 GIT binary patch literal 65742 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 literal 0 HcmV?d00001 diff --git a/docs/img/print-func-line-magic.png b/docs/img/print-func-line-magic.png new file mode 100644 index 0000000000000000000000000000000000000000..aeab7cbfb0671b894c9996a830e477ff465cb2d0 GIT binary patch literal 26277 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 literal 0 HcmV?d00001 diff --git a/docs/img/print-with-var-name.png b/docs/img/print-with-var-name.png index 1a6f7abf4ce63edb0ddc7ec70765d5f8549105f4..c1e3652b908166b3777831e5484babcbadcac026 100644 GIT binary patch literal 25947 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 literal 21337 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 Vfq;L50n1YWiMV*a%JN$BzW^xiv&{ei diff --git a/src/main/java/io/github/spencerpark/ijava/IJava.java b/src/main/java/io/github/spencerpark/ijava/IJava.java index ef47218..fc99ec3 100644 --- a/src/main/java/io/github/spencerpark/ijava/IJava.java +++ b/src/main/java/io/github/spencerpark/ijava/IJava.java @@ -46,6 +46,7 @@ public class IJava { public static final String STARTUP_SCRIPT_KEY = "IJAVA_STARTUP_SCRIPT"; public static final String DEFAULT_SHELL_INIT_RESOURCE_PATH = "ijava-jshell-init.jshell"; + public static final String SHELL_INIT_RESOURCE_PATH_PRINTER = "print.jshell"; public static final String VERSION; diff --git a/src/main/java/io/github/spencerpark/ijava/JavaKernel.java b/src/main/java/io/github/spencerpark/ijava/JavaKernel.java index a74d28d..0f32171 100644 --- a/src/main/java/io/github/spencerpark/ijava/JavaKernel.java +++ b/src/main/java/io/github/spencerpark/ijava/JavaKernel.java @@ -24,9 +24,7 @@ package io.github.spencerpark.ijava; import io.github.spencerpark.ijava.execution.*; -import io.github.spencerpark.ijava.magics.ClasspathMagics; -import io.github.spencerpark.ijava.magics.MavenResolver; -import io.github.spencerpark.ijava.magics.PrinterMagics; +import io.github.spencerpark.ijava.magics.*; import io.github.spencerpark.jupyter.kernel.BaseKernel; import io.github.spencerpark.jupyter.kernel.LanguageInfo; import io.github.spencerpark.jupyter.kernel.ReplacementOptions; @@ -70,7 +68,7 @@ public static String maybeCompleteCodeSignifier() { private final MavenResolver mavenResolver; private final MagicsSourceTransformer magicsTransformer; - private final Magics magics; + private static final Magics magics = new Magics(); private final LanguageInfo languageInfo; private final String banner; @@ -82,6 +80,7 @@ public static String maybeCompleteCodeSignifier() { // jupyter support ANSI_escape_code, java ansi code demo: https://stackoverflow.com/a/5762502 private static String varNamePattern = "\u001B[36m%s\u001B[0m: "; private Long snippetId = 0L; + private static final List COMMENT_PATTERNS = List.of("/\\*(.|\\s)*?\\*/", "//.*\\n*", "\\s+"); public JavaKernel() { // todo for debug @@ -96,6 +95,7 @@ public JavaKernel() { .addClasspathFromString(System.getenv(IJava.CLASSPATH_KEY)) .compilerOptsFromString(System.getenv(IJava.COMPILER_OPTS_KEY)) .startupScript(IJava.resource(IJava.DEFAULT_SHELL_INIT_RESOURCE_PATH)) + .startupScript(IJava.resource(IJava.SHELL_INIT_RESOURCE_PATH_PRINTER)) .startupScriptFiles(System.getenv(IJava.STARTUP_SCRIPTS_KEY)) .startupScript(System.getenv(IJava.STARTUP_SCRIPT_KEY)) .timeoutFromString(System.getenv(IJava.TIMEOUT_DURATION_KEY)) @@ -106,11 +106,12 @@ public JavaKernel() { this.mavenResolver = new MavenResolver(this::addToClasspath); this.magicsTransformer = new MagicsSourceTransformer(); - this.magics = new Magics(); - this.magics.registerMagics(this.mavenResolver); - this.magics.registerMagics(new ClasspathMagics(this::addToClasspath)); - this.magics.registerMagics(new Load(List.of(".jsh", ".jshell", ".java", ".ijava"), this::eval)); - this.magics.registerMagics(new PrinterMagics()); + magics.registerMagics(this.mavenResolver); + magics.registerMagics(new ClasspathMagics(this::addToClasspath)); + magics.registerMagics(new Load(List.of(".jsh", ".jshell", ".java", ".ijava"), this::eval)); + magics.registerMagics(new PrinterMagics()); + magics.registerMagics(new MagicsTool()); + magics.registerMagics(new TimeItMagics()); this.languageInfo = new LanguageInfo.Builder("Java") .version(Runtime.version().toString()) @@ -149,8 +150,8 @@ public MavenResolver getMavenResolver() { return this.mavenResolver; } - public Magics getMagics() { - return this.magics; + public static Magics getMagics() { + return magics; } @Override @@ -283,7 +284,6 @@ public Object evalRaw(String expr) throws Exception { public DisplayData eval(String expr) throws Exception { Object result = this.evalRaw(expr); - // last snippet is ExpressSnippet or VarSnippet -> getSource().replaceAll("\s+", "") if (result == null) return null; if (result instanceof DisplayData displayData) return displayData; @@ -293,7 +293,8 @@ public DisplayData eval(String expr) throws Exception { Snippet snippet = lastSnippet.get(); if (snippet instanceof ExpressionSnippet || snippet instanceof VarSnippet) { snippetId = snippet.id().matches("\\d+") ? (Long.parseLong(snippet.id()) - 1) : (snippetId + 1); - String sourceStr = snippet.source().replaceAll("\\s+", ""); + String sourceStr = snippet.source(); + for (String pattern : COMMENT_PATTERNS) sourceStr = sourceStr.replaceAll(pattern, ""); if (sourceStr.length() > 32) sourceStr = sourceStr.substring(0, 32) + "..."; return this.getRenderer().render(String.format(varNamePattern, sourceStr) + result); } diff --git a/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluatorBuilder.java b/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluatorBuilder.java index 27bf340..ddd91ca 100644 --- a/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluatorBuilder.java +++ b/src/main/java/io/github/spencerpark/ijava/execution/CodeEvaluatorBuilder.java @@ -23,7 +23,6 @@ */ package io.github.spencerpark.ijava.execution; -import io.github.spencerpark.ijava.utils.FileUtils; import io.github.spencerpark.jupyter.kernel.util.GlobFinder; import jdk.jshell.JShell; @@ -149,13 +148,13 @@ public CodeEvaluatorBuilder startupScript(InputStream scriptStream) { public CodeEvaluatorBuilder startupScriptFiles(String paths) { // todo debug - try { - String glob1 = "glob:*"; - String path1 = "."; - System.out.println("------------- startup: " + FileUtils.listMatchedFilePath(glob1, path1)); - } catch (Exception e) { - e.printStackTrace(); - } + //try { + // String glob1 = "glob:*"; + // String path1 = "."; + // System.out.println("------------- startup: " + FileUtils.listMatchedFilePath(glob1, path1)); + //} catch (Exception e) { + // e.printStackTrace(); + //} if (paths == null) return this; if (BLANK.matcher(paths).matches()) return this; @@ -182,6 +181,8 @@ public CodeEvaluatorBuilder startupScriptFile(Path path) { if (!Files.isReadable(path)) return this; + // debug + System.out.printf("found startup file: %s%n", path); try { String script = Files.readString(path); this.startupScripts.add(script); diff --git a/src/main/java/io/github/spencerpark/ijava/magics/MagicsTool.java b/src/main/java/io/github/spencerpark/ijava/magics/MagicsTool.java new file mode 100644 index 0000000..9c914f7 --- /dev/null +++ b/src/main/java/io/github/spencerpark/ijava/magics/MagicsTool.java @@ -0,0 +1,78 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 ${author} + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.spencerpark.ijava.magics; + +import io.github.spencerpark.ijava.JavaKernel; +import io.github.spencerpark.jupyter.kernel.magic.registry.LineMagic; +import io.github.spencerpark.jupyter.kernel.magic.registry.LineMagicFunction; +import io.github.spencerpark.jupyter.kernel.magic.registry.Magics; + +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class MagicsTool { + + @LineMagic + public void listLineMagic(List args) { + Magics magics = JavaKernel.getMagics(); + try { + System.out.printf("registered line magics: %n\t- %s%n", + String.join("\n\t- ", getMagicsName(magics, "lineMagics"))); + } catch (Exception e) { + System.out.printf("inspect line magics fail: %s%n", e.getMessage()); + } + } + + @LineMagic + public void listCellMagic(List args) { + Magics magics = JavaKernel.getMagics(); + try { + System.out.printf("registered cell magics: %n\t- %s%n", + String.join("\n\t- ", getMagicsName(magics, "cellMagics"))); + } catch (Exception e) { + System.out.printf("inspect cell magics fail: %s%n", e.getMessage()); + } + } + + @LineMagic(aliases = {"list"}) + public void listMagic(List args) { + listLineMagic(Collections.emptyList()); + listCellMagic(Collections.emptyList()); + } + + @SuppressWarnings("unchecked") + private Collection getMagicsName(Magics magics, String fieldName) throws NoSuchFieldException, IllegalAccessException { + Field field = magics.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + Map> lineMagics = (Map>) field.get(magics); + return lineMagics.entrySet() + .stream() + .collect(Collectors.groupingBy(Map.Entry::getValue, Collectors.mapping(Map.Entry::getKey, Collectors.joining(", ")))) + .values(); + } +} diff --git a/src/main/java/io/github/spencerpark/ijava/magics/TimeItMagics.java b/src/main/java/io/github/spencerpark/ijava/magics/TimeItMagics.java new file mode 100644 index 0000000..62841aa --- /dev/null +++ b/src/main/java/io/github/spencerpark/ijava/magics/TimeItMagics.java @@ -0,0 +1,82 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 ${author} + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.spencerpark.ijava.magics; + +import io.github.spencerpark.ijava.IJava; +import io.github.spencerpark.jupyter.kernel.magic.registry.CellMagic; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; +import java.util.stream.Collectors; + +public class TimeItMagics { + private final int epochs = 3; + private final int loops = 5; + + @CellMagic(aliases = {"time", "timeit"}) + public void timeIt(List args, String body) throws Exception { + if (args == null) args = Collections.emptyList(); + + if (!args.isEmpty() && ("-h".equals(args.get(0)) || "--help".equals(args.get(0)))) { + System.out.println("help: \nexample: \n"); + System.out.println("%%time epochs=3 loops=5\n1 + 1"); + return; + } + + // parse input args + Map params = args.stream() + .map(arg -> arg.split("=")) + .filter(kv -> kv.length > 0 && StringUtils.isNotEmpty(kv[0]) && StringUtils.isNotEmpty(kv[1]) && kv[1].matches("\\d+")) + .collect(Collectors.toMap(kv -> kv[0], kv -> Integer.parseInt(kv[1]))); + + // for each epoch + Integer epochNum = params.getOrDefault("epochs", epochs); + Integer loopNum = params.getOrDefault("loops", loops); + List> epochData = new ArrayList<>(epochNum); + for (int i = 0; i < epochNum; i++) { + // for each loop + List loopData = new ArrayList<>(loopNum); + for (int j = 0; j < loopNum; j++) { + loopData.add(System.currentTimeMillis()); + IJava.getKernelInstance().evalRaw(body); + loopData.add(System.currentTimeMillis()); + } + epochData.add(loopData); + } + + // Summary Statistics + List> epochDiff = new ArrayList<>(epochData.size()); + for (int i = 0; i < epochData.size(); i++) { + List loopData = epochData.get(i); + List diff = new ArrayList<>(loopData.size() / 2); + for (int j = 0; j < loopData.size() / 2; j++) { + diff.add(loopData.get(i * 2 + 1) - loopData.get(i * 2)); + } + LongSummaryStatistics statistics = diff.stream().collect(Collectors.summarizingLong(o -> o)); + System.out.printf("epoch %d: %s%n", i, statistics); + epochDiff.add(diff); + } + System.out.printf("total: %s%n", epochDiff.stream().flatMap(Collection::stream).collect(Collectors.summarizingLong(o -> o))); + } +} diff --git a/src/main/resources/print.jshell b/src/main/resources/print.jshell index a8e7047..925875b 100644 --- a/src/main/resources/print.jshell +++ b/src/main/resources/print.jshell @@ -1,24 +1,52 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 ${author} + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + import io.github.spencerpark.ijava.IJava; import io.github.spencerpark.ijava.JavaKernel; import io.github.spencerpark.ijava.execution.CodeEvaluator; +import io.github.spencerpark.jupyter.kernel.magic.registry.LineMagic; import jdk.jshell.JShell; import jdk.jshell.Snippet; import java.lang.reflect.Field; +import java.util.List; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; -System.out.println("\n----- printer init -----\n"); - +// System.out.println("\n----- printer init -----\n"); public class Printer { private static final Pattern VAR_IDX_PATTERN = Pattern.compile("(?i)\\$JShell\\$(\\d+)"); - private static final Pattern CONTENT_PATTERN = Pattern.compile(".*print\\((\\w+)\\)"); + private static final String METHOD_NAME = "print"; + + private static final List COMMENT_PATTERNS = List.of("/\\*(.|\\s)*?\\*/", "//.*\\n*", "\\s+"); private static int varIdx = 0; - private static String prefix = "printer|"; + //private static String prefix = "printer| "; + private static String prefix = ""; // java ansi code demo: https://stackoverflow.com/a/5762502 private static final String varNameStylePattern = "\u001B[36m%s\u001B[0m"; @@ -51,8 +79,13 @@ public class Printer { .filter(o -> o.id().equals(id)).findFirst(); if (snippetOptional.isPresent()) { Snippet snippet = snippetOptional.get(); - Matcher contentMatcher = CONTENT_PATTERN.matcher(snippet.source()); - varName = contentMatcher.find() ? contentMatcher.group(1) : null; + String source = snippet.source(); + if (source.contains(METHOD_NAME)) { + for (String pattern : COMMENT_PATTERNS) { + source = source.replaceAll(pattern, ""); + } + varName = source.substring(source.indexOf("(") + 1, source.lastIndexOf(")")); + } } } } @@ -60,15 +93,31 @@ public class Printer { varName = "var-" + varIdx; varIdx++; } - System.out.printf("%s %s | %s%n", prefix, String.format(varNameStylePattern, varName), obj); + System.out.printf("%s%s: %s%n", prefix, String.format(varNameStylePattern, varName), obj); } public static JShell getJshell() { return jshell; } + + @LineMagic + public void printerPrefix(List args) { + if (args == null || args.isEmpty()) { + System.out.printf("Printer current prefix=\"%s\", -h for help.", prefix); + return; + } + if (args.get(0).equals("-h") || args.get(0).equals("--help")) { + System.out.println("example %printerPrefix \"new prefix: \""); + return; + } + System.out.printf("Change printer prefix from \"%s\" to \"%s\"%n", prefix, args.get(0)); + prefix = args.get(0); + } } public void print(Object arg) { Printer.print(arg); } + +IJava.getKernelInstance().getMagics().registerMagics(new Printer()); diff --git a/src/test/java/io/github/spencerpark/ijava/TestUtils.java b/src/test/java/io/github/spencerpark/ijava/TestUtils.java index 930aaf7..5ebe91c 100644 --- a/src/test/java/io/github/spencerpark/ijava/TestUtils.java +++ b/src/test/java/io/github/spencerpark/ijava/TestUtils.java @@ -4,54 +4,36 @@ import org.junit.Assert; import org.junit.Test; -import javax.xml.stream.XMLStreamException; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.file.*; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.*; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; +import java.util.Set; public class TestUtils { - public static List match(String glob, String location) throws IOException { - final List matchedPath = new ArrayList<>(); - final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher(glob); - Files.walkFileTree(Path.of(location), new HashSet<>(2), 5, new SimpleFileVisitor<>() { - @Override - public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { - if (pathMatcher.matches(path) && Files.isReadable(path)) { - matchedPath.add(path); - } - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) { - return FileVisitResult.CONTINUE; - } - }); - return matchedPath; - } - @Test - public void testFileGlob() throws IOException { + public void testFileGlob() { String glob = "glob:**/*.zip"; String path = "E:/flink-platform-dev"; //path = "."; //glob = "glob:*"; - System.out.println(File.pathSeparator); + //System.out.println(File.pathSeparator); //List match = match(glob, path); //Assert.assertNotNull(match); } @Test - public void testReadXml() throws XMLStreamException, FileNotFoundException { - Path filePath = Path.of("D:\\Maven\\apache-maven-3.6.3\\conf\\settings.xml"); - Set elementNames = Collections.singleton("localRepository"); - Map elementTextData = FileUtils.readXmlElementText(filePath, elementNames); - Assert.assertNotNull(elementTextData); - for (String elementName : elementNames) { - Assert.assertEquals(elementTextData.get(elementName), "D:\\Maven\\repository"); + public void testReadXml() { + try { + Path filePath = Path.of("D:\\Maven\\apache-maven-3.6.3\\conf\\settings.xml"); + Set elementNames = Collections.singleton("localRepository"); + Map elementTextData = FileUtils.readXmlElementText(filePath, elementNames); + Assert.assertNotNull(elementTextData); + for (String elementName : elementNames) { + Assert.assertEquals(elementTextData.get(elementName), "D:\\Maven\\repository"); + } + } catch (Exception e) { + // pass + System.out.println(e.getMessage()); } } } From 4a0e1dc69d83dc5a6f643316efcfeca68520e994 Mon Sep 17 00:00:00 2001 From: potoo0 <1415615232@qq.com> Date: Fri, 3 Jun 2022 00:39:22 +0800 Subject: [PATCH 05/13] release 1.4.0. 1. upgrade deps; 2. print with name or source; 3. `list` line magic, `time` cell magic... --- .github/workflows/main.yml | 48 ++++++++++++++++++++++++++++++++++++++ build.gradle | 2 +- 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..c1f14c4 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,48 @@ +# Build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# Docs: https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions +# Docs: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle +# Docs: https://github.com/softprops/action-gh-release + +name: Build And Release + +#on: push +on: + push: + # Sequence of patterns matched against refs/tags + tags: + - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 + +# softprops/action-gh-release need write perm +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo github workspace=${{ github.workspace }} + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - name: Change wrapper permissions + run: chmod +x ./gradlew + + - name: Execute Gradle build + run: ./gradlew packDist + + - name: Release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + body_path: UPGRADE.md + files: | + README.md + build/distributions/ijava-1.4.0.zip diff --git a/build.gradle b/build.gradle index a57ee9c..7764b1d 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ plugins { } group = 'io.github.spencerpark' -version = '1.3.1' +version = '1.4.0' repositories { mavenLocal() From f259b73c62654777c04702ec7d8823f69803931c Mon Sep 17 00:00:00 2001 From: potoo0 Date: Mon, 22 Aug 2022 17:26:15 +0800 Subject: [PATCH 06/13] release 1.4.1. 1. fix print input param extract error; 2. add compiler util and cellMagic; 3. add read/write/cmd magic. --- .github/workflows/main.yml | 3 +- README.md | 8 + UPGRADE.md | 20 +- build.gradle | 4 +- docs/img/cmd-line-magic.png | Bin 0 -> 51639 bytes docs/img/compile-cell-magic.png | Bin 0 -> 39159 bytes docs/img/read-write-line-magic.png | Bin 0 -> 46652 bytes docs/img/write-cell-magic.png | Bin 0 -> 35849 bytes .../github/spencerpark/ijava/JavaKernel.java | 1 + .../ijava/magics/CompilerMagics.java | 119 +++++++++ .../spencerpark/ijava/magics/MagicsTool.java | 94 +++++++ .../ijava/utils/RuntimeCompiler.java | 242 ++++++++++++++++++ src/main/resources/print.jshell | 36 ++- .../github/spencerpark/ijava/TestUtils.java | 52 +++- 14 files changed, 557 insertions(+), 22 deletions(-) create mode 100644 docs/img/cmd-line-magic.png create mode 100644 docs/img/compile-cell-magic.png create mode 100644 docs/img/read-write-line-magic.png create mode 100644 docs/img/write-cell-magic.png create mode 100644 src/main/java/io/github/spencerpark/ijava/magics/CompilerMagics.java create mode 100644 src/main/java/io/github/spencerpark/ijava/utils/RuntimeCompiler.java diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c1f14c4..f2fa3a0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -44,5 +44,4 @@ jobs: with: body_path: UPGRADE.md files: | - README.md - build/distributions/ijava-1.4.0.zip + build/distributions/ijava-latest.zip diff --git a/README.md b/README.md index b16564f..7130ad1 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,14 @@ features and magics: ![timeout](docs/img/line-magic-list.png) * add `time` cell magic ![timeout](docs/img/cell-magic-time.png) +* add `compile` cellMagic (make sure `--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED` in env * + IJAVA_COMPILER_OPTS*) + ![compile](docs/img/compile-cell-magic.png) +* add `read/write` cell/body magic + ![r-w](docs/img/read-write-line-magic.png) + ![r-w](docs/img/write-cell-magic.png) +* add `cmd` line magic + ![cmd](docs/img/cmd-line-magic.png) [//]: # ([![badge](https://img.shields.io/badge/launch-binder-E66581.svg?logo=data:image/png;base64,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)](https://mybinder.org/v2/gh/SpencerPark/ijava-binder/master) [![badge](https://img.shields.io/badge/launch-binder%20lab-579ACA.svg?logo=data:image/png;base64,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)](https://mybinder.org/v2/gh/SpencerPark/ijava-binder/master?urlpath=lab)) diff --git a/UPGRADE.md b/UPGRADE.md index d1979d5..51b17c3 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,11 +1,13 @@ Upgrade Note: -* Upgrade to jdk 17 and gradle 7.3.3 -* Print with variable name or source - ![timeout](docs/img/print-with-var-name.png) -* add `print` function and `printerPrefix` line magic - ![timeout](docs/img/print-func-line-magic.png) -* add `list` line magic - ![timeout](docs/img/line-magic-list.png) -* add `time` cell magic - ![timeout](docs/img/cell-magic-time.png) +* Fix `print` input parameter extraction error in code blocks that are called multiple times; +* add `RuntimeCompiler` util; +* add `compile` cellMagic (make sure `--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED` in env * + IJAVA_COMPILER_OPTS*) + ![compile](docs/img/compile-cell-magic.png) +* add `read/write` cell/body magic + ![r-w](docs/img/read-write-line-magic.png) + ![r-w](docs/img/write-cell-magic.png) +* add `cmd` line magic + ![cmd](docs/img/cmd-line-magic.png) + diff --git a/build.gradle b/build.gradle index 7764b1d..c6efb14 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ plugins { } group = 'io.github.spencerpark' -version = '1.4.0' +version = '1.4.1' repositories { mavenLocal() @@ -98,6 +98,8 @@ licenseReport { // pack up tasks.register('packDist', Zip) { + archiveFileName = project.name + "-latest.zip" + from(layout.buildDirectory.dir("resources/main")) { include "install.py" } diff --git a/docs/img/cmd-line-magic.png b/docs/img/cmd-line-magic.png new file mode 100644 index 0000000000000000000000000000000000000000..d472d786f42ec8d232a97b0bba286f68900d69f9 GIT binary patch literal 51639 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 literal 0 HcmV?d00001 diff --git a/docs/img/compile-cell-magic.png b/docs/img/compile-cell-magic.png new file mode 100644 index 0000000000000000000000000000000000000000..c926afaca0b1433def7a528897f2a9cc2c5f504c GIT binary patch literal 39159 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
    - *
  1. ERROR
  2. - *
  3. WANRING
  4. - *
  5. INFO
  6. - *
  7. VERBOSE
  8. - *
  9. DEBUG
  10. - *
- * - * @return the fresh ivy instance. - */ - private Ivy createDefaultIvyInstance(int verbosity) { - MessageLogger logger = new DefaultMessageLogger(verbosity); - - // Set the default logger since not all things log to the ivy instance. - Message.setDefaultLogger(logger); - Ivy ivy = new Ivy(); - - ivy.getLoggerEngine().setDefaultLogger(logger); - ivy.setSettings(new IvySettings()); - ivy.bind(); - - return ivy; + // central + this.addRemoteRepo("central", "http://central.maven.org/maven2/"); } - // TODO support multiple at once. This is necessary for conflict resolution with multiple overlapping dependencies. - // TODO support classpath resolution - public List resolveMavenDependency(String canonical, Set repos, int verbosity) throws IOException, ParseException { - ChainResolver rootResolver = this.searchAllReposResolver(repos); - - Ivy ivy = this.createDefaultIvyInstance(verbosity); - IvySettings settings = ivy.getSettings(); - - settings.addResolver(rootResolver); - rootResolver.setCheckmodified(true); - settings.setDefaultResolver(rootResolver.getName()); - - ivy.getLoggerEngine().info("Searching for dependencies in: " + rootResolver.getResolvers()); - - ResolveOptions resolveOptions = new ResolveOptions(); - resolveOptions.setTransitive(true); - resolveOptions.setDownload(true); - - ModuleRevisionId artifactIdentifier = MavenResolver.parseCanonicalArtifactName(canonical); - DefaultModuleDescriptor containerModule = DefaultModuleDescriptor.newCallerInstance( - artifactIdentifier, - DEFAULT_RESOLVE_CONFS, - true, // Transitive - repos != null // Changing - the resolver will set this based on SNAPSHOT since they are all m2 compatible - // but if `repos` is specified, we want to force a lookup. - ); - - ResolveReport resolved = ivy.resolve(containerModule, resolveOptions); - if (resolved.hasError()) { - MessageLogger logger = ivy.getLoggerEngine(); - Arrays.stream(resolved.getAllArtifactsReports()) - .forEach(r -> { - logger.error("download " + r.getDownloadStatus() + ": " + r.getArtifact() + " of " + r.getType()); - if (r.getArtifactOrigin() == null) - logger.error("\tCouldn't find artifact."); - else - logger.error("\tfrom: " + r.getArtifactOrigin()); - }); - - // TODO better error... - throw new RuntimeException("Error resolving '" + canonical + "'. " + resolved.getAllProblemMessages()); - } - - return Arrays.stream(resolved.getAllArtifactsReports()) - .filter(a -> JAR_TYPE.equalsIgnoreCase(a.getType())) - .map(ArtifactDownloadReport::getLocalFile) - .toList(); - } - - private File convertPomToIvy(Ivy ivy, File pomFile) throws IOException, ParseException { - PomModuleDescriptorParser parser = PomModuleDescriptorParser.getInstance(); - - URL pomUrl = pomFile.toURI().toURL(); - - ModuleDescriptor pomModule = parser.parseDescriptor(new IvySettings(), pomFile.toURI().toURL(), false); - - File tempIvyFile = File.createTempFile("ijava-ivy-", ".xml").getAbsoluteFile(); - tempIvyFile.deleteOnExit(); - - parser.toIvyFile(pomUrl.openStream(), new URLResource(pomUrl), tempIvyFile, pomModule); - - MessageLogger logger = ivy.getLoggerEngine(); - logger.info(Files.readString(tempIvyFile.toPath(), StandardCharsets.UTF_8)); - - return tempIvyFile; - } - - private void addPomReposToIvySettings(IvySettings settings, File pomFile) throws ModelBuildingException { - Model mavenModel = Maven.getInstance().readEffectiveModel(pomFile).getEffectiveModel(); - ChainResolver pomRepos = MavenToIvy.createChainForModelRepositories(mavenModel); - pomRepos.setName(DEFAULT_RESOLVER_NAME); - - settings.addResolver(pomRepos); - settings.setDefaultResolver(DEFAULT_RESOLVER_NAME); - } - - private List resolveFromIvyFile(Ivy ivy, File ivyFile, List scopes) throws IOException, ParseException { - ResolveOptions resolveOptions = new ResolveOptions(); - resolveOptions.setTransitive(true); - resolveOptions.setDownload(true); - resolveOptions.setConfs(!scopes.isEmpty() - ? scopes.toArray(new String[0]) - : DEFAULT_RESOLVE_CONFS - ); - - ResolveReport resolved = ivy.resolve(ivyFile, resolveOptions); - if (resolved.hasError()) - // TODO better error... - throw new RuntimeException("Error resolving '" + ivyFile + "'. " + resolved.getAllProblemMessages()); - - return Arrays.stream(resolved.getAllArtifactsReports()) - .map(ArtifactDownloadReport::getLocalFile) - .toList(); - } - - - private String solidifyPartialPOM(String rawIn) throws ParserConfigurationException, IOException, SAXException, TransformerException { - DocumentBuilderFactory factory = DocumentBuilderFactory.newDefaultInstance(); - factory.setValidating(false); - DocumentBuilder builder = factory.newDocumentBuilder(); - - // Wrap in a dummy tag to allow fragments - InputStream inStream = new SequenceInputStream(Collections.enumeration(Arrays.asList( - new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8)), - new ByteArrayInputStream(rawIn.getBytes(StandardCharsets.UTF_8)), - new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8)) - ))); - - Document doc = builder.parse(inStream); - NodeList rootChildren = doc.getDocumentElement().getChildNodes(); - - // If input was a single "project" tag then we don't touch it. It is assumed - // to be complete. - if (rootChildren.getLength() == 1 && "project".equalsIgnoreCase(rootChildren.item(0).getNodeName())) - return this.writeDOM(new DOMSource(rootChildren.item(0))); - - // Put the pieces together and fill in the blanks. - Document fixed = builder.newDocument(); - - Node project = fixed.appendChild(fixed.createElement("project")); - - Node dependencies = project.appendChild(fixed.createElement("dependencies")); - Node repositories = project.appendChild(fixed.createElement("repositories")); - - boolean setModelVersion = false; - boolean setGroupId = false; - boolean setArtifactId = false; - boolean setVersion = false; - - for (int i = 0; i < rootChildren.getLength(); i++) { - Node child = rootChildren.item(i); - - switch (child.getNodeName()) { - case "modelVersion" -> { - setModelVersion = true; - this.appendChildInNewDoc(child, fixed, project); - } - case "groupId" -> { - setGroupId = true; - this.appendChildInNewDoc(child, fixed, project); - } - case "artifactId" -> { - setArtifactId = true; - this.appendChildInNewDoc(child, fixed, project); - } - case "version" -> { - setVersion = true; - this.appendChildInNewDoc(child, fixed, project); - } - case "dependency" -> this.appendChildInNewDoc(child, fixed, dependencies); - case "repository" -> this.appendChildInNewDoc(child, fixed, repositories); - case "dependencies" -> { - // Add all dependencies to the collecting tag - NodeList dependencyChildren = child.getChildNodes(); - for (int j = 0; j < dependencyChildren.getLength(); j++) - this.appendChildInNewDoc(dependencyChildren.item(j), fixed, dependencies); - } - case "repositories" -> { - // Add all repositories to the collecting tag - NodeList repositoryChildren = child.getChildNodes(); - for (int j = 0; j < repositoryChildren.getLength(); j++) - this.appendChildInNewDoc(repositoryChildren.item(j), fixed, repositories); - } - default -> this.appendChildInNewDoc(child, fixed, project); - } - } - - if (!setModelVersion) { - Node modelVersion = project.appendChild(fixed.createElement("modelVersion")); - modelVersion.setTextContent("4.0.0"); - } - - if (!setGroupId) { - Node groupId = project.appendChild(fixed.createElement("groupId")); - groupId.setTextContent("ijava.notebook"); - } - - if (!setArtifactId) { - Node artifactId = project.appendChild(fixed.createElement("artifactId")); - artifactId.setTextContent("cell"); - } - - if (!setVersion) { - Node version = project.appendChild(fixed.createElement("version")); - version.setTextContent("1"); - } - - return this.writeDOM(new DOMSource(fixed)); - } - - private void appendChildInNewDoc(Node oldNode, Document doc, Node newParent) { - Node newNode = oldNode.cloneNode(true); - doc.adoptNode(newNode); - newParent.appendChild(newNode); - } - - private String writeDOM(Source src) throws TransformerException { - Transformer idTransformer = TransformerFactory.newDefaultInstance().newTransformer(); - idTransformer.setOutputProperty(OutputKeys.INDENT, "yes"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Result dest = new StreamResult(out); - - idTransformer.transform(src, dest); - - return out.toString(StandardCharsets.UTF_8); + private void addRemoteRepo(String id, String url) { + this.remoteRepos.add(new RemoteRepository.Builder(id, DEFAULT_REPO_TYPE, url).build()); } public void addJarsToClasspath(Iterable jars) { jars.forEach(this.addToClasspath); } - @LineMagic(aliases = { "addMavenDependency", "maven" }) + @LineMagic(aliases = {"addMavenDependency", "maven"}) public void addMavenDependencies(List args) { - MagicsArgs schema = MagicsArgs.builder() - .varargs("deps") - .keyword("from") - .flag("verbose", 'v') - .onlyKnownKeywords() - .onlyKnownFlags() - .build(); - - Map> vals = schema.parse(args); - - List deps = vals.get("deps"); - List from = vals.get("from"); - int verbosity = vals.get("verbose").size(); - - Set repos = from.isEmpty() ? null : new LinkedHashSet<>(from); - - - for (String dep : deps) { - try { - this.addJarsToClasspath( - this.resolveMavenDependency(dep, repos, verbosity).stream() - .map(File::getAbsolutePath) - ::iterator - ); - } catch (IOException | ParseException e) { - throw new RuntimeException(e); - } + try { + this.addJarsToClasspath(ResolveDependency.resolve(remoteRepos, DEFAULT_REPO_LOCAL, args.toArray(new String[0]))); + } catch (DependencyResolutionException e) { + throw new RuntimeException(e); } } - @LineMagic(aliases = { "mavenRepo" }) + @LineMagic(aliases = {"mavenRepo"}) public void addMavenRepo(List args) { MagicsArgs schema = MagicsArgs.builder().required("id").required("url").build(); - Map> vals = schema.parse(args); - - String id = vals.get("id").get(0); - String url = vals.get("url").get(0); + Map> argData = schema.parse(args); + String id = argData.get("id").get(0); + String url = argData.get("url").get(0); this.addRemoteRepo(id, url); } - @CellMagic + @CellMagic(aliases = {"pom"}) public void loadFromPOM(List args, String body) throws Exception { try { - File tempPomPath = File.createTempFile("ijava-maven-", ".pom").getAbsoluteFile(); - tempPomPath.deleteOnExit(); - - String rawPom = this.solidifyPartialPOM(body); - Files.writeString(tempPomPath.toPath(), rawPom); - - List loadArgs = new ArrayList<>(args.size() + 1); - loadArgs.add(tempPomPath.getAbsolutePath()); - loadArgs.addAll(args); - - this.loadFromPOM(loadArgs); + Matcher reposMatcher = reposPattern.matcher(body); + String repos = reposMatcher.find() ? reposMatcher.group("repos") : ""; + Matcher depsMatcher = depsPattern.matcher(body); + String deps = depsMatcher.find() ? depsMatcher.group("deps") : ""; + String pomContent = String.format(pomSimpleTemplate, repos, deps); + MavenXpp3Reader reader = new MavenXpp3Reader(); + Model model = reader.read(new StringReader(pomContent)); + resolveModel(model); } catch (IOException e) { throw new RuntimeException(e); } } - @LineMagic + @LineMagic(aliases = {"pom"}) public void loadFromPOM(List args) { if (args.isEmpty()) throw new IllegalArgumentException("Loading from POM requires at least the path to the POM file"); MagicsArgs schema = MagicsArgs.builder() .required("pomPath") - .varargs("scopes") - .flag("verbose", 'v') .onlyKnownKeywords().onlyKnownFlags().build(); - Map> vals = schema.parse(args); + Map> argMap = schema.parse(args); - String pomPath = vals.get("pomPath").get(0); - List scopes = vals.get("scopes"); - int verbosity = vals.get("verbose").size(); - - File pomFile = new File(pomPath); + String pomPath = argMap.get("pomPath").get(0); try { - Ivy ivy = this.createDefaultIvyInstance(verbosity); - IvySettings settings = ivy.getSettings(); - - File ivyFile = this.convertPomToIvy(ivy, pomFile); - - this.addPomReposToIvySettings(settings, pomFile); - - this.addJarsToClasspath( - this.resolveFromIvyFile(ivy, ivyFile, scopes).stream() - .map(File::getAbsolutePath) - ::iterator - ); - } catch (IOException | ParseException | ModelBuildingException e) { + MavenXpp3Reader reader = new MavenXpp3Reader(); + Model model = reader.read(new FileReader(pomPath, StandardCharsets.UTF_8)); + resolveModel(model); + } catch (IOException | XmlPullParserException | DependencyResolutionException e) { throw new RuntimeException(e); } } + + private void resolveModel(Model model) throws DependencyResolutionException { + // add repo + model.getRepositories().forEach(repo -> addRemoteRepo(repo.getId(), repo.getUrl())); + // resolve dep + String[] coords = model.getDependencies() + .stream() + .map(dep -> String.format("%s:%s:%s", dep.getGroupId(), dep.getArtifactId(), dep.getVersion())) + .toArray(String[]::new); + this.addJarsToClasspath(ResolveDependency.resolve(remoteRepos, DEFAULT_REPO_LOCAL, coords)); + } } diff --git a/src/main/java/io/github/spencerpark/ijava/magics/dependencies/CommonRepositories.java b/src/main/java/io/github/spencerpark/ijava/magics/dependencies/CommonRepositories.java deleted file mode 100644 index c239c4b..0000000 --- a/src/main/java/io/github/spencerpark/ijava/magics/dependencies/CommonRepositories.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2022 ${author} - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package io.github.spencerpark.ijava.magics.dependencies; - -import org.apache.ivy.plugins.resolver.DependencyResolver; -import org.apache.ivy.plugins.resolver.IBiblioResolver; - -import javax.xml.stream.XMLStreamException; -import java.io.IOException; -import java.nio.file.Path; - -public class CommonRepositories { - protected static final String MAVEN_PATTERN_PREFIX = "[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])"; - protected static final String MAVEN_ARTIFACT_PATTERN = MAVEN_PATTERN_PREFIX + ".[ext]"; - protected static final String MAVEN_POM_PATTERN = MAVEN_PATTERN_PREFIX + ".pom"; - - public static DependencyResolver maven(String name, String urlRaw) { - IBiblioResolver resolver = new IBiblioResolver(); - resolver.setM2compatible(true); - resolver.setUseMavenMetadata(true); - resolver.setUsepoms(true); - - resolver.setRoot(urlRaw); - resolver.setName(name); - - return resolver; - } - - public static DependencyResolver mavenCentral() { - return CommonRepositories.maven("maven-central", "https://repo.maven.apache.org/maven2/"); - } - - public static DependencyResolver jcenter() { - return CommonRepositories.maven("jcenter", "https://jcenter.bintray.com/"); - } - - public static DependencyResolver mavenLocal() { - IBiblioResolver resolver = new IBiblioResolver(); - resolver.setM2compatible(true); - resolver.setUseMavenMetadata(true); - resolver.setUsepoms(true); - - resolver.setName("maven-local"); - - Path localRepoPath; - try { - localRepoPath = Maven.getInstance().getConfiguredLocalRepositoryPath(); - } catch (IOException e) { - throw new RuntimeException("Error reading maven settings. " + e.getLocalizedMessage(), e); - } catch (XMLStreamException e) { - throw new RuntimeException("Error parsing maven settings. " + e.getLocalizedMessage(), e); - } - - resolver.setRoot("file:///" + localRepoPath.toString()); - - return resolver; - } -} diff --git a/src/main/java/io/github/spencerpark/ijava/magics/dependencies/Maven.java b/src/main/java/io/github/spencerpark/ijava/magics/dependencies/Maven.java deleted file mode 100644 index 946defc..0000000 --- a/src/main/java/io/github/spencerpark/ijava/magics/dependencies/Maven.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2022 ${author} - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package io.github.spencerpark.ijava.magics.dependencies; - -import io.github.spencerpark.ijava.utils.FileUtils; -import org.apache.maven.building.StringSource; -import org.apache.maven.model.building.*; - -import javax.xml.stream.XMLStreamException; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collections; -import java.util.Map; -import java.util.Properties; -import java.util.function.UnaryOperator; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class Maven { - private static final Pattern MAVEN_VAR_PATTERN = Pattern.compile("\\$\\{(?[^}*])}"); - - private static final Maven INSTANCE = new Maven(new Properties(), Collections.emptyMap()); - - public static Maven getInstance() { - return INSTANCE; - } - - // User provider environment overrides. - private final Properties properties; - private final Map environment; - - public Maven(Properties properties, Map environment) { - this.properties = properties; - this.environment = environment; - } - - private String getProperty(String name, String def) { - String val = this.environment.get(name); - if (val != null) - return val; - - val = System.getProperty(name); - return val != null ? val : def; - } - - private String getProperty(String name) { - return this.getProperty(name, null); - } - - private String getEnv(String name, String def) { - String val = this.environment.get(name); - if (val != null) - return val; - - val = System.getenv(name); - return val != null ? val : def; - } - - private String getEnv(String name) { - return this.getEnv(name, null); - } - - public Path getUserSystemHomePath() { - String home = this.getProperty("user.home"); - return Paths.get(home).toAbsolutePath(); - } - - private String replaceMavenVars(String raw) { - StringBuilder replaced = new StringBuilder(); - - Matcher matcher = MAVEN_VAR_PATTERN.matcher(raw); - while (matcher.find()) - matcher.appendReplacement(replaced, - System.getProperty(matcher.group("name"), "")); - - matcher.appendTail(replaced); - - return replaced.toString(); - } - - // Thanks gradle! - - private Path getUserHomePath() { - return this.getUserSystemHomePath().resolve(".m2"); - } - - private Path getGlobalHomePath() { - String envM2Home = this.getEnv("M2_HOME"); - return envM2Home != null ? Paths.get(envM2Home).toAbsolutePath() : null; - } - - private Path getUserSettingsPath() { - return this.getUserHomePath().resolve("settings.xml"); - } - - private Path getGlobalSettingsPath() { - Path sysHome = this.getGlobalHomePath(); - return sysHome != null ? sysHome.resolve("conf").resolve("settings.xml") : null; - } - - private Path getDefaultLocalRepoPath() { - return this.getUserHomePath().resolve("repository"); - } - - private Path readConfiguredLocalRepositoryPath(Path settingsXmlPath) throws IOException, XMLStreamException { - if (!Files.isRegularFile(settingsXmlPath)) return null; - - String localRepositoryName = "localRepository"; - String localRepositoryVal = FileUtils.readXmlElementText(settingsXmlPath, Collections.singleton(localRepositoryName)) - .get(localRepositoryName); - if (localRepositoryVal == null) return null; - return Paths.get(this.replaceMavenVars(localRepositoryVal)); - } - - // TODO just use the effective settings - public Path getConfiguredLocalRepositoryPath() throws IOException, XMLStreamException { - Path userSettingsXmlPath = this.getUserSettingsPath(); - Path path = this.readConfiguredLocalRepositoryPath(userSettingsXmlPath); - - if (path == null) { - Path globalSettingsXmlPath = this.getGlobalSettingsPath(); - if (globalSettingsXmlPath != null) - path = this.readConfiguredLocalRepositoryPath(globalSettingsXmlPath); - } - - return path == null ? this.getDefaultLocalRepoPath() : path; - } - - /*public SettingsBuildingResult readEffectiveSettings() throws SettingsBuildingException { - DefaultSettingsBuilder settingsBuilder = new DefaultSettingsBuilderFactory().newInstance(); - - SettingsBuildingRequest request = new DefaultSettingsBuildingRequest(); - - request.setSystemProperties(System.getProperties()); - request.setUserProperties(this.properties); - - request.setUserSettingsFile(this.getUserSettingsPath().toFile()); - - Path globalSettingsPath = this.getGlobalSettingsPath(); - request.setGlobalSettingsFile(globalSettingsPath != null ? globalSettingsPath.toFile() : null); - - return settingsBuilder.build(request); - }*/ - - public ModelBuildingResult readEffectiveModel(CharSequence pom) throws ModelBuildingException { - return this.readEffectiveModel(req -> req.setModelSource((ModelSource) new StringSource(pom))); - } - - public ModelBuildingResult readEffectiveModel(File pom) throws ModelBuildingException { - return this.readEffectiveModel(req -> req.setPomFile(pom)); - } - - private ModelBuildingResult readEffectiveModel(UnaryOperator configuration) throws ModelBuildingException { - DefaultModelBuilder modelBuilder = new DefaultModelBuilderFactory().newInstance(); - - ModelBuildingRequest request = new DefaultModelBuildingRequest(); - - request.setSystemProperties(System.getProperties()); - request.setUserProperties(this.properties); - - // Allow force selection of active profile - // request.setActiveProfileIds() - // request.setInactiveProfileIds() - - // Better error messages for bad poms - request.setLocationTracking(true); - - // Don't run plugins, in most cases this is what we want. I don't know of any - // that would affect the POM. - request.setProcessPlugins(false); - - request = configuration.apply(request); - - return modelBuilder.build(request); - } -} diff --git a/src/main/java/io/github/spencerpark/ijava/magics/dependencies/MavenToIvy.java b/src/main/java/io/github/spencerpark/ijava/magics/dependencies/MavenToIvy.java deleted file mode 100644 index 5db6631..0000000 --- a/src/main/java/io/github/spencerpark/ijava/magics/dependencies/MavenToIvy.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2022 ${author} - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package io.github.spencerpark.ijava.magics.dependencies; - -import org.apache.ivy.plugins.resolver.ChainResolver; -import org.apache.ivy.plugins.resolver.DependencyResolver; -import org.apache.maven.model.Model; -import org.apache.maven.model.Repository; -import org.apache.maven.model.building.ModelBuildingException; - -import java.io.File; -import java.util.List; -import java.util.stream.Collectors; - -public class MavenToIvy { - public static List getRepositoriesFromModel(CharSequence pom) throws ModelBuildingException { - return MavenToIvy.getRepositoriesFromModel(Maven.getInstance().readEffectiveModel(pom).getEffectiveModel()); - } - - public static List getRepositoriesFromModel(File pom) throws ModelBuildingException { - return MavenToIvy.getRepositoriesFromModel(Maven.getInstance().readEffectiveModel(pom).getEffectiveModel()); - } - - public static List getRepositoriesFromModel(Model model) { - return model.getRepositories().stream() - .map(MavenToIvy::convertRepository) - .collect(Collectors.toList()); - } - - public static DependencyResolver convertRepository(Repository repository) { - return CommonRepositories.maven(repository.getId(), repository.getUrl()); - } - - public static ChainResolver createChainForModelRepositories(Model model) { - ChainResolver resolver = new ChainResolver(); - - // Maven central is always an implicit repository. - resolver.add(CommonRepositories.mavenCentral()); - - MavenToIvy.getRepositoriesFromModel(model).forEach(resolver::add); - - return resolver; - } -} diff --git a/src/main/java/io/github/spencerpark/ijava/utils/ResolveDependency.java b/src/main/java/io/github/spencerpark/ijava/utils/ResolveDependency.java new file mode 100644 index 0000000..6e439fd --- /dev/null +++ b/src/main/java/io/github/spencerpark/ijava/utils/ResolveDependency.java @@ -0,0 +1,98 @@ +package io.github.spencerpark.ijava.utils; + +import org.apache.maven.repository.internal.MavenRepositorySystemUtils; +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.collection.CollectRequest; +import org.eclipse.aether.collection.DependencyCollectionContext; +import org.eclipse.aether.collection.DependencySelector; +import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.impl.DefaultServiceLocator; +import org.eclipse.aether.repository.LocalRepository; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.DependencyRequest; +import org.eclipse.aether.resolution.DependencyResolutionException; +import org.eclipse.aether.resolution.DependencyResult; +import org.eclipse.aether.spi.connector.RepositoryConnectorFactory; +import org.eclipse.aether.spi.connector.transport.TransporterFactory; +import org.eclipse.aether.transport.file.FileTransporterFactory; +import org.eclipse.aether.transport.http.HttpTransporterFactory; +import org.eclipse.aether.util.artifact.JavaScopes; +import org.eclipse.aether.util.filter.DependencyFilterUtils; +import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator; + +import java.io.File; +import java.util.*; + +public class ResolveDependency { + private static final String DEFAULT_REPO_LOCAL = String.format("%s/.m2/repository", System.getProperty("user.home")); + private static final RemoteRepository DEFAULT_REPO_REMOTE = new RemoteRepository.Builder("central", "default", "https://repo1.maven.org/maven2/").build(); + private static final Set DEFAULT_SCOPES = Set.of("", JavaScopes.COMPILE); + + private static RepositorySystem system; + + public static List resolve(String... coords) throws DependencyResolutionException { + return resolve(List.of(DEFAULT_REPO_REMOTE), DEFAULT_REPO_LOCAL, coords); + } + + public static List resolve(List remoteRepos, String localRepo, String... coords) throws DependencyResolutionException { + if (Objects.isNull(coords) || coords.length == 0) return Collections.emptyList(); + if (system == null) system = buildRepositorySystem(); + + DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); + session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, new LocalRepository(localRepo))); + session.setDependencySelector(buildDependencySelector(DEFAULT_SCOPES)); + + List dependencies = Arrays.stream(coords).map(DefaultArtifact::new).map(artifact -> new Dependency(artifact, null)).toList(); + CollectRequest collectRequest = new CollectRequest(dependencies, null, remoteRepos); + DependencyRequest request = new DependencyRequest(collectRequest, DependencyFilterUtils.classpathFilter(DEFAULT_SCOPES)); + DependencyResult result = system.resolveDependencies(session, request); + + PreorderNodeListGenerator nodeListGenerator = new PreorderNodeListGenerator(); + result.getRoot().accept(nodeListGenerator); + + return nodeListGenerator.getFiles().stream().map(File::getAbsolutePath).toList(); + } + + private static DependencySelector buildDependencySelector(final Collection scopes) { + return new DependencySelector() { + @Override + public boolean selectDependency(Dependency dependency) { + return scopes.contains(dependency.getScope()) && Boolean.FALSE.equals(dependency.getOptional()); + } + + @Override + public DependencySelector deriveChildSelector(DependencyCollectionContext context) { + return this; + } + }; + } + + private static RepositorySystem buildRepositorySystem() { + DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator(); + locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class); + locator.addService(TransporterFactory.class, FileTransporterFactory.class); + locator.addService(TransporterFactory.class, HttpTransporterFactory.class); + return locator.getService(RepositorySystem.class); + } + + public static void main(String[] args) { + test(); + } + + public static void test() { + String localRepo = "out"; + List remotes = List.of( + DEFAULT_REPO_REMOTE, + new RemoteRepository.Builder("aliyun", "default", "https://maven.aliyun.com/repository/central").build() + ); + try { + List jars = resolve(remotes, localRepo, "org.apache.logging.log4j:log4j-core:2.17.0"); + System.out.printf(">>>>>> jars: %s%n", jars); + } catch (DependencyResolutionException e) { + e.printStackTrace(); + } + } +} From a34648938e4a4b2df381e163b435de60b7574190 Mon Sep 17 00:00:00 2001 From: potoo0 Date: Tue, 30 Aug 2022 15:33:30 +0800 Subject: [PATCH 08/13] jshell logger --- build.gradle | 2 + .../ijava/utils/LoggerInitiator.java | 108 ++++++++++++++++++ src/main/resources/ijava-jshell-init.jshell | 7 ++ src/main/resources/log4j2.xml | 21 ++++ 4 files changed, 138 insertions(+) create mode 100644 src/main/java/io/github/spencerpark/ijava/utils/LoggerInitiator.java create mode 100644 src/main/resources/log4j2.xml diff --git a/build.gradle b/build.gradle index 8415df5..16bfd04 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,8 @@ dependencies { implementation 'org.apache.maven:maven-aether-provider:3.3.9' // ------ for maven resolve and download ------ + implementation 'org.apache.logging.log4j:log4j-core:2.19.0' + testImplementation group: 'junit', name: 'junit', version: '4.13.2' } diff --git a/src/main/java/io/github/spencerpark/ijava/utils/LoggerInitiator.java b/src/main/java/io/github/spencerpark/ijava/utils/LoggerInitiator.java new file mode 100644 index 0000000..479fe0e --- /dev/null +++ b/src/main/java/io/github/spencerpark/ijava/utils/LoggerInitiator.java @@ -0,0 +1,108 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 ${author} + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.spencerpark.ijava.utils; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.appender.OutputStreamAppender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +import org.apache.logging.log4j.core.layout.PatternLayout; + +import java.io.OutputStream; +import java.text.SimpleDateFormat; + +import static org.apache.logging.log4j.LogManager.getLogger; + +public class LoggerInitiator { + private static volatile boolean initialized = false; + + static { + init(); + } + + private LoggerInitiator() { + } + + public static void init() { + // config doc https://logging.apache.org/log4j/2.x/manual/configuration.html + // demo from https://logging.apache.org/log4j/2.x/manual/customconfig.html + if (initialized) return; + String pattern = "%d{yyyy-MM-dd} %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"; + + ConfigurationBuilder builder = ConfigurationBuilderFactory.newConfigurationBuilder(); + builder.setStatusLevel(Level.WARN); + builder.setConfigurationName("Jshell-Logger"); + + // configure a console appender + builder.add( + builder.newAppender("Stdout", "CONSOLE") + .addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT) + .add(builder.newLayout("PatternLayout").addAttribute("pattern", pattern)) + ); + + String fileParent = System.getProperty("os.name").toLowerCase().contains("win") + ? System.getProperty("java.io.tmpdir") + : "/var"; + String fileName = String.format("%s/log/jshell/jshell-%s.log", fileParent, new SimpleDateFormat("yyyyMMdd_HHmmss.SSS").format(System.currentTimeMillis())); + builder.add( + builder.newAppender("File", "FILE") + .addAttribute("fileName", fileName) + .add(builder.newLayout("PatternLayout").addAttribute("pattern", pattern)) + ); + + // configure the root logger + builder.add(builder.newRootLogger(Level.ALL).add(builder.newAppenderRef("Stdout")).add(builder.newAppenderRef("File"))); + BuiltConfiguration config = builder.build(); + Configurator.initialize(config); + //addAppender(config, System.out, "Print", pattern); + + Logger test = test(); + initialized = true; + test.info("log to file: {}", fileName); + } + + public static void addAppender(final Configuration config, final OutputStream outputStream, final String outputStreamName, String pattern) { + // final Configuration config = LoggerContext.getContext(false).getConfiguration(); + final PatternLayout layout = PatternLayout.newBuilder().withConfiguration(config).withPattern(pattern).build(); + final Appender appender = OutputStreamAppender.newBuilder().setLayout(layout).setTarget(outputStream).setName(outputStreamName).build(); + appender.start(); + config.addAppender(appender); + + // update Loggers. root logger already in getLoggers()'s map, getRootLogger.addAppender is unnecessary + config.getLoggers().values().forEach(loggerConfig -> loggerConfig.addAppender(appender, null, null)); + } + + public static Logger test() { + Logger logger = getLogger("test-jshell-log"); + logger.debug("--------------- test-jshell-log debug ------------------"); + logger.error("--------------- test-jshell-log error ------------------"); + return logger; + } +} diff --git a/src/main/resources/ijava-jshell-init.jshell b/src/main/resources/ijava-jshell-init.jshell index 8ee7787..d1e27b0 100644 --- a/src/main/resources/ijava-jshell-init.jshell +++ b/src/main/resources/ijava-jshell-init.jshell @@ -10,6 +10,13 @@ import static io.github.spencerpark.ijava.runtime.Display.*; import static io.github.spencerpark.ijava.runtime.Kernel.*; import static io.github.spencerpark.ijava.runtime.Magics.*; +import io.github.spencerpark.ijava.utils.LoggerInitiator; +import static org.apache.logging.log4j.LogManager.getLogger; + + public void printf(String format, Object... args) { System.out.printf(format, args); } + + +LoggerInitiator.init(); diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..f96e168 --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,21 @@ + + + + %d{yyyy-MM-dd} %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + logs/jshell/jshell-$${date:yyyyMMdd_HHmmss.SSS}.log + + + + + + + + + + + + + + + + From 17daa332c0c6a2f348e41c4341dec436b8faad32 Mon Sep 17 00:00:00 2001 From: potoo0 Date: Fri, 28 Oct 2022 19:59:30 +0800 Subject: [PATCH 09/13] upgrade dep resolve api --- build.gradle | 15 ++- .../ijava/magics/MavenResolver.java | 16 ++-- .../ijava/utils/ResolveDependency.java | 94 +++++++++++-------- 3 files changed, 72 insertions(+), 53 deletions(-) diff --git a/build.gradle b/build.gradle index 16bfd04..6571a6c 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ import org.apache.tools.ant.filters.ReplaceTokens plugins { - id 'java' + id 'java-library' // id 'maven-publish' // use johnrengelman.shadow instead id 'com.github.hierynomus.license' version '0.16.1' id "com.github.jk1.dependency-license-report" version "2.1" @@ -28,10 +28,15 @@ dependencies { implementation group: 'io.github.spencerpark', name: 'jupyter-jvm-basekernel', version: '2.3.0' // ------ for maven resolve and download ------ - implementation 'org.eclipse.aether:aether-connector-basic:1.1.0' - implementation 'org.eclipse.aether:aether-transport-file:1.1.0' - implementation 'org.eclipse.aether:aether-transport-http:1.1.0' - implementation 'org.apache.maven:maven-aether-provider:3.3.9' + // implementation 'org.apache.maven:maven-resolver-provider:4.0.0-alpha-2' + implementation('org.apache.maven:maven-resolver-provider:3.8.6') { + exclude group: 'org.apache.maven.resolver' // manual include 1.8.2 + } + implementation 'org.apache.maven.resolver:maven-resolver-impl:1.8.2' + implementation 'org.apache.maven.resolver:maven-resolver-connector-basic:1.8.2' + implementation 'org.apache.maven.resolver:maven-resolver-transport-file:1.8.2' + implementation 'org.apache.maven.resolver:maven-resolver-transport-http:1.8.2' + implementation 'org.apache.maven.resolver:maven-resolver-transport-classpath:1.8.2' // ------ for maven resolve and download ------ implementation 'org.apache.logging.log4j:log4j-core:2.19.0' diff --git a/src/main/java/io/github/spencerpark/ijava/magics/MavenResolver.java b/src/main/java/io/github/spencerpark/ijava/magics/MavenResolver.java index dd32dee..a8ef797 100644 --- a/src/main/java/io/github/spencerpark/ijava/magics/MavenResolver.java +++ b/src/main/java/io/github/spencerpark/ijava/magics/MavenResolver.java @@ -30,6 +30,7 @@ import org.apache.maven.model.Model; import org.apache.maven.model.io.xpp3.MavenXpp3Reader; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import org.eclipse.aether.repository.NoLocalRepositoryManagerException; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.resolution.DependencyResolutionException; @@ -71,8 +72,8 @@ public void addJarsToClasspath(Iterable jars) { @LineMagic(aliases = {"addMavenDependency", "maven"}) public void addMavenDependencies(List args) { try { - this.addJarsToClasspath(ResolveDependency.resolve(remoteRepos, DEFAULT_REPO_LOCAL, args.toArray(new String[0]))); - } catch (DependencyResolutionException e) { + this.addJarsToClasspath(ResolveDependency.resolve(args, null, DEFAULT_REPO_LOCAL, remoteRepos)); + } catch (DependencyResolutionException | NoLocalRepositoryManagerException e) { throw new RuntimeException(e); } } @@ -119,19 +120,20 @@ public void loadFromPOM(List args) { MavenXpp3Reader reader = new MavenXpp3Reader(); Model model = reader.read(new FileReader(pomPath, StandardCharsets.UTF_8)); resolveModel(model); - } catch (IOException | XmlPullParserException | DependencyResolutionException e) { + } catch (IOException | XmlPullParserException | DependencyResolutionException | + NoLocalRepositoryManagerException e) { throw new RuntimeException(e); } } - private void resolveModel(Model model) throws DependencyResolutionException { + private void resolveModel(Model model) throws DependencyResolutionException, NoLocalRepositoryManagerException { // add repo model.getRepositories().forEach(repo -> addRemoteRepo(repo.getId(), repo.getUrl())); // resolve dep - String[] coords = model.getDependencies() + List coords = model.getDependencies() .stream() .map(dep -> String.format("%s:%s:%s", dep.getGroupId(), dep.getArtifactId(), dep.getVersion())) - .toArray(String[]::new); - this.addJarsToClasspath(ResolveDependency.resolve(remoteRepos, DEFAULT_REPO_LOCAL, coords)); + .toList(); + this.addJarsToClasspath(ResolveDependency.resolve(coords, null, DEFAULT_REPO_LOCAL, remoteRepos)); } } diff --git a/src/main/java/io/github/spencerpark/ijava/utils/ResolveDependency.java b/src/main/java/io/github/spencerpark/ijava/utils/ResolveDependency.java index 6e439fd..ccc5a99 100644 --- a/src/main/java/io/github/spencerpark/ijava/utils/ResolveDependency.java +++ b/src/main/java/io/github/spencerpark/ijava/utils/ResolveDependency.java @@ -1,22 +1,22 @@ package io.github.spencerpark.ijava.utils; import org.apache.maven.repository.internal.MavenRepositorySystemUtils; -import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.DefaultRepositoryCache; import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.collection.CollectRequest; -import org.eclipse.aether.collection.DependencyCollectionContext; -import org.eclipse.aether.collection.DependencySelector; import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory; import org.eclipse.aether.graph.Dependency; -import org.eclipse.aether.impl.DefaultServiceLocator; import org.eclipse.aether.repository.LocalRepository; +import org.eclipse.aether.repository.NoLocalRepositoryManagerException; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.resolution.DependencyRequest; import org.eclipse.aether.resolution.DependencyResolutionException; import org.eclipse.aether.resolution.DependencyResult; import org.eclipse.aether.spi.connector.RepositoryConnectorFactory; import org.eclipse.aether.spi.connector.transport.TransporterFactory; +import org.eclipse.aether.transport.classpath.ClasspathTransporterFactory; import org.eclipse.aether.transport.file.FileTransporterFactory; import org.eclipse.aether.transport.http.HttpTransporterFactory; import org.eclipse.aether.util.artifact.JavaScopes; @@ -24,58 +24,66 @@ import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator; import java.io.File; -import java.util.*; +import java.util.Collections; +import java.util.List; +import java.util.Set; public class ResolveDependency { private static final String DEFAULT_REPO_LOCAL = String.format("%s/.m2/repository", System.getProperty("user.home")); private static final RemoteRepository DEFAULT_REPO_REMOTE = new RemoteRepository.Builder("central", "default", "https://repo1.maven.org/maven2/").build(); - private static final Set DEFAULT_SCOPES = Set.of("", JavaScopes.COMPILE); + private static final Set DEFAULT_SCOPES = Set.of(JavaScopes.RUNTIME); - private static RepositorySystem system; + private static final RepositorySystem system; - public static List resolve(String... coords) throws DependencyResolutionException { - return resolve(List.of(DEFAULT_REPO_REMOTE), DEFAULT_REPO_LOCAL, coords); + static { + var locator = MavenRepositorySystemUtils.newServiceLocator(); + + locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class); + locator.addService(TransporterFactory.class, FileTransporterFactory.class); + locator.addService(TransporterFactory.class, HttpTransporterFactory.class); + locator.addService(TransporterFactory.class, ClasspathTransporterFactory.class); + + system = locator.getService(RepositorySystem.class); + } + + public static List resolve(String... coords) throws DependencyResolutionException, NoLocalRepositoryManagerException { + return resolve(List.of(coords), DEFAULT_SCOPES, DEFAULT_REPO_LOCAL, List.of(DEFAULT_REPO_REMOTE)); } - public static List resolve(List remoteRepos, String localRepo, String... coords) throws DependencyResolutionException { - if (Objects.isNull(coords) || coords.length == 0) return Collections.emptyList(); - if (system == null) system = buildRepositorySystem(); + /** + * resolve + * + * @param coords eg: org.apache.logging.log4j:log4j-core:2.19.0 + * @param scopes default to DEFAULT_SCOPES if null or empty + * @param localRepo default to DEFAULT_REPO_LOCAL if null + * @param remoteRepos default to DEFAULT_REPO_REMOTE if null or empty + * @return jar files absolute path + **/ + public static List resolve(List coords, Set scopes, String localRepo, List remoteRepos) throws DependencyResolutionException, NoLocalRepositoryManagerException { + if (coords.isEmpty()) return Collections.emptyList(); + if (scopes == null || scopes.isEmpty()) scopes = DEFAULT_SCOPES; + if (localRepo == null) localRepo = DEFAULT_REPO_LOCAL; + if (remoteRepos == null || remoteRepos.isEmpty()) remoteRepos = List.of(DEFAULT_REPO_REMOTE); - DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); - session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, new LocalRepository(localRepo))); - session.setDependencySelector(buildDependencySelector(DEFAULT_SCOPES)); + RepositorySystemSession session = buildSession(localRepo); - List dependencies = Arrays.stream(coords).map(DefaultArtifact::new).map(artifact -> new Dependency(artifact, null)).toList(); - CollectRequest collectRequest = new CollectRequest(dependencies, null, remoteRepos); - DependencyRequest request = new DependencyRequest(collectRequest, DependencyFilterUtils.classpathFilter(DEFAULT_SCOPES)); + List dependencies = coords.stream().map(DefaultArtifact::new).map(artifact -> new Dependency(artifact, null)).toList(); + var collectRequest = new CollectRequest(dependencies, null, remoteRepos); + + var request = new DependencyRequest(collectRequest, DependencyFilterUtils.classpathFilter(scopes)); DependencyResult result = system.resolveDependencies(session, request); - PreorderNodeListGenerator nodeListGenerator = new PreorderNodeListGenerator(); + var nodeListGenerator = new PreorderNodeListGenerator(); result.getRoot().accept(nodeListGenerator); return nodeListGenerator.getFiles().stream().map(File::getAbsolutePath).toList(); } - private static DependencySelector buildDependencySelector(final Collection scopes) { - return new DependencySelector() { - @Override - public boolean selectDependency(Dependency dependency) { - return scopes.contains(dependency.getScope()) && Boolean.FALSE.equals(dependency.getOptional()); - } - - @Override - public DependencySelector deriveChildSelector(DependencyCollectionContext context) { - return this; - } - }; - } - - private static RepositorySystem buildRepositorySystem() { - DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator(); - locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class); - locator.addService(TransporterFactory.class, FileTransporterFactory.class); - locator.addService(TransporterFactory.class, HttpTransporterFactory.class); - return locator.getService(RepositorySystem.class); + private static RepositorySystemSession buildSession(String localRepo) { + var session = MavenRepositorySystemUtils.newSession(); + session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, new LocalRepository(localRepo))); + session.setCache(new DefaultRepositoryCache()); + return session; } public static void main(String[] args) { @@ -89,9 +97,13 @@ public static void test() { new RemoteRepository.Builder("aliyun", "default", "https://maven.aliyun.com/repository/central").build() ); try { - List jars = resolve(remotes, localRepo, "org.apache.logging.log4j:log4j-core:2.17.0"); + var coords = List.of( + "org.apache.logging.log4j:log4j-core:2.19.0", + "org.apache.logging.log4j:log4j-core:2.19.0" + ); + List jars = resolve(coords, null, localRepo, remotes); System.out.printf(">>>>>> jars: %s%n", jars); - } catch (DependencyResolutionException e) { + } catch (DependencyResolutionException | NoLocalRepositoryManagerException e) { e.printStackTrace(); } } From 1e5ce5ec56dbf9eec02eeb52ea473d09d1f53433 Mon Sep 17 00:00:00 2001 From: potoo0 Date: Fri, 28 Oct 2022 22:44:50 +0800 Subject: [PATCH 10/13] release 1.4.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6571a6c..0e3a40a 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ plugins { } group = 'io.github.spencerpark' -version = '1.4.1' +version = '1.4.2' repositories { mavenLocal() From 63787235ba661ff4ec42e2caf6d9d8216fe6d18a Mon Sep 17 00:00:00 2001 From: potoo0 Date: Sat, 29 Oct 2022 23:30:09 +0800 Subject: [PATCH 11/13] fix json render --- build.gradle | 16 ++++++++- gradle.properties | 2 ++ .../io/github/spencerpark/ijava/IJava.java | 2 +- .../github/spencerpark/ijava/JavaKernel.java | 6 ++++ .../spencerpark/ijava/runtime/Display.java | 36 +++++++++++++++++-- .../spencerpark/ijava/runtime/Magics.java | 4 +-- 6 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 gradle.properties diff --git a/build.gradle b/build.gradle index 0e3a40a..c0ea449 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,10 @@ repositories { } dependencies { - implementation group: 'io.github.spencerpark', name: 'jupyter-jvm-basekernel', version: '2.3.0' + implementation('io.github.spencerpark:jupyter-jvm-basekernel:2.3.0') { + exclude group: 'com.google.code.gson', module: 'gson' + } + implementation 'com.google.code.gson:gson:2.10' // ------ for maven resolve and download ------ // implementation 'org.apache.maven:maven-resolver-provider:4.0.0-alpha-2' @@ -64,6 +67,17 @@ processResources { filter tokens: tokens, ReplaceTokens } +//java { +// withJavadocJar() +// withSourcesJar() +//} + +compileJava { + options.compilerArgs << '-parameters' + // ignore deprecation for jupyter-jvm-basekernel gson JsonParser api +// options.compilerArgs << '-Xlint:all' << '-Xlint:-deprecation' << '-Xlint:-rawtypes' << '-Xlint:-serial' +} + jar { manifest { attributes 'Main-class': 'io.github.spencerpark.ijava.IJava' diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..1e08b9f --- /dev/null +++ b/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.warning.mode=all +org.gradle.logging.stacktrace=all diff --git a/src/main/java/io/github/spencerpark/ijava/IJava.java b/src/main/java/io/github/spencerpark/ijava/IJava.java index fc99ec3..43a5a86 100644 --- a/src/main/java/io/github/spencerpark/ijava/IJava.java +++ b/src/main/java/io/github/spencerpark/ijava/IJava.java @@ -58,7 +58,7 @@ public static InputStream resource(String path) { InputStream metaStream = resource("ijava-kernel-metadata.json"); Reader metaReader = new InputStreamReader(metaStream); try { - JsonElement meta = new JsonParser().parse(metaReader); + JsonElement meta = JsonParser.parseReader(metaReader); VERSION = meta.getAsJsonObject().get("version").getAsString(); } finally { try { diff --git a/src/main/java/io/github/spencerpark/ijava/JavaKernel.java b/src/main/java/io/github/spencerpark/ijava/JavaKernel.java index 9634555..c100932 100644 --- a/src/main/java/io/github/spencerpark/ijava/JavaKernel.java +++ b/src/main/java/io/github/spencerpark/ijava/JavaKernel.java @@ -23,12 +23,14 @@ */ package io.github.spencerpark.ijava; +import com.google.gson.JsonElement; import io.github.spencerpark.ijava.execution.*; import io.github.spencerpark.ijava.magics.*; import io.github.spencerpark.jupyter.kernel.BaseKernel; import io.github.spencerpark.jupyter.kernel.LanguageInfo; import io.github.spencerpark.jupyter.kernel.ReplacementOptions; import io.github.spencerpark.jupyter.kernel.display.DisplayData; +import io.github.spencerpark.jupyter.kernel.display.mime.MIMEType; import io.github.spencerpark.jupyter.kernel.magic.common.Load; import io.github.spencerpark.jupyter.kernel.magic.registry.Magics; import io.github.spencerpark.jupyter.kernel.util.CharPredicate; @@ -132,6 +134,10 @@ public JavaKernel() { new LanguageInfo.Help("Java tutorial", "https://docs.oracle.com/javase/tutorial/java/nutsandbolts/index.html"), new LanguageInfo.Help("IJava homepage", "https://github.com/SpencerPark/IJava") ); + // todo io.github.spencerpark.jupyter.kernel.display.DisplayData putJSON, JsonParser.parseString + this.renderer.createRegistration(JsonElement.class) + .preferring(MIMEType.APPLICATION_JSON) + .register((data, context) -> context.renderIfRequested(MIMEType.APPLICATION_JSON, () -> data)); this.errorStyler = new StringStyler.Builder() .addPrimaryStyle(TextColor.BOLD_BLACK_FG) diff --git a/src/main/java/io/github/spencerpark/ijava/runtime/Display.java b/src/main/java/io/github/spencerpark/ijava/runtime/Display.java index 885674a..2044095 100644 --- a/src/main/java/io/github/spencerpark/ijava/runtime/Display.java +++ b/src/main/java/io/github/spencerpark/ijava/runtime/Display.java @@ -23,12 +23,21 @@ */ package io.github.spencerpark.ijava.runtime; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; import io.github.spencerpark.ijava.JavaKernel; import io.github.spencerpark.jupyter.kernel.display.DisplayData; +import io.github.spencerpark.jupyter.kernel.display.mime.MIMEType; +import java.io.Reader; +import java.util.Arrays; +import java.util.Objects; import java.util.UUID; public class Display { + private static final Gson GSON = new Gson(); + public static DisplayData render(Object o) { JavaKernel kernel = Kernel.getKernelInstance(); @@ -43,7 +52,17 @@ public static DisplayData render(Object o, String... as) { JavaKernel kernel = Kernel.getKernelInstance(); if (kernel != null) { - return kernel.getRenderer().renderAs(o, as); + return kernel.getRenderer().renderAs(parseAsJson(o, as), as); + } else { + throw new RuntimeException("No IJava kernel running"); + } + } + + public static DisplayData renderAsJson(Object o) { + JavaKernel kernel = Kernel.getKernelInstance(); + + if (kernel != null) { + return kernel.getRenderer().renderAs(parseAsJson(o), MIMEType.APPLICATION_JSON.toString()); } else { throw new RuntimeException("No IJava kernel running"); } @@ -73,7 +92,7 @@ public static String display(Object o, String... as) { JavaKernel kernel = Kernel.getKernelInstance(); if (kernel != null) { - DisplayData data = kernel.getRenderer().renderAs(o, as); + DisplayData data = kernel.getRenderer().renderAs(parseAsJson(o, as), as); String id = data.getDisplayId(); if (id == null) { @@ -104,10 +123,21 @@ public static void updateDisplay(String id, Object o, String... as) { JavaKernel kernel = Kernel.getKernelInstance(); if (kernel != null) { - DisplayData data = kernel.getRenderer().renderAs(o, as); + DisplayData data = kernel.getRenderer().renderAs(parseAsJson(o, as), as); kernel.getIO().display.updateDisplay(id, data); } else { throw new RuntimeException("No IJava kernel running"); } } + + private static Object parseAsJson(Object data, String... as) { + if (data instanceof JsonElement jsonElement) return jsonElement; + String jsonMime = MIMEType.APPLICATION_JSON.toString(); + if (Objects.isNull(as) || as.length == 0 || Arrays.stream(as).anyMatch(mime -> mime.contains(jsonMime))) { + if (data instanceof String jsonStr) return JsonParser.parseString(jsonStr); + else if (data instanceof Reader reader) return JsonParser.parseReader(reader); + else return GSON.toJsonTree(data); + } + return data; + } } diff --git a/src/main/java/io/github/spencerpark/ijava/runtime/Magics.java b/src/main/java/io/github/spencerpark/ijava/runtime/Magics.java index c2f9dea..7e8f5e5 100644 --- a/src/main/java/io/github/spencerpark/ijava/runtime/Magics.java +++ b/src/main/java/io/github/spencerpark/ijava/runtime/Magics.java @@ -35,7 +35,7 @@ public static T lineMagic(String name, List args) { if (kernel != null) { try { - return kernel.getMagics().applyLineMagic(name, args); + return JavaKernel.getMagics().applyLineMagic(name, args); } catch (UndefinedMagicException e) { throw e; } catch (Exception e) { @@ -51,7 +51,7 @@ public static T cellMagic(String name, List args, String body) { if (kernel != null) { try { - return kernel.getMagics().applyCellMagic(name, args, body); + return JavaKernel.getMagics().applyCellMagic(name, args, body); } catch (UndefinedMagicException e) { throw e; } catch (Exception e) { From 0f095a6b36aaf62ab997b62d159bf99f3535be53 Mon Sep 17 00:00:00 2001 From: potoo0 Date: Sun, 30 Oct 2022 17:29:15 +0800 Subject: [PATCH 12/13] release 1.4.3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c0ea449..1023669 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ plugins { } group = 'io.github.spencerpark' -version = '1.4.2' +version = '1.4.3' repositories { mavenLocal() From 0b66f42cd54b35015dc800e477d649f9c85eaf16 Mon Sep 17 00:00:00 2001 From: potoo0 Date: Sat, 26 Nov 2022 17:29:56 +0800 Subject: [PATCH 13/13] fix `CompilerMagics.compile` package path;remain jupyter.env --- UPGRADE.md | 18 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../github/spencerpark/ijava/JavaKernel.java | 6 +- .../ijava/magics/CompilerMagics.java | 11 +- .../jupyter/kernel/BaseKernel.java | 457 ++++++++++++++++++ 6 files changed, 477 insertions(+), 19 deletions(-) create mode 100644 src/main/java/io/github/spencerpark/jupyter/kernel/BaseKernel.java diff --git a/UPGRADE.md b/UPGRADE.md index 51b17c3..5715a7a 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,13 +1,9 @@ -Upgrade Note: +Updated: -* Fix `print` input parameter extraction error in code blocks that are called multiple times; -* add `RuntimeCompiler` util; -* add `compile` cellMagic (make sure `--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED` in env * - IJAVA_COMPILER_OPTS*) - ![compile](docs/img/compile-cell-magic.png) -* add `read/write` cell/body magic - ![r-w](docs/img/read-write-line-magic.png) - ![r-w](docs/img/write-cell-magic.png) -* add `cmd` line magic - ![cmd](docs/img/cmd-line-magic.png) +1. fix `CompilerMagics.compile` auto generated package path error +2. remain `JupyterIO.jupyterXXX.env`, keep thread stdout rewrite to jupyter +TODO: + +1. reload `CompilerMagics.compile` class? DirectExecutionControl > DefaultLoaderDelegate +2. thread stdout JupyterIO.retractEnv; BaseKernel.replaceOutputStreams diff --git a/build.gradle b/build.gradle index 1023669..87d0d54 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ plugins { } group = 'io.github.spencerpark' -version = '1.4.3' +version = '1.4.4' repositories { mavenLocal() diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 44ff29f..0671300 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/src/main/java/io/github/spencerpark/ijava/JavaKernel.java b/src/main/java/io/github/spencerpark/ijava/JavaKernel.java index c100932..6e3339d 100644 --- a/src/main/java/io/github/spencerpark/ijava/JavaKernel.java +++ b/src/main/java/io/github/spencerpark/ijava/JavaKernel.java @@ -88,7 +88,7 @@ public JavaKernel() { // todo for debug //try { // System.out.println("------------- sleep start -------------"); - // Thread.sleep(10 * 1000); + // Thread.sleep(10 * 1000L); // System.out.println("------------- sleep end -------------"); //} catch (InterruptedException e) { // e.printStackTrace(); @@ -157,6 +157,10 @@ public MavenResolver getMavenResolver() { return this.mavenResolver; } + public JShell getShell() { + return this.evaluator.getShell(); + } + public static Magics getMagics() { return magics; } diff --git a/src/main/java/io/github/spencerpark/ijava/magics/CompilerMagics.java b/src/main/java/io/github/spencerpark/ijava/magics/CompilerMagics.java index dad1c1e..6cdd7f8 100644 --- a/src/main/java/io/github/spencerpark/ijava/magics/CompilerMagics.java +++ b/src/main/java/io/github/spencerpark/ijava/magics/CompilerMagics.java @@ -57,16 +57,17 @@ public void compile(List args, String body) { String bodyCopy = body; for (String pattern : COMMENT_PATTERNS) bodyCopy = bodyCopy.replaceAll(pattern, ""); Matcher matcher = PACKAGE_PATTERN.matcher(bodyCopy); - String clzCanonicalName = args.get(0); - String[] namePart = clzCanonicalName.split("\\."); - if (!matcher.find()) body = String.format("package %s;", namePart[namePart.length - 1]) + body; + String canonicalName = args.get(0); + String rootPath = canonicalName.substring(0, canonicalName.indexOf(".")); + String packagePath = canonicalName.substring(0, canonicalName.lastIndexOf(".")); + if (!matcher.find()) body = String.format("package %s;%n%n", packagePath) + body; // 2. build - RuntimeCompiler.compile(clzCanonicalName, body, buildCompilerOptions(), true); + RuntimeCompiler.compile(canonicalName, body, buildCompilerOptions(), true); // 3. add to classpath // todo hot-reload class - GlobFinder resolver = new GlobFinder(namePart[0]); + GlobFinder resolver = new GlobFinder(rootPath); try { resolver.computeMatchingPaths().forEach(path -> this.addToClasspath.accept(path.getParent().toAbsolutePath().toString())); } catch (IOException e) { diff --git a/src/main/java/io/github/spencerpark/jupyter/kernel/BaseKernel.java b/src/main/java/io/github/spencerpark/jupyter/kernel/BaseKernel.java new file mode 100644 index 0000000..b823281 --- /dev/null +++ b/src/main/java/io/github/spencerpark/jupyter/kernel/BaseKernel.java @@ -0,0 +1,457 @@ +package io.github.spencerpark.jupyter.kernel; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import io.github.spencerpark.jupyter.channels.JupyterConnection; +import io.github.spencerpark.jupyter.channels.JupyterSocket; +import io.github.spencerpark.jupyter.channels.ShellReplyEnvironment; +import io.github.spencerpark.jupyter.kernel.comm.CommManager; +import io.github.spencerpark.jupyter.kernel.display.DisplayData; +import io.github.spencerpark.jupyter.kernel.display.Renderer; +import io.github.spencerpark.jupyter.kernel.display.common.Image; +import io.github.spencerpark.jupyter.kernel.display.common.Text; +import io.github.spencerpark.jupyter.kernel.display.common.Url; +import io.github.spencerpark.jupyter.kernel.history.HistoryEntry; +import io.github.spencerpark.jupyter.kernel.history.HistoryManager; +import io.github.spencerpark.jupyter.kernel.util.StringStyler; +import io.github.spencerpark.jupyter.kernel.util.TextColor; +import io.github.spencerpark.jupyter.messages.Header; +import io.github.spencerpark.jupyter.messages.Message; +import io.github.spencerpark.jupyter.messages.MessageType; +import io.github.spencerpark.jupyter.messages.publish.PublishError; +import io.github.spencerpark.jupyter.messages.publish.PublishExecuteInput; +import io.github.spencerpark.jupyter.messages.publish.PublishExecuteResult; +import io.github.spencerpark.jupyter.messages.reply.*; +import io.github.spencerpark.jupyter.messages.request.*; + +import java.io.*; +import java.nio.charset.Charset; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +/** + * copied from io.github.spencerpark:jupyter-jvm-basekernel + **/ +public abstract class BaseKernel { + protected static final Map KERNEL_META = ((Supplier>) () -> { + Map meta = null; + + InputStream metaStream = BaseKernel.class.getClassLoader().getResourceAsStream("kernel-metadata.json"); + if (metaStream != null) { + Reader metaReader = new InputStreamReader(metaStream); + try { + meta = new Gson().fromJson(metaReader, new TypeToken>() { + }.getType()); + } finally { + try { + metaReader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + if (meta == null) { + meta = new HashMap<>(2); + meta.put("version", "unknown"); + meta.put("project", "unknown"); + } + + return meta; + }).get(); + protected static final String IS_COMPLETE_YES = "complete"; + protected static final String IS_COMPLETE_BAD = "invalid"; + protected static final String IS_COMPLETE_MAYBE = "unknown"; + protected final AtomicInteger executionCount = new AtomicInteger(1); + private final JupyterIO io; + protected CommManager commManager; + protected Renderer renderer; + protected StringStyler errorStyler; + private boolean shouldReplaceStdStreams; + + public BaseKernel(Charset charset) { + this.io = new JupyterIO(charset); + this.shouldReplaceStdStreams = true; + + this.commManager = new CommManager(); + + this.renderer = new Renderer(); + Image.registerAll(this.renderer); + Url.registerAll(this.renderer); + Text.registerAll(this.renderer); + + this.errorStyler = new StringStyler.Builder() + .addPrimaryStyle(TextColor.BOLD_BLACK_FG) + .addSecondaryStyle(TextColor.BOLD_RED_FG) + .addHighlightStyle(TextColor.BOLD_BLACK_FG) + .addHighlightStyle(TextColor.RED_BG) + .build(); + } + + public BaseKernel() { + this(JupyterSocket.UTF_8); + } + + public Renderer getRenderer() { + return this.renderer; + } + + public void display(DisplayData data) { + this.io.display.display(data); + } + + public JupyterIO getIO() { + return this.io; + } + + public CommManager getCommManager() { + return this.commManager; + } + + public boolean shouldReplaceStdStreams() { + return this.shouldReplaceStdStreams; + } + + public void setShouldReplaceStdStreams(boolean shouldReplaceStdStreams) { + this.shouldReplaceStdStreams = shouldReplaceStdStreams; + } + + public String getBanner() { + LanguageInfo info = this.getLanguageInfo(); + return info != null ? info.getName() + " - " + info.getVersion() : ""; + } + + public List getHelpLinks() { + return null; + } + + /** + * Get the active history manager for the kernel. If the history is ignored this method + * should return {@code null}. + * + * @return the active {@link HistoryManager} or {@code null}. + */ + public HistoryManager getHistoryManager() { + return null; + } + + public abstract DisplayData eval(String expr) throws Exception; + + /** + * Inspect the code to get things such as documentation for a function. This is + * triggered by {@code shift-tab} in the Jupyter notebook which opens a tooltip displaying + * the returned bundle. + *

+ * This should aim to return docstrings, function signatures, variable types, etc for + * the value at the cursor position. + * + * @param code the entire code cell to inspect + * @param at the character position within the code cell + * @param extraDetail true if more in depth detail is requested (for example IPython + * includes the function source in addition to the documentation) + * @return an output bundle for displaying the documentation or null if nothing is found + * @throws Exception if the code cannot be inspected for some reason (such as it not + * compiling) + */ + public DisplayData inspect(String code, int at, boolean extraDetail) throws Exception { + return null; + } + + /** + * Try to autocomplete code at a user's cursor such as finishing a method call or + * variable name. This is triggered by {@code tab} in the Jupyter notebook. + *

+ * If a single value is returned the replacement range in the {@code code} is replaced + * with the return value. + *

+ * If multiple matches are returned, a tooltip with the values in the order they are + * returned is displayed that can be selected from. + *

+ * If no matches are returned, no replacements are made. Effectively this is a no-op + * in that case. + * + * @param code the entire code cell containing the code to complete + * @param at the character position that the completion is requested at + * @return the replacement options containing a list of replacement texts and a + * source range to overwrite with a user selected replacement from the list + * @throws Exception if code cannot be completed due to code compilation issues, or + * similar. This should not be thrown if not replacements are available but rather just + * an empty replacements returned. + */ + public ReplacementOptions complete(String code, int at) throws Exception { + return null; + } + + /** + * Check if the code is complete. This gives frontends the tools to provide + * console environments that hold of executing code in situations such as + * {@code "for (int i = 0; i < 10; i++)"} and rather let the newline go to + * the next line for the developer to input the body of the for loop. + *

+ * There are 4 cases to consider: + *

+ * 1. {@link #IS_COMPLETE_MAYBE} is returned by default and is the equivalent + * of abstaining from answering the request.
+ * 2. {@link #IS_COMPLETE_BAD} should be returned for invalid code that will + * result in an error when being parsed/compiled.
+ * 3. {@link #IS_COMPLETE_YES} if the code is a complete, well formed, statement + * and may be executed.
+ * 4. The code is valid but not yet complete (like the for loop example above). In + * this case a string describing the prefix to start the next line with (such as 4 spaces + * following the for loop).
+ * + * @param code the code to analyze + * @return {@link #IS_COMPLETE_MAYBE}, {@link #IS_COMPLETE_BAD}, {@link #IS_COMPLETE_YES}, + * or an indent string + */ + public String isComplete(String code) { + return IS_COMPLETE_MAYBE; + } + + public abstract LanguageInfo getLanguageInfo(); + + /** + * Invoked when the kernel is being shutdown. This is invoked before the + * connection is shutdown so any last minute messages by the concrete + * kernel get a chance to send. + * + * @param isRestarting true if this is a shutdown will soon be followed + * by a restart. If running in a container or some other + * spawned vm it may be beneficial to keep it alive for a + * bit longer as the kernel is likely to be started up + * again. + */ + public void onShutdown(boolean isRestarting) { + //no-op + } + + /** + * Invoked when the kernel.json specifies an {@code interrupt_mode} of {@code message} + * and the frontend requests an interrupt of the currently running cell. + */ + public void interrupt() { + //no-op + } + + /** + * Formats an error into a human friendly format. The default implementation prints + * the stack trace as written by {@link Throwable#printStackTrace()} with a dividing + * separator as a prefix. + *

+ * Subclasses may override this method write better messages for specific errors but + * may choose to still use this to display the stack trace. In this case it is recommended + * to add the output of this call to the end of the output list. + * + * @param e the error to format + * @return a list of lines that make up the formatted error. This format should + * not include strings with newlines but rather separate strings each to go on a + * new line. + */ + public List formatError(Exception e) { + List lines = new LinkedList<>(); + lines.add(this.errorStyler.secondary("---------------------------------------------------------------------------")); + + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + e.printStackTrace(printWriter); + printWriter.close(); + + String stackTrace = stringWriter.toString(); + lines.addAll(this.errorStyler.secondaryLines(stackTrace)); + + return lines; + } + + /* + * =================================== + * | Default handler implementations | + * =================================== + */ + + public void becomeHandlerForConnection(JupyterConnection connection) { + connection.setHandler(MessageType.EXECUTE_REQUEST, this::handleExecuteRequest); + connection.setHandler(MessageType.INSPECT_REQUEST, this::handleInspectRequest); + connection.setHandler(MessageType.COMPLETE_REQUEST, this::handleCompleteRequest); + connection.setHandler(MessageType.HISTORY_REQUEST, this::handleHistoryRequest); + connection.setHandler(MessageType.IS_COMPLETE_REQUEST, this::handleIsCodeCompeteRequest); + connection.setHandler(MessageType.KERNEL_INFO_REQUEST, this::handleKernelInfoRequest); + connection.setHandler(MessageType.SHUTDOWN_REQUEST, this::handleShutdownRequest); + connection.setHandler(MessageType.INTERRUPT_REQUEST, this::handleInterruptRequest); + + this.commManager.setIOPubChannel(connection.getIOPub()); + connection.setHandler(MessageType.COMM_OPEN_COMMAND, commManager::handleCommOpenCommand); + connection.setHandler(MessageType.COMM_MSG_COMMAND, commManager::handleCommMsgCommand); + connection.setHandler(MessageType.COMM_CLOSE_COMMAND, commManager::handleCommCloseCommand); + connection.setHandler(MessageType.COMM_INFO_REQUEST, commManager::handleCommInfoRequest); + } + + private void replaceOutputStreams(ShellReplyEnvironment env) { + PrintStream oldStdOut = System.out; + PrintStream oldStdErr = System.err; + InputStream oldStdIn = System.in; + + System.setOut(this.io.out); + System.setErr(this.io.err); + System.setIn(this.io.in); + + env.defer(() -> { + System.setOut(oldStdOut); + System.setErr(oldStdErr); + System.setIn(oldStdIn); + }); + } + + private synchronized void handleExecuteRequest(ShellReplyEnvironment env, Message executeRequestMessage) { + this.commManager.setMessageContext(executeRequestMessage); + + ExecuteRequest request = executeRequestMessage.getContent(); + + int count = executionCount.getAndIncrement(); + //KernelTimestamp start = KernelTimestamp.now(); + + env.setBusyDeferIdle(); + + env.publish(new PublishExecuteInput(request.getCode(), count)); + + if (this.shouldReplaceStdStreams()) + this.replaceOutputStreams(env); + + this.io.setEnv(env); + // todo: retractEnv will cause thread System.out cannot rewrite to jupyter + //env.defer(() -> this.io.retractEnv(env)); + + this.io.setJupyterInEnabled(request.isStdinEnabled()); + + try { + DisplayData out = eval(request.getCode()); + + if (out != null) { + PublishExecuteResult result = new PublishExecuteResult(count, out); + env.publish(result); + } + + env.defer().reply(new ExecuteReply(count, Collections.emptyMap())); + } catch (Exception e) { + ErrorReply error = ErrorReply.of(e); + error.setExecutionCount(count); + env.publish(PublishError.of(e, this::formatError)); + env.defer().replyError(ExecuteReply.MESSAGE_TYPE.error(), error); + } + } + + private void handleInspectRequest(ShellReplyEnvironment env, Message inspectRequestMessage) { + InspectRequest request = inspectRequestMessage.getContent(); + env.setBusyDeferIdle(); + try { + DisplayData inspection = this.inspect(request.getCode(), request.getCursorPos(), request.getDetailLevel() > 0); + env.reply(new InspectReply(inspection != null, DisplayData.emptyIfNull(inspection))); + } catch (Exception e) { + env.replyError(InspectReply.MESSAGE_TYPE.error(), ErrorReply.of(e)); + } + } + + private void handleCompleteRequest(ShellReplyEnvironment env, Message completeRequestMessage) { + CompleteRequest request = completeRequestMessage.getContent(); + env.setBusyDeferIdle(); + try { + ReplacementOptions options = this.complete(request.getCode(), request.getCursorPos()); + if (options == null) + env.reply(new CompleteReply(Collections.emptyList(), request.getCursorPos(), request.getCursorPos(), Collections.emptyMap())); + else + env.reply(new CompleteReply(options.getReplacements(), options.getSourceStart(), options.getSourceEnd(), Collections.emptyMap())); + } catch (Exception e) { + env.replyError(CompleteReply.MESSAGE_TYPE.error(), ErrorReply.of(e)); + } + } + + private void handleHistoryRequest(ShellReplyEnvironment env, Message historyRequestMessage) { + // If the manager is unset, short circuit and skip this message + HistoryManager manager = this.getHistoryManager(); + if (manager == null) return; + + HistoryRequest request = historyRequestMessage.getContent(); + env.setBusyDeferIdle(); + + Set flags = EnumSet.noneOf(HistoryManager.ResultFlag.class); + if (request.includeOutput()) flags.add(HistoryManager.ResultFlag.INCLUDE_OUTPUT); + if (!request.useRaw()) flags.add(HistoryManager.ResultFlag.TRANSFORMED_INPUT); + + List entries = null; + switch (request.getAccessType()) { + case TAIL: + HistoryRequest.Tail tailRequest = ((HistoryRequest.Tail) request); + entries = manager.lookupTail(tailRequest.getMaxReturnLength(), flags); + break; + case RANGE: + HistoryRequest.Range rangeRequest = ((HistoryRequest.Range) request); + entries = manager.lookupRange(rangeRequest.getSessionIndex(), rangeRequest.getStart(), rangeRequest.getStop(), flags); + break; + case SEARCH: + HistoryRequest.Search searchRequest = ((HistoryRequest.Search) request); + if (searchRequest.filterUnique()) flags.add(HistoryManager.ResultFlag.UNIQUE); + entries = manager.search(searchRequest.getPattern(), searchRequest.getMaxReturnLength(), flags); + break; + } + + if (entries != null) + env.reply(new HistoryReply(entries)); + } + + private void handleIsCodeCompeteRequest(ShellReplyEnvironment env, Message isCompleteRequestMessage) { + IsCompleteRequest request = isCompleteRequestMessage.getContent(); + env.setBusyDeferIdle(); + + String isCompleteResult = this.isComplete(request.getCode()); + + IsCompleteReply reply; + switch (isCompleteResult) { + case IS_COMPLETE_YES: + reply = IsCompleteReply.VALID_CODE; + break; + case IS_COMPLETE_BAD: + reply = IsCompleteReply.INVALID_CODE; + break; + case IS_COMPLETE_MAYBE: + reply = IsCompleteReply.UNKNOWN; + break; + default: + reply = IsCompleteReply.getIncompleteReplyWithIndent(isCompleteResult); + break; + } + env.reply(reply); + } + + private void handleKernelInfoRequest(ShellReplyEnvironment env, Message kernelInfoRequestMessage) { + env.setBusyDeferIdle(); + env.reply(new KernelInfoReply( + Header.PROTOCOL_VERISON, + KERNEL_META.get("project"), + KERNEL_META.get("version"), + this.getLanguageInfo(), + this.getBanner(), + this.getHelpLinks() + ) + ); + } + + private void handleShutdownRequest(ShellReplyEnvironment env, Message shutdownRequestMessage) { + ShutdownRequest request = shutdownRequestMessage.getContent(); + env.setBusyDeferIdle(); + + env.defer().reply(request.isRestart() ? ShutdownReply.SHUTDOWN_AND_RESTART : ShutdownReply.SHUTDOWN); + + this.onShutdown(request.isRestart()); + + env.resolveDeferrals(); //Resolve early because of shutdown + + env.markForShutdown(); + } + + private void handleInterruptRequest(ShellReplyEnvironment env, Message interruptRequestMessage) { + env.setBusyDeferIdle(); + env.defer().reply(new InterruptReply()); + + this.interrupt(); + } +}

wGFGsE1QAMZ?retR3fg6l`1V1 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 EzrMjdKmY&$ literal 0 HcmV?d00001 diff --git a/docs/img/read-write-line-magic.png b/docs/img/read-write-line-magic.png new file mode 100644 index 0000000000000000000000000000000000000000..8569a2965973f200cc0e24edc0e198ae1af00d68 GIT binary patch literal 46652 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%0DYUG`+59Q9;ojD%qq2r( zD@gwfTX5=u&n5NsmLvJUD1olJi&)G7o0Q%@$6_{!8oyyAXTzl4vZjAG1cu9H?epkE z0+{;{=CMFhbi#zs-+B_{5`Ul#fYw7Er&kuT*vNh!zdB@chTwgeq`A>Ke(Y4Zs$7E^ zNjV!}*j6pO4rPUxD+_yG1`MO&yQmEg+pYIc#qC`U`qbN8we6f89in!PG(H}>NW7rD z+pgA0`aud7Mb!+K@O*7|i=|f>UcO|AD$*e;T34T&joZd@Ma!$*C3}m^v_#25FhF=k z8nq$|{m~1inzQNn0UpD9!Plik?_Pu$#QDF1C-{fJVqHV2l7-D zRY1gse06%t>H1lpJXAK^?g*FnE$Ms$3eH(b5AWOAm&ui)H|4%h`&m2PX$~dojBurZi>yDU4P*-}>$BYwrkp1C7T$5lAOCtudRJKs$hPwq)HzNH6Cdj@ zQtac4fa&(>oUj(KYq>K&-PgGOIc9sal1KB;!_tb>?!E4l^ae)^!TfOC-~`(i;|aX+ zqp_WIzAa~84$yjv7Ej2w;OTjWSEJR5gUe||n5QrvvYnXhbiFU;aQknY-`H;HZr zk#0X9$NZP$8i`Oph7n_b9cnw9ENc5K*0S30yzM0Zs}|ewK8Kc`1TDp%^ec{q{KCVL zILBbR`#?|MHQD(U)$*IWLf{t-N#3`>|M3voMCVocYDIwMy%*r?jZ3O)%lmCg9ki1adS$P**>@#zkl33(1S*77NAy^{Y}Qybc_SHwsU zhL@*1d219oG{!Eg)B|%Wi`oFGtbjvCPvZ5bT<$`Vs)5pdDD4tDht*g9;=$&h*|k!| zrB%&5nGlO&&0hQ0DUZIcuR3PGql7$fO<#Vs=-x`c|4RC`r&fTrxS*_SfJLVtFe!O6 z#`6=%Qr(LRqePJEHW~hUBi3I1-r|ZjJAx(HW?9(!MmSucNq@MNQTu(vZ>Py(Ud0Cf zl`xM7a?)4%Hg$Aw7IazU=7RwxHovj0AfT6SQ{%d%_W^u=xBhl6 z&z(gRo<4ei0`itcY`;UCweVVJmS!p4i)pOaXfrG}<(dDX*Eq-n;a~CgPcQNsZv8#> zGa5Y+OHCES-NDJF6J(Sz?)suZa{*Y&bd7pWG9JKe-@>#ek2aT4t6cQZ(*&`~iyYv! zPhSu#Y5*54_F*S9D-+c)%9~_tZk0eQPZ1Kls%zP;OB$Y0hp;1Wmx;Yp8!=~p)fS;6 z9x5jYTTSKOFGlaO6$c~5)G8@;%O{k*K-aXID!L87eJBn=uUS=J8`P2~xwlwODo>yL zuyD;fWn28GEl&8r*+C0r!k%)CSSf8ge{ene_Hfjk<8TsRof~iicZTa<>>`fObjSe$GGeOsU>HX3cV1Z3`&E4 zz$1AvMNBr1?F^_-`OeLMUC1SNLjU<8yy7YIhO)FhgttAkl0en|toYpWVkQl5z-gc= zX@DEpRK#> z&)(HE?<>?qtc&?1)hTjn+68%)m=k+a{s?(7>N}=c0-)-pYaMDd_#MqDk{_d#zuJiZ z@ZVT9#;gZSxWSfJBI9PGPI@n$a__Vxjhx!uy4+ub{eyKcQ}0wKUIx;QyAe!kFg(^Z zfdU6U0Ip12Q{;k5h{y+e{icQFUaUf*w6ZG6eSJW|iKNnmNs;|@(BM8|%kG0nbjo1s zk;AOZKL~*$G)z;D_*Q=9TiUDji5w1th}cjkGYmUFv6-S1JJDcMH@ zXbL0Z_Nb`GRz62gAVKb@)gQ+5qxYwC2hd0D&x2&j9d*U+x%2X`!+ZRN=b-Oz4mC0u zh}LY8>na_gJ7(0H@$Yy8967vsQ^GIwO!LXI^YLC|u$_5!E_W@F6XPy>^}FWlL|W9t z+eNDJnWB^BaqM=U>oM>fMo(O+}UuNqG&z4(_KM|tPL%2K&``45jtF4 zD2;uWp&S1|GSLpAznANu_pGxcB>w8|!VLkrRF%UQE73q`tgsX?mrCN&h5kKTI+r4uC7&z^{2R(JK z9L?DF7R?htADDy{-C-Z##7W1lG-4l$ zfck1Q@ry(^n+7Pvde7J6#D=btOj+Lk^_n!*(1E3OsEEtWMkniNX-*MwgD@0Qzxy)9d$Ip7pVd2c*xI_WQt@oaU{WL1?T@F!b9l0$ z4iZG#aCJH-)_R5`6?HJ5uUGC3W%uXBICf99Je&Rjv(}@{FV(;3Q%mD`r}y4)8k4P^ z3bnE>QbtaWOfEx7>PCucqv7Z? zfluZ>lCqrrXARg8&8eRnx=}ho0xnWTG8x0|wavDcw2^}ZcbxOQ2b|zk*Xqp}gr)v_L&Iq}hb67aM;U=_%aONPE_%PX1A1ts0BU`lGXTr1ij~IlC3!jS zpRu_8JsD^vfy=$Wx6vE>W71!-8^)?=5=kS50g(b6}Q{AiTEVJN;+qe@-4c~Hj(K@&IeJW->-7yFcl2AX$p z*DDOwc%@%MhkDnqqB~;GIwp^LY1Bou(&nyTne{3~ryw=}gzQT&D#FLgR3vTbFtkdu zYPmf4`faw-T8DV(3npZd;&xnxoJtkL2tcM+TQt~;0UlkwH!g5&O5>~cP^n0ZBLp$(_#4(;Eh=J%g&^|lxD!l{*KeNU(6u+ma$ z`HaefwcrALSEtXzM3?tUY{OmAkGdY<(R*9!_LX% zZq_fO+Uqu1Aih-U1T3EoDj#<*ns_lAnqk#;u zJi1zQ8)*g(s<#V2i18sT10`%!FACV_;Om6`t{$DAo&pbS;(M~c0Dyqer`=l;@CQEl zqIm(Ai=b_Il?QFAEep#fRQ^=!No!-Ks;ugaCgV#+{9aW{7Pfb1UIL-V&~D{#?W?=A zdF%zurUEpny4__gO+?*}>q^Dv>V{n9Cck|JmFzmW`>tw@$vn)@|Dnm`F_0sC41FLZ z5JFk8>u?M~24H5Mf?8PJ1!8d>Z~>=My-URbU4ydmF&(6T(I>lx9IUxHoqkQ}(}2Fg zi(yD(iny!ctm;~Lg_Nb@TPCE!^h@5dt?Bj|V1{R`b2fe4D2(ypz?1GqOYK|xD^~|# z=;z&1D_*0}h$?OehfU&|;BkohN>8n)TJt}Cyr#g=&??WITMEzZGb(qkj}sh?Ux)|n zJKdfe!Gdm(#O;4Q;JZ!m?;m5~7YQ0{1G{xH(G3RyAJ&vR!Z3(T3F%~?rIY>+7p4|! zx7FVpul*I`6;T^ug08P^E1oos-=$Z(F0l-%l2^C5#iJkhwHMs=Vam7HBsbxvG@q05 z=blFiBYjF-?xIRzeJgkFxP&#?ehUum;`{4{Osf2!52xt*jv0TYh-M&7pLE^GJ6!Xc zemr+_?+4BMVfi_7iUsk27+m&$2XPN38vy21_qKea!P)==)i|s)JD7=JD^SiT90SR~ z!e_>r2&=X)h7!P^^;-fNfH2flj}o8OvKQJ;c$^BBB*?x84D#b=#{v7d$nnfUN0pj? zLI-3CN!}xa-&r){>pATB!zd%p=R*cO9m^!{UuZ^45l|Yp`8$|cL-Uce=Q8801Y)NO zsInrau7N1n7U7iYo(?%;LbYh8VNY6TfA7yU$dXVEWQTrQ=kWy)!86JF4Q0s}cQ%o$ zva(bj(>9lZ=w>5957JrDgBqmmWpzs#H}#|UA$lxZQig3sJ2UK*@G=ui{FveVJ?j(x zD{gU_XWT>ONZH{HzGvFq*SP+1e@>nJyZ3YCQb^^L9Jlqp9v+W;LoT^~xl>qI+gp0} zcT2j+Eks6!X&xVyr+QO{i|1tTP^)o{JXhB}3|&S6V_x}&44%?dBjEV?j@GP7wT3J2 z%#*)l-2ON}A&h+}t%WOsD~Q;|>+>8sMl8dtN6;IKDcmljH%VlCx8J`?NUg)y=sW5x z@9c7X7l|!?zW?Mv5#`nBeUoL>8az%+!iZMZ7Vguryf)4;hRc>46+*RpI3{yT)V5kv zNV^`VZsBi0t4jn=f6JNm=Y)#qCyD@wv)ipGk0lY)iv~YG zRy+pp-_m`v{=PWt!=&XQnPB>*Z%n8)nW2I4**ypi=qrlGXW4a96__cW0zJtU8J zIjTAb&Wc9Uq&JW-Ehu-L2}LLkhjHeA8ll3c%f7C9i)n3Hmcm8;K$k_IvMF5zmFi4n zB!~XK;XW^abb|AIGlO&=$}DO=FFGgr-4$1aKk73-vOB$*Bn_@qaO6C7_RGCAnu~)) z)G*$)e$&DM0kQf(U&L~j#^a+mV738a+V4u@TUC!-^nQyV$QVAvGQRrkaC_}O)xW&5 z{?-JqtjZ$mY^>Hv?Dl`xA$uVu0IawlSlnuX$>l<^E}wo2j8Y4{wmrG@u<Qujvp*1wjOkA@8fHAl*MiF^twa= zlk1Ov&Q_eA&}ws!##UH+eDh3;GF5m*_vNjJ)mdHAZrn3^|8^b2_5pg?w%cYm_n#%r zFlm@T2n?fYPFg`mjpsvlX#ytc68Y`t3eWUczT5RluBhr1%4!c7ZyzJGa1 zZCF|ptb~&R(tNqXLTgbq4!4YW%cnIDGf{0$(f5-zu{D|q8`UA(#rxj4m)>S`xIi_l z>LS-lmlVaf%z0c=4jxdeqOE}Vj zYaQ6bv+z)Qd{V7}fX|b|{NMi0gRA0a?Q_IjRSL2ei|1I98JiZm_3-x=i>)C{%oX z?0fA{J+7h$SU{-XTM#Q}zjkd)A6BqS5#b)md-`JR*qB|5leDY86S+aA5bhemvO5#m z%^R+tL5B=;7)S?<{}%cH#2|zA_}_zfeZ(4wo1Ma*TwICXa7Gp8t;-%t%QlCQV=C6R zw(AsaMw+Ih9eWSRsr4ROL`hxL7rv1{41iQ@U3FRsC|+K^cxTR(3Tvp;7fQMjZEWn0 z4ute-JmimSCH;YsUumk_<#sx(+P|%4hJTk=7oF~Y|9g)AM2yg$9rx(;4+wd*)%6Us zkD{*UN~X35QMu_`0ZIs}?G$x-v&&!0Xw6p)CjJZLEfn~)Wu`3sinC(R&G%ZeHsFoD z(4L3w2dAcXKOdJyw}ptoC4#QMdiPdc%_=5T7WE~jBLI>6Yh)LD{w!dHDf;qq;=VhRcy89avnKM>v;Jk+V8k=Zn8`&AV;{@2)%OnQ3zwd0 zJf|UkIv^C^XkLAMO`M+dgG!&cQc{F}+^65d* zi?gQIsks0-{Jgy2EwNvGBe?f*{p1n+uS@La0%T{N*j$h`NnASNa$b#{n3Id}VzZ7e zpPc6?bYUH6LQ-4jI;^51BW0buzXPGbv!-4f)Zcl0hf}o-nDqPRMiQ%!-}uf`KEHn_ ztEQ}kzTET()njbOm6V_KRH|`;A-cZZ0{LF6gcf5_{~*j;p`S6N4&xI=Nq;<_2ifH$b~5cxbW1Hnd1Q#UuCDUY9&=iS1lkR$(D6w$n;s5r z9@{d$+(9zu3_qc4Qcl+eHfeEvfRJk5VKfIsLkj!1>*Pf+})IgN3G8*GE21c;^}`X!-BNFe{}nNRRmzreg9 zB!7RJi<{z@E^-q%QTt@u6o=;6{VL9tp0l2>S6CFaTi| zsfurQeO&sEJNla&&=V5Jf-P@{;F$47(04!Y9_GHdVQpW+mPB69Tv7^pP7c8xvs!Lz zhDCj~jQV<)Lu$q|#nAPNk|VFZp4z%*9C-7(rNf1rTY%aoNmK;SwC>{;C!loTIDXEZwL%R1Xzmi(7=%4Sx%UQ^Wnnm#3Motp~_dbF-LOe zsAeYkVNg9jJ}xbVukqF8NJCnl0ZfP+7w&;?ZS4<}#ALqqrU{WgPHB%6cOLxK9NFJ| zXBA`}Cb@5BQX>%2T{eQ)VpDR6T(9V<*a}rm(j1Eur@92sELti&1*+Gyf49 z`qprh&!O+&>fGLf52`S0S~eQ*pdtw}aqVVNzzG;+^_PuUiiwFuCBlJ_jGsZH(x~sd zbOI)c*p6AxkR}Ws?pJ${q8(N?&tQ1^GveDj{2{nKY6yu%%v*4ZFh+{)O^-KX$UNtD z=BX*pa2@ETj?V$7{psc*Jk8@g*#03qbMdF+=}HBYGJD3bb1xgOsSn8aBk8@C1MM$e zN{mWxq3hJp=hHq;&Oe)NkM}iIoiQ${sAi}jaYjZR2Tooo*?U&prDdH_K)`e32Y8o7 z6T@Chc+|jA#(_a>2mYumG}ITnEh3-4d26@gl*`GN%Kj1^B*Z!_y={&tJ!)BqONNwc(857{ z>-c=+?| zZU1M}bK;HOx$}hFg1xu-Cx{6aOIUZ6m6P|8cnSf6-n}0qZg2D3UV*}rZW(_?R2CkX zKauo8zc9)>0NSKJdTPP=W7q!`$`Ah|rs(38xw4C3qGxo@*qEcG6MbRYT~HFQz9_Z-Rf+QykZ}~kQBipQnD7$W;pv) z+UpDDdHZ?M6J_K<{A)^zv^FLH3y7?8^;ub2OVF=x8xi>9xvb4^7QgMTNiH0%lcsv1 zI<1@1_NfqPPRxPQWYea1@_h0exm{kqN8C37OJ4073>bSbSg)G`NM7m?wOx*|uDx`P zT6CgX1~$xpxaRoH(Ps~eICTTAsZQ78zq)o;?apVk$~nm5_npc7i-k-^RfY9o4t(pnd}> zc^>?B%Q?JGDn2p5%M=Q2{i%@AlJ{px7}$Ht_0q1nxT4YYGf^< z5!o61t_P;@8MU!2J^_mq^3BixhoWAmpjDdHZx1ua+jciM1Z!Mxa2Le z4ONi)_`*O@viLgLJ7V;?NE#Is^6uW$qKBO)CJc*1RM$NN!(}Dp{RZg7l^*A@%;&2v zUgm%*0?r;?NFL2$p%cXUECEUS=Mdtq-83L)0QD|2E2?wyzK^B3vHe>m`p*cg;b=?4 zRH+cBu8j_8w=S*Eh=oOSE*FhLFG@VLy{BB(w7*(^V_V97t(vm#ysF)EYbUQ2c!f_i zUn);H&s%q3WN+kfkmL)U!6K_BVxF2Cg|;U;p2yj5kdC(md%{Y;u~W=H#OfVhsB)hv z*X7Tap${4ZqtMFhyP!h7YX*8c7Pe4puP&N2Oy2&-vCz~Au|6cSD>IOXZq@!g!{ zqwgu=a7aJLo?*wXO2%Xuj2TG>Gxj2G5oAW6xuhWfyS2MtuEb=t0ZqXWljqzXBwW3oZxs@49 zv>L!V?ikiAW(`VZ|IzZBaC9}#lle;i=1x<=FfS}In`afo6L_buFKuJyD||pJxUVoS z*V+36h8ysYa39fJ7Y%;#WP1^4gRGHmCq+ZrWC3usR?sM8s|uUy%;qHbB7f>lV2n#c zgbua*^mgY8)5;?lM_cor;_{`$$%g04UhQGo;MUvEXx#?vT6{P% zF)955$^fQjb8g(cb})Z1zK@7EsaSjY95{AxSWBHZHZD|U)t>&#&I7BqDDWWg4F zsC*DS25sMh9Wm&@&6V&{;i8?WtTdz}|9 z;PFr!|Adv+138<$$4vy|FZJ7--8|~>Z9sO%PzTzl1pdGIM%kUX@Y~LkJWRAikKVre z;e_#L4oYila~BWIav-4W(9>DNJI+Gnh_0^Wn^F*{bHD)sc{`8D0J2Jw6)JgwgjvJC&>X zCyF{X%rdqOXPrDD?}S%ua!-B~38hKlfHVD~+TD*kuK|mFz7F92 z69LUR2)a-h$P6-FV}^0adF)4D<{E3VQ^*1{gimSFx6|HGcrquwVDpI^F$US+-Os8@ z>-!O9)ycz>Y~Z=vM-Ccd^DN&oF%z&;o(?SX3yr``AVWh%l^!T~ae2N0f<9AU66SIF zjlc&YA~FMN$^wV~0e}5Vpuo2EyjgV~pv|1EzB{#(iC?80;Kf)Y`_M5$q$k58m<*~a zf{q|K$(MiFfr#;?0YrkR70{xWo_k^5i;!@(p2maTSxBRVJ{1Y=A3-v6@ero8=D;oi z_ZT5F-}U*WH!H+Rq6b?kx9tI~`r6il7krOl+Mc$M-%}@?y>wN1sQ5oDra~79ix~#4 z_Bt#n7;LzT`rL$5jboj!Gi(B@&z#C+w^&XdvfUUM8gKrVC(HlzK-Egs9KTMr1CqWe zL*9BC!F50tl1-f>l-hJ-7IYl;3`NgP&`p7oz!56C#Cr-0`#4H>C+LYSX}FHTm>$+) zjXxzcG_kI@2?qYH7L@)|bKB8ZA1fc(r)4c|=Tgc`=UX%=eNM=`NoA(Ci^;|JnQVX3 z{#yag`+v|48CiJWe@ZvO;U+UFgVU`lWC=1IN{k&``_Ic;FeTWV;#TfaZaW;o24NxeZrZ|T)_X7u5suYVZx>lH_Y z5Cz4)u^8X8wIGVunB{}nrYB~Z0#C;YU`Ejw-27O6j-^QAIgwL$-Mf|kC_3D3po0_Y zjklfihE!8M1hw=Oj7VtC2CS2QV4YnyL3*6p#~z^0?w-0?kW6g9clwIdeCiz6{LD5L zUoUlNzWwuZ6D-kg)cxy03K! zrG)lDL|H@xnjQd(2Z^0pPASO`laOj!ZRpgCI)_e_&na&Fg;s9iKu7GC z6!aLLxV*|XF{9v%O3wtO{Zq+nWAMcq-AE^h{Jts(QSqjbd5gNi#j&wlW^A`Kfo+u4|+mQl~j<*co3FOE`t2nhiO=7nqQ)O}Nv-}(DE25bTRLsXhldiS2* zXE}4IcbOjM@59S@Z>w7ahP$P3ABABnJ97UI7CG@k7VP$`rGqRfEnh!x76D?`^D;<< zd#Wp6l17J)#Y)r~M9L7H3)dd+P$ULM;FIsA3 zLnOD}LOD0jQ8yhi{{G5ajNb5cmV$%b^iawoPQQgPBZ|_OnhWGZCYb| zW7RCQDk9Wt>w(>8)thL-4R1Z<54)*d!v%B#p7M>0 z$`+R^Is9;HQ%~K1=nVcfq+~p!vk$P6t!Uml?uQM<_@pDK-yCSU_YS~umVdXjWd_NF zbPE=`$OwRFZ0C~r_j>iY+GOIr@@lQ)oUhGY=`RTM*w!y^n`1<2y^c1bpx|3^s0iKe z`V`Q#p2eS1Q2SKWBD<*xW@I}T371#3zbazH!k+g&UK}Xe_Q~ecsB}rV@QMPrNbAJv z-M(N+)lrk*HS8k@j1}^1_RboVEc!AaSDc&L*uoGt-DX0ERU&y4roGdr{$-b0{c_|Hvgo*Ji$~M?E>Dj zUo!tBznf>S5>%ty`}hlC2KIbJgZciGC7%3y#j?DvjoT5VEO0GhH~spd?-O!=VDfB{ zt8oRbGplTGP$+~g-E>+dDh($ literal 0 HcmV?d00001 diff --git a/docs/img/write-cell-magic.png b/docs/img/write-cell-magic.png new file mode 100644 index 0000000000000000000000000000000000000000..c98fa67e7764abd867d594f485318958c405605e GIT binary patch literal 35849 zcmc$`1y~%-y6+2LLIg+%5Zr>hLvToNch}(VGC0I=4er6+ZLk4?d+@;EZiBnt9=^Ty zy=U)r@4ox2bDqPKJT%?aRbBqp`}v?q-_NKF{NY9HuHk2pT5j-se7bL6Df84cul%QmFnYR$1TF`vl#R8}YBN zD<70z@cYfBtSzijQM_4FUM%k*p}3f)=HlYYxM|5a;6h|)XMYNOECK=>Hh2Q@@v4m> zs`d%|s+XwB?eR{7$kc22#XC^%>|>a<*IIn=8~$^o*3aP=QG6ya_-_QKe9aw%_-+`q zK7se|i0uV(wD{0zWUl8?p@qfP)SV9DU_rw0bl8mdE4!6-EOw;Y?g%~*Bw;x; zbI6Yi`U2s{ayQ^Pndb48oDr}t|CIPEAQNdJL?K`pn^o51#-=)n`B^w7Qme_CLcic| z#ZH!W+k4HpKXVs{U%taDm~ zWTmp|XFtdiEt;8wrjY7dh~6X`&mUjb6jJ&t=NqdJMmYsQGFxwxy6uq`ufvG1)=iXy z{OU4v2dld7wc>O&zuLFRh_XA5$TQw{&kiIt<3qpRj_lKA`ym%PNiTJL&4!mRCl<4mJFIuqAL`SVw2n6R|_T?uSY71+8g zahlo8Te7UoYBA^q>$gsLQ-d&?%o4a78GVtN(L3=%E7NUoy1%^wDu>68)7NU1p50go z?P_VLjb(nqKTf2dT^lubn1uOedEy-mJ2HQfaqs@glVz=zZz1aZznvTO{6x>|Q?QGa-C@@=Z20u|Fpze>HK< zVzU^ZW+~}{LnnM~L^Dj$s7Y>XCcxD>Yap0bSD)yFezb;&wl6cIq9nW^n6R5leZ0M! z3k$dB8k{$W)0$ki$0lM$8`!6RL(GQoGkzRYR8#GXRTOYM z^7bUj`E+4y>~!PDqQ_{rEQ~WSrB;IJkMq#l3j5XCdX;|kP9X2ZVG2GSw=oF?wWqzM zCGX^I_ac$)^TzNjlsu21z9QZ{3ZFHrJ(khI+gB?!<4T!1v*uI17#RX||OW>p8j@ znhQa2^1}pw4OgfgLGVOG(%5$`ljD!^X`mdDAwR3UFB(rv=ZUrNx@HB-Nc;U%xiyZR zkyKZQsurU50(pPmN}*V`Qp@IL*Dj8a85)yBKZrLGT3rfJ4&^Y;Z{(}x5%Q}j#?(g^ zl4gBjw@?9Q2>-5n6IYm_p&@EVSY#w$T;@n==6ps;$ZpS*10lhd?HT`phng(Mo{(;h z4#*zb5|#W#pl&(x3*VvZ*8KpE7BKu-B%R2!5?$k_N-zHa$mO?)*o}JJ8!*z?-Pppe zd`9K>0&PW|;Yo8ZnIhI%;;UQtt-vDR3C^VVZa1nC2Yren%jZ9^h3bQ-aWeX-$=YpS z0Y&NGRTJBy1dKvozJ8UEkoc}J+>kkM5Fn_Mc>1<9g-|FP_wwX)Ba(!I=82tJ04h2< ztsNm+b9w3lm~ZJi<@2~uvgdsXCzy5Xx@V3t)XbRV49{}>Irf;2-+Al=H?-xwAN=`7M3nDD^XX)z_YN9HRdq@KCkx#6~PEN+H zk4@fN2?8I;PZ7^%O+rXWL1u_|%}+49$p?JJ{Hq3iyf>GiDaPHu9-KlgPGp6nIQioX zIG0Ag80dQLjUcWr{x+~pMD_kA9K65yIwz5W6J6=1XL2Ve(E@f+GlxKRPg}Z?d`zj` z4m)Fk$apYiBnw>$gDrqCId|#mbgLv*_bSyVbtGy{w&VvYiOd)Rf_HzOIR-0*xoI?x z|7OyS9&!G`_(#ORd!>0Yk)rnI9-;aUy!SiM-fp(Wa-6ns%!kV>G00-eMoKEQ?(~6> z|8m^hik6m#exb4F@1e=4uga}@W^QyeIyRO<_V*?bE1`&4*+(QFH6o7t-ofhx;%{Rv z?);+(h*Z|U-GxRYwiE4z_gcZCk%Qk#f3~_HDJQ;k>AkE2NHh%6b5u*H_H2)W%uD#TzAr+p&U=i%O^$BX z_YaTd<`X{M)yBygWw$KEj*R4>pa>twdDpBa0a@F1C>ob_WUN`21JXXloMpNQ++;jV z5QBw#uJ^}51RqGWt9tEfAV(ThswNVYd9)xtCQ|}-a~sSwmUwed1ZEh-T#nc zLIzQ4Xy_Wqa~qBHeDB;sOl!bVVAS7^A`u2fvp0W@iLTUW8}=))gzkM|YM=4b^*#N2 z28+{$A{5mcALpR^>e9)Vcu zw4r_RTBOuw&Gr=ExyV<3U6miaY><625VoM>+TFY6NSd3V|H+`x@HjPVxd;M!*WQPIxJA3f#dc?_s#%v;cQ3t?Pz?|DNf4XP z`d|zG^Db**Vgk&aARi1Nz{4BeJ&TEn5q(QY$p3%^uSo<3ocnTX18l|VN8@@c8+2qH6hqhk5&|7zWaZ4R#(UG6eVD zSs$DjwXP}#FBT7_dfrR@aJH4iWAqQqVNbb0fU#WhL>wRxQBO&c93x09aWZhA6H}g_;Ef)qDvFc>u*X)RU9%zmUWIy zRW4%{u*09n`+z#2j{XXm;vwadcb01EsOT4mEOEa5W?gTTym(@Ie&f~s38f?02xskv zi4LJLwq}gWzzfme^rwj30>P$wfZ)GfGwb)MWYG)tbV`%EF$`_M&DE#n7q z_A_|;A5gWL3~~>~x+Mh`&mCV`f^0+G_%X||*G}P25z>DOo3WzKzsKNA*fWvt94Stq zB81=9Gy}>fU`t|BRMo23@o^z@_>#^98)?T7{`eCy@Hix~L<70N%mBSH177KEgg=GF zZGL82m`hc|&<%pq^*xtO)H#GavyE5W^1cPtNHD3w!sE3QB%N5jKI+xzY0lc|z!;wG z8u(t249}3%*@swAXKfZ66x|=;>E*6RKd$-YTJW>h7u6>e0r>Cem{L)^S(pGsr+ z^3Ol;ZIv7Ks1~(Wyy?vNl?>6#$9EJeClDD#Shdt_rn2lDEAH1p56grPOIJGyMSjiQ ztd2`YpBwL<+_x7Q5S&ySV{<-cN7JEq*3=#zSm<(DCVsJ;l#K0GsbO{guPVxYpnXo% zr*lE{i9|$Qu3*MVJNUvY@Gf=8_!Sw?;1CN2bP8F+jwp8{FQg9D8k9|1d(4qhbvuLP z!ox;7U%B%G0}w}A({ zYn5KijVqzCzX1o-G}Vdmr3Y)EIC&oE`QB6;yt&)c61O7ra*<$G`o)fKFb#J(ARGrQ z6oeXX)kPOHm{6;b`Rc=NM0c?L7zJbOOC^(&f>;!Gx+c2GNux+cAoAUu{4Og=I~kNyw@_y4uMPldr1VUc%QzMz_(rh4qKc)mY3!Zi3ae*5NzEQXFt zoUw1?6_@rvw+$MZ(Qdlr8(G4#);T@CE+NIEkDdf9^=6Ys%{7z@fsh!(wqF;#G@$u- zo6+Y^QhyP{!x8Mb$O0plJCZFdwCC72Q`;wN=z-pjFH;={Vt@1yvPtui;A5UwM_vP? z?e@gB&Vc6z*tR2ls;ctmoNJ-0CH$Wj}`VJAJOyR!}+_^JL^_FH| zy1I!uwm3e-WaUF6ik}M5ValS@7n1v-Br_PC<)dn-Dxzhh_b4* z=Q@2Ki7rM{O<8Y^XO~2#W|G=%8j^r$oi15=qVls^yV2*eLi2|}$!x*I)=i3+WDMAy zAXm%o10q7(v45T0XK*dvK9#LIjWc!EjVzd)Fb zUp5>qrYJ~r%s;n}CC5G~cc$az>_MC9h~yW%4SZ862|HLSJ!_>btHgx{kH7e8pAgIkCYh(|$;&zniQJBl-R#Sst*W@{|Oe14jV;Nsv z$tZEyp&TCbc)fHp*}=?vQunKUksU+l!3*bUTcsqJmq>FcO&ycnFXHO_y8|8iW8Dbl zV|T(~Pt?6OE4lzy{#mP5@?iG5S?6B^Z=h>Wm= z);o)2q=t%aUG+PL7=J3o1v+smo;I5JD{aMDUvB_svk~i%zHqBWomubv6hc>LOtauVj#J5rE{8|%9 zlnFjj;zBHbZ|7*jA1|*rO*9%0l<26Mt7Vu#7@NRA`FqE>Zhoc(o|Ylw2n-^Xp2-ch zLRW=9({mZlUqz+zRVC}(`CamAV9TX1=hR*FG=goUs4pcFD$WSQ>#Pvi41(nq5mA*ySNr-Pd=pIOm=<2z4Zv2Ion`;F&9n#n(zciI2 z(r_h@ZDQio>9g4o5J}2=qFH#yo?$MJ z92y*YO@I+5xdqG0Sc5o#fEQkL1$Uub$*`3MY>+i>|}$fhzAk6g$%`6tLgZ zeZQAv?Un;N@>B70^Jrw`E^(3B45ge<I{c&+(^JlXlz$ad!*KDmcIJ+3VQbpYMc<`m`NXt)f+!8b z*>k_Cj8Z%c&Se_1eUd|ES8KH+ZyR~3Wva;^-OhiZWP}~z(k2-E0$u;J#fo|{p%g!! zc#l-2^6G}TytibOkTCxHxoYK3x^^U~Si0D)`)_^dnYDGhMXk;seRsXBE4QR=>Y{=q z=N3Nz^{|!PKQE7IfP{WjJ{&*pC1!-?+!8O=Laxh9u_0EgRG4|BJ-S5K5#XhsZ=&rT zyh4%}kv>bSTpE^qk;@j?q`WgSdV#_`xIu4c`;2qD4(ocsX*5qr8V}EMjHJr{%zd;C2bDt>f2FOzX<(Q!9PML7qgo*w=AUHBE<4vg25n49%af{j2K97Xa4&Nk*^B(ngWQ}>{?&L#YSD3`} zQ`_><>CIr@s-HThLA|Yaq5?*$p*5tSmbdX z1{dqnAwG@WrJpW!D??Na=sJOgR@?eqrauIZoDf#9M|N~zMwen|H3Xt&uV&Fjrz<^) z7T@kFMcs}fn?Pn0a9~W2q7ig#HOpgSO0^btdrFACa~fukk<{P&4u2Qtwl0F1^s4j1 z6W3DSOA{}yZ0TAP+JJq`;jm5YfMYfNKK52*i*QW)Y>*uWxKT}s`+%y*Vw1>G$|%XD4z$&CxERy zw^zB;yQ0QvN9A?V*fXN$WNrD%)CYDq4}lfJ$Ep3D?8pf0d;uw4EcY&ZzYgVY?d_Bz z!4)f#ho$2*^roA9dquJHRBN)hqE{4?UTO_D)Lkwdo8Mr+I*xsV_r1l}BX8qsPA+@ZuR{q ziX~dgDz4IKrLgT&Jn@+gz?9|wNMJ4+Bweu!WOcrkurDf0HIrgSEt-B}ssS$wI^n41 z9%qV61hNy}KwjKr4Qa4ijTb>T!QZa4?{$`R&ezQt;5-`ULU)?&P@3s?z~8O!vESJ$ zOmk}X(I*VFi!?;EqmST7YigsfKA;pNt-GVaohJPz^ohc{c#asOI&bgX8eyA!5k}g6 z!7m7A4Pkc3XjEb9znKqQi(bF`h52RUN1Y7T7rt-5nsvDq$t51kLqDI#PG|_2H=#F+ z`L~y8sJzMKl4(%+KO3@FN~Qr89}h&5P3bOD?`hB*TC;$avT)nr;GlBPJM!BXTj!)N zE=k>OpIkIv9jVGg9dF+4zaC~sd2Wg`Eg165fI3UI+%G(?cN;3hq2F}F=(#p&pWR4O zu7{g^LgT(7Dp6IY9UE(r+Fd|g{Tx%h_<(wJN+5z`b3O3~u~|$a#S68{Mz-jFy#&_; z_LtA_>t8v-%zDKDgYqESox*0bMBbC+M@kvWzWC3JPRo&#%9mMABtItF&fThCj~%3P zK@FlUG(98BHLPmWLBDKzGEa70Pj+PJ&Cae`Kg_7|k{BU+UMzfG7YrS{Z; zh`jYgjqDGLT?K099$fsa6{ zz5lwv9b-^|v71P~9z{V|A~Jzh{0`V<22C@4&^r0zNKzr>H{GW|`q__Rh}aE61*~u# z(6HDleuJeETG9Ag9Oj-esEt&2l77wr0-Sez$_9%0_Xiz5|K}ZNTmhU44sx0Dtvm4b zc7;2@t=%aYB7HSg+(vx-%(wplcMo+AYJUDBc1V97#NCf7%w27>;_@2Kzx|3nDzFD3kaahjWz zb#!cuO;B)HKvqsJJTx@r!Ae40{M7SqZH)pQ>G|{g+*~nn@nOGOBg7{-B2c;~R3wU9 zkit_O>W<)`x6==_;ClP(rU%128MVVwak6z;+3g&&uW>=W7_+e?+(}$J|V ziOp7H#0`qIT%HT`kDf1kva(I6ce zAV)K?tAVle)o>qa@#(KVhMW_4`JrWg@1Lcd2l#h(w#cQds_MkJf>5dAp&>w~bIZ#k zZ-n^y`2_?77#M7hR(8?A3he!^Qy-L{VuoKJjW)K`)j#>mxkQIJ<9+Q@#T}Qy5Ip7K z-(05&LgSaEOF9{&9d6Q#fJ8PTh--hJm~akco-+dHGZSLZe&E+GNa(*W?_*RNkSN;H?(t1vM!flmY6{lFb( zSb>$#p4hH2v(v*fzoQuc?V9bOk=f)i4Oy#u+aEWeM*7oRKjp25^y_`{Im}V1-Jr(C zK$@A{ecj@S`a7Mp9#v}@A=6@`pxOQ;ALsyyDO%o}8EfNb_#tlbLGclQH?q1{Mb8Ux zhij-f1PCO_ii((r)*Ucp0lqt_62v(r}4WsSK zb>V7`WKL_Kp8}j6z^b*{rR8!P3Ib=b1}mxiXjePj-?0^JBhnm`a9Ixhz79SJWMAKX zO?=l=JsMe5*!4%^t-xVEK#&@78D5vF2b2s2EanwpA^MlYiaur}BX2y1p)mv(k`2B3c{h9ik4)^z0a1wSBX zkF43~gTm?Mpz(wELsKQYD91dBQw!a@%i6*RSFW7x4@*8MNC$O!&FAl^^hATcXpihdD%>YhRUHXo6>2W-t10uvz4;YPDb829N}>05A$wr z_W9`)+0xGJ^KMbVzs5V1!RmY1VZK7zb&9yScwTPq_SrR>&~20#6+gdsPXrMpf|Q+y zhi8-3q`Un2qE$}ZgJ6R%61rUZL$vFX!PnVzl*}IUAJU~yCP~i?bhD!R!tIgj)aV4c z<=dIHGkm-IxxDHX?-waB(Yq`_xhl?~334z%bEF>*`**I>GFru;V<;n z^A2cIhp1?*9RU&>fXd|P%tdoySNT_nl<+!Rf|7nR>vm0y^;muuC?zRbfrhhc@b7@r znJ6|bXzLsXmChZDRDy>`U?6O`eNP9!LI8jLhpmBKRMwIW|M?oEtfF$q=Cb-f2;i>D z-@wCvYB==MyQ%VTd=&U;iYt;n4m1DJLiy+O`aGbeAT?H*V0p(m03Uc+0Nt5U&!c}H z4FD|iS=I6Yv8>8JN#KQpTLc8+D!0Wh6SYc)0b*JGiPF~)jqEV)JnyTOpA((e36Zqq z5m?RCoapeN@qmzUpslUzOOWw{q{5wNWM^mlg;gy2=Jtoh7eFJ~`{?VUL`j_g-=3?VjkO z0bKY2z+1ShUn>|s{00@YeCgZ$kqqFV!eOC~Dz(2yV0vXg?KmuvP62*dK;@*x3TTRk z*H~Qw7|*l%13Zkw9Ql+^L1it>l7yH)xxe&qp*`Rqs|g zBZA=|5=L@}rhYe@m_bxs&<-!&wl9Dy%-(Y}%xBTt#RC_P~ORR2P<@eB-?Wk7`oA%oJDs(^c?t*1wW`|WG z0&;c`laW7)#`Jm^whsRee_2xN3o$@bHvoYDytX0KNsmGKReGSG?R_VoP452gyP3r2><${7unB z{>)d}+tlIEd=#^De|k+&_k(Z|yj8R?VIH9r!ZWhS^zUmkEH?NJ1gC##Mf4AS^?!;a z&8YB+CL4CBX`#qtGKbQC=j!nfxb)$F2RMZ%dO6kLCY+}CF)1#<0H)65${>?dFT1#i zlcd_fYE?*nH0Fc?4jf`#9MP+%ydmZOB?|`;nSo=tL&_2>zdJS(d#)bHAHk-4tYOpZIxxV~M7*HsU+vZ{MSq1-(J@Nb$la@lC_a5vXQAp+4{PgN1Oma#j=1(Yy$bolCR3-2R zw~5-wQ{N&za-+A?1Q$ZIo{cat?`Gb#?WV#$7ybpaq`S;dpakqI1Qb2y@k{@KF#A0c zq-v}P>}ulYL6$Z;y8BP!^S{kWhVCy^1ZU0db3kg@dJW``^CF5H(Tfl9hUW0Kd~$U& ztnC;HV3a>B95?|=6CTxU#|M7p4!#G-OY8(0%6#gcBMGv3aqNg)DhZt0vKA82+pL1F zT!LOZP4gkFaK4E2n^p*1TdAALIWRk9m0|2vAR}9L`wpUTU*QZjcero!Oy7C`X*A)B zI|;8`RkXVp(4ZG-?e;4r_w)-B=ONoGL^QJn*6fo)-qM7FPAU|;EP&Z}}C-jB+ z0HBg{$?T1|lPJk*%O-UbIv6Ig$zE$zO9gwSN;7k+PdMgqx4xe`fH`-sVLPWx&FWk*MO+%lmDZ|V*TN@Y6VaeE{n2|Ry?25~ zzp9Cg=z};gNUi{g`@POpm((Q51dl{Re#mavjS8U({Uy(00Xrn#NT&q$jko80Car|+ zjRv?RziYm#*`R>ucc&2dPbHG2havYKhhwChU|sN8SUGNGd`+Y!GW@WikQ4W0b~Lj-C_5)=W^S;6wKLCoz0>&jfrF$wWQw%6_* zk{KyRHqa{^KrbI<2=i5Jv=<*iGq{C-q@iQ*mltg{Nv@rUlO>6(rQNA_><7ph$ABH#P1uo2MouMc*&Y@=NVnGMYfWGpm{+n5H4aT(>l=(&m)nmN{p z)KN79kr0l|`R4OYBb{<#+l{lX93vXezLUoa>V2f#`bI<5jFPrJFsbO>i3Up2cdxXX zK9q!z8p&aMj&*=!V(e0q0Z59k0v6%loUFYK{8k=b$pI-E4aSyQriQniwjefFf`Oaw z$xl?TzB_^#90S40W(sq0%&8oZwvmxW?~tHN`p;n?)NY*ETlKoaRySJ2AHUhH-i?(U z->R|NfXyk%zMAYOf~Af3%VD@vmlIv%a3z6Aw3O?da=iR}xPJH@feim7pdFAM31KU( zyiYBA2dT{iMs`rbnW!VR&*SK0DD5+O1^FE4pTEC#niDKjD&wMGde@jc2En>hW8xd- zkJgqT_S_5B@h^Z6k@h+(&a_=tko6HDv7O-tR;t_M^9j#c0Qww?1m{$hs5Y1pe`zt< z0T;0wltwz_s62Bnwr%P-OK2LHtLJxIJUs^i%7;Jw>&OCLig z;^}hX=>nggj8e|-B1Grzly+Dt67p1(rf5cK=)v;jE3a`OlwjcF4Hj5!P~aE#K|!U^ zL+gSjQ%>Q3tn zQ&qHE(X}_9&tRFsR2&c`QS#P|tyd9vwnxNNAA!}ndh04_N*2tvSNWU@SsKU9Nm4hL zHIh$H(%~}nGdI-k=rWH0PE55|6Eo}GI;8`C> zf9L&r9xl+Bj@7bcak8%FAhxe*?k;bDPj?oMX-6i=Liu@7gtURSbQ#bZB4(St`uvTP zm$(!^xrzv;<^Om_Xv=cZk~^0ANO$u+gC6DoXLb)&O0Q~#Bu3olRW6<=S66Y|*L_(i zsP?OgWsX%hQ|_`SFo%JyaEYjqL34hw*tuGGqINMiJg!G;!}I5Lcvz)b;W`Q2Hn z-q6V(cpqC06M?v@9Lgm*NM1hJ04*@#^-pht99WcMnGi0X2|g>7;Fp|AX2V>&kZE}R zkeTOFL9v5#>@Yb+7Ua3Qa!C!fKvB3(D(2fB!M8qNF7NE2xkcf@4af(A_@QQdpZjRl zu(rHDQ6gjM@8jE@5S(Qq`Oo(OIatumG?C01mv3CpRZPN?mX*is&^ag(p zEbw?77AOMcyKPN3ak)F^c-o=l!M{j_u&Xni^F ztZ}rP-p4$Y!<3PfzTYQz%7!h8mU%DdC>6oxxTa6BVvp`hzmW65LB9{ovwDR3?mBWf zw2bQ(l+>;Q%<%~ubuw9iT%kWcz>}U#R1D}b#b+2vM)oJTg`$){0K#}#Q}!Yl#4pJm z8*gQO9;*BuBm8W0?l6`EjHc<1h?$FAo968${%Uyf8qkg%70$fs*aEA_zXK2A0*ZzZ z1C#_O9-m^$wUBDwl!Xn}o7~um+@NmE4$!k+KsL-}B0{TdXioJjAL zy2JA+Yx#O$&Q%x$Q@teJ_Hz8uz&Slg8i!4vJ%(k%!snK30^@6FzA#5nTPUv;OM z8D#P!n)bFEjYERYpqH&*#!J@XG7rD5-b!7(Q}24CW>TymNlBevt5@`M@E~7GBfn-l zQdwL)`P6lNJubteq`ttQGXYsGTy;W`@ z;`7X1B9BFp75iz6wbs0jN2F2D0TDjjs#+m;sgkUJz z_jz^|W}wbf)9YLP6}nE(KZMSphy@1jw8jje^6Yee`A%M|PmdRx*bU8>IK*H}nA%e| z&e;mxHP z>~?xLqli9G>(eujtPF-Nbv!cdPLU*~p}Ne=sCW1&_UntfH#xJ&wqAb-Pr%tudU)82 zVjPi+!c|zBXwwG!Wkl#2s3Y{BaFn;f^&fftWP-WtXC%+0IKv`<7G2eIpQDgKwooa1 zIs@H+k?Z|oL!Cyp1nFLLT*Pl9%}?xYjCQF6Ee4{K(&2*^Lx-E9_)l1u;3?}YWtb;J z1#7~q4Z)+@@23EvUbEyJIUvdS#x(dwh6qBM_}PxDBXocOj^z;~f6 z8WaLfwmDARha#`fH>U!TIeFy|d}m{TgrcH#2W16$4uEq~y|991^+fZ_cIEL5_+f@t z-zWNASU{os^KX_xsr;bS@v#iI9b<~g)z#8wmYp_HcIMg*NzZb<5`ksctW*>JxP5AIZB%H5RD4OwXv8Lg7l(*-XFT|G~qD%O31c+P=#z}IuwskkQv;~(nBMTN<>OI0# zqouy+Y!(KR_1cc+^a!Ja2-a~2L3?R$WIHZotG!biy>7tD*xSg6l{7=reIf7a*}Y~1 zfoSU`z!#)fte*__e~b**rrI!(@&Fd6XK&MAELW2qe~vn4B_C$}Ps=-;=J%0IclAKB zqJTZwR84Z>C>a^DB51%dWzQ@-9sWn73*S@x)$N^#uhk6hba7(20Z4%2QX169V8H#& z-!v<-b5pIXKh#TtU-pc!0@S40nCau9tNVS?-VgRe2x5j0*;M9UQxW4+sHRKpY&a>fottfSJ_nU9ks!DF8Aa}$aYl!&;qo$_%#3; zq{2ceW{+#flt#&+rW=g_>Ar8BB5iBgoTAH-UsA{ z)jHm5I?m0aT8-Vc=bqEMT_`O64B7kD;3l+Q)UWGRP6v9(NsQR|v)jjTkgzpuu6o}a z%Igi;O)q#eH_;w4Fa)3(a7+{d9z{X%M&mqpm8^<{0xWhl)@lb#cZ>ZN$4|nPzQeM_ z?{#ar)PxQd!(c6Hy=;T^q#N>|q-F8epDQuN3I$d#dp|Vq)mlxX%&MczZ(XUJ(U-#w|4Aj@BF+IG0JopXzDLe)HxB7pprIF_w>_ zv3V$*KZ^JD=39(c(+1ex<52#M4SXolw2bIxC3Rm`iU_b1AJ9yyd{1b1-Bm9R;&3b+ zYw`5doDYh7u=wNq*VMfR9-io1Um8Hv9zGg`JZ_8gCuA}Xki>h_gD)ADm&pOWSH%U5 zns_d~q<3B|?jET7u|W)&0czWU)PcKPXX_!%6`w@xVp51t`+E8&;7d@Sm)s)VM3>xD z9o?@%^U_R%UX$!og`GU_;qV?2UE6rOvvIl~Lp)_l(g=cmI|Th^Oc1idSfd7nK^!a` zz}8cPOG{Qjp~&O=bN=}LrOxcnxw{x6-tuv7t8vEg9$U-P7@@Mkw~RDXZo-KUe69s2 zxGb%=xyWQreqxAZ00YoUN&G00lFZg>AT27|U%EKL{nI)t?7*wC#GzT4cLc{h*w>QN z?V|6xd~)WNV%Z($t(90&BQU;eU`#3PF{J3BVq6{uQjHU6SkzS5vGEenuG~z~6$UKR z9fq_}(`Jf-jaqkqn768<K0ETKu>kS-4pyO9z5`pP!jL?#jNFTBJ31odsh9n>vt23vmyJ+Tb^EAQRS zhoqAY@A$*RuO;V4=HF3}h488Gi!bbwH|Nt9%&ECw`0#v6pEIzcQ!R#VgSYtfu734361%=mcxj@JA136Z zkVdYV$jn-=QDEL|%#F9JS$z~*`yIM>B_+O&Lrt{WciUgUTZSE1$M?)xcIIJZCGneO zB;Y@)NTikJk$MC^L@Nd+b!Cez}wCzatu!Z*DQyV0Um2zQ5;XiH-O@W zJ^{7|wRO-%qn}b7RV4tDB!*WL|2Vs3=f6Cvu zA%AHBvVCxWesBCS6iX5~zX8}>2W$i#G_rNXMy$VQ-y5((e%O58XV<%lJh|t?XpEMqODHnEXBp4GwVW+o z8OST7E9>5|`3LRmtDf%A9+&oY$;{xW0jFxbO-qB(_`I3BVHyp=n`FP2nt^X~t!RJ9 z2>%>F7C>vCGLQw}Ghp}Q{31Gl+ZY5N>T5@&|5y=YVV#|hE6~67#^thK0CXHn_mPp2 zemjNv*h2hSnP`jN=cQElpZ%a+TsXdGYMHI-RL;PLUZ{+=q}Q78q<#VsH~ct^oTQr2 z(Ze|shYYjW-IC8aFR1VjP_kS<+uA>Wx}jOmFWAxsjjwG=`0Kz|hZg4gv<9fU_vn+g&N^>i<>c1)+f^ z(ErA`Xusg5og0OqNEP<=^BIDz)ADriv}GOKQi^#Qa<4i1NX)ER43 zfHY6=Y7H)+0+dt>XCS$|9?%i>JT_~3#Ukl}`1m04a z?sDXr+OE=5nC-{QD=-8EeYgbqR-ezEy5}jDl~Gle2~U5|kll1PTaAi0yk(@<2X-ij zz%slK{_;|GTKN@}F?-oR_-8~!(4Cz{mg{U9?F-*7&24F)96jO3qcaLXszh4WgKI@o zqX2A^eN%2w5i82t{o$)I7p(Y*)?xV`C$0&s8umv<#l~J*|CQ3oGHfgq6Mgh_=;!AL zDo=0i^;7v5Pq14EH;V`)j)HBYo2|0qF?!T(C)q~5iZvx7(o_9JWf(I!_+a8_UZ2dR z*p-F^KWf^ZBZx}mE#cj_E%<_y3aCap2%Z8`Q&|RmotsA?%>^K`qVS4-{rWW^^MO)3 zvMb2T1AlnV?7A2k8JU=L=8Mf;c1|d)DcAj9&AkOsTwB*Rm>VMr3GM_4uEAXsXxt&V zyIXK*AOv@J3+|rA-JRg>E{(hOoX&m!uV&_(nwh_*W@;#^8oKv6z0a2QEP2-2sA;z& zDoI^zJ+Rt^sw4x~lyE3b$IW)N4eH8+fWuQ;bpXHXOxdX?n=tX$iSCE>2e}ESXRRObCTcX7xRbp_o$Ij!(EXG;>cCgEj53 zK8ND9V`SLXO4_fF=yHriBfDPBg-kD%^PbfG`tK!b4mA6hz%D#Ow-46b8oxbsqLRy( z%@|D1%-@YvP{stM{H_i>^y354=`#d+s{jtvO7l+-F?fxd9rahMtUsS?G*v0WUYSpyRtS9ef{KyNbCD) z61a%V$U-V?Sz}8q`frwR(Nm&L{zp?zCF$Gz1T6{-UGs16FGmWr-9JSs1IfJ=TTWvO zPG*Hk3(`U+vOpmXT#bg0&9%Q-o9j27Yp@o8krJgKNV}W>XC+76DxYNfuT1`d*&IJ* zC)b@fdwa_pNsui$Eg)DtcAxhS zk+JlXk>Tk6xjwiF*yIstNg5){xE(TSQC!48v*;02HT(1+{rR^tZr?Oc@-`9M*1g=) zl@*eZlIX9_hF}U7@ny@O7xqXb6r`xvEW0$^C38rFgTtO@9Icjih8jE~+?9azOU&tE zn0Z=qUeRiFf_C^JdcR8&csh;9m(4)J#XDQ?4lx-?RcmrN1zJ23!A^+G6$n|xdPZ%7dvk27bGGR-C zjyBktzu}V6N*h%5k6fZtJCKDjy?i z*;G3b6}+H-me_a9x&2FjkN#J`s+hx7YK8-;B}GPdX~+=12rB4~p!Z{}Rcw5jX*|vc z!?ioivgctx&pZmsHk?S#!FvnipJ^%Fb2xao%kW~t3S88F{3Ho0Ub=vB+>=-<#NS011F4dv$${kaYzg4}f3{i%Uu2vELp#m?@92UwNL>%_z7Q z9Qi8aO#@`=_EH1(*f+m&hRIyx#;h#xeXu>~_+xK^pYI}C1LKK+^9PHn!TCfDt-Y5G zOr<6>vByLWNf6e;dm@ut8B{*jhitFAr~F`4u$;h*@T8Hx)mcKd56p39VDJtdl9XeD zN^)|9vy;vasRH>K2s-NRLjGkM$uO+Nsou>0 zc$9-BLI-wUJRwk?^3*2aMZ`u3OmGmE2pyI4Och0n;e*7JvNu!bknEQey2XHoiyj}Q zWz^JCl>P|+@_NhWw46ABw`)cHbK8vADbR)Q=Gy_&EdZZyk-ew9%mXOL0|<`j>B4in zTVN4NgMcZhKPYQtW@dhFPUS5LDcH<+w$fIvB_H@ zyqM3^)&UzKWN*j{}DLvXGcg%pbp46 z+Q?iG7zp6msy0xxMtjuVx+L=bZTOibCL5Y_(cBQR&{whe>o@)luuh2?a+%s2DFok= zUZjvNc@d59F|eX_RjLPwAM@YeyE`&@#Df_hjGzxWh8OWaE_XyK1CyS zg@R7CQA($r5!U+B&nJzs3*DBG$`m+O6vL$@w(|G zQrGaykAAx86?j@`eO#Eu9*H1Ew{hbK8Sm9R=~zGjBKp9}#nGgn)P&k&ey)an_s_UQ z!u0ux0=B6TuivE;)99#i9IlcgWeGEJtX7lBT~lw-6wEV}_PE+(x5OnEy7eO_JDTq~ zotouFtqS&c3#!Cu3?1SqOEd{r%q!AQ5@qAJKBD9Mp-7X(jvz|L^O45%Mfa+kS`x*( z*6);!YV00)wOp;zku=<{t}LB2b5B7mx%pUA^_sDgZIBYw>tEVdc zPzE(fg)8-q?|ev_A_Mx;PdR@)XGrP^T0=-gTT0l zR7Dx$Rl9#%%tFyX3EUT@yt2pw2G~^sj~9w0?_2}N3p_5TRPtrg8l0ebsKU5k&#q$B z_N_=KNG0`<1?!aDUh?1++Zkrq_5)zRp6e<~JAd0o3e|IldcO zC7e3`gXdM26XXOkBB9MXf$KryyfKbFKj6hxk6B}(ZY&g;2wsAFrrqzMXCvl`6Z=_Z;T{^jILl4xN`OJfZm6l&Ndu!~v zfGCNNxdZ23|6mN9rwmKqF15OyT6i}ZW#T+lLFfKZNl~qk4b#OPR5$gRLNRmF5}yz$ z(wPYqgg^QZ)lcRLjjRSsS!&0cBs;AmGTN*>zXcyRlKP2D$CI&IveIDJ{*A&Rg?az7;cAEs9(gt?oW~kY9Bh75*uO`07!ak$nx5w7$J!rJ-M;pF3Y1>VZQQ z4c`YpQXN#WDTdMf8g>&G%Vqnjbg_DPeWTS8-vTZ1{>x05T^tagdODlZGfyRvb}#q5 zOrCYKG8jw8ck%USa?GZHb_e1=n^t=!# z<02p;s*v{7t(iwjp&ibX{q=l;qTvOj(_KA%7CpK}OlvB^LwTdJc7;q=s8B1=;Mpgc zz#orNgkcZw2;tOvr4F;Dlf< zXG*4zR2NQvo_s6s{*7pK$SNmcI;@>ErXv@rJTGTGeow**uX#+YG3_+my3D=De791+7P3RJHpY%@rN zKVIM)`Nk|2PjaN>r(&{&s6h=cya~* zTq-Z)+F+yrXfa_}lcc&=a7eEg<@Z6_xC+tAhLM0W2U&PrDZK*E7Z*PXo$%oM-uI<- z#$yT|)S$s~)ZPqKf7j7!&cKfg-(v%Uuel;1G+|$4bEv84DM_+q@1m^W0_+os1Rfz_ z`YylJ4@7KwAd!xWRWrHLx_ffSD}+sfNR|)KL66QTY{YX;i6|4&UzhI5PNlUf1&iIP6p4_n}o#OSHnGn+Jr)N2sAO&ok~=A-|cenR$4#547q19{ql zfZF`_1vaQpLH0G@u*L#AyK5X#R;=3kWqhb}1aR|blSqzG`0F$Cfd47!{hw52{Yqf2Vt;PU)wg-iFy71{V7p4JLb_*JHujnd3|CBJ8lysLTT%hjZyPc#@W- zCy^R0RBH}@sUv51B|Eqmme+@3`gs79j%HRS0;ee|>oECJhnGk5ZPO+enx~NGi!vyX z-s1dIr}vFECO#AH+TzLem-_=N%T3dq(9VdMz+Hy42{+j>4~_Ci(|wRL!&+5*QvaBf zf-CkE9TQ%kC;054LKs@nQ?dA^X2Sp-B~17|L33w=QTHb2cA};ZeKxN_hJ(AA`IvdS!Kb4$dObvC?%zmr;lw{G)FF35M26a^A+PIL=l|5W*pn{naH!Q~O%A2Qn{ zbFyJcKcmS91oE(r$V?)!yL5lAij$t$)~v=r`fJ(T%GlFn zUZmb$#2Mu$;Ue=ts_F%6^?Z>%q=?Ot$0iE28sdt0ntxh-n<^+u^|`xE9z2^5rQ{_f zX4|UReC4J8h%op)9G9m{#6E%Cq~yhQD_%=T^wZhxWQtdw1u&-{pN7~kHSnI~Pp9S# z8kRLS1XO#r)e0mQzY&gBA?x#r&Hz*0KzAjUxn^Ko#a2YS8$@Fs;5ZoE|Kx98T-_lI zwJmB-gjPJzpY0rSTT@oENi^}$YwS zYiUL*mT$ZW=Oe$RvKlXaZ*%U5#gb9JIe-uV|BYz57_JT_+jSt6=+Dg-0v{-rmmhtW zd(Tl@hfG`Un$Br+yDE3Pnj*R5E3-NmwSZr_G9vVIaEITCg&!TUwUgn7eerH4PgI9X1(FnJ9=++we4l9RYX>RGj{jTCk>AS-tI zo9yIO$M}h16QGKg)>Min7r`#0+G+_yeqsUM!hh=m=!}gZ@_g6@gf9M)w~{GjzK77Y zHD?dTN&h^sJ3G3nHFCwktcvm?UtOIJC*3H}aDVC6+uPqBD?8d5j*Wf|fSM8(Ay?m$!Ik74zz`MJ|!u+Qzja zbmb;lg0$jSjib|jMVq(T<=xGGGc@lKgj>hD@AbtZ)9TpcYD^z)tb}Z?qSeCsi0*c| zKX!KGc6Un5m(?ZsvxbcpW}io1{fTH2+J+#;ssqzTW(UpE*;gLt2zZgC_K5ByBpZ0T zl?qAOM!kH}PS2p&={H$v-nn$jRs0#H>9&JhKGu{CJ;%UDK0M4#_#V-Madn4vy_WDD zp|iL!XF=fVdFFsBj6Ran~nI8a{2Itscxo$_9pLEdd!{w3Q07Dsn7Ln&Uvpp zmpJX371OJFJf1eB!XvGf`f0oSsx6^>vCI`x{|JBdiMvDLt zVzbj{uPvOljt0x$A;%MVNew*!lHZa-`AI0fI_#~XKZXS#x|N&CQAzxXSJ;GT@@Y@R z)6>8J=}Ia0S(@FeM-#{K;cgC=1pj5f{n_}=GHedTxb_zQlN4x~o;@96N>?n7I0Hds zdHtb}kvBMrO{3;Rzf$M6TH+TH`wXt0+I_f#)W=TzBBHLhJ|%EI+@s*65uYt8575^+ zYvwi{5@Zph1h9S9x1iK zAw!#Q^pUniwNbZB1g*Y*T#5&yJCjW_D)PB%w)(PIQ-UzdfAl*apFq`A6{Ngb0s>cF zjB_7bTG+C@8tq?VV~An9!6a_n?!otIo?OwSc{Dd?UXZFnf2;C=c401_0NJxNY;mnX zq%}bV^|8X-y*`R-OmEofceAE(7R4xkHQih0jsX55q>zk8mrt|~vxY2^NX8-HT&EaI z21s9vBq@wA4D}IRLi`AHde{fmGNYB_Cl8bfl*mxcnRMAp%N}jvIW~1M9T81!4dpPz zz69fzygK!`MI9U6MyMTLJC7EOI@ck{bKm|P2TzkLpqRiK5Gl*Q|i|>Fr7??5+qX6oG^k6-Faxd&%xmv?#Es_ zxWPF=)_5*@b{SrvC88*&Df6wBo_B$OR-8Aja7FAQ|4B7Q_;xBSHwnyeAwmkdx;!nv zPwiDNd`J+y)7f3?=X5hE*gyGpa_OaKplsXkp+v1znFVltaMZFQG$ zpI2Z1%%UmjdCQ;4@+!}fuU$hbNMCMyLf;_BWszS(q|UFA@&&wuT3@P9Ieom9S7j#W1;W$sdrUaPTz%j4(AS}J3Pd2RHl&20WiqV<$hj*BweMrxp( ze$@iTFxL!GRh#&^WP<1(c2(IoDs8M55?P1%fXx+=!FhJw_Qo*7ppT8?Ysa-h!qT`= zMfw{s+aAqz618do>Bs2wcl{&EnN1ti&YVSEE9+bxdRw-1bZY{NqS&OKqFh}Y!6E?8 zt)_p2)mMYM2Y)X7CUIGcqnwj8%J95cA!Xx)P3gIQqIKNsm?4WGjq4o#wfr+s7%Dix zn^Px@!o}a>ux63vzLS>l%`pJesx`h?RiEDkinKJ~yrkZ;+&QB*B*xQbZI&Tg9!O3# zB1HW#CwAf!Hoq2mjfs_jC}?2u)D1xY;`v>81xxX(YwGPoqkJsyIGVKgpnW|XWclsK zj(u=icRzG89e)knR!v%~;^w^`&)JZO=DBnHopGI*fiLj|0#eo$N)ovsf8%nF<}g|s zlSi4MTCVo7jY?M{8VBCxfFjZb#7~x2fbWW5oD`^P(7e8vc3&6ve(*zdTprV0RWpbt z?Co+IJQjnJ6@XfAF}*-N-UUQUq^~fErltI4|Jv*i1|&;T{Gu8OtT@t5Lki_HjrM+L&i*4Gu{aP;yWiSP zm*z3O4z?u{yrIub8huG3pzZq@NyX+vTvZ<7wqP-dbj~qK{nR#ib;2sbi@PxzlE}Iz zNS1FtRu{e?G%5&aaNSxYZWiweC^9*}X0E0%M67WnA2Hdhnk6~h2S9VHV%khax%;oz zXH!HXliR79l2@B2AHDfl5hEc(Qe9dn=-sYjG|rrNblHoNq`0RQ?^>{LN9bBO#{<%L zkq~*(A391jbBMxkJR{cEVXZ7%DyAk4uQznw>HOw9ZWRvCG2&9!*;!WU(3cxar5uhf zI#|ShvBu0`KG1Z(Li)M|dj#+%YW%5}tJ(arN?+W+EjdJ9o0>-tdFwZSNgbMEDce6_ zc_AD4SR&gG^{CX+)@BwA7qFe#TYw9yg0t=q-LuIIFBRT}D0)?WXKRkfUo>5sf|gU4 zX`UK>dY!Obna`*b=eLCOslK%akWt(=i``;TFFW7+jJJQ}wMBu@Mq+hNmQR@DizGP7 zqAbntgNtC!nY#YvO)4_wJ>AaXF$vHr%kW%DZa+(~PpVb;&CFVvp0rbRUJIEf+o2ne zkp>S<4`bPf|B6@@Y9&uTMpUbRH-T8D-kyFL7puOs*8;JN6R3-!dWlWhq#I$Rn1zC5 zm1z7tNI%;SYmf5VR|Kk^buNzj#U6AkcF8tK$Of>b93H5dG3oFU5Kbiqji0kdp)7}e z+0$UN-P83|)}BPl#|zC&`i|C-+?G`x@0WXmo|h4G)SV3@jGRiSrf3{zeY+xwxuHUQ zbFlMqpf%9WfwQ6OD7V&0wVP}$Ag-)qLf+^N*rbOWXQZ!H6n&tYwEmGTI+jQBG|4Qw zC!1+I-Ssfu$xOOQRV6zyEGX6;DO!1?LSuestTDrx+M_9?X1hh~LOq}JA{fw;Z=ojQ zJD4`@u^%GWd1Ip&pXMd&z44XE)gB56D594f=Ve|68(dO#PopAw`x*uB;PhWo_g}W^ zB;S*a4ZI@8rvR$|$pH%cUo9>8l|cKwMYAlp>;t}~$&fVu%9J!c5KJv^`D!t5#iM{j zoy~=Gcc&g?dY}MRCLv)~uBFUk0aJ*3wr2Y_`m@dXOuZ76wq&ZXc1+;6*>3M3>t z_H%AL;*uC5?9H^!`zmV;u*Wdfap=>PnMc0Zd26|rb@JN%`S8%j4D4b5V)P(S;AC_~ z6#50&+rQ=9#I<^~29qt@2mfqZ&<|F3ooHumoGV!&>11}~g=m@UiC1=CcsDXj7M`ma zTR6!qhIjijcT$lhPJ!s*(>YkoyjBE_flfB6cv-vSE z2gO|+hz3?jZ9-Ry<_@nJVX7Au`OIUAv!}j0vs*ytZU*O_f=7EM3@AhR zvH!h9CB&uGY*X--%VLF2hc>B&v&l0^0M|5vZ;anWn_`R>`ifx-={0{#1sZI1QWmSp za~EpD!>4f(G*H+3tp3U+H-ATQ!LP+mBLh}Ynm&-y)V`pD?(l?id6#`qm)6(xl`9A7 zv*!sL$GjlLDM!M#Y6BU0r~=%n8R9cki?Z3h$1vf(AD#Em877J)bL*9XZiF^>yLG&w2qQAngA_2%d*X6iSn zUwWU0S30^Q;>0acUgGnjdk0>o!j|&!Rm>zl)#RbS5-EuD3x-4`IrepSZO`&rg}cHM z(X0`Nhucj`3aL>cX-O=0vs{-^twYec$RpuuE|Y%+?OU?xPin(VlVjv3>EHZ@Di7-T zYz;VcLUMV{r9Tpc(?nh8P#jidQ4!rGE`kOb>MJrk@hsvbJNEU^4#&M6Z3OP=n`a5N zslP^lY-0V@I_bEJuQtcM!B2-3OS^5v;OOb8ij(l?{9YySMiApk$C_b`W0V=#u2(OP zGd#j}ZBG}xt1f@GbscaspbjT_+4MzH2!%y|N4X_i?w>G42c}I{x3qaC_09z*_wGtJ%*Cj zBlc5$w1y6Y!PF=AyJhg0>~z*@$v~d|_rBf`+Zq4R0u-zFxOQA)ubmNH###hI7~9KH}M z&K#ckJ1&FFfS6i122AZ_dx+><+Am|gCX$7!Zg6bdClnZ)by8xq{f5bOGY&1P_0}ka zRKbO2y7Vo_D-jss>PipTAEPLP4HuCb=L--Y$-+qOs!j<1v4X4~YW2yxaRM*b4<(Nu*4WSt+#WlygL1NooM4UjjHg1ZB2e4e z(Suy)!&jMGPXm>ZrV9baSofnG=_%ElC_sG^RLzAESLyi|;*Y~H5Fa?im! zTI#JlkOra%eR5M{@JQGb8tya7`8Y!Yt@&@9Il$#Ij-OLxkUyBHmOtut-6|qpyD^)q>M5Rd zp`%Ib38D8U`(8&|K{@px@L)G4!E`kjxHz+@^E@=0j^SZi-w5!n?o{GAd7Mlz9iM+2 zpy;2pA)6<7q(A^T7wuKUN7~sg;W--0Qh6N7!yP>{N^Z$wXEGm@2-0;o^QkRWl{>2_ zf+zR;3yh42EbO`lNRmn<4*ZzoKlu8{kL(I^f2>M1GG=BYWR+y7;;HH9&}B4#>ruT% zne@osJ$> zDiw#W<^&NZ!~o2&lgtR|0LM6nj2MEU;Cg&vdCv%6jhvTAdd?rf@*tZq5h`(P%+@po zDU3H=X_U7C_f7E{4D}Q8rdu28{N)dp6^_gDTl%I+NBeI5EYU}SUwo&bSRPtJEGK8OrC-!k~1Zz%!V+h2E zYqZVo#haW%Bx=_r78HGKXq0}_?)3S^Fh!-TXwq8A&ZVcK&#>nJ4y6D=3j-`b0%9lRpo-8YlfLi`@b3ixZ!o0u$%ieR zJPs_e{Kx~KvouXh{43l0uajj?Nx91u(KC=86=ydC4;OTRI4x*8omd!aKNTe~7 z)~Y-o=7%=qhM8$QHvKJ)1`#Ur7~eHpo{!gQF8%Vs)d?WZWqA;4ivs?#^isQ^Fkm4x zv2VQ1gcQ!|_0w7)QR9pQ)S8?-$1ti`c$_s5U_I1JvP^$Yz8oR{{&a8%P$q(qXYDz*)y0BR!v0eDN~oAeoKanB*Q|eGwj} zZ{KVHSWw;{ttf>K^q3Kg!@k)lmc-a`m_yWK|G-D=TGKhP&CXfd$@|-I14i* z&A`?w*=c5IXJR<%TuXwIZ@OyP7OdYbv+w`MvzWu#KfVkM8gjgAS7<%(S@g@JBXxEY z{E5xCIYS#Okz=|02|J1!DJ;C~$6RW%Ig0>G`v$R-n& z*R0a&CT-bV!)lT^A3f4rr@HaM+JPl{Wo}vo|1t?R87_q1@n_B8#N!u)9PF6z%$ic# z$NT{SjwOql&NCiyg(ebP!e7*jx)fDJJBV7~;V^dQ8`6<+yUTC|r1ju* z!R};hxcZvtB<&hdp0*P{BQfJ7Q1#5hd3eDURR4RHf&9NLgLgPW+w9Y$j;M}Fswod! zhpS#o5aL${U0t>B;Pyvu$k6>}c2{6XIbcmQtIBr8SN{!Ozd?bl^o%E3X8B2{zuQWj zgPUE^dbl3LMy~O7%Bz49<`9)xl&wG$m5JLaOUMo;Hc`z}=m|v(XEoQNH-V`y-e{=$1bzGIbWiWcXxymQ; zPcx@Hj1q*L>*w6&Y#5`=Lmctr6)>Q3lpv_Q2QN!)l(zcyqYGJxVt|UGg597)E_>Lr zY0lD!izvqoV8l77eK%i{2m_cCQP8?S+eLalqrxNu|0`kweh$luTOa+Reo6+RcIh$f zL=_(uV$Jt!)(-fVA3VrePOr)zBtEh}s)*N+Ce%K$=GAw7T|B0-CN)p(VaUAONSxu` zx#xgEjdgn>Pv_Yp=r|ObS(d5TA-3iDKq#(qTz<~4m)Ptbe)5nZ_WZIgw~Q*ecYWEJ z@6@NE*}l{h)dWc_+Pr0gh3^>ghgBL4fq{nyxnX)j-EePeoX9WAaSV(EAhCh+v9AVB z6hJ`*m}bbb4n!1feu&sW4O$F5`eD^tee>}Yv=Ik0%OE!KJ5hDp;(nbFM z$O;VzDtKUR;CMfRSr7!D)-PoqXlq0i=H`L5QX+ASVHDk>&1Yay%f=vfEK);wi&bLt z3|+ROv~AJ7aM zd9LRQfUFnvCHU%i_9JmIb5!K-pNXLVRN4GL=c&Q30@xA%GhMjvvh{RmD7dd~34(Sd z@kzDOC#;i|X#vNk8M3Xn zyR$+PZWAt$dG9)Yw!PFL+~vQXc6@>5HBmBRj~!xTRgY6O9Zw*0csY{Z zIO{GTd)C~NG*{fn^IclE;!J0srw^|zYt`PgU$l=lkT@+s0;r2V7uX+Z#}$;+Szs&v z7P|53{^zJZKuARtp^oxi;xa}3R{Y9-)JQxJ2h8vXbjy=gX>@=9lV#O5l0uBYlrk{>(PMK8ex9;clz`oRv%jgON*SX_^ugwF$QxbKOhaO&WFN^!qp zFb)btKB~<)d->dXM>M|{c{u*@wz;k)7U3YP>o?-;BcjOuX_6uYOm|!Ud zDejWDmrs`Uk3xfC_4-SQMbiA|d#*BX(+Z>P7Sj#n7d!9Ew4}zugC8}8uudiaBq-2% zyAKpCEt+b;SFW{5L8Lr0beWr}Nv0Kc?B*+hk5oH7LF1p8>ql82A$VvG6AAYW9QOOi&pzMjBpZGu@mouuyG?qS^AoM0+Q< zLe5J;rNUCB4_gmGd+?I3ZC3xtyB|g;nQf*rIeS&S7wNej+k*;4-zy>rEP$^LP7gCA z9&EqzqvC(J|4Un1d^ZW{J$kyp?V9PN+H(Zd^ZXK}B0TX7E}z}|mbMon2mrrIGq1F3jI>8?oE3&T zQCaVMW%t^Q$K*U|X~WT<27P4T3$rgyVjN$UrX;LVi^1>nHii`_>^|lphiPZo`^Jc| z<7IR!d3L>Ia^8|g?GN)2Ph4xE-Dl4bt;IoKzolWB5FS;`4wA>T3<|{-hkGg?vaV93BM+7ea)+(-yiQaJX z`M!Qfls-xRP(gQjw-nx!S|-r{OT;Wf{R$&eQW`5^TCY8y2o#Es?PSGp&B;u}5j?RE$7lL4QF zCYYfw{e#p0MRG6tnn<9ZZHS!YJ5)b7K6bp{U&%fw@bwDqR4xC}CZ0lIpr(5w$)m`3 zKrApv{Vf~%Pi!@_>C|y?ptTLo@YOlg(D;O#RQu9(t zkS#rSMx>R!6@GyoI#!*&m?SH@B9N;Z{#Ut(!OFUFc>`fueEN(^F)QCicdHNd`{T}H zl1?ML>M-+-Hg)y1qI2{Hwp?X4Re0N9{@Rgc;5<5Q>rsx}aeCG6DcJmRodni!kS1Mv z%Tua9z&FAOcx5^~!9%cHZ@W+=@2JTR_dfFX~dJYJc zSlA7Tc)?>EGC7mSp{D!vG@25fr>fm&_h2U!D>Yuv+Wx<1m)5l{6>R8Lj0#OQxW27c zd*a4l(FevROI@5>XM2dy*y=OpBv*#$A2^%Zw(;vEKcT|78c7ikZc`zvr_X2n5c41! zB&yHAEIL?ymhbiqC4_GV01W(w>Trwz+|eBrdECzaX7|ILzKPu!Xx6{2Tr?|?F4PBa z*v2V1$xpvsHecbFBQ((U0?r(N)|N(Z=HlX#b^nV(5QR=fu2-Pe^rTtgG4D^$L)wVH zP7Vs}AzNVjRfg?fG08&v79wQAP!O_bCkAeJD*(4F_dr6Z=TWC|HlpD|Q8)U_|6qd` zM8e;A1}A^RU;igT^}jR7(K`Wwl65qCM^S@ra>e*hqh@eSofFgh&e#?X&JnG@f`J+Y zb*>1=*Z>xEs^cs4CBXg!j4E>g{+_usFUWr2>=yWode6l-R!veyJwH=y=kqt8L_w%G zCQD0pxQ=e^)K&f@0To!(vw|Oi^?zCepSs2HNDCF8WoZ8EU2oBA22Tgry6C}C%6^lJ z+r4Y0-AVPZN$apS)$f)_@J4Ah5n-Jbt+J*&j{oiB`lE)(*k8;Z9bYNFc)T{6 z9Bxu|IAw-K^PWF$c^}!^Xzjp=;;;CR-wp}0-urczXphfc?**i^HlRN6)IoQeD_pc! zBQA*YcY#M}-6KyH1;w7}rWsLBzpEQ3#-#T_+e;|qX{7_>_m-XV~HDkR~_rkv_ zhESQ-*Ay_95p_wMzi@*4G*jVaBYJNxJ_FyEl1{AuQ?XPOkUVvcGn_f%zHM z8FtaL`(%{AczZ3>q`ojHZqvCwylA1F?bgaZ-)7GEy=5D_+jrORN5Yfp`1_4&#T|o)nwi z6JAZLduANV4YRaEac8>>*~Xd2kYhD{jrKU zbjkCSdCXF1H>)arqi#oE4ZOa&!=PN^hI{DQ8b`w}7QIfrM;DcE^P{TgJO{o2SJ?r? za}ru~%sO}>Ol(9IExr4&dvQgqW}pr(MhV`&M5YQy>x^Jwx!m)s+&ZM}!`{&dQV&T~ zza5NvX9e%Iu3_Ljz=V_{-3hI~_c3B*{uj*`Gq`W&wySk|A$h zuD{&d#G{_OVcxF&1e0KOGQ+z;hO$3z;Ouqxe*Hi%_JZ3#Fj^5qX#XOKC9vffFx>8@ z<#t)Y>HLo^p$$XJomzoTbtp(tE zqF8Ls+S*#skx$WuwE)|yRJiJE^fMI2=&)^ivt%cmrRPOl_m1kn*lxfRg|ZA_5Nr$B zQq7IlV?KKrXS)*>3F=}ez(}w|p!qko=h?I8An0jIW0Cg;=TUu$ zokn)#l1j^S#+&pUJA=X1x=Z-xe%3SiJLu?HC>%-p?;|oc@wU-W>z$Z{B_;L@JB#W8 z?esWqUxUc``v+@zBYD#gY06AS>6eS%@8(ba&v4krv>vkJ0;N^xy>HjS0FLvk zRQ^5XfPxkTeXDeGqu(8PJ5;DX;02B%oAg`_h)(2pD&w=T2%^CfD5;C*6Xpr5p(AWv zF#L;bnyc26r;M{Z#O6y)zH@Y5X<;@@z}U!_lBF@&8zWcO#KTp0Qd9Vo)h&H8Cqf1` zG3r#>`{i$Vt?lt)!VnN9LM#?Aiw!mL1^NOaf6uG0BLGf+)(ov8q|gP-4$pJ>BF;8G zY;AlNxK(0Lt8_>-O1$~^`#%z!6Jgt{Sj`HK6C162N5XZPRhfkUXt3zSZ{leSHZn9= zZ?pjsZ))%)W75f>5a{(ZMs)B~mJ`pETB|B4&UZeu8hf()f|IO8m+u?Dj?Kn=HwN^L yMW|grd|@z(fBGLOTi^QNZvgiB|HmBvMi{bJSwiipRpn3k_lpb5fXW1QfBr8hM&U02 literal 0 HcmV?d00001 diff --git a/src/main/java/io/github/spencerpark/ijava/JavaKernel.java b/src/main/java/io/github/spencerpark/ijava/JavaKernel.java index 0f32171..9634555 100644 --- a/src/main/java/io/github/spencerpark/ijava/JavaKernel.java +++ b/src/main/java/io/github/spencerpark/ijava/JavaKernel.java @@ -112,6 +112,7 @@ public JavaKernel() { magics.registerMagics(new PrinterMagics()); magics.registerMagics(new MagicsTool()); magics.registerMagics(new TimeItMagics()); + magics.registerMagics(new CompilerMagics(this::addToClasspath)); this.languageInfo = new LanguageInfo.Builder("Java") .version(Runtime.version().toString()) diff --git a/src/main/java/io/github/spencerpark/ijava/magics/CompilerMagics.java b/src/main/java/io/github/spencerpark/ijava/magics/CompilerMagics.java new file mode 100644 index 0000000..dad1c1e --- /dev/null +++ b/src/main/java/io/github/spencerpark/ijava/magics/CompilerMagics.java @@ -0,0 +1,119 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 ${author} + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.spencerpark.ijava.magics; + +import io.github.spencerpark.ijava.IJava; +import io.github.spencerpark.ijava.JavaKernel; +import io.github.spencerpark.ijava.execution.CodeEvaluator; +import io.github.spencerpark.ijava.utils.RuntimeCompiler; +import io.github.spencerpark.jupyter.kernel.magic.registry.CellMagic; +import io.github.spencerpark.jupyter.kernel.util.GlobFinder; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.List; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CompilerMagics { + private static final List COMMENT_PATTERNS = List.of("/\\*(.|\\s)*?\\*/", "//.*\\n*"); + private static final Pattern PACKAGE_PATTERN = Pattern.compile("\\s*package\\s+(?\\w+(\\.\\w+){0,100})\\s*"); + + private final Consumer addToClasspath; + + private CodeEvaluator evaluator; + + public CompilerMagics(Consumer addToClasspath) { + this.addToClasspath = addToClasspath; + } + + @CellMagic(aliases = {"compile"}) + public void compile(List args, String body) { + if (args.isEmpty()) throw new RuntimeException("Please specify *Class Canonical Name* in args!"); + + // 1. autofill package base on class canonical name + String bodyCopy = body; + for (String pattern : COMMENT_PATTERNS) bodyCopy = bodyCopy.replaceAll(pattern, ""); + Matcher matcher = PACKAGE_PATTERN.matcher(bodyCopy); + String clzCanonicalName = args.get(0); + String[] namePart = clzCanonicalName.split("\\."); + if (!matcher.find()) body = String.format("package %s;", namePart[namePart.length - 1]) + body; + + // 2. build + RuntimeCompiler.compile(clzCanonicalName, body, buildCompilerOptions(), true); + + // 3. add to classpath + // todo hot-reload class + GlobFinder resolver = new GlobFinder(namePart[0]); + try { + resolver.computeMatchingPaths().forEach(path -> this.addToClasspath.accept(path.getParent().toAbsolutePath().toString())); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public RuntimeCompiler.CompileOptions buildCompilerOptions() { + RuntimeCompiler.CompileOptions compileOptions = new RuntimeCompiler.CompileOptions(); + try { + if (evaluator == null) getEvaluator(); + Object result = evaluator.eval(""" + import java.lang.invoke.MethodHandles; + + ClassLoader cl = MethodHandles.lookup().lookupClass().getClassLoader(); + + StringBuilder classpath = new StringBuilder(); + String separator = System.getProperty("path.separator"); + String cp = System.getProperty("java.class.path"); + String mp = System.getProperty("jdk.module.path"); + + if (cp != null && !"".equals(cp)) classpath.append(cp); + if (mp != null && !"".equals(mp)) classpath.append(mp); + + if (cl instanceof URLClassLoader) { + for (URL url : ((URLClassLoader) cl).getURLs()) { + if (classpath.length() > 0) classpath.append(separator); + if ("file".equals(url.getProtocol())) classpath.append(new File(url.toURI())); + } + } + classpath.toString() + """); + return compileOptions.options("-classpath", (String) result); + } catch (Exception e) { + System.err.println("get jshell instance class path error. keep default class path."); + } + return compileOptions; + } + + public void getEvaluator() { + try { + JavaKernel kernel = IJava.getKernelInstance(); + Field field = kernel.getClass().getDeclaredField("evaluator"); + field.setAccessible(true); + evaluator = (CodeEvaluator) field.get(kernel); + } catch (Exception e) { + throw new RuntimeException("Compiler get JShell evaluator instance error." + e.getMessage()); + } + } +} diff --git a/src/main/java/io/github/spencerpark/ijava/magics/MagicsTool.java b/src/main/java/io/github/spencerpark/ijava/magics/MagicsTool.java index 9c914f7..5927f0a 100644 --- a/src/main/java/io/github/spencerpark/ijava/magics/MagicsTool.java +++ b/src/main/java/io/github/spencerpark/ijava/magics/MagicsTool.java @@ -23,12 +23,18 @@ */ package io.github.spencerpark.ijava.magics; +import io.github.spencerpark.ijava.IJava; import io.github.spencerpark.ijava.JavaKernel; +import io.github.spencerpark.ijava.execution.CodeEvaluator; +import io.github.spencerpark.jupyter.kernel.magic.registry.CellMagic; import io.github.spencerpark.jupyter.kernel.magic.registry.LineMagic; import io.github.spencerpark.jupyter.kernel.magic.registry.LineMagicFunction; import io.github.spencerpark.jupyter.kernel.magic.registry.Magics; +import java.io.*; import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -36,6 +42,9 @@ import java.util.stream.Collectors; public class MagicsTool { + private static final String HIGHLIGHT_PATTERN = "\u001B[36m%s\u001B[0m"; + + private CodeEvaluator evaluator; @LineMagic public void listLineMagic(List args) { @@ -65,6 +74,80 @@ public void listMagic(List args) { listCellMagic(Collections.emptyList()); } + @LineMagic(value = "cmd") + public void runCommand(List args) throws IOException { + if (args.isEmpty()) return; + Process proc = Runtime.getRuntime().exec(args.toArray(new String[0])); + + String s; + try (InputStreamReader inputStreamReader = new InputStreamReader(proc.getInputStream()); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) { + while ((s = bufferedReader.readLine()) != null) { + System.out.println(s); + } + } + try (InputStreamReader inputStreamReader = new InputStreamReader(proc.getErrorStream()); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) { + while ((s = bufferedReader.readLine()) != null) { + System.err.println(s); + } + } + } + + @LineMagic(value = "read") + public String readFromFile(List args) throws IOException { + if (args.isEmpty()) { + System.out.println(""" + -h/--help for help. + help: + example: + 1. `String content = %read filename` will read file and return for content. + """); + return null; + } + return String.join("\n", Files.readAllLines(Path.of(args.get(0)))); + } + + @LineMagic(value = "write") + public void writeToFile(List args) throws IOException { + if (args.isEmpty()) { + System.out.println(""" + -h/--help for help. + help: + example: + 1. `%write variable filename` will read variable's write to file. + 2. `%write variable` will read variable's write to temp file. + """); + return; + } + + if (evaluator == null) getEvaluator(); + Object content; + try { + content = evaluator.eval(args.get(0)); + } catch (Exception e) { + throw new RuntimeException("eval variable `" + args.get(0) + "` error, variable not found or illegal express!"); + } + + List argsLast = args.size() > 1 ? Collections.singletonList(args.get(1)) : Collections.emptyList(); + writeToFile(argsLast, content.toString()); + } + + @CellMagic(value = "write") + public void writeToFile(List args, String body) throws IOException { + String fileName = args.isEmpty() + ? Files.createTempFile("jshell-", ".tmp").toAbsolutePath().toString() + : args.get(0); + File file = new File(fileName); + if (file.getParentFile() != null && !file.getParentFile().exists() && !file.getParentFile().mkdirs()) + throw new IOException("Cannot create parent folder: " + file.getParentFile()); + try (FileWriter writer = new FileWriter(file)) { + writer.write(body); + writer.flush(); + } + System.out.printf("Write to %s success.%n", String.format(HIGHLIGHT_PATTERN, file.getAbsolutePath())); + } + @SuppressWarnings("unchecked") private Collection getMagicsName(Magics magics, String fieldName) throws NoSuchFieldException, IllegalAccessException { Field field = magics.getClass().getDeclaredField(fieldName); @@ -75,4 +158,15 @@ private Collection getMagicsName(Magics magics, String fieldName) throws .collect(Collectors.groupingBy(Map.Entry::getValue, Collectors.mapping(Map.Entry::getKey, Collectors.joining(", ")))) .values(); } + + public void getEvaluator() { + try { + JavaKernel kernel = IJava.getKernelInstance(); + Field field = kernel.getClass().getDeclaredField("evaluator"); + field.setAccessible(true); + evaluator = (CodeEvaluator) field.get(kernel); + } catch (Exception e) { + throw new RuntimeException("Compiler get JShell evaluator instance error." + e.getMessage()); + } + } } diff --git a/src/main/java/io/github/spencerpark/ijava/utils/RuntimeCompiler.java b/src/main/java/io/github/spencerpark/ijava/utils/RuntimeCompiler.java new file mode 100644 index 0000000..129179a --- /dev/null +++ b/src/main/java/io/github/spencerpark/ijava/utils/RuntimeCompiler.java @@ -0,0 +1,242 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 ${author} + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.spencerpark.ijava.utils; + +import javax.annotation.processing.Processor; +import javax.tools.*; +import javax.tools.JavaCompiler.CompilationTask; +import java.io.File; +import java.io.FileWriter; +import java.io.StringWriter; +import java.lang.invoke.MethodHandles; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + + +public class RuntimeCompiler { + public static Class compile(String className, String content) { + return compile(className, content, new CompileOptions(), false); + } + + public static Class compile(String className, String content, boolean forceCompile) { + return compile(className, content, new CompileOptions(), forceCompile); + } + + public static Class compile(String className, String content, CompileOptions compileOptions, boolean forceCompile) { + ClassLoader cl = MethodHandles.lookup().lookupClass().getClassLoader(); + + try { + Class clzCompiled = cl.loadClass(className); + System.out.printf("%s already exist! Class: %s%n", className, clzCompiled); + if (!forceCompile) return clzCompiled; + } catch (ClassNotFoundException ignore) { + // ignore + } + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + if (compiler == null) + throw new RuntimeException("No compiler was provided by ToolProvider.getSystemJavaCompiler(). Make sure the jdk.compiler module is available."); + + // create source file + File sourceFile = new File(className.replace(".", File.separator) + ".java"); + if (!sourceFile.getParentFile().exists() && !sourceFile.getParentFile().mkdirs()) + throw new RuntimeException("Cannot create parent folder: " + sourceFile.getParentFile()); + + try { + // write source file + try (FileWriter writer = new FileWriter(sourceFile)) { + writer.write(content); + writer.flush(); + } + // 1. compiler output, use System.err if null + StringWriter out = new StringWriter(); + // 2. a diagnostic listener; if null use the compiler's default method for reporting diagnostics + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + // 3. a file manager; if null use the compiler's standard file manager + StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null); + // 4. compiler options, null means no options + List options = buildCompileOptions(compileOptions); + // 5. the compilation units to compile, null means no compilation units + Iterable compilationUnit = fileManager.getJavaFileObjectsFromFiles(Collections.singletonList(sourceFile)); + + CompilationTask task = compiler.getTask(out, fileManager, diagnostics, options, null, compilationUnit); + if (!compileOptions.processors.isEmpty()) task.setProcessors(compileOptions.processors); + Boolean isCompileSuccess = task.call(); + fileManager.close(); + + if (Boolean.FALSE.equals(isCompileSuccess)) { + diagnostics.getDiagnostics().forEach(System.err::println); + throw new RuntimeException("Error while compiling " + className + ", System.err for more."); + } + + // Load compiled class + URL[] generatedClassUrls = {new File("./").toURI().toURL()}; + try (URLClassLoader classLoader = new URLClassLoader(generatedClassUrls)) { + return classLoader.loadClass(className); + } + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException("Error while compiling " + className, e); + } + } + + private static List buildCompileOptions(CompileOptions compileOptions) throws URISyntaxException { + List options = new ArrayList<>(compileOptions.options); + if (!options.contains("-classpath")) { + options.add("-classpath"); + options.add(getClassPath()); + } + return options; + } + + public static String getClassPath() throws URISyntaxException { + ClassLoader cl = MethodHandles.lookup().lookupClass().getClassLoader(); + + StringBuilder classpath = new StringBuilder(); + String separator = System.getProperty("path.separator"); + String cp = System.getProperty("java.class.path"); + String mp = System.getProperty("jdk.module.path"); + + if (cp != null && !"".equals(cp)) classpath.append(cp); + if (mp != null && !"".equals(mp)) classpath.append(mp); + + /* [java-16] */ + // if (cl instanceof URLClassLoader) { + // for (URL url : ((URLClassLoader) cl).getURLs()) { + /* [/java-16] */ + if (cl instanceof URLClassLoader urlClassLoader) { + for (URL url : urlClassLoader.getURLs()) { + if (classpath.length() > 0) classpath.append(separator); + if ("file".equals(url.getProtocol())) classpath.append(new File(url.toURI())); + } + } + return classpath.toString(); + } + + public static void main(String... args) { + test(); + } + + public static void test() { + String name = "vo.Cat"; + String clzDef = """ + package vo; + + //import lombok.*; + + //@Builder + //@Data + public class Cat { + private String name; + private Integer age; + } + """; + Class clz = compile(name, clzDef); + List methods = Arrays.stream(clz.getDeclaredMethods()) + .map(method -> method.getName() + "(" + Arrays.stream(method.getGenericParameterTypes()) + .map(type -> type.getTypeName().substring(type.getTypeName().lastIndexOf('.') + 1)) + .collect(Collectors.joining(",")) + ")").toList(); + + System.out.printf("compile done, clz: %s, clz's declared methods: %s%n", clz, methods); + } + + public static final class CompileOptions { + + final List processors; + final List options; + + public CompileOptions() { + this( + Collections.emptyList(), + Collections.emptyList() + ); + } + + private CompileOptions( + List processors, + List options + ) { + this.processors = processors; + this.options = options; + } + + public CompileOptions processors(Processor... newProcessors) { + return processors(Arrays.asList(newProcessors)); + } + + public CompileOptions processors(List newProcessors) { + return new CompileOptions(newProcessors, options); + } + + public CompileOptions options(String... newOptions) { + return options(Arrays.asList(newOptions)); + } + + public CompileOptions options(List newOptions) { + return new CompileOptions(processors, newOptions); + } + + boolean hasOption(String opt) { + for (String option : options) + if (option.equalsIgnoreCase(opt)) + return true; + + return false; + } + } + + // get lombok AnnotationProcessor + //public static Processor createLombokAnnotationProcessor() { + // printf("----%ncreate processor%n"); + // Processor annotationProcessor = null; + // ClassLoader classLoader = Lombok.class.getClassLoader(); + // try { + // Class aClass = classLoader.loadClass("lombok.launch.AnnotationProcessorHider"); + // for (Class declaredClass : aClass.getDeclaredClasses()) { + // if ("AnnotationProcessor".equals(declaredClass.getSimpleName())) { + // for (Constructor declaredConstructor : declaredClass.getDeclaredConstructors()) { + // declaredConstructor.setAccessible(true); + // int parameterCount = declaredConstructor.getParameterCount(); + // if (parameterCount == 0) { + // annotationProcessor = (Processor) declaredConstructor.newInstance(); + // break; + // } + // } + // } + // } + // System.out.printf("found lombok annotation processor: %s%n", annotationProcessor.getClass().getCanonicalName()); + // } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + // throw new RuntimeException(e); + // } + // return annotationProcessor; + //} +} + diff --git a/src/main/resources/print.jshell b/src/main/resources/print.jshell index 925875b..31e2af2 100644 --- a/src/main/resources/print.jshell +++ b/src/main/resources/print.jshell @@ -41,14 +41,20 @@ import java.util.regex.Pattern; public class Printer { private static final Pattern VAR_IDX_PATTERN = Pattern.compile("(?i)\\$JShell\\$(\\d+)"); private static final String METHOD_NAME = "print"; + private static final Pattern methodPattern = Pattern.compile(METHOD_NAME + "\\s*\\(\\s*(?(.*)|(\".*\"))\\s*\\)"); + private static final Pattern methodStrictPattern = Pattern.compile(METHOD_NAME + "\\s*\\(\\s*(?\\w+(\\.\\w+(\\(.*\\))?)*)\\s*\\)"); + private static final List COMMENT_PATTERNS = List.of("/\\*(.|\\s)*?\\*/", "//.*\\n*"); - private static final List COMMENT_PATTERNS = List.of("/\\*(.|\\s)*?\\*/", "//.*\\n*", "\\s+"); + // {String: snippet id, int: pos} + private static final Object[] methodSourcePos = {null, 0}; + + // java ansi code demo: https://stackoverflow.com/a/5762502 + private static final String varNameStylePattern = "\u001B[36m%s\u001B[0m"; private static int varIdx = 0; + //private static String prefix = "printer| "; private static String prefix = ""; - // java ansi code demo: https://stackoverflow.com/a/5762502 - private static final String varNameStylePattern = "\u001B[36m%s\u001B[0m"; private static JShell jshell; @@ -64,6 +70,15 @@ public class Printer { } } + public static int countSubStr(String src, String dst, int startIdx) { + int cnt = 0; + while ((startIdx = src.indexOf(dst, startIdx)) >= 0) { + cnt++; + startIdx += dst.length() - 1; + } + return cnt; + } + public static void print(Object obj) { String varName = null; @@ -80,11 +95,18 @@ public class Printer { if (snippetOptional.isPresent()) { Snippet snippet = snippetOptional.get(); String source = snippet.source(); - if (source.contains(METHOD_NAME)) { - for (String pattern : COMMENT_PATTERNS) { - source = source.replaceAll(pattern, ""); + for (String pattern : COMMENT_PATTERNS) source = source.replaceAll(pattern, ""); + int occuCnt = countSubStr(source, METHOD_NAME, 0); + if (occuCnt > 0) { + if (!id.equals(methodSourcePos[0])) { + methodSourcePos[0] = id; + methodSourcePos[1] = 0; + } + Matcher methodMatcher = (occuCnt == 1 ? methodPattern : methodStrictPattern).matcher(source); + if (methodMatcher.find((int) methodSourcePos[1])) { + methodSourcePos[1] = methodMatcher.end(); + varName = methodMatcher.group("content").replaceAll("\\s+", " "); } - varName = source.substring(source.indexOf("(") + 1, source.lastIndexOf(")")); } } } diff --git a/src/test/java/io/github/spencerpark/ijava/TestUtils.java b/src/test/java/io/github/spencerpark/ijava/TestUtils.java index 5ebe91c..89dc5a9 100644 --- a/src/test/java/io/github/spencerpark/ijava/TestUtils.java +++ b/src/test/java/io/github/spencerpark/ijava/TestUtils.java @@ -1,13 +1,16 @@ package io.github.spencerpark.ijava; import io.github.spencerpark.ijava.utils.FileUtils; +import io.github.spencerpark.ijava.utils.RuntimeCompiler; import org.junit.Assert; import org.junit.Test; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; import java.nio.file.Path; -import java.util.Collections; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; public class TestUtils { @Test @@ -36,4 +39,47 @@ public void testReadXml() { System.out.println(e.getMessage()); } } + + //@Test + public void testCompile() { + String name = "vo.Cat"; + String clzDef = """ + package vo; + + //import lombok.Data; + + //@Data + public class Cat { + private String name; + private Integer age; + } + """; + Class clz = RuntimeCompiler.compile(name, clzDef, true); + List methods = Arrays.stream(clz.getDeclaredMethods()) + .map(method -> method.getName() + "(" + Arrays.stream(method.getGenericParameterTypes()) + .map(type -> type.getTypeName().substring(type.getTypeName().lastIndexOf('.') + 1)) + .collect(Collectors.joining(",")) + ")").toList(); + + System.out.printf("compile done, clz: %s, clz's declared methods: %s%n", clz, methods); + } + + //@Test + public void testProcess() throws IOException { + String[] commands = {"ping", "localhost"}; + Process proc = Runtime.getRuntime().exec(commands); + + String s = null; + try (InputStreamReader inputStreamReader = new InputStreamReader(proc.getInputStream()); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) { + while ((s = bufferedReader.readLine()) != null) { + System.out.println(s); + } + } + try (InputStreamReader inputStreamReader = new InputStreamReader(proc.getErrorStream()); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) { + while ((s = bufferedReader.readLine()) != null) { + System.out.println(s); + } + } + } } From 3bbf16553ae16e9c96090cc1311f2e1d5e244bd7 Mon Sep 17 00:00:00 2001 From: potoo0 Date: Mon, 5 Sep 2022 10:49:47 +0800 Subject: [PATCH 07/13] upgrade dep resolve api --- build.gradle | 9 +- .../ijava/magics/MavenResolver.java | 497 ++---------------- .../dependencies/CommonRepositories.java | 79 --- .../ijava/magics/dependencies/Maven.java | 200 ------- .../ijava/magics/dependencies/MavenToIvy.java | 65 --- .../ijava/utils/ResolveDependency.java | 98 ++++ 6 files changed, 163 insertions(+), 785 deletions(-) delete mode 100644 src/main/java/io/github/spencerpark/ijava/magics/dependencies/CommonRepositories.java delete mode 100644 src/main/java/io/github/spencerpark/ijava/magics/dependencies/Maven.java delete mode 100644 src/main/java/io/github/spencerpark/ijava/magics/dependencies/MavenToIvy.java create mode 100644 src/main/java/io/github/spencerpark/ijava/utils/ResolveDependency.java diff --git a/build.gradle b/build.gradle index c6efb14..8415df5 100644 --- a/build.gradle +++ b/build.gradle @@ -26,8 +26,13 @@ repositories { dependencies { implementation group: 'io.github.spencerpark', name: 'jupyter-jvm-basekernel', version: '2.3.0' - implementation group: 'org.apache.ivy', name: 'ivy', version: '2.5.0' - implementation group: 'org.apache.maven', name: 'maven-model-builder', version: '3.8.5' + + // ------ for maven resolve and download ------ + implementation 'org.eclipse.aether:aether-connector-basic:1.1.0' + implementation 'org.eclipse.aether:aether-transport-file:1.1.0' + implementation 'org.eclipse.aether:aether-transport-http:1.1.0' + implementation 'org.apache.maven:maven-aether-provider:3.3.9' + // ------ for maven resolve and download ------ testImplementation group: 'junit', name: 'junit', version: '4.13.2' } diff --git a/src/main/java/io/github/spencerpark/ijava/magics/MavenResolver.java b/src/main/java/io/github/spencerpark/ijava/magics/MavenResolver.java index 097fc61..dd32dee 100644 --- a/src/main/java/io/github/spencerpark/ijava/magics/MavenResolver.java +++ b/src/main/java/io/github/spencerpark/ijava/magics/MavenResolver.java @@ -23,496 +23,115 @@ */ package io.github.spencerpark.ijava.magics; -import io.github.spencerpark.ijava.magics.dependencies.CommonRepositories; -import io.github.spencerpark.ijava.magics.dependencies.Maven; -import io.github.spencerpark.ijava.magics.dependencies.MavenToIvy; +import io.github.spencerpark.ijava.utils.ResolveDependency; import io.github.spencerpark.jupyter.kernel.magic.registry.CellMagic; import io.github.spencerpark.jupyter.kernel.magic.registry.LineMagic; import io.github.spencerpark.jupyter.kernel.magic.registry.MagicsArgs; -import org.apache.ivy.Ivy; -import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor; -import org.apache.ivy.core.module.descriptor.ModuleDescriptor; -import org.apache.ivy.core.module.id.ModuleRevisionId; -import org.apache.ivy.core.report.ArtifactDownloadReport; -import org.apache.ivy.core.report.ResolveReport; -import org.apache.ivy.core.resolve.ResolveOptions; -import org.apache.ivy.core.settings.IvySettings; -import org.apache.ivy.plugins.parser.m2.PomModuleDescriptorParser; -import org.apache.ivy.plugins.repository.url.URLResource; -import org.apache.ivy.plugins.resolver.ChainResolver; -import org.apache.ivy.plugins.resolver.DependencyResolver; -import org.apache.ivy.util.DefaultMessageLogger; -import org.apache.ivy.util.Message; -import org.apache.ivy.util.MessageLogger; import org.apache.maven.model.Model; -import org.apache.maven.model.building.ModelBuildingException; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.*; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; -import java.io.*; -import java.net.MalformedURLException; -import java.net.URL; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.DependencyResolutionException; + +import java.io.FileReader; +import java.io.IOException; +import java.io.StringReader; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.text.ParseException; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; public class MavenResolver { - private static final String DEFAULT_RESOLVER_NAME = "default"; - - /** - * "master" includes the artifact published by the module. - * "runtime" includes the dependencies required for the module to run and - * extends "compile" which is the dependencies required to compile the module. - */ - private static final String[] DEFAULT_RESOLVE_CONFS = { "master", "runtime" }; - - /** - * The ivy artifact type corresponding to a binary artifact for a module. - */ - private static final String JAR_TYPE = "jar"; - - /** - * The ivy artifact type corresponding to a source code artifact for a module. This - * is still usually a ".jar" file but that corresponds to the "ext" not the "type". - */ - private static final String SOURCE_TYPE = "source"; - - /** - * The ivy artifact type corresponding to a javadoc (HTML) artifact for a module. This - * is still usually a ".jar" file but that corresponds to the "ext" not the "type". - */ - private static final String JAVADOC_TYPE = "javadoc"; - - private static final Pattern IVY_MRID_PATTERN = Pattern.compile( - "^(?[-\\w/._+=]*)#(?[-\\w/._+=]+)(?:#(?[-\\w/._+=]+))?;(?[-\\w/._+=,\\[\\]{}():@]+)$" - ); - private static final Pattern MAVEN_MRID_PATTERN = Pattern.compile( - "^(?[^:\\s]+):(?[^:\\s]+)(?::(?[^:\\s]*)(?::(?[^:\\s]+))?)?:(?[^:\\s]+)$" - ); + private static final String DEFAULT_REPO_LOCAL = String.format("%s/.m2/repository", System.getProperty("user.home")); + private static final String DEFAULT_REPO_TYPE = "default"; private final Consumer addToClasspath; - private final List repos; + final Pattern reposPattern = Pattern.compile("(?s).*?(?.*).*"); + final Pattern depsPattern = Pattern.compile("(?s).*?(?.*).*"); + final String pomSimpleTemplate = "%s%s"; + private final List remoteRepos = new ArrayList<>(); public MavenResolver(Consumer addToClasspath) { this.addToClasspath = addToClasspath; - this.repos = new LinkedList<>(); - this.repos.add(CommonRepositories.mavenCentral()); - this.repos.add(CommonRepositories.mavenLocal()); - } - - public void addRemoteRepo(String name, String url) { - if (DEFAULT_RESOLVER_NAME.equals(name)) - throw new IllegalArgumentException("Illegal repository name, cannot use '" + DEFAULT_RESOLVER_NAME + "'."); - - this.repos.add(CommonRepositories.maven(name, url)); - } - - private ChainResolver searchAllReposResolver(Set repos) { - ChainResolver resolver = new ChainResolver(); - resolver.setName(DEFAULT_RESOLVER_NAME); - - this.repos.stream() - .filter(r -> repos == null || repos.contains(r.getName().toLowerCase())) - .forEach(resolver::add); - - if (repos != null) { - Set resolverNames = resolver.getResolvers().stream() - .map(d -> d.getName().toLowerCase()) - .collect(Collectors.toSet()); - repos.removeAll(resolverNames); - - repos.forEach(r -> { - try { - URL url = new URL(r); - resolver.add(CommonRepositories.maven("from-" + url.getHost(), r)); - } catch (MalformedURLException e) { - // Ignore as we will assume that a bad url was a name - } - }); - } - - return resolver; - } - - private static ModuleRevisionId parseCanonicalArtifactName(String canonical) { - Matcher m = IVY_MRID_PATTERN.matcher(canonical); - if (m.matches()) { - return ModuleRevisionId.newInstance( - m.group("organization"), - m.group("name"), - m.group("branch"), - m.group("revision") - ); - } - - m = MAVEN_MRID_PATTERN.matcher(canonical); - if (m.matches()) { - String packaging = m.group("packaging"); - String classifier = m.group("classifier"); - - return ModuleRevisionId.newInstance( - m.group("group"), - m.group("artifact"), - m.group("version"), - packaging == null - ? Collections.emptyMap() - : classifier == null - ? Map.of("ext", packaging) - : Map.of("ext", packaging, "m:classifier", classifier) - ); - } - - throw new IllegalArgumentException("Cannot resolve '" + canonical + "' as maven or ivy coordinates."); - } - - /** - * Create an ivy instance with the specified verbosity. The instance is relatively plain. - * - * @param verbosity the verbosity level. - *