]> git.kianting.info Git - taikoothong/commitdiff
initial table
authorTan Kian-ting <chenjt30@gmail.com>
Mon, 10 Jul 2023 16:49:33 +0000 (00:49 +0800)
committerTan Kian-ting <chenjt30@gmail.com>
Mon, 10 Jul 2023 17:07:04 +0000 (01:07 +0800)
Cargo.toml [new file with mode: 0644]
src/assets/style.css [new file with mode: 0644]
src/main.bk [new file with mode: 0644]
src/main.rs [new file with mode: 0644]
src/tests.rs [new file with mode: 0644]
templates/tw_stock.html.hbs [new file with mode: 0644]

diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644 (file)
index 0000000..0ccfec4
--- /dev/null
@@ -0,0 +1,25 @@
+[package]
+name = "taikoothong"
+version = "0.0.0"
+workspace = "../"
+edition = "2021"
+publish = false
+
+
+
+[dependencies]
+curl = "0.4.28"
+serde = {version = "1.0.51", features = ["derive"]}
+serde_json = "1.0.51"
+anyhow = "1.0.28"
+chrono = "0.4.11"
+rocket = "0.5.0-rc.2"
+round = "0.1.0"
+num-format = "0.4.4"
+
+[dependencies.rocket_dyn_templates]
+rocket_dyn_templates = "0.5.0-rc.3-2023-06-09"
+features = ["handlebars"]
+
+[toolchain]
+channel = "nightly" # nassessary to install rocket
diff --git a/src/assets/style.css b/src/assets/style.css
new file mode 100644 (file)
index 0000000..0bb7822
--- /dev/null
@@ -0,0 +1,8 @@
+entry{
+    min-width: 5em;
+  }
+
+  entry#entry-2-0{
+    color: red;
+  }
+
diff --git a/src/main.bk b/src/main.bk
new file mode 100644 (file)
index 0000000..55724fa
--- /dev/null
@@ -0,0 +1,200 @@
+use gtk4::prelude::*;
+use gtk4::glib;
+use gtk4::*;
+use gdk4::Display;
+use serde_json::{Result, Value}; // JSON
+use anyhow; // Exception Handling
+use chrono::{Utc, TimeZone};
+use curl::easy::Easy;
+
+enum Date{
+    Day(u8),
+    Week(u8),
+    Month(u8),
+    YearToDate, // days of current year
+    Max
+}
+impl Date {
+    fn as_str(&self) -> String {
+        match &self{
+            Date::Day(d) =>  format!("{:?}d", d),
+            Date::Week(wk) =>  format!("{:?}wk", wk),
+            Date::Month(mo) =>  format!("{:?}mo", mo),
+            Date::YearToDate => format!("ytd"),
+            Date::Max =>  format!("max"),
+        }
+    }
+}
+
+// Try visiting:
+//   http://127.0.0.1:8000/wave/Rocketeer/100
+
+
+fn get_tw_stock(stock_id: &str) -> Option<Vec<String>> {
+    // let a = "👋 Hello, stock no: a22";
+
+    let response_body = get_stock_data(stock_id, Date::Day(1),Date::Week(1));
+    let response_json: Value =  serde_json::from_str(response_body.as_str()).ok()?;
+
+    let days_in_unix_time  = &response_json["chart"]["result"][0]["timestamp"];
+
+    let days_in_custom_format = match days_in_unix_time {
+        serde_json::Value::Array(days_vec) =>  days_vec
+                                            .iter()
+                                            .map(|day|
+                                                {json_unix_time_to_date(day)})
+                                            .collect::<Vec<_>>(),
+        _ => vec![format!("Not a series of date")],
+    };
+
+    let price_and_volume_infos  = &response_json["chart"]["result"][0]["indicators"]["quote"][0];
+
+    print!("{:}", price_and_volume_infos);
+            
+    
+    return Some(days_in_custom_format);    
+}
+
+fn json_unix_time_to_date(json_value : &Value) -> String{
+
+    let unix_time = json_value.as_i64().unwrap();
+    println!("{:?}", unix_time);
+
+
+    let naive_time = Utc.timestamp_opt(unix_time, 0).unwrap();
+    
+    let date = format!("{}", naive_time.format("%Y-%m-%d"));
+    println!("{:?}", date);
+
+    return date;
+}
+
+fn get_stock_data(stock_id : &str, interval : Date, range : Date) -> 
+        String {
+
+    let intrval_str = interval.as_str();
+    let range_str = range.as_str();
+
+    let url = format!("https://query1.finance.yahoo.com/v8/finance/chart/\
+        {:}.TW?metrics=Date,High,Low,Open,Close,Volume&interval={:}&range={:}", stock_id,
+        intrval_str, range_str);
+
+    let mut curl_easy = Easy::new(); // fetch the data with the curl binding
+    let mut response = String::new(); 
+
+    {
+        curl_easy.url(url.as_str()).unwrap();
+
+        
+        let mut curl_transfer = curl_easy.transfer();
+
+        curl_transfer.write_function(|data| {
+            response.push_str(std::str::from_utf8(data).unwrap());  
+            Ok(data.len())
+        }).unwrap();
+    
+        curl_transfer.perform().unwrap();
+    }
+
+    let response_returned = response.clone();
+
+
+
+    return response_returned;
+
+
+}
+
+fn main() -> glib::ExitCode{
+    let application = Application::builder()
+        .application_id("info.kianting.sns.taiuankoo") // app ê id
+        .build();
+
+    application.connect_startup(|_| load_css());
+    application.connect_activate(build_ui); // 連結起做ui ê
+    application.run() // 紡app
+}
+
+fn load_css() {
+    let provider = CssProvider::new();
+
+    //載css kàu style provider
+    provider.load_from_data(include_str!("./assets/style.css"));
+
+    // kā載入去
+    gtk4::style_context_add_provider_for_display(
+        &gdk4::Display::default().expect("無法連結到顯示"),
+        &provider,
+        gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION,
+    );
+}
+
+
+// 起做ui
+fn build_ui(application: &gtk4::Application) {
+    // Create a new window, set its title and default size
+    let window = gtk4::ApplicationWindow::new(application);
+    window.set_title(Some("臺灣股"));
+    window.set_default_size(1200, 1000);
+
+    let main_grid = Grid::builder()
+        .margin_start(1) // 倒爿ê邊仔留空白ê闊度
+        .margin_end(1) // 正爿ê邊仔留空白ê闊度
+        .margin_top(1)// 頂懸ê邊仔留空白ê懸度
+        .margin_bottom(1) // 下底ê邊仔留空白ê懸度
+        .halign(Align::Start)//水平排列
+        .valign(Align::Start)//垂直排列
+        .row_spacing(1)// 逐列ê縫留空白ê懸度
+        .column_spacing(1)// 逐欄ê縫留空白ê闊度
+        .build();
+    window.set_child(Some(&main_grid));
+
+
+    // create and add the wrapper scrolled to the window
+    let data_grid_scrolled_wrapper = ScrolledWindow::new();
+    data_grid_scrolled_wrapper.set_min_content_width(800);
+
+    main_grid.attach(&data_grid_scrolled_wrapper, 1, 1, 1, 1);
+
+
+    // Here we construct the grid that is going contain our buttons.
+    let data_grid = Grid::builder()
+        .margin_start(1) // 倒爿ê邊仔留空白ê闊度
+        .margin_end(1) // 正爿ê邊仔留空白ê闊度
+        .margin_top(1)// 頂懸ê邊仔留空白ê懸度
+        .margin_bottom(1) // 下底ê邊仔留空白ê懸度
+        .halign(Align::Start)//水平排列
+        .valign(Align::Start)//垂直排列
+        .row_spacing(1)// 逐列ê縫留空白ê懸度
+        .column_spacing(1)// 逐欄ê縫留空白ê闊度
+        .build();
+
+    // data_grid.set_widget_name("data-grid"); // for css usage
+    data_grid_scrolled_wrapper.set_child(Some(&data_grid));
+
+
+
+    let default_col_titles =vec!["日期", "開盤", "收盤", "最高", "最低", "成交量"];
+    let default_col_titles_len = default_col_titles.len();
+    let col_no = 20;
+    for i in 0..(col_no-1){
+        let mut entry = Entry::new();
+
+        if i < default_col_titles_len{
+            entry.set_text (default_col_titles[i]);
+            entry.set_hexpand(true);
+            entry.set_vexpand(true);
+            gtk4::prelude::EntryExt::set_alignment(&entry, 0.5); // 0.5表示khǹg佇中央
+            data_grid.attach(&entry, i as i32, 0, 1, 1);
+            data_grid.set_widget_name(format!("entry-{}-0", i).as_str()); // for css usage
+        }
+        else{
+            entry.set_text ("");
+            data_grid.attach(&entry, i as i32, 0, 1, 1);
+        }
+    }
+
+    let a = get_tw_stock("0050");
+
+    window.present();
+}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
new file mode 100644 (file)
index 0000000..970821b
--- /dev/null
@@ -0,0 +1,190 @@
+#![feature(proc_macro_hygiene, decl_macro)]
+
+
+use anyhow; // Exception Handling
+use chrono::{TimeZone, Utc};
+use curl::easy::Easy;
+use serde_json::{Result, Value}; // JSON
+use std::collections::HashMap;
+use serde_json::Value::Array;
+use rocket_dyn_templates::Template;
+use rocket::{Rocket, Build};
+use round::round;
+use num_format::{Locale, WriteFormatted};
+
+#[macro_use] extern crate rocket;
+
+enum Date {
+    Day(u8),
+    Week(u8),
+    Month(u8),
+    Max,
+    YearToDate}
+
+
+impl Date {
+    fn as_str(&self) -> String {
+        match &self {
+            Date::Day(d) => format!("{:?}d", d),
+            Date::Week(wk) => format!("{:?}wk", wk),
+            Date::Month(mo) => format!("{:?}mo", mo),
+            Date::YearToDate => format!("ytd"),
+            Date::Max => format!("max"),
+        }
+    }
+}
+
+
+
+#[get("/<stock_id>")]
+fn get_tw_stock(stock_id: String) -> Template {
+    // let a = "👋 Hello, stock no: a22";
+
+    let response_body = get_stock_data(stock_id.as_str(), Date::Day(1), Date::Day(5));
+    let response_json: Value = serde_json::from_str(response_body.as_str()).unwrap();
+
+    let days_in_unix_time = &response_json["chart"]["result"][0]["timestamp"];
+
+    let mut stock_total_data = HashMap::new();
+
+    let days_in_custom_format = match days_in_unix_time {
+        serde_json::Value::Array(days_vec) => days_vec
+            .iter()
+            .map(|day|json_unix_time_to_date(day))
+            .collect::<Vec<_>>(),
+        _ => vec![format!("Not a series of date")],
+    };
+
+    println!("{:?}", &days_in_unix_time);
+
+    stock_total_data.insert("date",  days_in_custom_format);
+
+    let mut open_prices : Vec<String> = vec![];
+    let mut close_prices : Vec<String> = vec![];
+    let mut high_prices : Vec<String> = vec![];
+    let mut low_prices : Vec<String> = vec![];
+    let mut volumes : Vec<String> = vec![];
+
+
+    let price_and_volume_infos = &response_json["chart"]["result"][0]["indicators"]["quote"][0];
+    
+    let open_prices_orig = &price_and_volume_infos["open"];
+    let close_prices_orig = &price_and_volume_infos["close"];
+    let high_prices_orig = &price_and_volume_infos["high"];
+    let low_prices_orig = &price_and_volume_infos["low"];
+    let volumes_orig = &price_and_volume_infos["volume"];
+
+
+    match (open_prices_orig, close_prices_orig, high_prices_orig, low_prices_orig, volumes_orig){
+        (Array(o), Array(c), Array(h),
+            Array(l), Array(v)) => {
+            for i in 0..(o.len()){
+                open_prices.push(format!("{:0.2}", o[i].as_f64().unwrap()));
+                close_prices.push(format!("{:0.2}", c[i].as_f64().unwrap()));
+                high_prices.push(format!("{:0.2}", h[i].as_f64().unwrap()));
+                low_prices.push(format!("{:0.2}", l[i].as_f64().unwrap()));
+                let mut formatted_volume = String::new();
+                formatted_volume.write_formatted(&v[i].as_i64().unwrap(), &Locale::zh);
+
+                volumes.push(formatted_volume);
+            }
+            
+        },
+        _ => (),
+    }
+
+    stock_total_data.insert("open", open_prices);
+    stock_total_data.insert("close", close_prices);
+    stock_total_data.insert("high", high_prices);
+    stock_total_data.insert("low", low_prices);
+    stock_total_data.insert("volume", volumes);
+
+
+    let mut stock_total_data_by_date = transverse_stock_data_by_date(stock_total_data.clone());
+    //let mut stock_total_data_by_date_wrapper = HashMap::new();
+
+    //stock_total_data_by_date_wrapper.insert("data", stock_total_data_by_date);
+
+    //println!("{:?}", stock_total_data_by_date_wrapper);
+    println!("{:?}", stock_total_data);
+    return Template::render("tw_stock", stock_total_data);
+}
+
+fn json_unix_time_to_date(json_value: &Value) -> String {
+    let unix_time = json_value.as_i64().unwrap();
+    println!("{:?}", unix_time);
+
+    let naive_time = Utc.timestamp_opt(unix_time, 0).unwrap();
+
+    let date = format!("{}", naive_time.format("%Y-%m-%d"));
+    println!("{:?}", date);
+
+    return date;
+}
+
+fn transverse_stock_data_by_date(orig_data : HashMap<&str, Vec<String>>) -> 
+    Vec<HashMap<String, String>>{
+    let mut stock_data_by_date = vec![];
+    let dates = &orig_data["date"];
+
+    for i in 0..dates.len()-1{
+        let mut day_hash_map = HashMap::new();
+        day_hash_map.insert(format!("date"), orig_data["date"][i].clone());
+        day_hash_map.insert(format!("open"), orig_data["open"][i].clone());
+        day_hash_map.insert(format!("close"), orig_data["close"][i].clone());
+        day_hash_map.insert(format!("high"), orig_data["high"][i].clone());
+        day_hash_map.insert(format!("low"), orig_data["low"][i].clone());
+        day_hash_map.insert(format!("volume"), orig_data["volume"][i].clone());
+
+        stock_data_by_date.push(day_hash_map);
+
+    }
+
+    return stock_data_by_date;
+
+
+}
+
+
+fn get_stock_data(stock_id: &str, interval: Date, range: Date) -> String {
+    let intrval_str = interval.as_str();
+    let range_str = range.as_str();
+
+    let url = format!(
+        "https://query1.finance.yahoo.com/v8/finance/chart/\
+        {:}.TW?metrics=Date,High,Low,Open,Close,Volume&interval={:}&range={:}",
+        stock_id, intrval_str, range_str
+    );
+
+    let mut curl_easy = Easy::new(); // fetch the data with the curl binding
+    let mut response = String::new();
+
+    {
+        curl_easy.url(url.as_str()).unwrap();
+
+        let mut curl_transfer = curl_easy.transfer();
+
+        curl_transfer
+            .write_function(|data| {
+                response.push_str(std::str::from_utf8(data).unwrap());
+                Ok(data.len())
+            })
+            .unwrap();
+
+        curl_transfer.perform().unwrap();
+    }
+
+    let response_returned = response.clone();
+
+    return response_returned;
+}
+
+
+#[launch]
+fn rocket() -> Rocket<Build>  {
+    // rocket::ignite().mount("/", routes![index]).launch();
+    rocket::build().attach(Template::fairing())
+        .mount("/tw", routes![get_tw_stock])
+
+
+}
diff --git a/src/tests.rs b/src/tests.rs
new file mode 100644 (file)
index 0000000..fd5b628
--- /dev/null
@@ -0,0 +1,71 @@
+use rocket::local::blocking::Client;
+use rocket::http::{RawStr, Status};
+
+#[test]
+fn hello() {
+    let langs = &["", "ru", "%D1%80%D1%83", "en", "unknown"];
+    let ex_lang = &["Hi", "Привет", "Привет", "Hello", "Hi"];
+
+    let emojis = &["", "on", "true", "false", "no", "yes", "off"];
+    let ex_emoji = &["", "👋 ", "👋 ", "", "", "👋 ", ""];
+
+    let names = &["", "Bob", "Bob+Smith"];
+    let ex_name = &["!", ", Bob!", ", Bob Smith!"];
+
+    let client = Client::tracked(super::rocket()).unwrap();
+    for n in 0..(langs.len() * emojis.len() * names.len()) {
+        let i = n / (emojis.len() * names.len());
+        let j = n % (emojis.len() * names.len()) / names.len();
+        let k = n % (emojis.len() * names.len()) % names.len();
+
+        let (lang, ex_lang) = (langs[i], ex_lang[i]);
+        let (emoji, ex_emoji) = (emojis[j], ex_emoji[j]);
+        let (name, ex_name) = (names[k], ex_name[k]);
+        let expected = format!("{}{}{}", ex_emoji, ex_lang, ex_name);
+
+        let q = |name, s: &str| match s.is_empty() {
+            true => "".into(),
+            false => format!("&{}={}", name, s)
+        };
+
+        let uri = format!("/?{}{}{}", q("lang", lang), q("emoji", emoji), q("name", name));
+        let response = client.get(uri).dispatch();
+        assert_eq!(response.into_string().unwrap(), expected);
+
+        let uri = format!("/?{}{}{}", q("emoji", emoji), q("name", name), q("lang", lang));
+        let response = client.get(uri).dispatch();
+        assert_eq!(response.into_string().unwrap(), expected);
+    }
+}
+
+#[test]
+fn hello_world() {
+    let client = Client::tracked(super::rocket()).unwrap();
+    let response = client.get("/hello/world").dispatch();
+    assert_eq!(response.into_string(), Some("Hello, world!".into()));
+}
+
+#[test]
+fn hello_mir() {
+    let client = Client::tracked(super::rocket()).unwrap();
+    let response = client.get("/hello/%D0%BC%D0%B8%D1%80").dispatch();
+    assert_eq!(response.into_string(), Some("Привет, мир!".into()));
+}
+
+#[test]
+fn wave() {
+    let client = Client::tracked(super::rocket()).unwrap();
+    for &(name, age) in &[("Bob%20Smith", 22), ("Michael", 80), ("A", 0), ("a", 127)] {
+        let uri = format!("/wave/{}/{}", name, age);
+        let real_name = RawStr::new(name).percent_decode_lossy();
+        let expected = format!("👋 Hello, {} year old named {}!", age, real_name);
+        let response = client.get(uri).dispatch();
+        assert_eq!(response.into_string().unwrap(), expected);
+
+        for bad_age in &["1000", "-1", "bird", "?"] {
+            let bad_uri = format!("/wave/{}/{}", name, bad_age);
+            let response = client.get(bad_uri).dispatch();
+            assert_eq!(response.status(), Status::NotFound);
+        }
+    }
+}
diff --git a/templates/tw_stock.html.hbs b/templates/tw_stock.html.hbs
new file mode 100644 (file)
index 0000000..2fcfbb5
--- /dev/null
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+
+<head>
+<title>台股通</title>
+<style type="text/css">
+#stock-table{
+    border-collapse: collapse;
+
+}
+#stock-table th {
+    border: 1px solid red;
+} 
+#stock-table td {
+  text-align: right;
+  padding: 1em 0.6em;
+  border: 1px solid red;
+}
+</style>
+
+</head>
+
+<html>
+    <body>
+        <table id="stock-table">
+            <tr id="title-of-table">
+            <th>日期</th>
+            <th>開盤</th>
+            <th>收盤</th>
+            <th>最高</th>
+            <th>最低</th>
+            <th>成交量</th>
+            </tr>
+{{#each date}}
+   <tr id={{this}}>
+    <td id="date">{{this}}</td>
+    <td class="open">{{lookup ../open @index}}</td>
+    <td class="close">{{lookup ../close @index}}</td>
+    <td class="high">{{lookup ../high @index}}</td>
+    <td class="low">{{lookup ../low @index}}</td>
+    <td class="volume">{{lookup ../volume @index}}</td>
+    </tr>
+{{/each}}
+    </table>
+    </body>
+</html>
\ No newline at end of file