diff options
Diffstat (limited to 'server')
| -rw-r--r-- | server/gleam.toml | 22 | ||||
| -rw-r--r-- | server/manifest.toml | 25 | ||||
| -rw-r--r-- | server/src/server.gleam | 200 |
3 files changed, 247 insertions, 0 deletions
diff --git a/server/gleam.toml b/server/gleam.toml new file mode 100644 index 0000000..d97c632 --- /dev/null +++ b/server/gleam.toml @@ -0,0 +1,22 @@ +name = "server" +version = "1.0.0" + +# Fill out these fields if you intend to generate HTML documentation or publish +# your project to the Hex package manager. +# +# description = "" +# licences = ["Apache-2.0"] +# repository = { type = "github", user = "username", repo = "project" } +# links = [{ title = "Website", href = "https://gleam.run" }] +# +# For a full reference of all the available options, you can have a look at +# https://gleam.run/writing-gleam/gleam-toml/. + +[dependencies] +gleam_stdlib = ">= 0.34.0 and < 2.0.0" +xmlm = ">= 1.0.0 and < 2.0.0" +gleam_http = ">= 3.6.0 and < 4.0.0" +gleam_hackney = ">= 1.2.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/server/manifest.toml b/server/manifest.toml new file mode 100644 index 0000000..3911bd6 --- /dev/null +++ b/server/manifest.toml @@ -0,0 +1,25 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "certifi", version = "2.12.0", build_tools = ["rebar3"], requirements = [], otp_app = "certifi", source = "hex", outer_checksum = "EE68D85DF22E554040CDB4BE100F33873AC6051387BAF6A8F6CE82272340FF1C" }, + { name = "gleam_hackney", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib", "hackney"], otp_app = "gleam_hackney", source = "hex", outer_checksum = "066B1A55D37DBD61CC72A1C4EDE43C6015B1797FAF3818C16FE476534C7B6505" }, + { name = "gleam_http", version = "3.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "8C07DF9DF8CC7F054C650839A51C30A7D3C26482AC241C899C1CEA86B22DBE51" }, + { name = "gleam_stdlib", version = "0.38.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "663CF11861179AF415A625307447775C09404E752FF99A24E2057C835319F1BE" }, + { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, + { name = "hackney", version = "1.20.1", build_tools = ["rebar3"], requirements = ["certifi", "idna", "metrics", "mimerl", "parse_trans", "ssl_verify_fun", "unicode_util_compat"], otp_app = "hackney", source = "hex", outer_checksum = "FE9094E5F1A2A2C0A7D10918FEE36BFEC0EC2A979994CFF8CFE8058CD9AF38E3" }, + { name = "idna", version = "6.1.1", build_tools = ["rebar3"], requirements = ["unicode_util_compat"], otp_app = "idna", source = "hex", outer_checksum = "92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA" }, + { name = "metrics", version = "1.0.1", build_tools = ["rebar3"], requirements = [], otp_app = "metrics", source = "hex", outer_checksum = "69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16" }, + { name = "mimerl", version = "1.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "mimerl", source = "hex", outer_checksum = "A1E15A50D1887217DE95F0B9B0793E32853F7C258A5CD227650889B38839FE9D" }, + { name = "parse_trans", version = "3.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "parse_trans", source = "hex", outer_checksum = "620A406CE75DADA827B82E453C19CF06776BE266F5A67CFF34E1EF2CBB60E49A" }, + { name = "ssl_verify_fun", version = "1.1.7", build_tools = ["mix", "rebar3", "make"], requirements = [], otp_app = "ssl_verify_fun", source = "hex", outer_checksum = "FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8" }, + { name = "unicode_util_compat", version = "0.7.0", build_tools = ["rebar3"], requirements = [], otp_app = "unicode_util_compat", source = "hex", outer_checksum = "25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521" }, + { name = "xmlm", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "xmlm", source = "hex", outer_checksum = "295F483E1A930DC4C45ED0F07C3466C0A7381972C3C2FC64D66BD2FBB307ED6A" }, +] + +[requirements] +gleam_hackney = { version = ">= 1.2.0 and < 2.0.0"} +gleam_http = { version = ">= 3.6.0 and < 4.0.0" } +gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } +gleeunit = { version = ">= 1.0.0 and < 2.0.0" } +xmlm = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/server/src/server.gleam b/server/src/server.gleam new file mode 100644 index 0000000..2ea7b3e --- /dev/null +++ b/server/src/server.gleam @@ -0,0 +1,200 @@ +import gleam/dict.{type Dict} +import gleam/hackney +import gleam/http +import gleam/http/request +import gleam/io +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/result +import gleam/string +import gleam/uri +import xmlm + +type RssFeed { + RssFeed(title: String, link: String, items: List(RssItem)) +} + +type RssItem { + RssItem(title: String, link: String, description: String) +} + +// iterate through top level signals element to form rss feed. +// top level is the RssFeed +// top level's children are RssItem +fn signals_to_rss_feed(signals: #(List(xmlm.Signal), xmlm.Input)) { + let _ = io.debug(list.last(signals.0)) + let _ = io.debug(signals.1) + + case xmlm.signal(signals.1) { + Ok(#(xmlm.ElementStart(e), input)) -> { + io.debug(e) + io.debug(input) + + Nil + } + _ -> { + io.debug("error") + + Nil + } + } + + "" +} + +pub fn main() { + let feeds = [ + "https://www.reddit.com/.rss", "https://www.reddit.com/r/programming/.rss", + ] + + let _body = + list.map(feeds, fn(feed) { + let assert Ok(uri) = uri.parse(feed) + let assert Ok(request) = request.from_uri(uri) + + case hackney.send(request) { + Ok(response) -> { + let _ = io.debug(parse_rss(response.body)) + + case + response.body + |> xmlm.from_string + |> xmlm.with_stripping(True) + |> xmlm.signals + { + Ok(signals) -> { + signals_to_rss_feed(signals) + } + Error(error) -> { + io.debug(error) + + "" + } + } + + "" + } + Error(error) -> { + io.debug(error) + + "" + } + } + }) +} + +fn parse_rss(xml: String) -> Result(RssFeed, String) { + let root = parse_element(xml) + case find_element(root, "channel") { + Some(channel) -> { + let title = find_element_text(channel, "title") |> result.unwrap_both + let link = find_element_text(channel, "link") |> result.unwrap_both + let items = + find_elements(channel, "item") + |> list.map(parse_item) + |> result.all + case items { + Ok(items) -> Ok(RssFeed(title, link, items)) + Error(e) -> Error(e) + } + } + None -> Error("Channel element not found") + } +} + +fn parse_item(item: Element) -> Result(RssItem, String) { + let title = find_element_text(item, "title") |> result.unwrap_both + let link = find_element_text(item, "link") |> result.unwrap_both + let description = find_element_text(item, "description") |> result.unwrap_both + Ok(RssItem(title, link, description)) +} + +type Element { + Element( + name: String, + attributes: Dict(String, String), + children: List(Element), + text: String, + ) +} + +fn parse_element(xml: String) -> Element { + // Very basic parsing implementation + // This won't handle all XML cases, just a simple subset for RSS + let name = extract_tag_name(xml) + let text = extract_text(xml) + let children = extract_children(xml) + Element(name, dict.new(), children, text) +} + +fn string_find(s: String, sub: String) -> Int { + case + string.split(s, "") + |> list.index_map(fn(x, i) { #(i, x) }) + |> list.filter(fn(a) { a.1 == sub }) + |> list.first + { + Ok(i) -> i.0 + _ -> 0 + } +} + +fn string_rfind(s: String, sub: String, default) -> Int { + case + string.split(s, "") + |> list.index_map(fn(x, i) { #(i, x) }) + |> list.filter(fn(a) { a.1 == sub }) + |> list.last + { + Ok(i) -> i.0 + _ -> default + } +} + +fn extract_tag_name(xml: String) -> String { + // Extract the tag name from the XML string + // This is a simple implementation + let start = string_find(xml, "<") + let end = string_find(xml, ">") + string.slice(xml, start + 1, end) +} + +fn extract_text(xml: String) -> String { + // Extract the text content from the XML string + // This is a simple implementation + let start = string_find(xml, ">") + let end = string_rfind(xml, "<", string.length(xml)) + string.slice(xml, start + 1, end) +} + +fn extract_children(xml: String) -> List(Element) { + // Extract child elements from the XML string + // This is a simple implementation + let children_xml = + string.split(xml, "</") + |> list.filter(fn(part) { string.contains(part, "<") }) + |> list.map(parse_element) + children_xml +} + +fn find_element(element: Element, tag: String) -> Option(Element) { + // element.children + // |> list.find(fn(child) { child.name == tag }) + + case list.find(element.children, fn(child) { child.name == tag }) { + Ok(child) -> Some(child) + _ -> None + } +} + +fn find_elements(element: Element, tag: String) -> List(Element) { + element.children + |> list.filter(fn(child) { child.name == tag }) +} + +fn find_element_text(element: Element, tag: String) -> Result(String, String) { + case find_element(element, tag) { + Some(child) -> Ok(child.text) + None -> Error("Element not found") + } +} |