Compare commits
9 Commits
41b1930c70
...
5a4bbc5297
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a4bbc5297 | ||
| 6e7ac29a27 | |||
| 15d8f488d6 | |||
| c21d895f35 | |||
|
|
8fc8dc1cc9 | ||
| d2589affe5 | |||
| 4f55658b87 | |||
|
|
6b4cc88efa | ||
| ce3cf003ff |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -20,3 +20,8 @@ Cargo.lock
|
|||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
!.env.*.example
|
||||||
|
# Ignore the .env file that contains sensitive information
|
||||||
45
.vscode/launch.json
vendored
Normal file
45
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
// Verwendet IntelliSense zum Ermitteln möglicher Attribute.
|
||||||
|
// Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen.
|
||||||
|
// Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug executable 'no-man-sky'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"--bin=no-man-sky",
|
||||||
|
"--package=no-man-sky"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "no-man-sky",
|
||||||
|
"kind": "bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug unit tests in executable 'no-man-sky'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"test",
|
||||||
|
"--no-run",
|
||||||
|
"--bin=no-man-sky",
|
||||||
|
"--package=no-man-sky"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "no-man-sky",
|
||||||
|
"kind": "bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
11
.vscode/settings.json
vendored
Normal file
11
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"cSpell.words": [
|
||||||
|
"Herstellung",
|
||||||
|
"Kochen",
|
||||||
|
"Quelle",
|
||||||
|
"Raffination",
|
||||||
|
"serde",
|
||||||
|
"Stück",
|
||||||
|
"Verwendung"
|
||||||
|
]
|
||||||
|
}
|
||||||
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "no-man-sky"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
publish = ["merlin"]
|
||||||
|
description = "Utility functions to read environment with fallback and values from a file"
|
||||||
|
license = "MIT"
|
||||||
|
repository = "ssh://git@gitea.merlinserver.de:2222/Stefan/merlin_env_helper.git"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
diesel = { version= "2.2.10", features = ["serde_json", "postgres", "uuid"]}
|
||||||
|
env_logger = "0.11.8"
|
||||||
|
log = "0.4.27"
|
||||||
|
merlin_env_helper = { version = "0.2.0", registry = "merlin" }
|
||||||
|
regex = "1.11.1"
|
||||||
|
reqwest = {version="0.12.15", features=["blocking"]}
|
||||||
|
#scraper = "0.23.1"
|
||||||
|
select = "0.6.1"
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
# no-man-sky-wiki
|
# no-man-sky-wiki
|
||||||
|
|
||||||
No-man-sky resource parser & resource lib
|
No-man-sky resource parser & resource lib
|
||||||
|
|
||||||
|
Test1
|
||||||
|
|||||||
9
diesel.toml
Normal file
9
diesel.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# For documentation on how to configure this file,
|
||||||
|
# see https://diesel.rs/guides/configuring-diesel-cli
|
||||||
|
|
||||||
|
[print_schema]
|
||||||
|
file = "src/db/schema.rs"
|
||||||
|
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
|
||||||
|
|
||||||
|
[migrations_directory]
|
||||||
|
dir = "/home/stefan/projects/rust/no-man-sky-wiki/migrations"
|
||||||
0
migrations/.keep
Normal file
0
migrations/.keep
Normal file
6
migrations/00000000000000_diesel_initial_setup/down.sql
Normal file
6
migrations/00000000000000_diesel_initial_setup/down.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
-- This file was automatically created by Diesel to setup helper functions
|
||||||
|
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||||
|
-- changes will be added to existing projects as new migrations.
|
||||||
|
|
||||||
|
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
|
||||||
|
DROP FUNCTION IF EXISTS diesel_set_updated_at();
|
||||||
36
migrations/00000000000000_diesel_initial_setup/up.sql
Normal file
36
migrations/00000000000000_diesel_initial_setup/up.sql
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
-- This file was automatically created by Diesel to setup helper functions
|
||||||
|
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||||
|
-- changes will be added to existing projects as new migrations.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- Sets up a trigger for the given table to automatically set a column called
|
||||||
|
-- `updated_at` whenever the row is modified (unless `updated_at` was included
|
||||||
|
-- in the modified columns)
|
||||||
|
--
|
||||||
|
-- # Example
|
||||||
|
--
|
||||||
|
-- ```sql
|
||||||
|
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
|
||||||
|
--
|
||||||
|
-- SELECT diesel_manage_updated_at('users');
|
||||||
|
-- ```
|
||||||
|
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
|
||||||
|
BEGIN
|
||||||
|
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
|
||||||
|
BEGIN
|
||||||
|
IF (
|
||||||
|
NEW IS DISTINCT FROM OLD AND
|
||||||
|
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
|
||||||
|
) THEN
|
||||||
|
NEW.updated_at := current_timestamp;
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
11
migrations/2025-06-14-100926_init/down.sql
Normal file
11
migrations/2025-06-14-100926_init/down.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
DROP INDEX IF EXISTS idx_icon_name;
|
||||||
|
DROP INDEX IF EXISTS idx_resource_name;
|
||||||
|
DROP INDEX IF EXISTS idx_resource_title;
|
||||||
|
DROP INDEX IF EXISTS idx_recipe_type;
|
||||||
|
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS "ingredient";
|
||||||
|
DROP TABLE IF EXISTS "recipe";
|
||||||
|
DROP TABLE IF EXISTS "resource";
|
||||||
|
DROP TABLE IF EXISTS "icon";
|
||||||
56
migrations/2025-06-14-100926_init/up.sql
Normal file
56
migrations/2025-06-14-100926_init/up.sql
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
CREATE TABLE "icon"(
|
||||||
|
"id" UUID NOT NULL PRIMARY KEY,
|
||||||
|
"name" VARCHAR(255) NOT NULL,
|
||||||
|
"content_type" VARCHAR(255),
|
||||||
|
"url" VARCHAR(512),
|
||||||
|
"width" INT4,
|
||||||
|
"height" INT4,
|
||||||
|
"state" JSON
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "resource"(
|
||||||
|
"id" UUID NOT NULL PRIMARY KEY,
|
||||||
|
"name" VARCHAR(255) NOT NULL,
|
||||||
|
"title" VARCHAR(255) NOT NULL,
|
||||||
|
"url" VARCHAR(512),
|
||||||
|
"icon" UUID,
|
||||||
|
"state" JSON,
|
||||||
|
FOREIGN KEY ("icon") REFERENCES "icon"("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "recipe"(
|
||||||
|
"id" UUID NOT NULL PRIMARY KEY,
|
||||||
|
"resource" UUID NOT NULL,
|
||||||
|
"recipe_type" VARCHAR(50) NOT NULL,
|
||||||
|
"duration" INT4 NOT NULL,
|
||||||
|
"state" JSON,
|
||||||
|
FOREIGN KEY ("resource") REFERENCES "resource"("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "ingredient"(
|
||||||
|
"id" UUID NOT NULL PRIMARY KEY,
|
||||||
|
"resource" UUID NOT NULL,
|
||||||
|
"quantity" INT4 NOT NULL,
|
||||||
|
"state" JSON,
|
||||||
|
"recipe" UUID NOT NULL,
|
||||||
|
FOREIGN KEY ("resource") REFERENCES "resource"("id"),
|
||||||
|
FOREIGN KEY ("recipe") REFERENCES "recipe"("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_icon_name
|
||||||
|
ON icon USING btree
|
||||||
|
(name COLLATE pg_catalog."default" ASC NULLS LAST);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_recipe_type
|
||||||
|
ON recipe USING btree
|
||||||
|
(recipe_type COLLATE pg_catalog."default" ASC NULLS LAST);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_resource_name
|
||||||
|
ON resource USING btree
|
||||||
|
(name COLLATE pg_catalog."default" ASC NULLS LAST);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_resource_title
|
||||||
|
ON resource USING btree
|
||||||
|
(title COLLATE pg_catalog."default" ASC NULLS LAST);
|
||||||
188
snipped.html
Normal file
188
snipped.html
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
<span
|
||||||
|
class="resource-substanzmithoherenergie"
|
||||||
|
style="border: 1px solid #d3d3d3"
|
||||||
|
>
|
||||||
|
<span class="mw-valign-text-bottom" typeof="mw:File">
|
||||||
|
<a href="/de/wiki/Diwasserstoff" title="Diwasserstoff">
|
||||||
|
<img
|
||||||
|
class="mw-file-element lazyload"
|
||||||
|
data-image-key="SUBSTANCE.LAUNCHSUB.1.png"
|
||||||
|
data-image-name="SUBSTANCE.LAUNCHSUB.1.png"
|
||||||
|
data-relevant="1"
|
||||||
|
data-src="https://static.wikia.nocookie.net/nomanssky_gamepedia/images/0/03/SUBSTANCE.LAUNCHSUB.1.png/revision/latest/scale-to-width-down/18?cb=20180725071716"
|
||||||
|
decoding="async"
|
||||||
|
height="18"
|
||||||
|
loading="lazy"
|
||||||
|
src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAQAICTAEAOw%3D%3D"
|
||||||
|
width="18"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<a href="/de/wiki/Diwasserstoff" title="Diwasserstoff">
|
||||||
|
<span class="itemlink ajaxttlink">Diwasserstoff</span>
|
||||||
|
</a>
|
||||||
|
x1 +
|
||||||
|
<span
|
||||||
|
class="resource-konzentrierterflüssigtreibstoff"
|
||||||
|
style="border: 1px solid #d3d3d3"
|
||||||
|
>
|
||||||
|
<span class="mw-valign-text-bottom" typeof="mw:File">
|
||||||
|
<a href="/de/wiki/Sauerstoff" title="Sauerstoff">
|
||||||
|
<img
|
||||||
|
class="mw-file-element lazyload"
|
||||||
|
data-image-key="SUBSTANCE.AIR.1.png"
|
||||||
|
data-image-name="SUBSTANCE.AIR.1.png"
|
||||||
|
data-relevant="1"
|
||||||
|
data-src="https://static.wikia.nocookie.net/nomanssky_gamepedia/images/e/ec/SUBSTANCE.AIR.1.png/revision/latest/scale-to-width-down/18?cb=20221007220715"
|
||||||
|
decoding="async"
|
||||||
|
height="18"
|
||||||
|
loading="lazy"
|
||||||
|
src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAQAICTAEAOw%3D%3D"
|
||||||
|
width="18"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<a href="/de/wiki/Sauerstoff" title="Sauerstoff">
|
||||||
|
<span class="itemlink ajaxttlink">Sauerstoff</span>
|
||||||
|
</a>
|
||||||
|
x1 →
|
||||||
|
<span
|
||||||
|
class="resource-aquatischesmineral-extrakt"
|
||||||
|
style="border: 1px solid #d3d3d3"
|
||||||
|
>
|
||||||
|
<span class="mw-valign-text-bottom" typeof="mw:File">
|
||||||
|
<a href="/de/wiki/Salz" title="Salz">
|
||||||
|
<img
|
||||||
|
class="mw-file-element lazyload"
|
||||||
|
data-image-key="SUBSTANCE.WATER.1.png"
|
||||||
|
data-image-name="SUBSTANCE.WATER.1.png"
|
||||||
|
data-relevant="1"
|
||||||
|
data-src="https://static.wikia.nocookie.net/nomanssky_gamepedia/images/9/9f/SUBSTANCE.WATER.1.png/revision/latest/scale-to-width-down/18?cb=20180726042119"
|
||||||
|
decoding="async"
|
||||||
|
height="18"
|
||||||
|
loading="lazy"
|
||||||
|
src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAQAICTAEAOw%3D%3D"
|
||||||
|
width="18"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<strong class="mw-selflink selflink">
|
||||||
|
<span class="itemlink ajaxttlink">Salz</span>
|
||||||
|
</strong>
|
||||||
|
x1
|
||||||
|
<small>( <i>"Schnelle Formation/Verdunstung"</i>, 0,08 sek./Stück)</small>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span
|
||||||
|
class="resource-verarbeiteteswassermineral"
|
||||||
|
style="border: 1px solid #d3d3d3"
|
||||||
|
><span class="mw-valign-text-bottom" typeof="mw:File"
|
||||||
|
><a href="/de/wiki/Chlor" title="Chlor"
|
||||||
|
><img
|
||||||
|
src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAQAICTAEAOw%3D%3D"
|
||||||
|
decoding="async"
|
||||||
|
loading="lazy"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
class="mw-file-element lazyload"
|
||||||
|
data-image-name="SUBSTANCE.WATER.2.png"
|
||||||
|
data-image-key="SUBSTANCE.WATER.2.png"
|
||||||
|
data-relevant="1"
|
||||||
|
data-src="https://static.wikia.nocookie.net/nomanssky_gamepedia/images/2/25/SUBSTANCE.WATER.2.png/revision/latest/scale-to-width-down/18?cb=20180726040634" /></a></span
|
||||||
|
></span>
|
||||||
|
<a href="/de/wiki/Chlor" title="Chlor"
|
||||||
|
><span class="itemlink ajaxttlink">Chlor</span></a
|
||||||
|
>
|
||||||
|
x1  →  
|
||||||
|
<span
|
||||||
|
class="resource-aquatischesmineral-extrakt"
|
||||||
|
style="border: 1px solid #d3d3d3"
|
||||||
|
><span class="mw-valign-text-bottom" typeof="mw:File"
|
||||||
|
><a href="/de/wiki/Salz" title="Salz"
|
||||||
|
><img
|
||||||
|
src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAQAICTAEAOw%3D%3D"
|
||||||
|
decoding="async"
|
||||||
|
loading="lazy"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
class="mw-file-element lazyload"
|
||||||
|
data-image-name="SUBSTANCE.WATER.1.png"
|
||||||
|
data-image-key="SUBSTANCE.WATER.1.png"
|
||||||
|
data-relevant="1"
|
||||||
|
data-src="https://static.wikia.nocookie.net/nomanssky_gamepedia/images/9/9f/SUBSTANCE.WATER.1.png/revision/latest/scale-to-width-down/18?cb=20180726042119" /></a></span
|
||||||
|
></span>
|
||||||
|
<strong class="mw-selflink selflink"
|
||||||
|
><span class="itemlink ajaxttlink">Salz</span></strong
|
||||||
|
>
|
||||||
|
x2  <small>(<i>"Salzproduktion"</i>, 0,24 sek./Stück)</small>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span
|
||||||
|
class="resource-substanzmithoherenergie"
|
||||||
|
style="border: 1px solid #d3d3d3"
|
||||||
|
><span class="mw-valign-text-bottom" typeof="mw:File"
|
||||||
|
><a href="/de/wiki/Diwasserstoff" title="Diwasserstoff"
|
||||||
|
><img
|
||||||
|
src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAQAICTAEAOw%3D%3D"
|
||||||
|
decoding="async"
|
||||||
|
loading="lazy"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
class="mw-file-element lazyload"
|
||||||
|
data-image-name="SUBSTANCE.LAUNCHSUB.1.png"
|
||||||
|
data-image-key="SUBSTANCE.LAUNCHSUB.1.png"
|
||||||
|
data-relevant="1"
|
||||||
|
data-src="https://static.wikia.nocookie.net/nomanssky_gamepedia/images/0/03/SUBSTANCE.LAUNCHSUB.1.png/revision/latest/scale-to-width-down/18?cb=20180725071716" /></a></span
|
||||||
|
></span>
|
||||||
|
<a href="/de/wiki/Diwasserstoff" title="Diwasserstoff"
|
||||||
|
><span class="itemlink ajaxttlink">Diwasserstoff</span></a
|
||||||
|
>
|
||||||
|
x1  +  
|
||||||
|
<span
|
||||||
|
class="resource-konzentrierterflüssigtreibstoff"
|
||||||
|
style="border: 1px solid #d3d3d3"
|
||||||
|
><span class="mw-valign-text-bottom" typeof="mw:File"
|
||||||
|
><a href="/de/wiki/Sauerstoff" title="Sauerstoff"
|
||||||
|
><img
|
||||||
|
src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAQAICTAEAOw%3D%3D"
|
||||||
|
decoding="async"
|
||||||
|
loading="lazy"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
class="mw-file-element lazyload"
|
||||||
|
data-image-name="SUBSTANCE.AIR.1.png"
|
||||||
|
data-image-key="SUBSTANCE.AIR.1.png"
|
||||||
|
data-relevant="1"
|
||||||
|
data-src="https://static.wikia.nocookie.net/nomanssky_gamepedia/images/e/ec/SUBSTANCE.AIR.1.png/revision/latest/scale-to-width-down/18?cb=20221007220715" /></a></span
|
||||||
|
></span>
|
||||||
|
<a href="/de/wiki/Sauerstoff" title="Sauerstoff"
|
||||||
|
><span class="itemlink ajaxttlink">Sauerstoff</span></a
|
||||||
|
>
|
||||||
|
x1  →  <span
|
||||||
|
class="resource-aquatischesmineral-extrakt"
|
||||||
|
style="border: 1px solid #d3d3d3"
|
||||||
|
><span class="mw-valign-text-bottom" typeof="mw:File"
|
||||||
|
><a href="/de/wiki/Salz" title="Salz"
|
||||||
|
><img
|
||||||
|
src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAQAICTAEAOw%3D%3D"
|
||||||
|
decoding="async"
|
||||||
|
loading="lazy"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
class="mw-file-element lazyload"
|
||||||
|
data-image-name="SUBSTANCE.WATER.1.png"
|
||||||
|
data-image-key="SUBSTANCE.WATER.1.png"
|
||||||
|
data-relevant="1"
|
||||||
|
data-src="https://static.wikia.nocookie.net/nomanssky_gamepedia/images/9/9f/SUBSTANCE.WATER.1.png/revision/latest/scale-to-width-down/18?cb=20180726042119" /></a></span
|
||||||
|
></span>
|
||||||
|
<strong class="mw-selflink selflink"
|
||||||
|
><span class="itemlink ajaxttlink">Salz</span></strong
|
||||||
|
>
|
||||||
|
x1  <small
|
||||||
|
>(<i>"Schnelle Formation/Verdunstung"</i>, 0,08 sek./Stück)</small
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
2
src/db/mod.rs
Normal file
2
src/db/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod models;
|
||||||
|
pub use models::*;
|
||||||
47
src/db/models.rs
Normal file
47
src/db/models.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// Generated by diesel_ext
|
||||||
|
|
||||||
|
#![allow(unused)]
|
||||||
|
#![allow(clippy::all)]
|
||||||
|
use diesel::{
|
||||||
|
prelude::Queryable,
|
||||||
|
sql_types::{Json, Uuid},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Queryable, Debug)]
|
||||||
|
pub struct RowIcon {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub name: String,
|
||||||
|
pub content_type: Option<String>,
|
||||||
|
pub url: Option<String>,
|
||||||
|
pub width: Option<i32>,
|
||||||
|
pub height: Option<i32>,
|
||||||
|
pub state: Option<Json>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Debug)]
|
||||||
|
pub struct RowIngredient {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub resource: Uuid,
|
||||||
|
pub quantity: i32,
|
||||||
|
pub state: Option<Json>,
|
||||||
|
pub recipe: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Debug)]
|
||||||
|
pub struct RowRecipe {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub resource: Uuid,
|
||||||
|
pub recipe_type: String,
|
||||||
|
pub duration: i32,
|
||||||
|
pub state: Option<Json>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Debug)]
|
||||||
|
pub struct RowResource {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub name: String,
|
||||||
|
pub title: String,
|
||||||
|
pub url: Option<String>,
|
||||||
|
pub icon: Option<Uuid>,
|
||||||
|
pub state: Option<Json>,
|
||||||
|
}
|
||||||
63
src/db/schema.rs
Normal file
63
src/db/schema.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// @generated automatically by Diesel CLI.
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
icon (id) {
|
||||||
|
id -> Uuid,
|
||||||
|
#[max_length = 255]
|
||||||
|
name -> Varchar,
|
||||||
|
#[max_length = 255]
|
||||||
|
content_type -> Nullable<Varchar>,
|
||||||
|
#[max_length = 512]
|
||||||
|
url -> Nullable<Varchar>,
|
||||||
|
width -> Nullable<Int4>,
|
||||||
|
height -> Nullable<Int4>,
|
||||||
|
state -> Nullable<Json>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
ingredient (id) {
|
||||||
|
id -> Uuid,
|
||||||
|
resource -> Uuid,
|
||||||
|
quantity -> Int4,
|
||||||
|
state -> Nullable<Json>,
|
||||||
|
recipe -> Uuid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
recipe (id) {
|
||||||
|
id -> Uuid,
|
||||||
|
resource -> Uuid,
|
||||||
|
#[max_length = 50]
|
||||||
|
recipe_type -> Varchar,
|
||||||
|
duration -> Int4,
|
||||||
|
state -> Nullable<Json>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
resource (id) {
|
||||||
|
id -> Uuid,
|
||||||
|
#[max_length = 255]
|
||||||
|
name -> Varchar,
|
||||||
|
#[max_length = 255]
|
||||||
|
title -> Varchar,
|
||||||
|
#[max_length = 512]
|
||||||
|
url -> Nullable<Varchar>,
|
||||||
|
icon -> Nullable<Uuid>,
|
||||||
|
state -> Nullable<Json>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::joinable!(ingredient -> recipe (recipe));
|
||||||
|
diesel::joinable!(ingredient -> resource (resource));
|
||||||
|
diesel::joinable!(recipe -> resource (resource));
|
||||||
|
diesel::joinable!(resource -> icon (icon));
|
||||||
|
|
||||||
|
diesel::allow_tables_to_appear_in_same_query!(
|
||||||
|
icon,
|
||||||
|
ingredient,
|
||||||
|
recipe,
|
||||||
|
resource,
|
||||||
|
);
|
||||||
35
src/main.rs
Normal file
35
src/main.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
mod db;
|
||||||
|
mod parse;
|
||||||
|
mod types;
|
||||||
|
use std::{fs::File, io::Read};
|
||||||
|
|
||||||
|
use parse::parse;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
env_logger::init();
|
||||||
|
let html = read("test_mordit.html")?;
|
||||||
|
parse(&html);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _download_file(url: &str, _path: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
// Some simple CLI args requirements...
|
||||||
|
|
||||||
|
eprintln!("Fetching {url:?}...");
|
||||||
|
|
||||||
|
// reqwest::blocking::get() is a convenience function.
|
||||||
|
//
|
||||||
|
// In most cases, you should create/build a reqwest::Client and reuse
|
||||||
|
// it for all requests.
|
||||||
|
let res = reqwest::blocking::get(url)?;
|
||||||
|
|
||||||
|
let body = res.text()?;
|
||||||
|
Ok(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(path: &str) -> Result<String, std::io::Error> {
|
||||||
|
let mut file = File::open(path)?;
|
||||||
|
let mut contents = String::new();
|
||||||
|
file.read_to_string(&mut contents)?;
|
||||||
|
Ok(contents)
|
||||||
|
}
|
||||||
581
src/parse/mod.rs
Normal file
581
src/parse/mod.rs
Normal file
@@ -0,0 +1,581 @@
|
|||||||
|
use crate::types::{Duration, Icon, Ingredient, Recipe, RecipeType, Resource, ResourceState};
|
||||||
|
use regex::Regex;
|
||||||
|
use select::{
|
||||||
|
document::Document,
|
||||||
|
node::Node,
|
||||||
|
predicate::{Attr, Name, Or},
|
||||||
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
enum ParseType {
|
||||||
|
Link { url: String, title: String },
|
||||||
|
Img(Icon),
|
||||||
|
Count(u32),
|
||||||
|
Resource(String),
|
||||||
|
ResourceAdd,
|
||||||
|
ResourceLast,
|
||||||
|
DashDash,
|
||||||
|
Duration(Duration),
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResourceTmp = (
|
||||||
|
Option<String>, // name
|
||||||
|
Option<String>, // title
|
||||||
|
Option<Icon>, // icon
|
||||||
|
Option<u32>, // count
|
||||||
|
Option<Duration>, // duration
|
||||||
|
);
|
||||||
|
|
||||||
|
pub fn parse(html: &str) {
|
||||||
|
let document = Document::from(html);
|
||||||
|
|
||||||
|
let mut map_resource: HashMap<String, Resource> = HashMap::new();
|
||||||
|
let mut recipes: Vec<Recipe> = Vec::new();
|
||||||
|
|
||||||
|
parse_source(&document, &mut map_resource, &mut recipes);
|
||||||
|
|
||||||
|
parse_dst(
|
||||||
|
&document,
|
||||||
|
"Raffination",
|
||||||
|
RecipeType::Refining,
|
||||||
|
&mut map_resource,
|
||||||
|
&mut recipes,
|
||||||
|
);
|
||||||
|
|
||||||
|
parse_dst(
|
||||||
|
&document,
|
||||||
|
"Herstellung",
|
||||||
|
RecipeType::Production,
|
||||||
|
&mut map_resource,
|
||||||
|
&mut recipes,
|
||||||
|
);
|
||||||
|
|
||||||
|
parse_dst(
|
||||||
|
&document,
|
||||||
|
"Raffination_2",
|
||||||
|
RecipeType::Refining,
|
||||||
|
&mut map_resource,
|
||||||
|
&mut recipes,
|
||||||
|
);
|
||||||
|
|
||||||
|
parse_dst(
|
||||||
|
&document,
|
||||||
|
"Kochen",
|
||||||
|
RecipeType::Cooking,
|
||||||
|
&mut map_resource,
|
||||||
|
&mut recipes,
|
||||||
|
);
|
||||||
|
|
||||||
|
print_recipe(&recipes, RecipeType::Refining);
|
||||||
|
print_recipe(&recipes, RecipeType::Production);
|
||||||
|
print_recipe(&recipes, RecipeType::Cooking);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn first<T>(iter: &mut impl Iterator<Item = T>) -> Option<T> {
|
||||||
|
iter.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn first_child_element(node: Node<'_>) -> Option<Node<'_>> {
|
||||||
|
if node.children().next().is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut next = node.children().next();
|
||||||
|
|
||||||
|
while next.is_some() {
|
||||||
|
let child = next.unwrap();
|
||||||
|
|
||||||
|
if child.name().is_some() {
|
||||||
|
return Some(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
next = child.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_resource_items(
|
||||||
|
resource_items: Vec<ParseType>,
|
||||||
|
recipe_type: RecipeType,
|
||||||
|
map_resource: &mut HashMap<String, Resource>,
|
||||||
|
) -> Option<Recipe> {
|
||||||
|
let mut tmp_resource: ResourceTmp = (None, None, None, None, None);
|
||||||
|
|
||||||
|
let mut ingredient_to_add: Vec<Ingredient> = Vec::new();
|
||||||
|
let mut not_add = false;
|
||||||
|
|
||||||
|
for item in resource_items.iter() {
|
||||||
|
match item {
|
||||||
|
ParseType::Link { url, title } => {
|
||||||
|
if tmp_resource.0.is_none() {
|
||||||
|
tmp_resource.0 = Some(title.to_string());
|
||||||
|
}
|
||||||
|
if tmp_resource.1.is_none() {
|
||||||
|
tmp_resource.1 = Some(url.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// println!("Link: {} - {}", url, title);
|
||||||
|
}
|
||||||
|
ParseType::Img(icon) => {
|
||||||
|
if tmp_resource.2.is_none() {
|
||||||
|
tmp_resource.2 = Some(icon.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParseType::Count(count) => {
|
||||||
|
if tmp_resource.3.is_none() {
|
||||||
|
tmp_resource.3 = Some(*count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParseType::Resource(resource) => {
|
||||||
|
// println!("Resource: {}", resource);
|
||||||
|
if tmp_resource.0.is_none() {
|
||||||
|
tmp_resource.0 = Some(resource.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParseType::ResourceAdd => {
|
||||||
|
if !not_add {
|
||||||
|
add(&tmp_resource, map_resource, &mut ingredient_to_add);
|
||||||
|
}
|
||||||
|
|
||||||
|
not_add = false;
|
||||||
|
|
||||||
|
tmp_resource = (None, None, None, None, None); // Reset for next resource
|
||||||
|
// println!("ResourceAdd");
|
||||||
|
}
|
||||||
|
ParseType::ResourceLast => {
|
||||||
|
if !not_add {
|
||||||
|
add(&tmp_resource, map_resource, &mut ingredient_to_add);
|
||||||
|
}
|
||||||
|
|
||||||
|
not_add = false;
|
||||||
|
|
||||||
|
tmp_resource = (None, None, None, None, None); // Reset for next resource
|
||||||
|
// println!("ResourceLast");
|
||||||
|
}
|
||||||
|
ParseType::Duration(duration) => {
|
||||||
|
// println!(">>> Duration: {} {}", duration, unit);
|
||||||
|
|
||||||
|
if tmp_resource.4.is_none() {
|
||||||
|
tmp_resource.4 = Some(duration.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParseType::DashDash => {
|
||||||
|
not_add = false;
|
||||||
|
tmp_resource = (None, None, None, None, None); // Reset for next resource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (_, ingredient) = create_resource_and_ingredient(&tmp_resource, map_resource);
|
||||||
|
|
||||||
|
if !ingredient_to_add.is_empty() {
|
||||||
|
let recipe = crate::types::Recipe {
|
||||||
|
recipe_type: recipe_type,
|
||||||
|
resource: ingredient,
|
||||||
|
duration: tmp_resource.4.unwrap_or(Duration {
|
||||||
|
millis: 0,
|
||||||
|
unit: "Stück".to_string(),
|
||||||
|
}),
|
||||||
|
ingredients: ingredient_to_add,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Some(recipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_recipe(recipes: &Vec<Recipe>, recipe_type: RecipeType) {
|
||||||
|
for recipe in recipes
|
||||||
|
.iter()
|
||||||
|
.filter(|recipe| recipe.recipe_type == recipe_type)
|
||||||
|
{
|
||||||
|
println!("Recipe Type: {:?}", recipe.recipe_type);
|
||||||
|
println!(
|
||||||
|
"Resource: {} ({})",
|
||||||
|
recipe.resource.resource.name, recipe.resource.quantity
|
||||||
|
);
|
||||||
|
println!("Duration: {} ms", recipe.duration.millis);
|
||||||
|
println!("Ingredients:");
|
||||||
|
for ingredient in &recipe.ingredients {
|
||||||
|
println!(
|
||||||
|
"- {} ({} x {})",
|
||||||
|
ingredient.resource.name, ingredient.quantity, ingredient.resource.title
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_dst(
|
||||||
|
document: &Document,
|
||||||
|
id: &str,
|
||||||
|
recipe_type: RecipeType,
|
||||||
|
map_resource: &mut HashMap<String, Resource>,
|
||||||
|
recipes: &mut Vec<Recipe>,
|
||||||
|
) -> bool {
|
||||||
|
let mut dest = document.find(Attr("id", id));
|
||||||
|
let dest = first(&mut dest);
|
||||||
|
|
||||||
|
if dest.is_none() {
|
||||||
|
eprintln!("No element found with the selector '#{}'", id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dst = dest.unwrap();
|
||||||
|
let mut dst = dst.parent().unwrap();
|
||||||
|
|
||||||
|
let mut elt_ul = None;
|
||||||
|
|
||||||
|
while dst.next().is_some() {
|
||||||
|
dst = dst.next().unwrap();
|
||||||
|
|
||||||
|
if let Some(name) = dst.name() {
|
||||||
|
if name == "ul" {
|
||||||
|
elt_ul = Some(dst);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "h2" {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let first_child = first_child_element(dst);
|
||||||
|
if name == "h3"
|
||||||
|
&& first_child.is_some()
|
||||||
|
&& first_child.unwrap().name().unwrap() == "span"
|
||||||
|
&& first_child.unwrap().attr("id").is_some()
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if elt_ul.is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let elt_ul = elt_ul.unwrap();
|
||||||
|
let li = elt_ul.find(Name("li"));
|
||||||
|
|
||||||
|
for item in li {
|
||||||
|
if let Some(recipe) = parse_li_to_resource(&item, recipe_type.clone(), map_resource) {
|
||||||
|
recipes.push(recipe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_source(
|
||||||
|
document: &Document,
|
||||||
|
map_resource: &mut HashMap<String, Resource>,
|
||||||
|
recipes: &mut Vec<Recipe>,
|
||||||
|
) -> bool {
|
||||||
|
let mut source = document.find(Attr("id", "Quelle"));
|
||||||
|
let source = first(&mut source);
|
||||||
|
|
||||||
|
if source.is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let source = source.unwrap();
|
||||||
|
let mut source = source.parent().unwrap();
|
||||||
|
|
||||||
|
let mut c = 0;
|
||||||
|
let mut elt_ul = None;
|
||||||
|
|
||||||
|
while source.next().is_some() {
|
||||||
|
source = source.next().unwrap();
|
||||||
|
|
||||||
|
if let Some(name) = source.name() {
|
||||||
|
if name == "ul" {
|
||||||
|
c += 1;
|
||||||
|
|
||||||
|
if c > 1 {
|
||||||
|
elt_ul = Some(source);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "h2" {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if elt_ul.is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let elt_ul = elt_ul.unwrap();
|
||||||
|
let li = elt_ul.find(Name("li"));
|
||||||
|
|
||||||
|
for item in li {
|
||||||
|
if let Some(recipe) = parse_li_to_resource(&item, RecipeType::Refining, map_resource) {
|
||||||
|
recipes.push(recipe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_li_to_resource(
|
||||||
|
item: &Node<'_>,
|
||||||
|
recipe_type: RecipeType,
|
||||||
|
map_resource: &mut HashMap<String, Resource>,
|
||||||
|
) -> Option<Recipe> {
|
||||||
|
if item.children().next().is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut resource_items: Vec<ParseType> = Vec::new();
|
||||||
|
|
||||||
|
let selector = item.find(Or(
|
||||||
|
Name("strong"),
|
||||||
|
Or(Or(Name("span"), Name("a")), Or(Name("img"), Name("small"))),
|
||||||
|
));
|
||||||
|
|
||||||
|
for child in selector {
|
||||||
|
let name = child.name().unwrap();
|
||||||
|
|
||||||
|
if name == "a" && child.attr("href").is_some() && child.attr("title").is_some() {
|
||||||
|
let txt = get_text_next(&child);
|
||||||
|
|
||||||
|
resource_items.push(ParseType::Link {
|
||||||
|
url: child.attr("href").unwrap().to_string(),
|
||||||
|
title: child.attr("title").unwrap().to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if !txt.is_empty() {
|
||||||
|
parse_text(&txt, &mut resource_items);
|
||||||
|
}
|
||||||
|
if txt == "--" {
|
||||||
|
resource_items.push(ParseType::DashDash);
|
||||||
|
}
|
||||||
|
} else if name == "img"
|
||||||
|
&& child.attr("data-src").is_some()
|
||||||
|
&& child.attr("width").is_some()
|
||||||
|
&& child.attr("height").is_some()
|
||||||
|
&& child.attr("data-image-name").is_some()
|
||||||
|
{
|
||||||
|
let url = child.attr("data-src").unwrap();
|
||||||
|
let name = child.attr("data-image-name").unwrap();
|
||||||
|
let width = child.attr("width").unwrap().parse().unwrap_or(0);
|
||||||
|
let height = child.attr("height").unwrap().parse().unwrap_or(0);
|
||||||
|
|
||||||
|
resource_items.push(ParseType::Img(Icon {
|
||||||
|
name: name.to_string(),
|
||||||
|
url: url.to_string(),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
content_type: "image/png".to_string(), // Assuming PNG, adjust as needed
|
||||||
|
}));
|
||||||
|
} else if name == "span"
|
||||||
|
&& !child.text().is_empty()
|
||||||
|
&& (child.parent().unwrap().name().unwrap() == "strong"
|
||||||
|
|| child.parent().unwrap().name().unwrap() == "span")
|
||||||
|
{
|
||||||
|
let txt = child.text().trim().to_string();
|
||||||
|
resource_items.push(ParseType::Resource(txt));
|
||||||
|
let txt = get_text_next(&child.parent().unwrap());
|
||||||
|
parse_text(&txt, &mut resource_items);
|
||||||
|
} else if name == "strong" {
|
||||||
|
// let txt = get_text_next(&child);
|
||||||
|
// parse_text(&txt, &mut resource_items);
|
||||||
|
} else if name == "small" {
|
||||||
|
let txt = get_text(&child);
|
||||||
|
parse_text(&txt, &mut resource_items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_resource_items(resource_items, recipe_type, map_resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_name(name: &str) -> String {
|
||||||
|
normalize_text(name).replace(" ", "_").to_lowercase()
|
||||||
|
}
|
||||||
|
fn create_resource_and_ingredient(
|
||||||
|
tmp_resource: &ResourceTmp,
|
||||||
|
map_resource: &mut HashMap<String, Resource>,
|
||||||
|
) -> (Resource, Ingredient) {
|
||||||
|
let title = tmp_resource.0.as_ref().unwrap().clone();
|
||||||
|
let name = to_name(&title);
|
||||||
|
let url = tmp_resource.1.clone();
|
||||||
|
let icon = tmp_resource.2.clone();
|
||||||
|
let count = tmp_resource.3.unwrap_or(1);
|
||||||
|
|
||||||
|
let mut resource = Resource {
|
||||||
|
name: name.clone(),
|
||||||
|
title,
|
||||||
|
url,
|
||||||
|
icon,
|
||||||
|
state: ResourceState::Unparsed,
|
||||||
|
};
|
||||||
|
|
||||||
|
if map_resource.contains_key(&name) {
|
||||||
|
let res = map_resource.get_mut(&name).unwrap();
|
||||||
|
|
||||||
|
if res.url.is_some() {
|
||||||
|
resource.url = res.url.clone();
|
||||||
|
}
|
||||||
|
if res.icon.is_some() {
|
||||||
|
resource.icon = res.icon.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ingredient = Ingredient {
|
||||||
|
resource: resource.clone(),
|
||||||
|
quantity: count,
|
||||||
|
};
|
||||||
|
|
||||||
|
(resource, ingredient)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(
|
||||||
|
tmp_resource: &ResourceTmp,
|
||||||
|
map_resource: &mut HashMap<String, Resource>,
|
||||||
|
ingredient_to_add: &mut Vec<Ingredient>,
|
||||||
|
) {
|
||||||
|
if tmp_resource.0.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (resource, ingredient) = create_resource_and_ingredient(tmp_resource, map_resource);
|
||||||
|
|
||||||
|
map_resource.insert(resource.name.clone(), resource);
|
||||||
|
|
||||||
|
ingredient_to_add.push(ingredient);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_text(text: &str) -> String {
|
||||||
|
let mut text = text
|
||||||
|
.trim()
|
||||||
|
.replace('\n', " ")
|
||||||
|
.replace('\r', " ")
|
||||||
|
.replace('\t', " ");
|
||||||
|
|
||||||
|
for c in text.clone().chars() {
|
||||||
|
if c.is_control() || c.is_whitespace() {
|
||||||
|
text = text.replace(c, " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while text.contains(" ") {
|
||||||
|
text = text.replace(" ", " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
text
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_text(node: &Node<'_>) -> String {
|
||||||
|
let mut text = String::new();
|
||||||
|
|
||||||
|
if node.as_text().is_some() {
|
||||||
|
text.push_str(node.as_text().unwrap().to_string().as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.children().next().is_some() {
|
||||||
|
for child in node.descendants() {
|
||||||
|
if child.as_text().is_some() {
|
||||||
|
text.push_str(child.as_text().unwrap().to_string().as_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.next().is_some() {
|
||||||
|
let mut next = node.next();
|
||||||
|
|
||||||
|
while next.is_some() {
|
||||||
|
let next_node = next.unwrap();
|
||||||
|
|
||||||
|
if next_node.as_text().is_some() {
|
||||||
|
text.push_str(next_node.as_text().unwrap());
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
next = next_node.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalize_text(&text);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_text_next(node: &Node<'_>) -> String {
|
||||||
|
if node.as_text().is_some() {
|
||||||
|
return normalize_text(&node.as_text().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let next = node.next();
|
||||||
|
|
||||||
|
if !next.is_some() {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let next = next.unwrap();
|
||||||
|
|
||||||
|
if next.as_text().is_some() {
|
||||||
|
let mut text = next.as_text().unwrap().trim().to_string();
|
||||||
|
let mut next = next.next();
|
||||||
|
|
||||||
|
while next.is_some() {
|
||||||
|
let node = next.unwrap();
|
||||||
|
next = node.next();
|
||||||
|
|
||||||
|
if node.as_text().is_some() {
|
||||||
|
text.push_str(node.as_text().unwrap().trim());
|
||||||
|
} else if node.name().is_some() && node.name().unwrap() == "i" {
|
||||||
|
text.push_str(node.text().trim());
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalize_text(&text);
|
||||||
|
}
|
||||||
|
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_text(text: &str, resource_items: &mut Vec<ParseType>) {
|
||||||
|
let reg_count_next = Regex::new(r"^\s*x(?<count>\d+)\s+(?<end>[→+])\s*$").unwrap();
|
||||||
|
let reg_count = Regex::new(r"^\s*x(?<count>\d+)\s*.*$").unwrap();
|
||||||
|
let reg_duration =
|
||||||
|
Regex::new(r"^.*\(.*(?<duration>\d+(|,\d+))\ssek\./(?<unit>\w+)\s*\)$").unwrap();
|
||||||
|
|
||||||
|
if let Some(res) = reg_count_next.captures(text) {
|
||||||
|
let count = res.name("count").unwrap().as_str().parse().unwrap_or(0);
|
||||||
|
|
||||||
|
resource_items.push(ParseType::Count(count));
|
||||||
|
|
||||||
|
let end = res.name("end").unwrap().as_str().to_string();
|
||||||
|
|
||||||
|
if end == "+" {
|
||||||
|
resource_items.push(ParseType::ResourceAdd);
|
||||||
|
} else {
|
||||||
|
resource_items.push(ParseType::ResourceLast);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(res) = reg_count.captures(text) {
|
||||||
|
let count = res.name("count").unwrap().as_str().parse().unwrap_or(0);
|
||||||
|
|
||||||
|
resource_items.push(ParseType::Count(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(res) = reg_duration.captures(text) {
|
||||||
|
let duration_str = res.name("duration").unwrap().as_str();
|
||||||
|
let duration: f64 = duration_str.replace(',', ".").parse().unwrap_or(0.0);
|
||||||
|
let unit = res.name("unit").unwrap().as_str().to_string();
|
||||||
|
let duration: u64 = (duration * 1000.0) as u64; // Convert to milliseconds
|
||||||
|
|
||||||
|
resource_items.push(ParseType::Duration(Duration {
|
||||||
|
millis: duration,
|
||||||
|
unit,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/test.html
Normal file
8
src/test.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<img
|
||||||
|
src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAQAICTAEAOw%3D%3D"
|
||||||
|
width="18"
|
||||||
|
/>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
2
src/types/mod.rs
Normal file
2
src/types/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod types;
|
||||||
|
pub use types::*;
|
||||||
61
src/types/types.rs
Normal file
61
src/types/types.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub enum ResourceState {
|
||||||
|
Parsed(bool),
|
||||||
|
Unparsed,
|
||||||
|
}
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct Icon {
|
||||||
|
pub name: String,
|
||||||
|
pub url: String,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub content_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Icon {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Icon {
|
||||||
|
name: self.name.clone(),
|
||||||
|
url: self.url.clone(),
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
content_type: self.content_type.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Resource {
|
||||||
|
pub name: String,
|
||||||
|
pub title: String,
|
||||||
|
pub url: Option<String>,
|
||||||
|
pub icon: Option<Icon>,
|
||||||
|
pub state: ResourceState,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Ingredient {
|
||||||
|
pub resource: Resource,
|
||||||
|
pub quantity: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub enum RecipeType {
|
||||||
|
Production,
|
||||||
|
Refining,
|
||||||
|
Cooking,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Duration {
|
||||||
|
pub millis: u64,
|
||||||
|
pub unit: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Recipe {
|
||||||
|
pub recipe_type: RecipeType,
|
||||||
|
pub resource: Ingredient,
|
||||||
|
pub duration: Duration,
|
||||||
|
pub ingredients: Vec<Ingredient>,
|
||||||
|
}
|
||||||
3
src/util/cmd.rs
Normal file
3
src/util/cmd.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub fn parse_command_line_args() -> Vec<String> {
|
||||||
|
std::env::args().skip(1).collect()
|
||||||
|
}
|
||||||
2
src/util/mod.rs
Normal file
2
src/util/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod cmd;
|
||||||
|
pub use cmd::*;
|
||||||
3501
test_mordit.html
Normal file
3501
test_mordit.html
Normal file
File diff suppressed because one or more lines are too long
3877
test_vc.html
Normal file
3877
test_vc.html
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user