<commit_before>import { inspect } from 'util';

import { Answerable } from '../screenplay/Answerable';
import { Question } from '../screenplay/Question';

interface InspectedConfig {
    inline: boolean;
    markQuestions?: boolean;
}

/**
 * @desc
 *  Provides a human-readable description of the {@link Answerable<T>}.
 *  Similar to {@link util~inspect}.
 *
 * @public
 * @param {Answerable<any>} value
 * @param config
 *  - inline - Return a single-line string instead of the default potentially multi-line description
 *  - markQuestions - Surround the description of async values, such as Promises and Questions with <<value>>
 * @return {string}
 */
export function inspected(value: Answerable<any>, config?: InspectedConfig): string {

    const { inline, markQuestions } = { inline: false, markQuestions: false, ...config };

    if (! isDefined(value)) {
        return inspect(value);
    }

    if (Array.isArray(value)) {
        const indentation   = inline ? '' : '  ';
        const separator     = inline ? ' ' : '\n';

        const inspectedItem = (item: unknown, index: number) =>
            [
                indentation,
                inspected(item, { inline, markQuestions: true }),
                index < value.length - 1 ? ',' : ''
            ].join('')

        return [
            '[',
            ...value.map(inspectedItem),
            ']',
        ].join(separator);
    }

    if (isAPromise(value)) {
        return mark('Promise', true);
    }

    if (Question.isAQuestion(value)) {
        return mark(value.toString(), markQuestions);
    }

    if (isADate(value)) {
        return value.toISOString();
    }

    if (hasItsOwnToString(value)) {
        return value.toString();
    }

    if (isInspectable(value)) {
        return value.inspect();
    }

    if (isAFunction(value)) {
        return hasName(value)
            ? value.name
            : mark(`Function`, true);
    }

    if (! hasCustomInspectionFunction(value) && isPlainObject(value) && isSerialisableAsJSON(value)) {
        const indentation = inline ? 0 : 4;

        return JSON.stringify(value, undefined, indentation);
    }

    return inspect(value, { breakLength: Number.POSITIVE_INFINITY, compact: ! inline, sorted: false  });
}

function mark(value: string, markValue: boolean): string {
    const [left, right] = markValue && ! value.startsWith('<<')
        ? [ '<<', '>>' ]
        : ['', ''];

    return [ left, value, right ].join('');
}

/**
 * @desc
 * Checks if the value is defined
 *
 * @private
 * @param {Answerable<any>} v
 */
function isDefined(v: Answerable<any>) {
    return !! v;
}
<commit_msg>refactor(core): extracted a function to simplify the implementation<commit_after>import { inspect } from 'util';

import { Answerable } from '../screenplay/Answerable';
import { Question } from '../screenplay/Question';

interface InspectedConfig {
    inline: boolean;
    markQuestions?: boolean;
}

/**
 * @desc
 *  Provides a human-readable description of the {@link Answerable<T>}.
 *  Similar to {@link util~inspect}.
 *
 * @public
 * @param {Answerable<any>} value
 * @param config
 *  - inline - Return a single-line string instead of the default potentially multi-line description
 *  - markQuestions - Surround the description of async values, such as Promises and Questions with <<value>>
 * @return {string}
 */
export function inspected(value: Answerable<any>, config?: InspectedConfig): string {

    const { inline, markQuestions } = { inline: false, markQuestions: false, ...config };

    if (! isDefined(value)) {
        return inspect(value);
    }

    if (Array.isArray(value)) {
        return stringifiedArray(value, inline);
    }

    if (isAPromise(value)) {
        return markAs('Promise', true);
    }

    if (Question.isAQuestion(value)) {
        return markAs(value.toString(), markQuestions);
    }

    if (isADate(value)) {
        return value.toISOString();
    }

    if (hasItsOwnToString(value)) {
        return value.toString();
    }

    if (isInspectable(value)) {
        return value.inspect();
    }

    if (isAFunction(value)) {
        return hasName(value)
            ? value.name
            : markAs(`Function`, true);
    }

    if (! hasCustomInspectionFunction(value) && isPlainObject(value) && isSerialisableAsJSON(value)) {
        return stringifiedToJson(value, inline);
    }

    return inspect(value, { breakLength: Number.POSITIVE_INFINITY, compact: ! inline, sorted: false  });
}

function stringifiedToJson(value: any, inline: boolean): string {
    const indentation = inline ? 0 : 4;

    return JSON.stringify(value, undefined, indentation);
}

function stringifiedArray(value: any[], inline: boolean): string {
    const indentation   = inline ? '' : '  ';
    const separator     = inline ? ' ' : '\n';

    const inspectedItem = (item: unknown, index: number) =>
        [
            indentation,
            inspected(item, { inline, markQuestions: true }),
            index < value.length - 1 ? ',' : ''
        ].join('')

    return [
        '[',
        ...value.map(inspectedItem),
        ']',
    ].join(separator);
}

function markAs(value: string, markValue: boolean): string {
    const [left, right] = markValue && ! value.startsWith('<<')
        ? [ '<<', '>>' ]
        : ['', ''];

    return [ left, value, right ].join('');
}

/**
 * @desc
 * Checks if the value is defined
 *
 * @private
 * @param {Answerable<any>} v
 */
function isDefined(v: Answerable<any>) {
    return !! v;
}

{'fraction_non_alphanumeric': 0.1454183266932271, 'fraction_numerical': 0.0010865628395508873, 'mean_word_length': 2.682, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3083371', 'n_tokens_mistral': 1664, 'n_tokens_neox': 1600, 'n_words': 411}