///|
/// Lightweight view of a MoonBit project directory.
pub(all) struct ProjectSnapshot {
root : String
files : Map[String, String]
dirs : Array[String]
} derive(Debug, Eq)
///|
pub fn ProjectSnapshot::new(
root : String,
files : Map[String, String],
dirs : Array[String],
) -> ProjectSnapshot {
{ root, files, dirs }
}
///|
pub fn ProjectSnapshot::has_file(self : ProjectSnapshot, path : String) -> Bool {
self.files.contains(path)
}
///|
pub fn ProjectSnapshot::has_dir(self : ProjectSnapshot, path : String) -> Bool {
self.dirs.contains(path)
}
///|
pub fn ProjectSnapshot::read_file(
self : ProjectSnapshot,
path : String,
) -> String? {
self.files.get(path)
}
///|
pub fn ProjectSnapshot::moon_mod_field(
self : ProjectSnapshot,
key : String,
) -> String? {
match self.read_file("moon.mod") {
None => None
Some(content) => extract_assignment(content, key)
}
}
///|
pub fn ProjectSnapshot::has_any_test_file(self : ProjectSnapshot) -> Bool {
self.files
.keys()
.any(fn(name) {
name.has_suffix("_test.mbt") ||
name.has_suffix("_wbtest.mbt") ||
name.contains("/test/") ||
name.contains("\\test\\")
})
}
///|
pub fn ProjectSnapshot::has_workflow(self : ProjectSnapshot) -> Bool {
self.has_dir(".github/workflows") ||
self.files
.keys()
.any(fn(name) {
name.has_prefix(".github/workflows/") ||
name.has_prefix(".github\\workflows\\")
})
}
///|
pub fn ProjectSnapshot::gitignore_mentions(
self : ProjectSnapshot,
expected : String,
) -> Bool {
match self.read_file(".gitignore") {
Some(content) => content.contains(expected)
None => false
}
}
///|
pub fn ProjectSnapshot::license_mentions(
self : ProjectSnapshot,
expected : String,
) -> Bool {
match self.read_file("LICENSE") {
Some(content) => content.contains(expected)
None => false
}
}
///|
pub fn ProjectSnapshot::has_readme_file(self : ProjectSnapshot) -> Bool {
self.has_file("README.md") || self.has_file("README.mbt.md")
}
///|
pub fn ProjectSnapshot::has_exact_file(
self : ProjectSnapshot,
path : String,
) -> Bool {
self.has_file(path)
}
///|
pub fn ProjectSnapshot::has_example_content(self : ProjectSnapshot) -> Bool {
self.files
.keys()
.any(fn(name) {
name.has_prefix("examples/") &&
!name.has_suffix(".keep") &&
(
name.has_suffix(".mbt") ||
name.has_suffix(".md") ||
name.has_suffix("moon.pkg")
)
})
}
///|
pub fn ProjectSnapshot::workflow_mentions(
self : ProjectSnapshot,
needle : String,
) -> Bool {
self.files
.keys()
.any(fn(name) {
if name.has_prefix(".github/workflows/") {
match self.read_file(name) {
Some(content) => content.contains(needle)
None => false
}
} else {
false
}
})
}
///|
pub fn ProjectSnapshot::field_mentions(
self : ProjectSnapshot,
key : String,
needle : String,
) -> Bool {
match self.read_file("moon.mod") {
Some(content) => {
for line in content.split("\n") {
let text = line.trim().to_owned()
if !text.has_prefix("//") &&
text.has_prefix(key) &&
text.contains(needle) {
return true
}
}
false
}
None => false
}
}
///|
pub fn ProjectSnapshot::readme_mentions(
self : ProjectSnapshot,
needle : String,
) -> Bool {
match self.read_file("README.md") {
Some(content) => content.contains(needle)
None => false
}
}
///|
fn extract_assignment(content : String, key : String) -> String? {
for line in content.split("\n") {
let text = line.trim().to_owned()
if !text.has_prefix("//") && text.has_prefix(key) && text.contains("=") {
let pieces = text.split("\"").to_array()
if pieces.length() >= 3 {
return Some(pieces[1].to_owned())
}
}
}
None
}
///|
fn join_path(root : String, rel : String) -> String {
if root.has_suffix("/") || root.has_suffix("\\") {
root + rel
} else {
root + "/" + rel
}
}
///|
fn add_file_if_exists(
files : Map[String, String],
root : String,
rel : String,
) -> Unit raise {
let path = join_path(root, rel)
if @fs.path_exists(path) && @fs.is_file(path) {
files[rel] = @fs.read_file_to_string(path)
}
}
///|
fn add_dir_if_exists(
dirs : Array[String],
root : String,
rel : String,
) -> Unit raise {
let path = join_path(root, rel)
if @fs.path_exists(path) && @fs.is_dir(path) {
dirs.push(rel)
}
}
///|
fn add_dir_files_if_exists(
files : Map[String, String],
root : String,
rel : String,
) -> Unit raise {
let dir_path = join_path(root, rel)
if @fs.path_exists(dir_path) && @fs.is_dir(dir_path) {
for entry in @fs.read_dir(dir_path) {
let child_rel = rel + "/" + entry
let child_path = join_path(root, child_rel)
if @fs.path_exists(child_path) && @fs.is_file(child_path) {
add_file_if_exists(files, root, child_rel)
}
}
}
}
///|
/// Load the project files Moon Doctor needs for a first-pass health check.
pub fn load_project(root : String) -> ProjectSnapshot raise {
let files : Map[String, String] = {}
let dirs : Array[String] = []
for
rel in [
"moon.mod", "moon.pkg", "README.md", "README.mbt.md", "LICENSE", ".gitignore",
] {
add_file_if_exists(files, root, rel)
}
for
rel in ["examples", ".github", ".github/workflows", "cmd", "test", "tests"] {
add_dir_if_exists(dirs, root, rel)
}
if @fs.path_exists(root) && @fs.is_dir(root) {
for entry in @fs.read_dir(root) {
if entry.has_suffix("_test.mbt") || entry.has_suffix("_wbtest.mbt") {
add_file_if_exists(files, root, entry)
}
}
}
if dirs.contains(".github/workflows") {
add_dir_files_if_exists(files, root, ".github/workflows")
}
if dirs.contains("examples") {
add_dir_files_if_exists(files, root, "examples")
}
if dirs.contains("cmd") {
add_dir_files_if_exists(files, root, "cmd")
let cmd_root = join_path(root, "cmd")
for entry in @fs.read_dir(cmd_root) {
let child_rel = "cmd/" + entry
let child_path = join_path(root, child_rel)
if @fs.path_exists(child_path) && @fs.is_dir(child_path) {
add_dir_files_if_exists(files, root, child_rel)
}
}
}
ProjectSnapshot::new(root, files, dirs)
}