All files / ini / stringify.ts

100.00% Branches 11/11
100.00% Lines 37/37
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x5
x44
x44
 
x15
x15
x23
x25
x15
 
x27
x27
x27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x5
x5
x5
 
x22
x22
x22
x22
x22
x22
 
x22
 
x22
x22
x47
x78
x78
x78
x87
x87
x87
x70
x87
x87
x87
x47
x22
x22





































































































































// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.

/** Function for replacing JavaScript values with INI string values. */
export type ReplacerFunction = (
  key: string,
  // deno-lint-ignore no-explicit-any
  value: any,
  section?: string,
) => string;

/** Options for {@linkcode stringify}. */
export interface StringifyOptions {
  /**
   * Character(s) used to break lines in the config file.
   *
   * @default {"\n"}
   */
  lineBreak?: "\n" | "\r\n" | "\r";
  /**
   * Use a plain assignment char or pad with spaces.
   *
   * @default {false}
   */
  pretty?: boolean;
  /**
   * Provide custom string conversion for the value in a key/value pair.
   * Similar to the
   * {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#replacer | replacer}
   * function in {@linkcode JSON.stringify}.
   */
  replacer?: ReplacerFunction;
}

function isPlainObject(object: unknown): object is object {
  return Object.prototype.toString.call(object) === "[object Object]";
}

function sort([_a, valA]: [string, unknown], [_b, valB]: [string, unknown]) {
  if (isPlainObject(valA)) return 1;
  if (isPlainObject(valB)) return -1;
  return 0;
}

function defaultReplacer(_key: string, value: unknown, _section?: string) {
  return `${value}`;
}

/**
 * Compile an object into an INI config string. Provide formatting options to modify the output.
 *
 * @example Usage
 * ```ts
 * import { stringify } from "@std/ini/stringify";
 * import { assertEquals } from "@std/assert";
 *
 * const str = stringify({
 *   key1: "value1",
 *   key2: "value2",
 *   section1: {
 *     foo: "bar",
 *   },
 *   section2: {
 *     hello: "world",
 *   },
 * });
 *
 * assertEquals(str, `key1=value1
 * key2=value2
 * [section1]
 * foo=bar
 * [section2]
 * hello=world`);
 * ```
 *
 * @example Using replacer option
 * ```ts
 * import { stringify } from "@std/ini/stringify";
 * import { assertEquals } from "@std/assert";
 *
 * const str = stringify({
 *   "section X": {
 *     date: new Date("2024-06-10"),
 *   },
 *   "section Y": {
 *     name: "John"
 *   }
 * }, {
 *   replacer(key, value, section) {
 *     if (section === "section X" && key === "date") {
 *       return value.toISOString().slice(0, 10);
 *     }
 *     return value;
 *   },
 * });
 *
 * assertEquals(str, `[section X]
 * date=2024-06-10
 * [section Y]
 * name=John`);
 * ```
 *
 * @param object The object to stringify
 * @param options The option to use
 * @returns The INI string
 */
export function stringify(
  object: object,
  options: StringifyOptions = {},
): string {
  const {
    replacer = defaultReplacer,
    pretty = false,
    lineBreak = "\n",
  } = options;
  const assignment = pretty ? " = " : "=";

  const entries = Object.entries(object).sort(sort);

  const lines = [];
  for (const [key, value] of entries) {
    if (isPlainObject(value)) {
      const sectionName = key;
      lines.push(`[${sectionName}]`);
      for (const [key, val] of Object.entries(value)) {
        const line = `${key}${assignment}${replacer(key, val, sectionName)}`;
        lines.push(line);
      }
    } else {
      const line = `${key}${assignment}${replacer(key, value)}`;
      lines.push(line);
    }
  }
  return lines.join(lineBreak);
}