# IJulia does not use IPython magic syntax, because there are better
# alternatives in Julia, whereas "magics" are inherently limited to
# running in Jupyter cells only.   The alternatives in Julia, which
# can work in any Julia code regardless of whether it is in a notebook
# cell, are:
#     * To interact with the running kernel, we can use IJulia.foo(...),
#       since the running IJulia kernel is loaded into Main.
#     * For implementing special syntax, Julia has @macros and
#       stringmacros"....".
# However, to ease the transition for users coming from other Jupyter kernels,
# we implement some magics that tell the user how to do the corresponding
# thing in Julia.   These are executed whenever a cell beginning with %
# is encountered.

# regex to find the presence of magics in a code-cell string
const magics_regex = r"^\s*(%%?[A-Za-z_][A-Za-z_0-9]*\b)\s*(.*)\s*$"m

# given the cell contents "code" (which start with %foo) output help
# that indicates the Julia equivalent, if possible.
function magics_help(code::AbstractString)
    for m in eachmatch(magics_regex, code)
        display(get(magic_help, m[1], generic_magic_help)(m[1], m[2]))
    end
end

using Markdown

const magic_help_string = """
    Julia does not use the IPython `%magic` syntax.   To interact
    with the IJulia kernel, use `IJulia.somefunction(...)`, for
    example.  Julia macros, string macros, and functions can be used to
    accomplish most of the other functionalities of IPython magics."""
generic_magic_help(magic::AbstractString, args::AbstractString) =
    Markdown.parse("""
        Unrecognized magic `$magic`.

        $magic_help_string""")

lsmagic_help(magic::AbstractString, args::AbstractString) =
    Markdown.parse("""
    $magic_help_string

    The Julia analogues of many IPython magics are printed if
    you try to input the IPython magic in a code cell.""")

