ganymede.notebook.Magic Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ganymede-notebook Show documentation
Show all versions of ganymede-notebook Show documentation
Ganymede Notebook API and JShell Application
The newest version!
package ganymede.notebook;
/*-
* ##########################################################################
* Ganymede
* %%
* Copyright (C) 2021, 2022 Allen D. Ball
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ##########################################################################
*/
import com.fasterxml.jackson.databind.JsonNode;
import java.util.Optional;
import java.util.stream.Stream;
import jdk.jshell.JShell;
import lombok.Data;
import org.codehaus.plexus.util.cli.CommandLineUtils;
/**
* {@link Magic} service interface.
*
* @author {@link.uri mailto:[email protected] Allen D. Ball}
*/
public interface Magic {
/**
* Cell {@link Magic} indicator.
*/
public static final String CELL = "%%";
/**
* Special-case {@link Magic} name.
*/
public static final String BANG = "!";
/**
* Method to get a configured instance of this {@link Magic}.
* Subclasses may override to return an instance that requires
* additional resources and/or configuration. Default implementaion
* returns {@link.this}.
*
* @return An {@link Optional} containing the configured instance.
*/
public default Optional extends Magic> instance() {
return Optional.of(this);
}
/**
* Method to get the names associated with {@link.this} {@link Magic}.
*
* @return The names.
*/
public String[] getMagicNames();
/**
* Method to get the description of {@link.this} {@link Magic}.
*
* @return The description.
*/
public String getDescription();
/**
* Method to get the usage of {@link.this} {@link Magic}.
*
* @return The usage.
*/
public String getUsage();
/**
* Return result of {@link #isComplete(String,String)}.
*/
public enum completeness { complete, incomplete, invalid, unknown };
/**
* Method to determine code's {@link completeness}.
*
* @param line0 The initial magic line.
* @param code The code to execute.
*
* @return The code's {@link completeness}.
*/
public completeness isComplete(String line0, String code);
/**
* {@link JShell} configuration method.
*
* @param context The {@link NotebookContext}.
*/
public void configure(NotebookContext context);
/**
* {@link JShell} execution method.
*
* @param line0 The initial magic line.
* @param code The remainder of the cell.
* @param metadata The metadata from the Jupyter
* {@code execute_request}.
*/
public void execute(String line0, String code, JsonNode metadata) throws Exception;
/**
* Method to determine if the code is cell magic (starts with
* '{@code %%}').
*
* @param code The code to test.
*
* @return {@code true} if cell magic; {@code false} otherwise.
*/
public static boolean isCellMagic(String code) {
return code.startsWith(CELL);
}
/**
* Method to parse a cell magic command. The code must start with
* "{@code %%}". "{@code #}" to the EOL is a comment. The command is
* either a word or a single punctuation character. Words may be quoted
* with double quotes.
*
* @param code The code to parse.
*
* @return The array of command arguments.
*
* @throws IllegalArgumentException
* If the magic line can't be parsed.
*/
public static String[] getCellMagicCommand(String code) {
var line0 = code.split("\\R", 2)[0];
var argv = new String[] { line0 };
try {
argv = CommandLineUtils.translateCommandline(argv[0]);
if (argv[0].startsWith(CELL)) {
argv[0] = argv[0].substring(CELL.length()).trim();
if (argv[0].isBlank()) {
argv = Stream.of(argv).skip(1).toArray(String[]::new);
}
if (argv[0].startsWith(BANG) && argv[0].length() > BANG.length()) {
argv =
Stream.concat(Stream.of(BANG, argv[0].substring(BANG.length())),
Stream.of(argv).skip(1))
.toArray(String[]::new);
}
} else {
throw new IllegalArgumentException();
}
} catch (IllegalStateException exception) {
throw exception;
} catch (Exception exception) {
throw new IllegalArgumentException(line0);
}
return argv;
}
/**
* {@link Magic} {@link Application Application} instance to parse code,
* find {@link Magic}, and execute.
*
* {@bean.info}
*/
@Data
public static class Application {
private final String line0;
private final String code;
private final String[] argv;
/**
* Sole public constructor.
*
* @param code The complete cell code.
*/
public Application(String code) {
this(isCellMagic(code) ? code.split("\\R", 2) : new String[] { null, code });
}
private Application(String[] lines) {
this(lines[0], (lines.length > 1) ? lines[1] : "");
}
private Application(String line0, String code) {
this.line0 = line0;
this.code = code;
this.argv = (line0 != null) ? getCellMagicCommand(line0) : new String[] { };
}
/**
* Method to determine if the code has a "magic" line.
*
* @return {@code true} or {@code false}.
*/
public boolean hasMagicLine() { return (line0 != null); }
/**
* Method to get the specified {@link Magic} name.
*
* @return The specified {@link Magic} name; {@code null} if
* none is specified.
*/
public String getMagicName() {
return (argv.length > 0) ? argv[0] : null;
}
}
}