|
| 1 | +import Lake.CLI.Main |
| 2 | + |
| 3 | +/-! |
| 4 | +A simple script that inserts `module` and `@[expose] public section` into un-modulized files and |
| 5 | +bumps their imports to `public`. |
| 6 | +-/ |
| 7 | + |
| 8 | +open Lean Parser.Module |
| 9 | + |
| 10 | +def main (args : List String) : IO Unit := do |
| 11 | + initSearchPath (← findSysroot) |
| 12 | + let mods := args.toArray.map (·.toName) |
| 13 | + |
| 14 | + -- Determine default module(s) to run modulize on |
| 15 | + let defaultTargetModules : Array Name ← try |
| 16 | + let (elanInstall?, leanInstall?, lakeInstall?) ← Lake.findInstall? |
| 17 | + let config ← Lake.MonadError.runEIO <| Lake.mkLoadConfig { elanInstall?, leanInstall?, lakeInstall? } |
| 18 | + let some workspace ← Lake.loadWorkspace config |>.toBaseIO |
| 19 | + | throw <| IO.userError "failed to load Lake workspace" |
| 20 | + let defaultTargetModules := workspace.root.defaultTargets.flatMap fun target => |
| 21 | + if let some lib := workspace.root.findLeanLib? target then |
| 22 | + lib.roots |
| 23 | + else if let some exe := workspace.root.findLeanExe? target then |
| 24 | + #[exe.config.root] |
| 25 | + else |
| 26 | + #[] |
| 27 | + pure defaultTargetModules |
| 28 | + catch _ => |
| 29 | + pure #[] |
| 30 | + |
| 31 | + -- the list of root modules |
| 32 | + let mods := if mods.isEmpty then defaultTargetModules else mods |
| 33 | + -- Only submodules of `pkg` will be edited or have info reported on them |
| 34 | + let pkg := mods[0]!.components.head! |
| 35 | + |
| 36 | + -- Load all the modules |
| 37 | + let imps := mods.map ({ module := · }) |
| 38 | + let env ← importModules imps {} |
| 39 | + |
| 40 | + let srcSearchPath ← getSrcSearchPath |
| 41 | + |
| 42 | + for mod in env.header.moduleNames do |
| 43 | + if !pkg.isPrefixOf mod then |
| 44 | + continue |
| 45 | + |
| 46 | + -- Parse the input file |
| 47 | + let some path ← srcSearchPath.findModuleWithExt "lean" mod |
| 48 | + | throw <| .userError "error: failed to find source file for {mod}" |
| 49 | + let mut text ← IO.FS.readFile path |
| 50 | + let inputCtx := Parser.mkInputContext text path.toString |
| 51 | + let (header, parserState, msgs) ← Parser.parseHeader inputCtx |
| 52 | + if !msgs.toList.isEmpty then -- skip this file if there are parse errors |
| 53 | + msgs.forM fun msg => msg.toString >>= IO.println |
| 54 | + throw <| .userError "parse errors in file" |
| 55 | + let `(header| $[module%$moduleTk?]? $imps:import*) := header |
| 56 | + | throw <| .userError s!"unexpected header syntax of {path}" |
| 57 | + if moduleTk?.isSome then |
| 58 | + continue |
| 59 | + |
| 60 | + let looksMeta := mod.components.any (· ∈ [`Tactic, `Linter]) |
| 61 | + |
| 62 | + -- initial whitespace if empty header |
| 63 | + let startPos := header.raw.getPos? |>.getD parserState.pos |
| 64 | + |
| 65 | + -- insert section if any trailing text |
| 66 | + if header.raw.getTrailingTailPos?.all (· < text.endPos) then |
| 67 | + let insertPos := header.raw.getTailPos? |>.getD startPos -- empty header |
| 68 | + let mut sec := if looksMeta then |
| 69 | + "public meta section" |
| 70 | + else |
| 71 | + "@[expose] public section" |
| 72 | + if !imps.isEmpty then |
| 73 | + sec := "\n\n" ++ sec |
| 74 | + if header.raw.getTailPos?.isNone then |
| 75 | + sec := sec ++ "\n\n" |
| 76 | + text := text.extract 0 insertPos ++ sec ++ text.extract insertPos text.endPos |
| 77 | + |
| 78 | + -- prepend each import with `public ` |
| 79 | + for imp in imps.reverse do |
| 80 | + let insertPos := imp.raw.getPos?.get! |
| 81 | + let prfx := if looksMeta then "public meta " else "public " |
| 82 | + text := text.extract 0 insertPos ++ prfx ++ text.extract insertPos text.endPos |
| 83 | + |
| 84 | + -- insert `module` header |
| 85 | + let mut initText := text.extract 0 startPos |
| 86 | + if !initText.trim.isEmpty then |
| 87 | + -- If there is a header comment, preserve it and put `module` in the line after |
| 88 | + initText := initText.trimRight ++ "\n" |
| 89 | + text := initText ++ "module\n\n" ++ text.extract startPos text.endPos |
| 90 | + |
| 91 | + IO.FS.writeFile path text |
0 commit comments