alias_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The equivalent of `%alias` magic in Julia is simply to write
    a function that [runs a command object](http://docs.julialang.org/en/latest/manual/running-external-programs/).

    For example, the equivalent of `%alias bracket echo "Input in brackets: <%l>"`
    in IPython would be the Julia function

        bracket(l) = run(`echo "Input in brackets: <$l>"`)

    which you can then run with e.g. `bracket("hello world")`."""

function cd_magic_help(magic::AbstractString, args::AbstractString)
    if magic == "%cd" && !occursin(r"\s*-", args)
        return md"""The equivalent of `%cd 'dir'` in IPython is `cd("dir")` in Julia."""
    else
        return md"""
    The equivalent of `%cd 'dir'` in IPython is `cd("dir")` in Julia.

    Julia does not keep a history of visited directories, so it
    has no built-in equivalent of IPython's `%cd -`, `%dhist`,
    `%pushd`, and `%popd`.

    However, if you are interested, it would be easy to write a
    Julia function `mycd(x)` that worked like IPython's `%cd`
    (accepting a string, `-`, `-n`, and so on, and keeping a stack
    of directory history).  This might make a nice little Julia package
    if you are sufficiently motivated."""
    end
end

debug_magic_help(magic::AbstractString, args::AbstractString) =
md"""The Julia interactive debugger is provided by the [Gallium](https://github.com/Keno/Gallium.jl) package."""

edit_magic_help(magic::AbstractString, args::AbstractString) = md"""
    An analogue of IPython's `%edit` magic is provided by
    the `edit` function and the `@edit` macro in Julia, as
    described [in the Julia manual](http://docs.julialang.org/en/latest/stdlib/base/#Base.edit).

    For example, instead of `%edit -n line 'filename'` in IPython, you would do
    `edit("filename", line)` followed by `include("filename")` if you
    want to execute the file when you are done editing.  The analogue
    of `%edit` (which creates a temporary file) is `n=tempname(); edit(n)`
    followed by `include(n)`.

    If you have a function `foo()`, then `%edit foo` in IPython opens
    the file where `foo` was defined.  The analogue of this in Julia is
    to do `@edit foo()`   Note that you need to supply sample arguments
    to `foo` (which is not actually called), so that Julia knows *which*
    method of `foo` you want to edit.

    If `s` is a string variable, IPython's `%edit s` loads its contents
    into an editor.  In Julia, you would have to explicitly write
    to a temporary file with `n=tempname(); write(n, s); edit(n)` followed
    by `include(n)` to execute it."""

env_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The equivalent of `%env` in IPython, which lists all environment
    variables, is `ENV` in Julia, and the equivalent of
    `%env var=val` is `ENV["var"]=val`.

    (The `ENV` built-in global variable in Julia provides a dictionary-like
    interface to the system environment variables.)
"""

gui_magic_help(magic::AbstractString, args::AbstractString) = md"""
    There is an analogue of IPython's `%gui` in the
    [PyCall package](https://github.com/stevengj/PyCall.jl)
    for calling Python from Julia.   If you have done `using PyCall`
    to load PyCall, then the analogue of `%gui wx` is `pygui_start(:wx)`,
    and the analogue of `%gui` is `pygui_stop()`.  See the PyCall
    documentation for more information.

    Other Julia packages for GUI toolkits, e.g. the
    [Tk package](https://github.com/JuliaGraphics/Tk.jl) or
    the [Gtk package](https://github.com/JuliaGraphics/Gtk.jl), also
    provide their own event-loop integration."""

history_magic_help(magic::AbstractString, args::AbstractString) = md"""
    An analogue of the `%history` or `%hist` magic of IPython, which provides
    access to the input history, is given by IJulia.history(). It is
    based on the global variable `In` in IJulia. `In` is a dictionary
    mapping cell numbers to the inputs. However, IJulia does not currently
    keep any other history, e.g. it discards input cells that you overwrite.
"""

load_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of `%load filename` in IPython is `IJulia.load("filename")`
    in IJulia (to load code from `filename` into the current frontend)

    The analogue of `%load url` is `IJulia.load(download("url"))`.
"""

paste_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of `%paste` in IPython is `IJulia.load_string(clipboard())`
    in IJulia (to load code from the clipboard into the current frontend).
"""

matplotlib_magic_help(magic::AbstractString, args::AbstractString) =
Markdown.parse("""
    The analogue of IPython's `$magic` in Julia is to use
    the [PyPlot package](https://github.com/stevengj/PyPlot.jl),
    which gives a Julia interface to Matplotlib including inline
    plots in IJulia notebooks.   (The equivalent of `numpy` is already
    loaded by default in Julia.)

    Given PyPlot, the analogue of `$magic inline` is `using PyPlot`,
    since PyPlot defaults to inline plots in IJulia.

    To enable separate GUI windows in PyPlot, analogous to `$magic`,
    do `using PyPlot; pygui(true)`.   To specify a particular gui
    backend, analogous to `$magic gui`, you can either do
    `using PyPlot; pygui(:gui); using PyPlot; pygui(true)` (where
    `gui` is `wx`, `qt`, `tk`, or `gtk`), or you can do
    `ENV["MPLBACKEND"]=backend; using PyPlot; pygui(true)` (where
    `backend` is the name of a Matplotlib backend, like `tkagg`).

    For more options, see the PyPlot documentation.""")

pdef_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%pdef somefunction` in Julia is
    `methods(somefunction)`, which prints out all of the possible
    calling signatures of `somefunction`.  (Note that, unlike Python,
    Julia is based on multiple dispatch, so a given function will
    often be callable with many different argument signatures.)"""

pdoc_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%pdoc object` is `?object` in IJulia."""

pfile_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%pfile somefunction` (also `%psource`) is
    roughly `@less somefunction(somearguments...)` in Julia.   The
    reason that you need to supply sample arguments (the function is
    not actually evaluated) is because Julia functions can
    have multiple definitions for different argument types.
    `@less somefunction(somearguments...)` will print (or run
    through the `less` pager) the `somefunction` definition
    corresponding to the arguments you supply."""

file_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%file somefunction` is
    roughly `methods(somefunction)` in Julia.   This lists
    all the methods of `sumfunction` along with the locations where
    they are defined."""

precision_magic_help(magic::AbstractString, args::AbstractString) = md"""
    There currently is no way to globally set the output precision
    in Julia, analogous to `%precision` in IPython, as discussed
    in [Julia issue #6493](https://github.com/JuliaLang/julia/issues/6493)."""

prun_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%prun statement` in Julia is
    `@profile statement`, which runs the
    [Julia profiler](http://docs.julialang.org/en/latest/manual/profile/).
    The analogue of `%%prun ...code...` is
    ```julia
    @profile begin
        ...code...
    end
    ```
    Note, however, that you should put all performance-critical
    code into a function, avoiding global variables, before
    doing performance measurements in Julia; see the
    [performance tips in the Julia manual](http://docs.julialang.org/en/latest/manual/performance-tips/).

    See also the
    [ProfileView package](https://github.com/timholy/ProfileView.jl) for
    richer graphical display of profiling output."""

psearch_magic_help(magic::AbstractString, args::AbstractString) = md"""
    A rough analogue of IPython's `%psearch PATTERN` in Julia might be
    `filter(s -> occursin(r"PATTERN", string(s)), names(Base))`, which
    searches all the symbols defined in the `Base` module for a given
    regular-expression pattern `PATTERN`."""

pwd_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%pwd` is `pwd()` in Julia."""

qtconsole_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%qtconsole` is `IJulia.qtconsole()` in Julia."""

recall_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%recall n` in IJulia is
    `IJulia.load_string(In[n])`, and the analogue of
    `%recall n-m` is `IJulia.load_string(join([get(In,i,"") for i in n:m],"\n"))`."""

run_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%run file` is `include("file")` in Julia.

    The analogue of `%run -t file`, to print timing information, is
    `@time include("file")` in Julia.  The analogue of `%run -t -N n file`
    is `@time for i in 1:n; include("file"); end` in Julia.

    For running under a debugger, see the
    [Gallium Julia debugger](https://github.com/Keno/Gallium.jl).  To
    run other IJulia notebooks, see the
    [NBInclude package](https://github.com/stevengj/NBInclude.jl)."""

save_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%save filename n1-n2 n3-n4` is
    ```julia
    open("filename","w") do io
        for i in [n1:n2; n3:n4]
            println(get(In,i,""))
        end
    end
    ```
    in IJulia."""

sc_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%sc shell command` is `; shell command` in IJulia, or
        read(`shell command`, String)
    to capture the output as a string in Julia."""

set_env_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%set_env var val` is `ENV["var"]=val` in Julia."""

sx_magic_help(magic::AbstractString, args::AbstractString) =
Markdown.parse("""
    The analogue of IPython's `$magic shell command` is
        split(read(`shell command`, String),'\n')
    in Julia.""")

time_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%time statement` (also `%timeit`) in Julia is
    `@time statement`.  The analogue of `%%time ...code...` is
    ```julia
    @time begin
        ...code...
    end
    ```
    Note, however, that you should put all performance-critical
    code into a function, avoiding global variables, before
    doing performance measurements in Julia; see the
    [performance tips in the Julia manual](http://docs.julialang.org/en/latest/manual/performance-tips/).

    The `@time` macro prints the timing results, and returns the
    value of evaluating the expression.  To instead return the time
    (in seconds), use `@elapsed statement`.

    For more extensive benchmarking tools, including the ability
    to collect statistics from multiple runs, see the
    [BenchmarkTools package](https://github.com/JuliaCI/BenchmarkTools.jl)."""

who_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%who` and `%whos` is `whos()` in Julia.

    You can also use `whos(r"PATTERN")` to find variables matching
    a given regular expression `PATTERN`."""

html_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%%html` is
    ```julia
    HTML(""\"
    ...html text...
    ""\")
    ```
    in Julia."""

javascript_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%%javascript ...code...` in Julia can be
    constructed by first evaluating
    ```julia
    macro javascript_str(s) display("text/javascript", s); end
    ```
    to define the `javascript"...."` [string macro](http://docs.julialang.org/en/latest/manual/strings/#non-standard-string-literals)
    in Julia.  Subsequently, you can simply do:
    ```julia
    javascript""\"
    ...code...
    ""\"
    ```
    to execute the script in an IJulia notebook."""

latex_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%%latex` is
    ```julia
    display("text/latex", ""\"
    ...latex text...
    ""\")
    ```
    in Julia.  Note, however, that `$` and `\` in the LaTeX text
    needs to be escaped as `\$` and `\\` so that they aren't
    interpreted by Julia.  See, however, the
    [LaTeXStrings package](https://github.com/stevengj/LaTeXStrings.jl)
    for easier input of LaTeX text as
    L""\"
    ...latex text...
    ""\"
    without requiring any extra backslashes.
"""

function pipe_magic_help(magic::AbstractString, args::AbstractString)
    cmd = magic[3:end] # magic is "%%cmd"
    if cmd == "script"
        arglist = split(args)
        cmd = isempty(arglist) ? "someprogram" : arglist[end]
        magic = "%%script $cmd"
    end
    Markdown.parse("""
    The analogue of IPython's `$magic ...code...` in Julia can be
    constructed by first evaluating
    ```julia
    macro $(cmd)_str(s) open(`$cmd`,"w",stdout) do io; print(io, s); end; end
    ```
    to define the `$cmd"...."` [string macro](http://docs.julialang.org/en/latest/manual/strings/#non-standard-string-literals)
    in Julia.  Subsequently, you can simply do:
    ```julia
    $cmd""\"
    ...code...
    ""\"
    ```
    to evaluate the code in `$cmd` (outputting to `stdout`).""")
end

svg_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%%svg` is
    ```julia
    display("image/svg+xml", ""\"
    ...svg text...
    ""\")
    ```
    in Julia. To be even nicer, you can define
    ```julia
    macro svg_str(s) display("image/svg+xml", s); end
    ```
    to define the `svg"...."` [string macro](http://docs.julialang.org/en/latest/manual/strings/#non-standard-string-literals)
    in Julia.  Subsequently, you can simply do:
    ```julia
    svg""\"
    ...svg text...
    ""\"
    ```
    to display the SVG image.  Using a custom string macro like this
    has the advantage that you don't need to escape `$` and `\` if they
    appear in the SVG code."""

writefile_magic_help(magic::AbstractString, args::AbstractString) = md"""
    The analogue of IPython's `%%writefile filename` is
    `write("filename", In[IJulia.n])`.

    (`IJulia.n` is the index of the current code cell.  Of
    course, you can also use `In[N]` for some other `N` to output
    the contents of a different input cell.)"""

# map from magic to helpfunction(magic, magicargument)
const magic_help = Dict{String, Function}(
    "%alias" => alias_magic_help,
    "%cd" => cd_magic_help,
    "%dhist" => cd_magic_help,
    "%dirs" => cd_magic_help,
    "%popd" => cd_magic_help,
    "%pushd" => cd_magic_help,
    "%debug" => debug_magic_help,
    "%pdb" => debug_magic_help,
    "%edit" => edit_magic_help,
    "%env" => env_magic_help,
    "%gui" => gui_magic_help,
    "%hist" => history_magic_help,
    "%history" => history_magic_help,
    "%load" => load_magic_help,
    "%loadpy" => load_magic_help,
    "%paste" => paste_magic_help,
    "%lsmagic" => lsmagic_help,
    "%matplotlib" => matplotlib_magic_help,
    "%pylab" => matplotlib_magic_help,
    "%pdef" => pdef_magic_help,
    "%pdoc" => pdoc_magic_help,
    "%pinfo" => pdoc_magic_help,
    "%pinfo2" => pdoc_magic_help,
    "%pfile" => pfile_magic_help,
    "%file" => file_magic_help,
    "%psource" => pfile_magic_help,
    "%precision" => precision_magic_help,
    "%prun" => prun_magic_help,
    "%%prun" => prun_magic_help,
    "%psearch" => psearch_magic_help,
    "%pwd" => pwd_magic_help,
    "%qtconsole" => qtconsole_magic_help,
    "%recall" => recall_magic_help,
    "%run" => run_magic_help,
    "%save" => save_magic_help,
    "%sc" => sc_magic_help,
    "%set_env" => set_env_magic_help,
    "%sx" => sx_magic_help,
    "%system" => sx_magic_help,
    "%time" => time_magic_help,
    "%%time" => time_magic_help,
    "%timeit" => time_magic_help,
    "%%timeit" => time_magic_help,
    "%who" => who_magic_help,
    "%who_ls" => who_magic_help,
    "%whos" => who_magic_help,
    "%%html" => html_magic_help,
    "%%javascript" => javascript_magic_help,
    "%%latex" => latex_magic_help,
    "%%bash" => pipe_magic_help,
    "%%perl" => pipe_magic_help,
    "%%python" => pipe_magic_help,
    "%%python2" => pipe_magic_help,
    "%%python3" => pipe_magic_help,
    "%%ruby" => pipe_magic_help,
    "%%script" => pipe_magic_help,
    "%%sh" => pipe_magic_help,
    "%%svg" => svg_magic_help,
    "%%writefile" => writefile_magic_help,
)
