mirror of
https://github.com/Kvan7/Exiled-Exchange-2.git
synced 2025-12-18 22:15:48 +00:00
refactor: remove exceptions from parser
This commit is contained in:
@@ -30,6 +30,7 @@ module.exports = {
|
||||
'@typescript-eslint/no-floating-promises': 'off',
|
||||
'@typescript-eslint/no-misused-promises': 'off',
|
||||
'@typescript-eslint/prefer-reduce-type-parameter': 'off',
|
||||
'@typescript-eslint/no-invalid-void-type': 'off',
|
||||
// TODO: refactor IPC and enable
|
||||
'@typescript-eslint/consistent-type-assertions': 'off'
|
||||
},
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fastest-levenshtein": "^1.0.16",
|
||||
"luxon": "3.x.x",
|
||||
"neverthrow": "^6.0.0",
|
||||
"object-hash": "^3.0.0",
|
||||
"sockette": "^2.0.6",
|
||||
"tailwindcss": "3.x.x",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Result, ok, err } from 'neverthrow'
|
||||
import {
|
||||
CLIENT_STRINGS as _$,
|
||||
CLIENT_STRINGS_REF as _$REF,
|
||||
@@ -20,9 +21,9 @@ type SectionParseResult =
|
||||
| 'PARSER_SKIPPED'
|
||||
|
||||
type ParserFn = (section: string[], item: ParserState) => SectionParseResult
|
||||
type VirtualParserFn = (item: ParserState) => void
|
||||
type VirtualParserFn = (item: ParserState) => Result<never, string> | void
|
||||
|
||||
export interface ParserState extends ParsedItem {
|
||||
interface ParserState extends ParsedItem {
|
||||
name: string
|
||||
baseType: string | undefined
|
||||
infoVariants: BaseType[]
|
||||
@@ -70,11 +71,51 @@ const parsers: Array<ParserFn | { virtual: VirtualParserFn }> = [
|
||||
{ virtual: calcBasePercentile }
|
||||
]
|
||||
|
||||
export function parseClipboard (clipboard: string) {
|
||||
const lines = clipboard.split(/\r?\n/)
|
||||
export function parseClipboard (clipboard: string): Result<ParsedItem, string> {
|
||||
try {
|
||||
let sections = itemTextToSections(clipboard)
|
||||
|
||||
if (sections[0][2] === _$.CANNOT_USE_ITEM) {
|
||||
sections[0].pop() // remove CANNOT_USE_ITEM line
|
||||
sections[1].unshift(...sections[0]) // prepend item class & rarity into second section
|
||||
sections.shift() // remove first section where CANNOT_USE_ITEM line was
|
||||
}
|
||||
const parsed = parseNamePlate(sections[0])
|
||||
if (!parsed.isOk()) return parsed
|
||||
|
||||
sections.shift()
|
||||
parsed.value.rawText = clipboard
|
||||
|
||||
// each section can be parsed at most by one parser
|
||||
for (const parser of parsers) {
|
||||
if (typeof parser === 'object') {
|
||||
const error = parser.virtual(parsed.value)
|
||||
if (error) return error
|
||||
continue
|
||||
}
|
||||
|
||||
for (const section of sections) {
|
||||
const result = parser(section, parsed.value)
|
||||
if (result === 'SECTION_PARSED') {
|
||||
sections = sections.filter(s => s !== section)
|
||||
break
|
||||
} else if (result === 'PARSER_SKIPPED') {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.freeze(parsed)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
return err('item.parse_error')
|
||||
}
|
||||
}
|
||||
|
||||
function itemTextToSections (text: string) {
|
||||
const lines = text.split(/\r?\n/)
|
||||
lines.pop()
|
||||
|
||||
let sections: string[][] = [[]]
|
||||
const sections: string[][] = [[]]
|
||||
lines.reduce((section, line) => {
|
||||
if (line !== '--------') {
|
||||
section.push(line)
|
||||
@@ -85,40 +126,7 @@ export function parseClipboard (clipboard: string) {
|
||||
return section
|
||||
}
|
||||
}, sections[0])
|
||||
sections = sections.filter(section => section.length)
|
||||
|
||||
if (sections[0][2] === _$.CANNOT_USE_ITEM) {
|
||||
sections[0].pop() // remove CANNOT_USE_ITEM line
|
||||
sections[1].unshift(...sections[0]) // prepend item class & rarity into second section
|
||||
sections.shift() // remove first section where CANNOT_USE_ITEM line was
|
||||
}
|
||||
const parsed = parseNamePlate(sections[0])
|
||||
if (!parsed) {
|
||||
return null
|
||||
}
|
||||
|
||||
sections.shift()
|
||||
parsed.rawText = clipboard
|
||||
|
||||
// each section can be parsed at most by one parser
|
||||
for (const parser of parsers) {
|
||||
if (typeof parser === 'object') {
|
||||
parser.virtual(parsed)
|
||||
continue
|
||||
}
|
||||
|
||||
for (const section of sections) {
|
||||
const result = parser(section, parsed)
|
||||
if (result === 'SECTION_PARSED') {
|
||||
sections = sections.filter(s => s !== section)
|
||||
break
|
||||
} else if (result === 'PARSER_SKIPPED') {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object.freeze(parsed)
|
||||
return sections.filter(section => section.length)
|
||||
}
|
||||
|
||||
function normalizeName (item: ParserState) {
|
||||
@@ -180,7 +188,7 @@ function findInDatabase (item: ParserState) {
|
||||
info = ITEM_BY_REF('ITEM', item.baseType ?? item.name)
|
||||
}
|
||||
if (!info?.length) {
|
||||
throw new Error('UNKNOWN_ITEM')
|
||||
return err('item.unknown')
|
||||
}
|
||||
if (info[0].unique) {
|
||||
info = info.filter(info => info.unique!.base === item.baseType)
|
||||
@@ -267,7 +275,7 @@ function parseNamePlate (section: string[]) {
|
||||
if (section.length < 3 ||
|
||||
!section[0].startsWith(_$.ITEM_CLASS) ||
|
||||
!section[1].startsWith(_$.RARITY)) {
|
||||
return null
|
||||
return err('item.parse_error')
|
||||
}
|
||||
|
||||
const item: ParserState = {
|
||||
@@ -311,10 +319,10 @@ function parseNamePlate (section: string[]) {
|
||||
item.rarity = ItemRarity.Unique
|
||||
break
|
||||
default:
|
||||
return null
|
||||
return err('item.unknown')
|
||||
}
|
||||
|
||||
return item
|
||||
return ok(item)
|
||||
}
|
||||
|
||||
function parseInfluence (section: string[], item: ParsedItem) {
|
||||
|
||||
@@ -43,7 +43,7 @@ export default defineComponent({
|
||||
if (e.target !== 'item-check') return
|
||||
|
||||
checkPosition.value = e.position
|
||||
item.value = parseClipboard(e.clipboard)
|
||||
item.value = parseClipboard(e.clipboard).unwrapOr(null)
|
||||
if (item.value) {
|
||||
wm.show(props.config.wmId)
|
||||
}
|
||||
|
||||
@@ -7,17 +7,17 @@ const POEDB_LANGS = { 'en': 'us', 'ru': 'ru', 'cmn-Hant': 'tw' }
|
||||
export function registerActions () {
|
||||
Host.onEvent('MAIN->CLIENT::item-text', (e) => {
|
||||
if (!['open-wiki', 'open-craft-of-exile', 'open-poedb', 'search-similar'].includes(e.target)) return
|
||||
const item = parseClipboard(e.clipboard)
|
||||
if (!item) return
|
||||
const parsed = parseClipboard(e.clipboard)
|
||||
if (!parsed.isOk()) return
|
||||
|
||||
if (e.target === 'open-wiki') {
|
||||
openWiki(item)
|
||||
openWiki(parsed.value)
|
||||
} else if (e.target === 'open-craft-of-exile') {
|
||||
openCoE(item)
|
||||
openCoE(parsed.value)
|
||||
} else if (e.target === 'open-poedb') {
|
||||
openPoedb(item)
|
||||
openPoedb(parsed.value)
|
||||
} else if (e.target === 'search-similar') {
|
||||
findSimilarItems(item)
|
||||
findSimilarItems(parsed.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -31,17 +31,17 @@
|
||||
<background-info />
|
||||
<check-position-circle v-if="showCheckPos"
|
||||
:position="checkPosition" style="z-index: -1;" />
|
||||
<template v-if="item && ('error' in item)">
|
||||
<template v-if="item?.isErr()">
|
||||
<ui-error-box class="m-4">
|
||||
<template #name>{{ t(item.error.name) }}</template>
|
||||
<p>{{ t(item.error.message) }}</p>
|
||||
</ui-error-box>
|
||||
<pre class="bg-gray-900 rounded m-4 overflow-x-hidden p-2">{{ item.rawText }}</pre>
|
||||
<pre class="bg-gray-900 rounded m-4 overflow-x-hidden p-2">{{ item.error.rawText }}</pre>
|
||||
</template>
|
||||
<template v-else>
|
||||
<unidentified-resolver :item="item" @identify="item = $event" />
|
||||
<checked-item v-if="isLeagueSelected && item"
|
||||
:item="item" :advanced-check="advancedCheck" />
|
||||
<template v-else-if="item?.isOk()">
|
||||
<unidentified-resolver :item="item.value" @identify="handleIdentification($event)" />
|
||||
<checked-item v-if="isLeagueSelected"
|
||||
:item="item.value" :advanced-check="advancedCheck" />
|
||||
</template>
|
||||
<div v-if="isBrowserShown" class="bg-gray-900 px-6 py-2 truncate">
|
||||
<i18n-t keypath="app.toggle_browser_hint" tag="div">
|
||||
@@ -58,8 +58,8 @@
|
||||
'flex-row': clickPosition === 'stash',
|
||||
'flex-row-reverse': clickPosition === 'inventory'
|
||||
}">
|
||||
<related-items v-if="item && !('error' in item)" class="pointer-events-auto"
|
||||
:item="item" :click-position="clickPosition" />
|
||||
<related-items v-if="item?.isOk()" class="pointer-events-auto"
|
||||
:item="item.value" :click-position="clickPosition" />
|
||||
<rate-limiter-state class="pointer-events-auto" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,6 +68,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject, PropType, shallowRef, watch, computed, nextTick, provide } from 'vue'
|
||||
import { Result, ok, err } from 'neverthrow'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import CheckedItem from './CheckedItem.vue'
|
||||
import BackgroundInfo from './BackgroundInfo.vue'
|
||||
@@ -83,13 +84,7 @@ import CheckPositionCircle from './CheckPositionCircle.vue'
|
||||
import ItemQuickPrice from '@/web/ui/ItemQuickPrice.vue'
|
||||
import { PriceCheckWidget, WidgetManager } from '../overlay/interfaces'
|
||||
|
||||
interface ParseError {
|
||||
error: {
|
||||
name: string
|
||||
message: string
|
||||
}
|
||||
rawText: ParsedItem['rawText']
|
||||
}
|
||||
type ParseError = { name: string; message: string; rawText: ParsedItem['rawText'] }
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -116,7 +111,7 @@ export default defineComponent({
|
||||
props.config.wmFlags = ['hide-on-blur', 'skip-menu']
|
||||
})
|
||||
|
||||
const item = shallowRef<ParsedItem | ParseError | null>(null)
|
||||
const item = shallowRef<null | Result<ParsedItem, ParseError>>(null)
|
||||
const advancedCheck = shallowRef(false)
|
||||
const checkPosition = shallowRef({ x: 1, y: 1 })
|
||||
|
||||
@@ -149,28 +144,28 @@ export default defineComponent({
|
||||
wm.show(props.config.wmId)
|
||||
checkPosition.value = e.position
|
||||
advancedCheck.value = e.focusOverlay
|
||||
try {
|
||||
const parsed = parseClipboard(e.clipboard)
|
||||
if (parsed != null && (
|
||||
(parsed.category === ItemCategory.HeistContract && parsed.rarity !== ItemRarity.Unique) ||
|
||||
(parsed.category === ItemCategory.Sentinel && parsed.rarity !== ItemRarity.Unique)
|
||||
)) {
|
||||
throw new Error('UNKNOWN_ITEM')
|
||||
} else {
|
||||
item.value = parsed
|
||||
|
||||
item.value = parseClipboard(e.clipboard)
|
||||
.andThen(item => (
|
||||
(item.category === ItemCategory.HeistContract && item.rarity !== ItemRarity.Unique) ||
|
||||
(item.category === ItemCategory.Sentinel && item.rarity !== ItemRarity.Unique))
|
||||
? err('item.unknown')
|
||||
: ok(item))
|
||||
.mapErr(err => ({
|
||||
name: `${err}`,
|
||||
message: `${err}_help`,
|
||||
rawText: e.clipboard
|
||||
}))
|
||||
|
||||
if (item.value.isOk()) {
|
||||
queuePricesFetch()
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
const strings = (err instanceof Error && err.message === 'UNKNOWN_ITEM')
|
||||
? 'item.unknown'
|
||||
: 'item.parse_error'
|
||||
|
||||
item.value = {
|
||||
error: { name: `${strings}`, message: `${strings}_help` },
|
||||
rawText: e.clipboard
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function handleIdentification (identified: ParsedItem) {
|
||||
item.value = ok(identified)
|
||||
}
|
||||
|
||||
MainProcess.onEvent('MAIN->OVERLAY::hide-exclusive-widget', () => {
|
||||
wm.hide(props.config.wmId)
|
||||
})
|
||||
@@ -253,6 +248,7 @@ export default defineComponent({
|
||||
checkPosition,
|
||||
item,
|
||||
advancedCheck,
|
||||
handleIdentification,
|
||||
overlayKey,
|
||||
isLeagueSelected,
|
||||
openLeagueSelection
|
||||
|
||||
@@ -1693,6 +1693,11 @@ natural-compare@^1.4.0:
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||
|
||||
neverthrow@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/neverthrow/-/neverthrow-6.0.0.tgz#bacd7661cade296ccc5c35760bb3b679214155b6"
|
||||
integrity sha512-kPZKRs4VkdloCGQXPoP84q4sT/1Z+lYM61AXyV8wWa2hnuo5KpPBF2S3crSFnMrOgUISmEBP8Vo/ngGZX60NhA==
|
||||
|
||||
node-releases@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
|
||||
|
||||
Reference in New Issue
Block a user