///|
fn json_error(message : String) -> SourceMapError {
SourceMapError::At(0, message)
}
///|
fn required_field(
object : Map[String, Json],
name : String,
) -> Json raise SourceMapError {
match object.get(name) {
Some(value) => value
None => raise json_error("missing required field '" + name + "'")
}
}
///|
fn required_string(
object : Map[String, Json],
name : String,
) -> String raise SourceMapError {
match required_field(object, name) {
Json::String(value) => value
_ => raise json_error("field '" + name + "' must be a string")
}
}
///|
fn optional_string(
object : Map[String, Json],
name : String,
) -> String? raise SourceMapError {
match object.get(name) {
None => None
Some(Json::String(value)) => Some(value)
Some(_) => raise json_error("field '" + name + "' must be a string")
}
}
///|
fn string_array(
value : Json,
name : String,
) -> Array[String] raise SourceMapError {
match value {
Json::Array(values) => {
let result : Array[String] = []
for index, value in values {
match value {
Json::String(item) => result.push(item)
_ =>
raise json_error(
"field '" +
name +
"' item " +
index.to_string() +
" must be a string",
)
}
}
result
}
_ => raise json_error("field '" + name + "' must be an array")
}
}
///|
fn optional_sources_content(
object : Map[String, Json],
) -> Array[String?] raise SourceMapError {
match object.get("sourcesContent") {
None => []
Some(Json::Array(values)) => {
let result : Array[String?] = []
for index, value in values {
match value {
Json::String(content) => result.push(Some(content))
Json::Null => result.push(None)
_ =>
raise json_error(
"field 'sourcesContent' item " +
index.to_string() +
" must be a string or null",
)
}
}
result
}
Some(_) => raise json_error("field 'sourcesContent' must be an array")
}
}
///|
/// Parse a regular (non-indexed) Source Map v3 JSON document.
pub fn parse_json(input : String) -> SourceMap raise SourceMapError {
let document = @json.parse(input) catch {
error => raise json_error("invalid JSON: " + error.to_string())
}
let object = match document {
Json::Object(value) => value
_ => raise json_error("Source Map JSON root must be an object")
}
if object.get("sections") is Some(_) {
raise json_error("Index Map 'sections' are not supported")
}
match required_field(object, "version") {
Json::Number(version, ..) if version == 3.0 => ()
Json::Number(_, ..) => raise json_error("field 'version' must equal 3")
_ => raise json_error("field 'version' must be the number 3")
}
let sources = string_array(required_field(object, "sources"), "sources")
let names = string_array(required_field(object, "names"), "names")
let encoded = required_string(object, "mappings")
let file = optional_string(object, "file")
let source_root = optional_string(object, "sourceRoot")
let sources_content = optional_sources_content(object)
let decoded = parse_mappings(encoded, sources, names)
SourceMap::make(
decoded.sources,
decoded.names,
decoded.mappings,
file~,
source_root~,
sources_content~,
)
}
///|
fn strings_to_json(values : Array[String]) -> Json {
Json::array(values.map(value => Json::string(value)))
}
///|
fn sources_content_to_json(values : Array[String?]) -> Json {
Json::array(
values.map(value => {
match value {
Some(content) => Json::string(content)
None => Json::null()
}
}),
)
}
///|
/// Serialize a regular Source Map v3 document as compact or indented JSON.
pub fn SourceMap::to_json(self : SourceMap, pretty? : Bool = false) -> String {
let object : Map[String, Json] = Map([])
object.set("version", Json::number(3.0, repr="3"))
object.set("sources", strings_to_json(self.sources))
object.set("names", strings_to_json(self.names))
object.set("mappings", Json::string(self.encode_mappings()))
match self.file {
Some(file) => object.set("file", Json::string(file))
None => ()
}
match self.source_root {
Some(root) => object.set("sourceRoot", Json::string(root))
None => ()
}
if self.sources_content.length() > 0 {
object.set("sourcesContent", sources_content_to_json(self.sources_content))
}
let document = Json::object(object)
if pretty {
document.stringify(indent=2)
} else {
document.stringify()
}
}