diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 6fe90d93..0eede3f3 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -32,26 +32,23 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Check - run: wolframscript -f Scripts/CheckPaclet.wls - - name: Build run: wolframscript -f Scripts/BuildPaclet.wls - - name: UploadArtifact + - name: Upload Artifact uses: actions/upload-artifact@v4 with: path: ${{ env.PACLET_BUILD_DIR }} - - name: InstallTestDependencies + - name: Install Test Dependencies run: | apt-get update && apt-get install libgomp1 -y wolframscript -f Scripts/InstallTestDependencies.wls - + - name: Test run: wolframscript -f Scripts/TestPaclet.wls - - name: UploadStackData + - name: Upload Stack Data if: always() && env.PACLET_STACK_HISTORY uses: actions/upload-artifact@v4 with: diff --git a/Scripts/BuildMX.wls b/Scripts/BuildMX.wls index b0d1d401..e64bc114 100755 --- a/Scripts/BuildMX.wls +++ b/Scripts/BuildMX.wls @@ -15,6 +15,7 @@ Needs[ "PacletTools`" -> "pt`" ]; Needs[ "Wolfram`PacletCICD`" -> "cicd`" ]; Wolfram`ChatbookInternal`$BuildingMX = True; +$assertions = getBooleanArgument[ { "a", "assertions" }, True ]; (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) @@ -152,8 +153,11 @@ If[ FileExistsQ @ $mxFile, cicd`ConsoleLog[ "Copying files..." ]; tmp = cicd`ScriptConfirmBy[ copyTemporary @ $pacletDir, DirectoryQ ]; cicd`ScriptConfirmBy[ setPacletReleaseID[ tmp, releaseID @ $pacletDir ], StringQ ]; -cicd`ConsoleLog[ "Inserting confirmation source info..." ]; -cicd`ScriptConfirm @ expandTags @ tmp; + +If[ $assertions, + cicd`ConsoleLog[ "Inserting confirmation source info..." ]; + cicd`ScriptConfirm @ expandTags @ tmp +]; cicd`ConsoleLog[ "Loading paclet..." ]; PacletDirectoryUnload @ $pacletDir; diff --git a/Scripts/BuildPaclet.wls b/Scripts/BuildPaclet.wls index 7cc42c4a..0e9b90d0 100755 --- a/Scripts/BuildPaclet.wls +++ b/Scripts/BuildPaclet.wls @@ -6,8 +6,25 @@ BeginPackage[ "Wolfram`ChatbookScripts`" ]; (* ::Section::Closed:: *) (*Initialization*) If[ ! TrueQ @ $loadedDefinitions, Get @ FileNameJoin @ { DirectoryName @ $InputFileName, "Common.wl" } ]; -Get @ cFile @ FileNameJoin @ { DirectoryName @ $InputFileName, "UnformatFiles.wls" }; -Get @ cFile @ FileNameJoin @ { DirectoryName @ $InputFileName, "BuildMX.wls" }; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*Arguments*) +$check = getBooleanArgument[ { "c", "check" }, True ]; +$install = getBooleanArgument[ { "i", "install" }, False ]; +$mx = getBooleanArgument[ { "m", "mx" }, True ]; +$unformat = getBooleanArgument[ { "u", "unformat" }, True ]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*Optional Dependencies*) +If[ $unformat, Get @ cFile @ FileNameJoin @ { $scriptDir, "UnformatFiles.wls" } ]; +If[ $mx , Get @ cFile @ FileNameJoin @ { $scriptDir, "BuildMX.wls" } ]; +If[ $check , Get @ cFile @ FileNameJoin @ { $scriptDir, "Resources", "CodeInspectorRules.wl" } ]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*Other*) Needs[ "Wolfram`PacletCICD`" -> "cicd`" ]; (* ::**************************************************************************************************************:: *) @@ -19,7 +36,7 @@ Needs[ "Wolfram`PacletCICD`" -> "cicd`" ]; (*Build*) result = checkResult @ cicd`BuildPaclet[ $defNB, - "Check" -> False, + "Check" -> $check, "ExitOnFail" -> True, "Target" -> "Submit" ]; @@ -27,7 +44,7 @@ result = checkResult @ cicd`BuildPaclet[ (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) (*Install*) -If[ MemberQ[ $scriptCommandLine, "-i"|"--install"|"--install=true" ], +If[ $install, archive = cFile @ result[ "PacletArchive" ]; cicd`ConsoleNotice @ SequenceForm[ "Installing paclet file: ", archive ]; installed = cicd`ScriptConfirmBy[ PacletInstall[ archive, ForceVersionInstall -> True ], PacletObjectQ ]; diff --git a/Scripts/CheckPaclet.wls b/Scripts/CheckPaclet.wls index 7f12a1e4..085cae37 100644 --- a/Scripts/CheckPaclet.wls +++ b/Scripts/CheckPaclet.wls @@ -3,129 +3,7 @@ BeginPackage[ "Wolfram`ChatbookScripts`" ]; If[ ! TrueQ @ $loadedDefinitions, Get @ FileNameJoin @ { DirectoryName @ $InputFileName, "Common.wl" } ]; - -Needs[ "CodeInspector`" -> "ci`" ]; -Needs[ "CodeParser`" -> "cp`" ]; - -(* ::**************************************************************************************************************:: *) -(* ::Section::Closed:: *) -(*Config*) -$inGitHub := $inGitHub = StringQ @ Environment[ "GITHUB_ACTIONS" ]; - -(* ::**************************************************************************************************************:: *) -(* ::Section::Closed:: *) -(*Custom Rules*) -CodeInspector`AbstractRules`$DefaultAbstractRules = <| - CodeInspector`AbstractRules`$DefaultAbstractRules, - cp`CallNode[ cp`LeafNode[ Symbol, "Throw", _ ], { _ }, _ ] -> scanSingleArgThrow, - cp`LeafNode[ Symbol, _String? privateContextQ, _ ] -> scanPrivateContext, - cp`LeafNode[ Symbol, _String? globalSymbolQ, _ ] -> scanGlobalSymbol -|>; - -CodeInspector`ConcreteRules`$DefaultConcreteRules = <| - CodeInspector`ConcreteRules`$DefaultConcreteRules, - cp`LeafNode[ - Token`Comment, - _String? (StringStartsQ[ "(*"~~WhitespaceCharacter...~~"FIXME:" ]), - _ - ] -> scanFixMeComment -|>; - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*scanSingleArgThrow*) -scanSingleArgThrow // ClearAll; -scanSingleArgThrow[ pos_, ast_ ] := Catch[ - Replace[ - Fold[ walkASTForCatch, ast, pos ], - { - cp`CallNode[ cp`LeafNode[ Symbol, "Throw", _ ], _, as_Association ] :> - ci`InspectionObject[ - "NoSurroundingCatch", - "``Throw`` has no tag or surrounding ``Catch``", - "Error", - <| as, ConfidenceLevel -> 0.9 |> - ], - ___ :> { } - } - ], - $tag -]; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*walkASTForCatch*) -walkASTForCatch // ClearAll; - -walkASTForCatch[ cp`CallNode[ cp`LeafNode[ Symbol, "Catch"|"Hold"|"HoldForm"|"HoldComplete", _ ], { _ }, _ ], _ ] := - Throw[ { }, $tag ]; - -walkASTForCatch[ ast_, pos_ ] := - Extract[ ast, pos ]; - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*scanPrivateContext*) -scanPrivateContext // ClearAll; -scanPrivateContext[ pos_, ast_ ] := - Enclose @ Module[ { node, name, as }, - node = ConfirmMatch[ Extract[ ast, pos ], _[ _, _, __ ], "Node" ]; - name = ConfirmBy[ node[[ 2 ]], StringQ, "Name" ]; - as = ConfirmBy[ node[[ 3 ]], AssociationQ, "Metadata" ]; - ci`InspectionObject[ - "PrivateContextSymbol", - "The symbol ``" <> name <> "`` is in a private context", - "Warning", - <| as, ConfidenceLevel -> 0.9 |> - ] - ]; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*privateContextQ*) -privateContextQ // ClearAll; -privateContextQ[ name_String ] /; StringStartsQ[ name, "System`Private`" ] := False; -privateContextQ[ name_String ] := StringContainsQ[ name, __ ~~ ("`Private`"|"`PackagePrivate`") ]; -privateContextQ[ ___ ] := False; - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*scanGlobalSymbol*) -scanGlobalSymbol // ClearAll; -scanGlobalSymbol[ pos_, ast_ ] := - Enclose @ Module[ { node, name, as }, - node = ConfirmMatch[ Extract[ ast, pos ], _[ _, _, __ ], "Node" ]; - name = ConfirmBy[ node[[ 2 ]], StringQ, "Name" ]; - as = ConfirmBy[ node[[ 3 ]], AssociationQ, "Metadata" ]; - ci`InspectionObject[ - "GlobalSymbol", - "The symbol ``" <> name <> "`` is in the global context", - "Error", - <| as, ConfidenceLevel -> 0.9 |> - ] - ]; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*globalSymbolQ*) -globalSymbolQ // ClearAll; -globalSymbolQ[ name_String ] := StringStartsQ[ name, "Global`" ]; -globalSymbolQ[ ___ ] := False; - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*scanFixMeComment*) -scanFixMeComment // ClearAll; - -scanFixMeComment[ pos_, ast_ ] /; $inGitHub := - Enclose @ Module[ { node, comment, as }, - node = ConfirmMatch[ Extract[ ast, pos ], _[ _, _, __ ], "Node" ]; - comment = StringTrim @ StringTrim[ ConfirmBy[ node[[ 2 ]], StringQ, "Comment" ], { "(*", "*)" } ]; - as = ConfirmBy[ node[[ 3 ]], AssociationQ, "Metadata" ]; - ci`InspectionObject[ "FixMeComment", comment, "Remark", <| as, ConfidenceLevel -> 0.9 |> ] - ]; - -scanFixMeComment[ pos_, ast_ ] := { }; +Get @ cFile @ FileNameJoin @ { $scriptDir, "Resources", "CodeInspectorRules.wl" }; (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) diff --git a/Scripts/Common.wl b/Scripts/Common.wl index 25a7cbef..88f9d639 100644 --- a/Scripts/Common.wl +++ b/Scripts/Common.wl @@ -38,6 +38,7 @@ $messageHistory = <| |>; $stackHistory = <| |>; $inputFileName = cFile @ Replace[ $InputFileName, "" :> NotebookFileName[ ] ]; $pacletDir = cDir @ DirectoryName[ $inputFileName, 2 ]; +$scriptDir = DirectoryName @ $inputFileName; Internal`AddHandler[ "Message", messageHandler ]; @@ -105,9 +106,59 @@ messageString[ ___ ] := "-- Message text not found --"; (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) -(*Definitions*) -$scriptCommandLine := Replace[ $ScriptCommandLine, { } :> $CommandLine ]; +(*Command Line Arguments*) +$scriptCommandLine := Select[ Flatten @ { Replace[ $ScriptCommandLine, { } :> $CommandLine ] }, StringQ ]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*getBooleanArgument*) +getBooleanArgument // Attributes = { HoldRest }; + +getBooleanArgument[ name_ ] := + getBooleanArgument[ name, False ]; + +getBooleanArgument[ name_String, default_ ] := + getBooleanArgument[ { name, name }, default ]; + +getBooleanArgument[ { short_String, full_String }, default_ ] := Catch[ + Module[ { named, interpreted, res }, + If[ MemberQ[ $scriptCommandLine, "-"<>short | "--"<>full ], Throw[ True, $booleanTag ] ]; + named = getNamedArgument @ full; + If[ ! StringQ @ named, Throw[ default, $booleanTag ] ]; + interpreted = Interpreter[ "Boolean" ][ named ]; + If[ BooleanQ @ interpreted, + interpreted, + res = default; + cicd`ConsoleError @ TemplateApply[ + "The value \"`1`\" specified for \"`2`\" is not a valid boolean value. Using default value: \"`3`\".", + { named, full, res } + ]; + res + ] + ], + $booleanTag +]; +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*getNamedArgument*) +getNamedArgument // Attributes = { HoldRest }; + +getNamedArgument[ name_ ] := + getNamedArgument[ name, Missing[ "NotSpecified" ] ]; + +getNamedArgument[ name_String, default_ ] := + Module[ { arg }, + arg = SelectFirst[ $scriptCommandLine, StringQ @ # && StringStartsQ[ #, "--"<>name<>"=" ] & ]; + If[ StringQ @ arg, + StringDelete[ arg, StartOfString ~~ "--"<>name<>"=" ], + default + ] + ]; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Definitions*) $$ws = WhitespaceCharacter...; $$id = "\"" ~~ Except[ "\"" ].. ~~ "\""; diff --git a/Scripts/Resources/CodeInspectorRules.wl b/Scripts/Resources/CodeInspectorRules.wl new file mode 100644 index 00000000..7c42eca7 --- /dev/null +++ b/Scripts/Resources/CodeInspectorRules.wl @@ -0,0 +1,126 @@ +BeginPackage[ "Wolfram`ChatbookScripts`" ]; + +Needs[ "CodeInspector`" -> "ci`" ]; +Needs[ "CodeParser`" -> "cp`" ]; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Config*) +$inGitHub := $inGitHub = StringQ @ Environment[ "GITHUB_ACTIONS" ]; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Custom Rules*) +CodeInspector`AbstractRules`$DefaultAbstractRules = <| + CodeInspector`AbstractRules`$DefaultAbstractRules, + cp`CallNode[ cp`LeafNode[ Symbol, "Throw", _ ], { _ }, _ ] -> scanSingleArgThrow, + cp`LeafNode[ Symbol, _String? privateContextQ, _ ] -> scanPrivateContext, + cp`LeafNode[ Symbol, _String? globalSymbolQ, _ ] -> scanGlobalSymbol +|>; + +CodeInspector`ConcreteRules`$DefaultConcreteRules = <| + CodeInspector`ConcreteRules`$DefaultConcreteRules, + cp`LeafNode[ + Token`Comment, + _String? (StringStartsQ[ "(*"~~WhitespaceCharacter...~~"FIXME:" ]), + _ + ] -> scanFixMeComment +|>; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*scanSingleArgThrow*) +scanSingleArgThrow // ClearAll; +scanSingleArgThrow[ pos_, ast_ ] := Catch[ + Replace[ + Fold[ walkASTForCatch, ast, pos ], + { + cp`CallNode[ cp`LeafNode[ Symbol, "Throw", _ ], _, as_Association ] :> + ci`InspectionObject[ + "NoSurroundingCatch", + "``Throw`` has no tag or surrounding ``Catch``", + "Error", + <| as, ConfidenceLevel -> 0.9 |> + ], + ___ :> { } + } + ], + $tag +]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*walkASTForCatch*) +walkASTForCatch // ClearAll; + +walkASTForCatch[ cp`CallNode[ cp`LeafNode[ Symbol, "Catch"|"Hold"|"HoldForm"|"HoldComplete", _ ], { _ }, _ ], _ ] := + Throw[ { }, $tag ]; + +walkASTForCatch[ ast_, pos_ ] := + Extract[ ast, pos ]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*scanPrivateContext*) +scanPrivateContext // ClearAll; +scanPrivateContext[ pos_, ast_ ] := + Enclose @ Module[ { node, name, as }, + node = ConfirmMatch[ Extract[ ast, pos ], _[ _, _, __ ], "Node" ]; + name = ConfirmBy[ node[[ 2 ]], StringQ, "Name" ]; + as = ConfirmBy[ node[[ 3 ]], AssociationQ, "Metadata" ]; + ci`InspectionObject[ + "PrivateContextSymbol", + "The symbol ``" <> name <> "`` is in a private context", + "Warning", + <| as, ConfidenceLevel -> 0.9 |> + ] + ]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*privateContextQ*) +privateContextQ // ClearAll; +privateContextQ[ name_String ] /; StringStartsQ[ name, "System`Private`" ] := False; +privateContextQ[ name_String ] := StringContainsQ[ name, __ ~~ ("`Private`"|"`PackagePrivate`") ]; +privateContextQ[ ___ ] := False; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*scanGlobalSymbol*) +scanGlobalSymbol // ClearAll; +scanGlobalSymbol[ pos_, ast_ ] := + Enclose @ Module[ { node, name, as }, + node = ConfirmMatch[ Extract[ ast, pos ], _[ _, _, __ ], "Node" ]; + name = ConfirmBy[ node[[ 2 ]], StringQ, "Name" ]; + as = ConfirmBy[ node[[ 3 ]], AssociationQ, "Metadata" ]; + ci`InspectionObject[ + "GlobalSymbol", + "The symbol ``" <> name <> "`` is in the global context", + "Error", + <| as, ConfidenceLevel -> 0.9 |> + ] + ]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*globalSymbolQ*) +globalSymbolQ // ClearAll; +globalSymbolQ[ name_String ] := StringStartsQ[ name, "Global`" ]; +globalSymbolQ[ ___ ] := False; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*scanFixMeComment*) +scanFixMeComment // ClearAll; + +scanFixMeComment[ pos_, ast_ ] /; $inGitHub := + Enclose @ Module[ { node, comment, as }, + node = ConfirmMatch[ Extract[ ast, pos ], _[ _, _, __ ], "Node" ]; + comment = StringTrim @ StringTrim[ ConfirmBy[ node[[ 2 ]], StringQ, "Comment" ], { "(*", "*)" } ]; + as = ConfirmBy[ node[[ 3 ]], AssociationQ, "Metadata" ]; + ci`InspectionObject[ "FixMeComment", comment, "Remark", <| as, ConfidenceLevel -> 0.9 |> ] + ]; + +scanFixMeComment[ pos_, ast_ ] := { }; + +EndPackage[ ]; \ No newline at end of file diff --git a/Scripts/TestPaclet.wls b/Scripts/TestPaclet.wls index 733fc282..59f9fb0e 100644 --- a/Scripts/TestPaclet.wls +++ b/Scripts/TestPaclet.wls @@ -3,7 +3,6 @@ BeginPackage[ "Wolfram`ChatbookScripts`" ]; If[ ! TrueQ @ $loadedDefinitions, Get @ FileNameJoin @ { DirectoryName @ $InputFileName, "Common.wl" } ]; -Get @ cFile @ FileNameJoin @ { DirectoryName @ $InputFileName, "BuildMX.wls" }; SetOptions[ TestReport, ProgressReporting -> False ]; diff --git a/Tests/Common.wl b/Tests/Common.wl index 5ae54bd5..602498d5 100644 --- a/Tests/Common.wl +++ b/Tests/Common.wl @@ -24,7 +24,7 @@ Off[ General::shdw ]; Off[ PacletInstall::samevers ]; If[ ! PacletObjectQ @ PacletObject[ "Wolfram/PacletCICD" ], - PacletInstall[ "https://github.com/WolframResearch/PacletCICD/releases/download/v0.36.0/Wolfram__PacletCICD-0.36.0.paclet" ] + PacletInstall[ "https://github.com/WolframResearch/PacletCICD/releases/download/v0.36.2/Wolfram__PacletCICD-0.36.2.paclet" ] ]; Needs[ "Wolfram`PacletCICD`" -> "cicd`" ]; @@ -71,6 +71,7 @@ $$rules = (Rule|RuleDelayed)[ _, _ ]..; If[ ! DirectoryQ @ $pacletDirectory, abort[ "Paclet directory ", $pacletDirectory, " does not exist!" ] ]; Quiet @ PacletDirectoryUnload @ $sourceDirectory; PacletDataRebuild[ ]; +cicd`ConsoleNotice @ SequenceForm[ "Loading paclet from ", $pacletDirectory, " for running tests..." ]; PacletDirectoryLoad @ $pacletDirectory; Get[ "Wolfram`Chatbook`" ]; If[ ! MemberQ[ $LoadedFiles, FileNameJoin @ { $pacletDirectory, "Source", "Chatbook", "64Bit", "Chatbook.mx" } ],