From 0493f679a4b49045042c09785d4dbcb3e36f0953 Mon Sep 17 00:00:00 2001
From: Alex Yang <himself65@outlook.com>
Date: Sun, 20 Oct 2024 20:07:48 -0700
Subject: [PATCH] fix(core): inline `python-format-js` (#1356)

---
 .changeset/metal-worms-decide.md     |   5 +
 package.json                         |   3 -
 packages/core/package.json           |   3 +-
 packages/core/src/prompts/base.ts    |   2 +-
 packages/core/src/prompts/format.ts  | 207 +++++++++++++++++++++++++++
 patches/python-format-js@1.4.3.patch |   0
 pnpm-lock.yaml                       |  13 --
 7 files changed, 214 insertions(+), 19 deletions(-)
 create mode 100644 .changeset/metal-worms-decide.md
 create mode 100644 packages/core/src/prompts/format.ts
 delete mode 100644 patches/python-format-js@1.4.3.patch

diff --git a/.changeset/metal-worms-decide.md b/.changeset/metal-worms-decide.md
new file mode 100644
index 000000000..4c60cd91a
--- /dev/null
+++ b/.changeset/metal-worms-decide.md
@@ -0,0 +1,5 @@
+---
+"@llamaindex/core": patch
+---
+
+fix(core): inline `python-format-js`
diff --git a/package.json b/package.json
index ad275d09d..0b1ddfa4b 100644
--- a/package.json
+++ b/package.json
@@ -39,9 +39,6 @@
     "overrides": {
       "trim": "1.0.1",
       "protobufjs": "7.2.6"
-    },
-    "patchedDependencies": {
-      "python-format-js@1.4.3": "patches/python-format-js@1.4.3.patch"
     }
   },
   "lint-staged": {
diff --git a/packages/core/package.json b/packages/core/package.json
index d5113b004..994670235 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -336,8 +336,7 @@
     "ajv": "^8.17.1",
     "bunchee": "5.5.1",
     "happy-dom": "^15.7.4",
-    "natural": "^8.0.1",
-    "python-format-js": "^1.4.3"
+    "natural": "^8.0.1"
   },
   "dependencies": {
     "@llamaindex/env": "workspace:*",
diff --git a/packages/core/src/prompts/base.ts b/packages/core/src/prompts/base.ts
index 0ec65ba37..a4e83426e 100644
--- a/packages/core/src/prompts/base.ts
+++ b/packages/core/src/prompts/base.ts
@@ -1,7 +1,7 @@
-import format from "python-format-js";
 import type { ChatMessage } from "../llms";
 import type { BaseOutputParser, Metadata } from "../schema";
 import { objectEntries } from "../utils";
+import { format } from "./format";
 import { PromptType } from "./prompt-type";
 
 type MappingFn<TemplatesVar extends string[] = string[]> = (
diff --git a/packages/core/src/prompts/format.ts b/packages/core/src/prompts/format.ts
new file mode 100644
index 000000000..4fb875958
--- /dev/null
+++ b/packages/core/src/prompts/format.ts
@@ -0,0 +1,207 @@
+/**
+ * MIT License
+ *
+ * Copyright (c) 2019 jhonararipe
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+"use strict";
+function formatImpl(this: string, ...args_: any[]) {
+  // Create variables
+  let self = this;
+  const __patterns__ = self.match(/({.*?})/g);
+  const {
+    REF,
+    FILL_CHAR,
+    MASK_NUMBER,
+    ALIGN_OP,
+    CROP_SIZE,
+    DOT,
+    FRACTION,
+    TYPE_VAR,
+  } = {
+    REF: 1,
+    FILL_CHAR: 2,
+    MASK_NUMBER: 3,
+    ALIGN_OP: 4,
+    CROP_SIZE: 5,
+    DOT: 6,
+    FRACTION: 7,
+    TYPE_VAR: 8,
+  };
+  const DEFAULT_PLACE = 6;
+  const ALL_REGEXP =
+    /{(\w+)?:([^>\^<\d#]|0)?([#%,])?([>^<\.])?(\d+)?(\.)?(\d+)?([eEfFgGdxXobn#%])?}/g;
+  const regExpBasic = /{\[?(\w+)\]?}/; // it's not best solution
+  const isObject = typeof args_[0] === "object";
+  // types/use logic
+  __patterns__?.map((pattern, patt_index) => {
+    const kargs = ALL_REGEXP.exec(pattern) || ALL_REGEXP.exec(pattern);
+    const wargs = regExpBasic.exec(pattern);
+
+    // Insert values (one 2 one / array / object)
+    const INDEX_VAR =
+      (wargs ? wargs[REF] : kargs ? kargs[REF] : patt_index) || patt_index;
+    // @ts-expect-error
+    const NATUAL_VALUE = isObject ? args_[0][INDEX_VAR] : args_[INDEX_VAR];
+    // @ts-expect-error
+    let ACTUAL_VALUE = isObject ? args_[0][INDEX_VAR] : args_[INDEX_VAR];
+
+    // Verify sintax/semantic
+    if (ACTUAL_VALUE === null || ACTUAL_VALUE === undefined)
+      throw new Error(
+        `Replacement index ${INDEX_VAR} out of range for positional args tuple`,
+      );
+    if (kargs) {
+      // If TYPE_VAR is not defined and the first argument is a number, pad a string should from left, so set TYPE_VAR to "d"
+      if (kargs[TYPE_VAR] === undefined && typeof ACTUAL_VALUE === "number") {
+        kargs[TYPE_VAR] = "d";
+      }
+      const LETTER =
+        (!kargs[FILL_CHAR]
+          ? false
+          : !kargs[ALIGN_OP] &&
+              [..."FfbefoxXn"].includes(kargs[FILL_CHAR].toLowerCase())
+            ? kargs[FILL_CHAR]
+            : kargs[TYPE_VAR]) || kargs[TYPE_VAR];
+      //  padronaze
+      if (LETTER) {
+        const floatSize = pattern.includes(".")
+          ? Number(kargs[FRACTION] || kargs[CROP_SIZE])
+          : DEFAULT_PLACE;
+        switch (LETTER) {
+          case "E":
+            ACTUAL_VALUE =
+              ACTUAL_VALUE.toExponential(DEFAULT_PLACE).toUpperCase();
+            break;
+          case "e":
+            ACTUAL_VALUE = ACTUAL_VALUE.toExponential(DEFAULT_PLACE);
+            break;
+          case "X":
+            ACTUAL_VALUE = ACTUAL_VALUE.toString(16).toUpperCase();
+            break;
+          case "x":
+            ACTUAL_VALUE = ACTUAL_VALUE.toString(16); // Hexadecimal
+            break;
+          case "b":
+            ACTUAL_VALUE = ACTUAL_VALUE.toString(2); // Binary
+            break;
+          case "f":
+          case "F":
+            ACTUAL_VALUE = ACTUAL_VALUE.toFixed(floatSize);
+            break;
+          case "o":
+            ACTUAL_VALUE = ACTUAL_VALUE.toString(8); // Octal
+            break;
+          default:
+            break;
+        }
+        //  mask
+        switch (kargs[MASK_NUMBER]) {
+          case "#":
+            const MASK = {
+              x: "0x",
+              X: "0X",
+              o: "0o",
+              b: "0b",
+            }[LETTER];
+            ACTUAL_VALUE = MASK + ACTUAL_VALUE;
+            break;
+        }
+      }
+      // signal
+      if (
+        // @ts-expect-error
+        [..." +-,%"].includes(kargs[FILL_CHAR]) &&
+        typeof NATUAL_VALUE === "number"
+      ) {
+        ACTUAL_VALUE = ACTUAL_VALUE.toString().replace("-", "");
+        if (NATUAL_VALUE >= 0)
+          switch (kargs[FILL_CHAR]) {
+            case "+":
+              ACTUAL_VALUE = "+" + ACTUAL_VALUE;
+              break;
+            case " ":
+              ACTUAL_VALUE = " " + ACTUAL_VALUE;
+              break;
+            case ",":
+              ACTUAL_VALUE = NATUAL_VALUE.toString()
+                .split(/(?=(?:...)*$)/)
+                .join(kargs[FILL_CHAR]);
+              break;
+            case "%":
+              ACTUAL_VALUE =
+                (NATUAL_VALUE * 100).toFixed(
+                  // @ts-expect-error
+                  kargs[FRACTION] || DEFAULT_PLACE,
+                ) + "%";
+              break;
+          }
+        else ACTUAL_VALUE = "-" + ACTUAL_VALUE;
+      }
+      // space / order / trim
+      if (kargs[CROP_SIZE]) {
+        ACTUAL_VALUE = ACTUAL_VALUE.toString();
+        const FILL_ELEMENT = kargs[FILL_CHAR] || " ";
+        const SIZE_STRING = ACTUAL_VALUE.length;
+        const SIZE_ARG = kargs[CROP_SIZE];
+        const FILL_LENGTH = SIZE_STRING > SIZE_ARG ? SIZE_STRING : SIZE_ARG;
+        const FILL = FILL_ELEMENT.repeat(FILL_LENGTH);
+
+        switch (kargs[ALIGN_OP] || kargs[FILL_CHAR]) {
+          case "<":
+            ACTUAL_VALUE = ACTUAL_VALUE.padEnd(FILL_LENGTH, FILL_ELEMENT);
+            break;
+          case ".":
+            if (!(LETTER && /[fF]/.test(LETTER)))
+              ACTUAL_VALUE = ACTUAL_VALUE.slice(0, SIZE_ARG);
+            break;
+          case ">":
+            ACTUAL_VALUE = ACTUAL_VALUE.padStart(FILL_LENGTH, FILL_ELEMENT);
+            break;
+          case "^":
+            const length_start = Math.floor((FILL_LENGTH - SIZE_STRING) / 2);
+            const string_start =
+              length_start > 0
+                ? FILL_ELEMENT.repeat(length_start) + ACTUAL_VALUE
+                : ACTUAL_VALUE;
+
+            ACTUAL_VALUE = FILL.replace(
+              RegExp(`.{${string_start.length}}`),
+              string_start,
+            );
+            break;
+          default:
+            ACTUAL_VALUE = LETTER
+              ? ACTUAL_VALUE.padStart(FILL_LENGTH, FILL_ELEMENT)
+              : ACTUAL_VALUE.padEnd(FILL_LENGTH, FILL_ELEMENT);
+            break;
+        }
+      }
+    }
+
+    // SET Definitive value
+    self = self.replace(pattern, ACTUAL_VALUE);
+  });
+
+  return self;
+}
+
+export const format = (inputString: string, ...param: any[]) =>
+  formatImpl.apply(inputString, param);
diff --git a/patches/python-format-js@1.4.3.patch b/patches/python-format-js@1.4.3.patch
deleted file mode 100644
index e69de29bb..000000000
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cfcf56519..9fc77eefc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,11 +8,6 @@ overrides:
   trim: 1.0.1
   protobufjs: 7.2.6
 
-patchedDependencies:
-  python-format-js@1.4.3:
-    hash: 2qoyzwmpaczaj2mabgmoz6ccpy
-    path: patches/python-format-js@1.4.3.patch
-
 importers:
 
   .:
@@ -420,9 +415,6 @@ importers:
       natural:
         specifier: ^8.0.1
         version: 8.0.1(@aws-sdk/credential-providers@3.675.0)
-      python-format-js:
-        specifier: ^1.4.3
-        version: 1.4.3(patch_hash=2qoyzwmpaczaj2mabgmoz6ccpy)
 
   packages/core/tests:
     devDependencies:
@@ -10326,9 +10318,6 @@ packages:
     resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==}
     engines: {node: '>=12.20'}
 
-  python-format-js@1.4.3:
-    resolution: {integrity: sha512-0iK5zP5HMf4F3Xc3Uo6hggPu4ylEQCKNoLXUYe3S1YfYkFG6DxGDO3KozCrySntAZTPmP9yRI+eMq0MXweHqIw==}
-
   qs@6.11.0:
     resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
     engines: {node: '>=0.6'}
@@ -25286,8 +25275,6 @@ snapshots:
     dependencies:
       escape-goat: 4.0.0
 
-  python-format-js@1.4.3(patch_hash=2qoyzwmpaczaj2mabgmoz6ccpy): {}
-
   qs@6.11.0:
     dependencies:
       side-channel: 1.0.6
-- 
GitLab