How to write a program as a Julia package?

The latter will be easier to debug, because you will have smaller functions to check, test, etc.

1 Like

If not a GUI, perhaps tell them to edit an excel spreadsheet? To have them choose files, you can use NativeFileDialogue.jl.

1 Like

Hmm, I thought I would be ok regardless of where they run the file from based on the documentation of @__dir__: “Expand to a string with the absolute path to the directory of the file containing the macrocall.”

Yeah, most of the data files I am reading are already .xlsx. I wasn’t sure that .xlsx was a good medium for reading scalar inputs though. NativeFileDialog.jl seems really useful.

In any case, I can test out some different file types and helper packages (maybe __init__ too). I just wanted to make sure I wasn’t overlooking some obvious workflow. It sounds like I should still be using a package (and thus a module) but that it would be better to put function calls inside process rather than include statements, even if that means I need to define containers for everything.

Give the name of the input file as argument? julia script.jl arg1 arg2 (Getting Started · The Julia Language)

I think OP’s audience is on Windows and probably doesn’t know how to use the command line.

2 Likes

What if your users had to create this type of script file?

user_custom_script.jl_script:

#!JLScript -iq --startup-file=no --project=@GlobalScripting_ASME
using ASME_Materials

#raw"": Allows paths with "\" without escaping (ex: "S:\\Material..."):
data_root = raw"S:\Material Properties"

options = (
	input_file = joinpath(data_root, "Section II-D Tables.xlsx"),
	output_dir = joinpath(data_root, raw"Excel Material Data\AIP Q&T Steels"),
)

result = ASME_Materials.RunTheScript(options)
display(result) #Because: Why not?
:DONE

…and execute it from with a right-click in Windows Explorer?
image

Example code

→ see: GitHub - ma-laforge/JuliaScripting.jl

You could provide (copy) the basic “Scripting interface” from JuliaScripting.jl:
(copying mostly JuliaScripting/src/install.jl)

  • Creates a sample “shared” Julia environment ( GlobalScripting_ASME ).
  • Copies a powershell script ( launch_julia_script.ps1 ) to the active Julia directory.
    • This might require Windows admin privileges.
  • Adds RMB “Execute with Julia” action for .jl_script files in Windows Explorer.
    • Also requires Windows admin privileges.

That’s really cool, but admin privileges might be an issue.

If admin privileges are an issue:

  • :white_check_mark: You won’t need them to write the .ps1 file … since it gets copied to your Julia install directory (which presumably was installed without admin privileges either)
  • :exclamation: You would only need them to execute/install the .inf file to register the RMB action item in Windows Explorer.

:white_check_mark: But since the .inf file is relatively simple/easy to decode, you can probably convince someone in IT to install it for you:

[version]
signature="$CHICAGO$"

[DefaultInstall]
AddReg = Explore.AddReg

[Strings]
ACTION_TEXT = "Execute with Julia v1.7.2"
ACTION_CMD = "powershell -WindowStyle hidden -ExecutionPolicy Bypass -Command "C:\APPS\Julia-1.7.2\bin\launch_julia_script.ps1" '%1'"

[Explore.AddReg]
HKCR,SystemFileAssociations\\.jl_script\\shell\\JLScript-v1.7.2,,,%ACTION_TEXT%
HKCR,SystemFileAssociations\\.jl_script\\shell\\JLScript-v1.7.2\\command,,,%ACTION_CMD%

(It gets installed by double-clicking on this file if it has a .inf extension!)

RE:

That’s because you shouldn’t run include() from within a function (much in the same way you shouldn’t run import/using from within a function).

Doing so appears to run include() in the calling scope… and so you start getting strange behaviours. I am almost certain running include() from within a function is bad practice, and I would personally avoid it. include() isn’t exactly the same as #include in C/C++.

A small tweak to your methodology

What you should instead be doing is something more like:

include("ReadTables.jl")
include("BuildTables.jl")
include("WriteTables.jl")
include("PlotTables.jl")

function process(options)
    read_input_tables(options) # defined in ReadTables.jl
    build_more_tables(options) # defined in BuildTables.jl
    write_some_tables(options) # defined in WriteTables.jl
    plot_important_tables(options) # defined in PlotTables.jl
    return results #written somewhere in the module's global namespace, I suppose
end

This coding shift requires encapsulating much of the code you wrote in the global namespace (of *Tables.jl files) inside these read_input_tables()/build_more_tables()/… functions. You can leave the code in their respective files - just wrap them inside one-or-more function call(s).

If you want to keep the data around inside the global namespace: just remember to declare/tag the variable as global:

function readtable(...) #This low-level function is fine as it is. No need to change it.

function read_input_tables(options)  #New encapsulating function
    #...
    global tableY = readtable(options.inputfilepath, "Table Y-1")
    #...
end

:warning: No scripting inside modules.

One of the consequences of what @mkitti mentioned:

is that you shouldn’t write your *Tables.jl files as conventional scripts with code running in the global namespace (ie: “global” = not “inside” functions).

That only works for executing files in the REPL. As soon as you wrap files into modules, your non-static, “execution-mode” code should all be written inside one (or-more) functions.

I don’t think this is explicitly stated anywhere (I’m sort of sad to make this realization just now)… but it is a natural consequence of what @mkitti just said.

Only “static” code that can effectively be pre-compiled is allowed to exist outside functions.

:smile: A more Julia-friendly solution

I have a few more relatively important suggestions for improving your code, but this particular issue seems to be your dominant problem - so I’ll refrain from giving you more confusing tips.

The JuilaScripting.jl sample I provided earlier shows a more Julian way to write your module (I modelled it closely after your ASME_Materials.jl module to help you better understand the Matlab->Julia transition)

:eyeglasses: take another look!: GitHub - ma-laforge/JuliaScripting.jl

I also tried to add useful comments to the global namespace & __init__() functions so you can get a better idea of what should go where.

1 Like

Thank you so much! I am happy to hear all your suggestions. Give me a few days to try to update my package with the suggestions already provided (maybe longer to figure out the jl_script, .inf, .ps1 stuff :face_with_spiral_eyes:). Then I will reach back out to you privately.