diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba39cc5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +Manifest.toml diff --git a/Project.toml b/Project.toml index 1a3a791..e2b5e01 100644 --- a/Project.toml +++ b/Project.toml @@ -2,3 +2,13 @@ name = "Public" uuid = "431bcebd-1456-4ced-9d72-93c2757fff0b" authors = ["The Julia Project", "contributors"] version = "1.0.0-DEV" + +[compat] +Test = "<0.0.1, 1" +julia = "1" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..d73fe63 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Public.jl + +## Example usage + +```julia +module HelloWorld + +using Public: @public + +@public f + +function f() + return "hello" +end + +function g() + return "world" +end + +end # module +``` diff --git a/src/Public.jl b/src/Public.jl new file mode 100644 index 0000000..89ff731 --- /dev/null +++ b/src/Public.jl @@ -0,0 +1,82 @@ +module Public + +# Based on Compat.jl +# License: MIT +# https://github.com/JuliaLang/Compat.jl +# https://github.com/JuliaLang/Compat.jl/blob/master/src/compatmacro.jl +# https://github.com/JuliaLang/Compat.jl/blob/a62d95906f3c16c9fa0fe8369a6613dcfd2a4659/src/compatmacro.jl + +# https://github.com/JuliaLang/julia/pull/50105 +@static if Base.VERSION >= v"1.11.0-DEV.469" + macro public(symbols_expr::Union{Symbol, Expr}) + symbols = _get_symbols(symbols_expr) + return esc(Expr(:public, symbols...)) + end +else + macro public(symbols_expr::Union{Symbol, Expr}) + return nothing + end +end + +_get_symbols(sym::Symbol) = [sym] + +function _get_symbols(expr::Expr) + # `expr` must either be a "valid macro expression" or a tuple expression. + + # If `expr` is a "valid macro expression", then we simply return it: + if _is_valid_macro_expr(expr::Expr) + return [expr.args[1]] + end + + # If `expr` is not a "valid macro expression", then we check to make sure + # that it is a tuple expression: + if expr.head != :tuple + msg = """ + Invalid expression head `$(expr.head)` in expression `$(expr)`. + Try `@public foo, bar, @hello, @world` + """ + throw(ArgumentError(msg)) + end + + # Now that we know that `expr` is a tuple expression, we iterate over + # each element of the tuple + num_symbols = length(expr.args) + symbols = Vector{Symbol}(undef, num_symbols) + for (i, arg) in enumerate(expr.args) + end + return symbols +end + +# Return true if and only if `expr` is a valid macro call with no arguments. +# +# Example of "good" input: `@foo` +function _is_valid_macro_expr(expr::Expr) + # `expr` must be a `:macrocall` expression: + Meta.isexpr(expr, :macrocall) || return false + + # `expr` must have exactly two arguments: + (length(expr.args) == 2) || return false + + # The first argument must be a Symbol: + (expr.args[1] isa Symbol) || return false + + # The first argument must begin with `@` + arg1_str = string(expr.args[1]) + (arg1_str[1] == '@') || return false + + # The first argument must have length >= 2 + # (because otherwise the first argument would just be `@`, which doesn't + # make sense) + (length(arg1_str) >= 2) || return false + + # The second argument must be a `LineNumberNode` + (expr.args[2] isa LineNumberNode) || return false + + return true +end + +# TODO: figure out if we actually want to `export`, +# or if we just want to mark as public. +export @public + +end # module Public diff --git a/test/runtests.jl b/test/runtests.jl new file mode 100644 index 0000000..4494a55 --- /dev/null +++ b/test/runtests.jl @@ -0,0 +1,55 @@ +import Public +import Test + +using Test: @testset +using Test: @test + +@testset "Public.jl package" begin + @testset "_is_valid_macro_expr" begin + good_exprs = [ + :(@hello), + Meta.parse("@hello"), + ] + bad_exprs = [ + Meta.parse("@foo bar"), + Meta.parse("@foo(bar)"), + ] + for expr in good_exprs + @testset let context = (; expr) + @test Public._is_valid_macro_expr(expr) + end + + end + for expr in bad_exprs + @testset let context = (; expr) + @test !Public._is_valid_macro_expr(expr) + end + end + end +end + +module TestModule1 + +using Public: @public + +export f +@public g + +function f end +function g end +function h end + +end # module TestModule1 + +@test Base.isexported(TestModule1, :f) +@test !Base.isexported(TestModule1, :g) +@test !Base.isexported(TestModule1, :h) + +# @test Base.ispublic(TestModule1, :f) +# @test !Base.ispublic(TestModule1, :h) + +@static if Base.VERSION >= v"1.11.0-DEV.469" + # @test Base.ispublic(TestModule1, :g) +else + # @test !Base.ispublic(TestModule1, :g) +end