File size: 2,835 Bytes
f51c224
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<?php
defined( 'ABSPATH' ) || exit;

class SA_Orch_Asterion {

    /**
     * Root directory for the Asterion ledger.
     * Default: WP_CONTENT_DIR/sa-asterion. Filterable.
     */
    public static function root() {
        $default = WP_CONTENT_DIR . '/sa-asterion';
        return apply_filters( 'sa_orch_asterion_root', $default );
    }

    /**
     * Asterion-first write. The .md file is the canonical record.
     * Returns relative path (e.g. "arbitration/<hash>.md") or WP_Error.
     */
    public static function write( $kind, $hash, array $frontmatter, $body ) {
        $root = self::root();
        if ( ! is_dir( $root ) && ! wp_mkdir_p( $root ) ) {
            return new WP_Error( 'asterion_mkdir_failed', "Could not create root: $root" );
        }

        $kind_safe = sanitize_file_name( $kind );
        $hash_safe = sanitize_file_name( $hash );

        $kind_dir = $root . '/' . $kind_safe;
        if ( ! is_dir( $kind_dir ) && ! wp_mkdir_p( $kind_dir ) ) {
            return new WP_Error( 'asterion_mkdir_failed', "Could not create kind dir: $kind_dir" );
        }

        $rel = $kind_safe . '/' . $hash_safe . '.md';
        $abs = $root . '/' . $rel;

        if ( file_exists( $abs ) ) {
            return new WP_Error( 'asterion_collision', "Ledger entry already exists: $rel" );
        }

        $contents = "---\n" . self::build_frontmatter( $frontmatter ) . "---\n\n" . $body . "\n";

        $written = @file_put_contents( $abs, $contents, LOCK_EX );
        if ( $written === false ) {
            return new WP_Error( 'asterion_write_failed', "Could not write $abs" );
        }

        return $rel;
    }

    private static function build_frontmatter( array $fm ) {
        $out = '';
        foreach ( $fm as $k => $v ) {
            if ( is_array( $v ) ) {
                if ( empty( $v ) ) {
                    $out .= "{$k}: []\n";
                } else {
                    $out .= "{$k}:\n";
                    foreach ( $v as $item ) {
                        $out .= "  - " . self::yaml_scalar( $item ) . "\n";
                    }
                }
            } else {
                $out .= "{$k}: " . self::yaml_scalar( $v ) . "\n";
            }
        }
        return $out;
    }

    private static function yaml_scalar( $v ) {
        if ( $v === null )    return '~';
        if ( is_bool( $v ) )  return $v ? 'true' : 'false';
        if ( is_int( $v ) )   return (string) $v;
        if ( is_float( $v ) ) return (string) $v;

        $s = (string) $v;
        // Quote strings that contain YAML-special chars or whitespace edges
        if ( $s === '' || preg_match( '/[:#\-\?&\*\!\|>\'"%@`\{\}\[\],\n]/', $s ) || $s !== trim( $s ) ) {
            return '"' . str_replace( [ '\\', '"' ], [ '\\\\', '\\"' ], $s ) . '"';
        }
        return $s;
    }
}