Pure MoonBit robots.txt parser, crawler access decision, and policy audit library
moon add JQ-ai-nb/robopolicyfn main {
let text = "User-agent: *\nDisallow: /admin/\nAllow: /admin/help"
let policy = @robopolicy.parse(text)
println(policy.is_allowed("ExampleBot", "/admin/panel"))
println(policy.is_allowed("ExampleBot", "/admin/help"))
}false
truemoon fmt --check
moon info
moon check --target all --deny-warn
moon build
moon test --target wasm --deny-warn
moon test --target wasm-gc --deny-warn
moon run cmd/mainmoon publish --dry-runmoon fmt --check
moon info
moon check --target all --deny-warn
moon build
moon test --target wasm --deny-warn
moon test --target wasm-gc --deny-warn
moon run cmd/main
moon publish --dry-runpub(all) struct AgentMatrix {
rows : Array[AgentMatrixRow]
agent_count : Int
path_count : Int
allowed_count : Int
blocked_count : Int
} derive(Eq, Debug)pub(all) struct AuditTarget {
label : String
user_agent : String
url_or_path : String
expected_allowed : Bool?
importance : TargetImportance
note : String
} derive(Eq, Debug)pub(all) struct ExportOptions {
detail : ExportDetailLevel
include_parse_issues : Bool
include_rule_profiles : Bool
include_sitemaps : Bool
include_targets : Bool
include_notes : Bool
} derive(Eq, Debug)pub(all) struct Finding {
code : String
severity : FindingSeverity
message : String
detail : String
} derive(Eq, Debug)pub(all) struct GroupRuleSummary {
agents : Array[String]
group_line : Int
rule_count : Int
allow_count : Int
disallow_count : Int
wildcard_rule_count : Int
anchored_rule_count : Int
root_block_count : Int
empty_rule_count : Int
max_depth : Int
max_literal_weight : Int
crawl_delay : Int?
has_sitemap_hint : Bool
notes : Array[String]
} derive(Eq, Debug)pub(all) struct PathReferenceParts {
source : PathSourceKind
path : String
query : String?
fragment : String?
had_query : Bool
had_fragment : Bool
} derive(Eq, Debug)pub(all) struct PathSnapshot {
original : String
normalized : String
source : PathSourceKind
query : String?
fragment : String?
had_query : Bool
had_fragment : Bool
collapsed_slashes : Bool
removed_dot_segments : Bool
root_fallback : Bool
} derive(Eq, Debug)pub(all) struct PolicyProfile {
group_summaries : Array[GroupRuleSummary]
total_rules : Int
total_allows : Int
total_disallows : Int
total_wildcards : Int
total_anchors : Int
total_root_blocks : Int
highest_depth : Int
highest_weight : Int
has_wildcard_group : Bool
has_specific_group : Bool
has_sitemap : Bool
issue_count : Int
notes : Array[String]
} derive(Eq, Debug)pub(all) struct RobotsPolicy {
groups : Array[AgentGroup]
sitemaps : Array[String]
host : String?
issues : Array[ParseIssue]
} derive(Eq, Debug)fn RobotsPolicy::audit_targets(self : RobotsPolicy, targets : Array[AuditTarget]) -> SiteAuditReportfn RobotsPolicy::decide(self : RobotsPolicy, user_agent : StringView, url_or_path : StringView) -> AccessDecisionfn RobotsPolicy::is_allowed(self : RobotsPolicy, user_agent : StringView, url_or_path : StringView) -> Boolpub(all) struct RulePairAnalysis {
first : RuleProfile
second : RuleProfile
relation : RuleRelation
message : String
same_action : Bool
same_pattern : Bool
first_matches_second_prefix : Bool
second_matches_first_prefix : Bool
} derive(Eq, Debug)pub(all) struct RuleProfile {
rule : Rule
kind : RulePatternKind
impact : RuleImpact
risk : RuleRiskBand
normalized_pattern : String
literal_weight : Int
depth : Int
wildcard_count : Int
has_anchor : Bool
has_query_like_text : Bool
has_fragment_like_text : Bool
is_root_rule : Bool
is_empty_pattern : Bool
is_directory_rule : Bool
is_file_rule : Bool
is_wildcard_rule : Bool
prefix : String
extension : String?
explanation : String
notes : Array[String]
} derive(Eq, Debug)pub(all) struct SiteAuditReport {
policy : RobotsPolicy
targets : Array[AuditTarget]
results : Array[TargetAuditResult]
summary : SiteAuditSummary
rule_profile : PolicyProfile
} derive(Eq, Debug)pub(all) struct SiteAuditSummary {
total_targets : Int
allowed_targets : Int
blocked_targets : Int
expected_targets : Int
matched_expectations : Int
mismatched_expectations : Int
critical_targets : Int
critical_mismatches : Int
high_risk_targets : Int
normalized_targets : Int
query_targets : Int
fragment_targets : Int
unique_agents : Int
coverage_band : CoverageBand
score : Int
notes : Array[String]
} derive(Eq, Debug)pub(all) struct SitemapEntry {
raw : String
scheme : SitemapScheme
host : String
path : String
normalized_path : String
format : SitemapFormat
secure : Bool
absolute : Bool
query : String?
fragment : String?
depth : Int
issue_count : Int
issues : Array[SitemapIssue]
} derive(Eq, Debug)pub(all) struct SitemapIssue {
kind : SitemapIssueKind
sitemap : String
message : String
} derive(Eq, Debug)fn SitemapSummary::has_cross_host_entries(self : SitemapSummary, expected_host : StringView) -> Boolpub(all) struct TargetAuditResult {
target : AuditTarget
decision : AccessDecision
snapshot : PathSnapshot
expectation_status : ExpectationStatus
matched_expected : Bool
importance_score : Int
risk_points : Int
tags : Array[String]
} derive(Eq, Debug)fn build_agent_matrix(policy : RobotsPolicy, user_agents : Array[String], paths : Array[String]) -> AgentMatrixfn critical_private_target(label : StringView, user_agent : StringView, url_or_path : StringView) -> AuditTargetfn decision_trace_text(policy : RobotsPolicy, user_agent : StringView, url_or_path : StringView) -> Stringfn expected_target(label : StringView, user_agent : StringView, url_or_path : StringView, expected_allowed : Bool, importance : TargetImportance, note : StringView) -> AuditTargetfn path_contains_segment(path : StringView, segment : StringView) -> Boolfn path_starts_with_segment(path : StringView, segment : StringView) -> BoolPure MoonBit robots.txt parser, crawler access decision, and policy audit library