///|
pub enum DocError {
InvalidPageName(String)
PageNotFound(String)
EmptyBody
}
///|
fn docs_dir(root : String) -> String {
root + "/.bit/docs"
}
///|
fn normalize_page_name(raw : String) -> String {
if raw.has_suffix(".md") && raw.length() > 3 {
return String::unsafe_substring(raw, start=0, end=raw.length() - 3)
}
raw
}
///|
fn is_valid_page_name(page_name : String) -> Bool {
let page = normalize_page_name(page_name)
if page.length() == 0 {
return false
}
if page == "." || page == ".." {
return false
}
if page.has_prefix("/") {
return false
}
for token in ["\\", "\n", "\r", "\t"] {
if page.contains(token) {
return false
}
}
for view in page.split("/") {
let seg = view.to_owned()
if seg.length() == 0 || seg == "." || seg == ".." {
return false
}
}
true
}
///|
pub fn list_pages(
rfs : &@bit.RepoFileSystem,
root : String,
prefix : String?,
) -> Array[String] {
let pages : Array[String] = []
collect_pages(rfs, docs_dir(root), "", prefix, pages)
let sorted : Array[String] = []
for page in pages {
let mut inserted = false
for i in 0.. Result[String, DocError] raise @bit.GitError {
let page = normalize_page_name(page_name)
let path = match resolve_page_path(root, page_name) {
Some(path) => path
None => return Err(InvalidPageName(page_name))
}
if !rfs.is_file(path) {
return Err(PageNotFound(page))
}
Ok(decode_bytes(rfs.read_file(path)))
}
///|
pub fn put_page(
wfs : &@bit.FileSystem,
root : String,
page_name : String,
body : String,
) -> Result[String, DocError] raise @bit.GitError {
if body.length() == 0 {
return Err(EmptyBody)
}
let page = normalize_page_name(page_name)
let path = match resolve_page_path(root, page_name) {
Some(path) => path
None => return Err(InvalidPageName(page_name))
}
wfs.mkdir_p(parent_dir(path))
wfs.write_string(path, body + "\n")
Ok(page)
}
///|
pub fn append_page(
wfs : &@bit.FileSystem,
rfs : &@bit.RepoFileSystem,
root : String,
page_name : String,
message : String,
timestamp : Int64,
) -> Result[String, DocError] raise @bit.GitError {
if message.length() == 0 {
return Err(EmptyBody)
}
let page = normalize_page_name(page_name)
let path = match resolve_page_path(root, page_name) {
Some(path) => path
None => return Err(InvalidPageName(page_name))
}
wfs.mkdir_p(parent_dir(path))
let mut content = if rfs.is_file(path) {
decode_bytes(rfs.read_file(path))
} else {
""
}
if content.length() > 0 && !content.has_suffix("\n") {
content = content + "\n"
}
content = content + "- [\{timestamp}] \{message}\n"
wfs.write_string(path, content)
Ok(page)
}
///|
pub fn remove_page(
wfs : &@bit.FileSystem,
rfs : &@bit.RepoFileSystem,
root : String,
page_name : String,
) -> Result[String, DocError] raise @bit.GitError {
let page = normalize_page_name(page_name)
let path = match resolve_page_path(root, page_name) {
Some(path) => path
None => return Err(InvalidPageName(page_name))
}
if !rfs.is_file(path) {
return Err(PageNotFound(page))
}
wfs.remove_file(path)
Ok(page)
}
///|
fn resolve_page_path(root : String, page_name : String) -> String? {
let page = normalize_page_name(page_name)
if !is_valid_page_name(page) {
return None
}
Some(docs_dir(root) + "/" + page + ".md")
}
///|
fn collect_pages(
rfs : &@bit.RepoFileSystem,
abs_dir : String,
rel_dir : String,
prefix : String?,
out : Array[String],
) -> Unit {
if !rfs.is_dir(abs_dir) {
return
}
let entries = rfs.readdir(abs_dir) catch { _ => [] }
for entry in entries {
if entry == "." || entry == ".." {
continue
}
let abs_path = abs_dir + "/" + entry
let rel_path = if rel_dir.length() == 0 {
entry
} else {
rel_dir + "/" + entry
}
if rfs.is_dir(abs_path) {
collect_pages(rfs, abs_path, rel_path, prefix, out)
continue
}
if !entry.has_suffix(".md") {
continue
}
let page = String::unsafe_substring(
rel_path,
start=0,
end=rel_path.length() - 3,
)
match prefix {
Some(p) if !page.has_prefix(p) => continue
_ => ()
}
out.push(page)
}
}
///|
fn decode_bytes(data : Bytes) -> String {
@encoding.decoder(@encoding.Encoding::UTF8).decode_lossy(data[:])
}
///|
fn parent_dir(path : String) -> String {
if path.length() == 0 || path == "/" {
return "/"
}
let sep = @path.sep.to_string()
match path.rev_find(sep) {
None => ""
Some(idx) =>
if idx == 0 {
sep
} else {
String::unsafe_substring(path, start=0, end=idx)
}
}
}
///|
fn page_lex_less(a : String, b : String) -> Bool {
let min_len = if a.length() < b.length() { a.length() } else { b.length() }
for i in 0.. rhs {
return false
}
}
a.length() < b.length()
}