Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.21% covered (warning)
86.21%
25 / 29
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
RegexInsertablesSuggester
86.21% covered (warning)
86.21%
25 / 29
66.67% covered (warning)
66.67%
2 / 3
12.38
0.00% covered (danger)
0.00%
0 / 1
 __construct
69.23% covered (warning)
69.23%
9 / 13
0.00% covered (danger)
0.00%
0 / 1
7.05
 getInsertables
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 mapInsertables
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\TranslatorInterface\Insertable;
5
6use InvalidArgumentException;
7
8/**
9 * Regex InsertablesSuggester implementation that can be extended or used
10 * for insertables in message groups
11 * @author Abijeet Patro
12 * @license GPL-2.0-or-later
13 * @since 2020.12
14 */
15class RegexInsertablesSuggester implements InsertablesSuggester {
16    /**
17     * The regex to run on the message. The regex must use named group captures
18     * @var string
19     */
20    protected $regex = null;
21    /**
22     * The named parameter from the regex that should be used for
23     * insertable display.
24     * @var string
25     */
26    protected $display = null;
27    /**
28     * The named parameter from the regex that should be used as pre
29     * @var string
30     */
31    protected $pre = null;
32    /**
33     * The named paramater from the regex that should be used as post
34     * @var string
35     */
36    protected $post = null;
37
38    /**
39     * Constructor function
40     * @param array|string $params If params is specified as a string, it is used as the regex.
41     * Eg: "/\$[a-z0-9]+/". In this case `display` is the first value from the regex match.
42     * `pre` is also the first value from the regex match, `post` is left empty.
43     *
44     * If params is specified as a collection / array, see below for further details.
45     *
46     * Example:
47     *
48     * ```
49     * params:
50     *     regex: "/(?<pre>\[)[^]]+(?<post>\]\([^)]+\))/"
51     *     display: "$pre $post"
52     *     pre: "$pre"
53     *     post: "$post"
54     * ```
55     *
56     * Details:
57     *
58     * $params = [
59     *   'regex' => (string, required) The regex to be used for insertable. Must use named captures.
60     *          When specifying named captures, do not use the $ symbol in the name. In the above
61     *          example, two named captures are used - `pre` and `post`
62     *   'display' => (string) Mandatory value. The display value for the insertable. Named captures
63     *          prefixed with $ are used here.
64     *   'pre' => (string) The pre value for the insertable. Named captures prefixed with $ are used
65     *          here. If not specified, is set to the display value.
66     *   'post' => (string) The post value for the insertable. Named captures prefixed with $ are used
67     *          here. If not specified, defaults to an empty string.
68     * ]
69     */
70    public function __construct( $params ) {
71        if ( is_string( $params ) ) {
72            $this->regex = $params;
73        } elseif ( is_array( $params ) ) {
74            // Validate if the array is in a proper format.
75            $this->regex = $params['regex'] ?? null;
76            $this->display = $params['display'] ?? null;
77            $this->pre = $params['pre'] ?? null;
78            $this->post = $params['post'] ?? null;
79        }
80
81        if ( $this->regex === null ) {
82            throw new InvalidArgumentException(
83                'Invalid configuration for the RegexInsertablesSuggester. Did not find a regex specified'
84            );
85        }
86
87        if ( $this->display !== null && $this->pre === null ) {
88            // if display value is set, and pre value is not set, set the display to pre.
89            // makes the configuration easier.
90            $this->pre = $this->display;
91        }
92    }
93
94    public function getInsertables( string $text ): array {
95        $matches = [];
96        preg_match_all( $this->regex, $text, $matches, PREG_SET_ORDER );
97
98        return array_map( [ $this, 'mapInsertables' ], $matches );
99    }
100
101    protected function mapInsertables( array $match ) {
102        if ( $this->display === null ) {
103            return new Insertable( $match[0], $match[0] );
104        }
105
106        $replacements = [];
107        // add '$' to the other keys for replacement.
108        foreach ( $match as $key => $value ) {
109            if ( !is_int( $key ) ) {
110                $tmpKey = '$' . $key;
111                $replacements[ $tmpKey ] = $value;
112            }
113        }
114
115        $displayVal = strtr( $this->display, $replacements );
116        $preVal = strtr( $this->pre, $replacements );
117        $postVal = '';
118        if ( $this->post !== null ) {
119            $postVal = strtr( $this->post, $replacements );
120        }
121
122        return new Insertable( $displayVal, $preVal, $postVal );
123    }
124}