///|
let crawl_url_option : @admiral.OptionDef[String] = @admiral.string(
"url",
description="Target URL",
env="BW_URL",
config="url",
)
///|
let crawl_html_option : @admiral.OptionDef[String] = @admiral.string(
"html",
description="Path to local HTML file",
env="BW_HTML",
config="html",
)
///|
let crawl_wait_until_option : @admiral.OptionDef[String] = @admiral.string(
"wait-until",
description="Page load strategy",
env="BW_WAIT_UNTIL",
config="wait_until",
)
///|
let crawl_output_option : @admiral.OptionDef[String] = @admiral.string(
"output",
short='o',
description="Output file path",
env="BW_OUTPUT",
config="output",
)
///|
let crawl_limit_option : @admiral.OptionDef[Int] = @admiral.int(
"limit",
description="Max pages to crawl",
env="BW_CRAWL_LIMIT",
config="limit",
)
///|
let crawl_depth_option : @admiral.OptionDef[Int] = @admiral.int(
"depth",
description="Max link depth",
env="BW_CRAWL_DEPTH",
config="depth",
)
///|
let crawl_format_option : @admiral.OptionDef[Array[String]] = @admiral.strings(
"format",
description="Output formats: html,markdown,json",
env="BW_CRAWL_FORMATS",
config="formats",
)
///|
let crawl_id_option : @admiral.OptionDef[String] = @admiral.string(
"id",
description="Crawl job ID",
env="BW_CRAWL_ID",
config="id",
required=true,
)
///|
struct CrawlStartOptionsInput {
account_id : String
api_token : String
url : String?
html : String?
wait_until : String?
output : String?
limit : Int?
depth : Int?
formats : Array[String]?
}
///|
struct CrawlGetOptionsInput {
account_id : String
api_token : String
id : String
output : String?
}
///|
struct CrawlGotoOptions {
waitUntil : String
}
///|
struct CrawlStartBody {
url : String?
html : String?
gotoOptions : CrawlGotoOptions?
limit : Int?
depth : Int?
formats : Array[String]?
}
///|
let crawl_goto_wait_until_lens : @lens.Lens[String] = @lens.root().string(
"waitUntil",
)
///|
let crawl_body_url_lens : @lens.PresenceLens[String] = @lens.root()
.string("url")
.optional()
///|
let crawl_body_html_lens : @lens.PresenceLens[String] = @lens.root()
.string("html")
.optional()
///|
let crawl_body_goto_options_lens : @lens.PresenceLens[Json] = @lens.root()
.json("gotoOptions")
.optional()
///|
let crawl_body_limit_lens : @lens.PresenceLens[Int] = @lens.root()
.int("limit")
.optional()
///|
let crawl_body_depth_lens : @lens.PresenceLens[Int] = @lens.root()
.int("depth")
.optional()
///|
let crawl_body_formats_lens : @lens.PresenceLens[Array[String]] = @lens.root()
.string("formats")
.array()
.optional()
///|
impl ToJson for CrawlGotoOptions with fn to_json(self) -> Json {
let builder = @lens.JsonBuilder::JsonBuilder()
crawl_goto_wait_until_lens.set_or_abort(builder, self.waitUntil)
builder.to_json()
}
///|
extend CrawlGotoOptions with ToJson::{to_json}
///|
impl ToJson for CrawlStartBody with fn to_json(self) -> Json {
let builder = @lens.JsonBuilder::JsonBuilder()
crawl_body_url_lens.set_or_abort(builder, self.url)
crawl_body_html_lens.set_or_abort(builder, self.html)
crawl_body_goto_options_lens.set_or_abort(
builder,
self.gotoOptions.map(fn(value) { value.to_json() }),
)
crawl_body_limit_lens.set_or_abort(builder, self.limit)
crawl_body_depth_lens.set_or_abort(builder, self.depth)
crawl_body_formats_lens.set_or_abort(builder, self.formats)
builder.to_json()
}
///|
extend CrawlStartBody with ToJson::{to_json}
///|
fn crawl_command_def(
run_start : async (@admiral.Context) -> Unit,
run_status : async (@admiral.Context) -> Unit,
run_results : async (@admiral.Context) -> Unit,
) -> @admiral.CommandDef {
@admiral.CommandDef::CommandDef(
name="crawl",
description="Manage async crawl jobs",
subcommands=[
@admiral.CommandDef::CommandDef(
name="start",
description="Start a crawl job",
options=[
account_id_option, api_token_option, crawl_url_option, crawl_html_option,
crawl_wait_until_option, crawl_output_option, crawl_limit_option, crawl_depth_option,
crawl_format_option,
],
run=Some(run_start),
),
@admiral.CommandDef::CommandDef(
name="status",
description="Check crawl job status",
options=[account_id_option, api_token_option, crawl_id_option],
run=Some(run_status),
),
@admiral.CommandDef::CommandDef(
name="results",
description="Retrieve crawl results",
options=[
account_id_option, api_token_option, crawl_id_option, crawl_output_option,
],
run=Some(run_results),
),
],
)
}
///|
fn split_csv(value : String) -> Array[String] {
value.split(",").map(fn(item) { item.to_owned() }).collect()
}
///|
async fn crawl_start_options(
context : @admiral.Context,
) -> CrawlStartOptionsInput {
let format_values = context.get_strings(crawl_format_option)
let formats : Array[String] = format_values
.iter()
.flat_map(fn(value) { split_csv(value).iter() })
.collect()
{
account_id: context.get_string_required(account_id_option),
api_token: context.get_string_required(api_token_option),
url: context.get_string(crawl_url_option),
html: read_optional_file(context.get_string(crawl_html_option)),
wait_until: context.get_string(crawl_wait_until_option),
output: context.get_string(crawl_output_option),
limit: context.get_int(crawl_limit_option),
depth: context.get_int(crawl_depth_option),
formats: if formats.is_empty() {
None
} else {
Some(formats)
},
}
}
///|
fn CrawlStartBody::from_options(
options : CrawlStartOptionsInput,
) -> CrawlStartBody raise {
required_body_source(options.url, options.html)
{
url: options.url,
html: options.html,
gotoOptions: match options.wait_until {
Some(value) => Some({ waitUntil: value })
None => None
},
limit: options.limit,
depth: options.depth,
formats: options.formats,
}
}
///|
async fn run_crawl_start(context : @admiral.Context) -> Unit {
let options = crawl_start_options(context)
let body = CrawlStartBody::from_options(options)
let (code, reason, data) = post_json(
options.account_id,
options.api_token,
"/crawl",
body.to_json(),
)
ensure_success(code, reason, data)
write_text_or_stdout(options.output, data.text())
}
///|
fn crawl_get_options(context : @admiral.Context) -> CrawlGetOptionsInput raise {
{
account_id: context.get_string_required(account_id_option),
api_token: context.get_string_required(api_token_option),
id: context.get_string_required(crawl_id_option),
output: context.get_string(crawl_output_option),
}
}
///|
async fn run_crawl_get(
context : @admiral.Context,
suffix : String,
output : Bool,
) -> Unit {
let options = crawl_get_options(context)
let (code, reason, data) = get_json(
options.account_id,
options.api_token,
"/crawl/\{options.id}\{suffix}",
)
ensure_success(code, reason, data)
if output {
write_text_or_stdout(options.output, data.text())
} else {
println(data.text())
}
}
///|
async fn run_crawl_status(context : @admiral.Context) -> Unit {
run_crawl_get(context, "", false)
}
///|
async fn run_crawl_results(context : @admiral.Context) -> Unit {
run_crawl_get(context, "/results", true)
}