Skip to content

Commit d3482bf

Browse files
authored
Add curl support, and default to use curl, fallback to url.
1 parent 51963a6 commit d3482bf

1 file changed

Lines changed: 129 additions & 23 deletions

File tree

stock-tracker.el

Lines changed: 129 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
;;; stock-tracker.el --- Track stock price -*- lexical-binding: t; -*-
22

3-
;; Copyright (C) 2019-2025 Huming Chen
3+
;; Copyright (C) 2019-2026 Huming Chen
44

55
;; Author: Huming Chen <chenhuming@gmail.com>
66
;; URL: https://github.com/beacoder/stock-tracker
7-
;; Version: 0.1.8
7+
;; Version: 0.2.0
88
;; Created: 2019-08-18
99
;; Keywords: convenience, stock, finance
1010
;; Package-Requires: ((emacs "27.1") (dash "2.16.0") (async "1.9.5"))
@@ -53,6 +53,7 @@
5353
;; Add test for both CHN and US stocks
5454
;; 0.1.8 Disable test for CHN stocks due to API not working
5555
;; 0.1.9 Fix wrong stock order issue for us-stocks during auto-refreshing
56+
;; 0.2.0 Add curl support, and default to use curl, fallback to url
5657

5758
;;; Code:
5859

@@ -101,6 +102,26 @@
101102
:type 'boolean
102103
:group 'stock-tracker)
103104

105+
(defcustom stock-tracker-fetch-backend 'curl
106+
"Backend used for HTTP requests.
107+
When set to 'url, use `url-retrieve-synchronously'.
108+
When set to 'curl, use the curl command-line tool.
109+
When set to 'auto, use curl if available, otherwise fall back to url."
110+
:type '(choice (const :tag "url-retrieve-synchronously" url)
111+
(const :tag "curl" curl)
112+
(const :tag "auto-detect" auto))
113+
:group 'stock-tracker)
114+
115+
(defcustom stock-tracker-curl-program "curl"
116+
"Name or path of the curl executable."
117+
:type 'string
118+
:group 'stock-tracker)
119+
120+
(defcustom stock-tracker-curl-timeout 10
121+
"Timeout in seconds for curl requests."
122+
:type 'integer
123+
:group 'stock-tracker)
124+
104125
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
105126
;; Interface
106127
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -300,20 +321,67 @@ It defaults to a comma."
300321
;; Core Functions
301322
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
302323

324+
(defun stock-tracker--effective-backend ()
325+
"Return the effective fetch backend, resolving 'auto to curl or url."
326+
(cond
327+
((eq stock-tracker-fetch-backend 'curl) 'curl)
328+
((eq stock-tracker-fetch-backend 'url) 'url)
329+
(t
330+
(if (executable-find stock-tracker-curl-program) 'curl 'url))))
331+
332+
(defun stock-tracker--fetch-url (url)
333+
"Fetch URL using `url-retrieve-synchronously'.
334+
Return the response body as a string, or nil on failure."
335+
(ignore-errors
336+
(with-current-buffer
337+
(url-retrieve-synchronously url t nil 5)
338+
(set-buffer-multibyte t)
339+
(goto-char (point-min))
340+
(let (body)
341+
(when (string-match "200 OK" (buffer-string))
342+
;; Skip HTTP headers
343+
(re-search-forward "\n\n" nil t)
344+
(setq body (buffer-substring-no-properties (point) (point-max))))
345+
(kill-current-buffer)
346+
body))))
347+
348+
(defun stock-tracker--fetch-curl (url)
349+
"Fetch URL using the curl command-line tool.
350+
Return the response body as a string, or nil on failure."
351+
(ignore-errors
352+
(with-temp-buffer
353+
(let* ((args (list "--silent" "--show-error"
354+
"--max-time" (number-to-string stock-tracker-curl-timeout)
355+
"--location"
356+
"-H" "User-Agent: Mozilla/5.0"
357+
url))
358+
(status (apply #'call-process stock-tracker-curl-program nil t nil args)))
359+
(if (and (numberp status) (zerop status))
360+
(buffer-string)
361+
nil)))))
362+
363+
(defun stock-tracker--fetch (url)
364+
"Fetch URL and return the response body as a string, or nil on failure.
365+
Uses the backend specified by `stock-tracker-fetch-backend'."
366+
(let ((backend (stock-tracker--effective-backend)))
367+
(if (eq backend 'curl)
368+
(or (stock-tracker--fetch-curl url)
369+
(stock-tracker--fetch-url url))
370+
(stock-tracker--fetch-url url))))
371+
303372
(defun stock-tracker--request-synchronously (stock tag)
304373
"Get STOCK data with TAG synchronously, return a list of JSON each as alist."
305374
(let* (jsons response)
306375
(ignore-errors
307-
(with-current-buffer
308-
(url-retrieve-synchronously
309-
(format (stock-tracker--api-url tag) (url-hexify-string stock)) t nil 5)
310-
(set-buffer-multibyte t)
311-
(goto-char (point-min))
312-
(when (string-match "200 OK" (buffer-string))
313-
(re-search-forward (stock-tracker--result-prefix tag) nil 'move)
314-
(setq response (buffer-substring-no-properties (point) (point-max)))
315-
(setq jsons (json-read-from-string (replace-regexp-in-string "[\\[\\]]" "" response))))
316-
(kill-current-buffer)))
376+
(let ((body (stock-tracker--fetch
377+
(format (stock-tracker--api-url tag) (url-hexify-string stock)))))
378+
(when body
379+
(with-temp-buffer
380+
(insert body)
381+
(goto-char (point-min))
382+
(re-search-forward (stock-tracker--result-prefix tag) nil 'move)
383+
(setq response (buffer-substring-no-properties (point) (point-max)))
384+
(setq jsons (json-read-from-string (replace-regexp-in-string "[\\[\\]]" "" response)))))))
317385
jsons))
318386

319387
(defun stock-tracker--format-json (json tag)
@@ -465,11 +533,15 @@ It defaults to a comma."
465533
;; libraries
466534
(require 'subr-x)
467535
(require 'url)
536+
(require 'json)
468537

469538
;; pass params to subprocess, use literal (string, integer, float) here
470539
(setq subprocess-chn-stocks-string ,chn-stocks-string
471540
subprocess-us-stocks-string ,us-stocks-string
472-
subprocess-kill-delay ,stock-tracker-subprocess-kill-delay)
541+
subprocess-kill-delay ,stock-tracker-subprocess-kill-delay
542+
subprocess-fetch-backend ',(stock-tracker--effective-backend)
543+
subprocess-curl-program ,stock-tracker-curl-program
544+
subprocess-curl-timeout ,stock-tracker-curl-timeout)
473545

474546
;; mininum required functions in subprocess
475547
(defun stock-tracker--subprocess-api-url (string-tag)
@@ -483,20 +555,54 @@ It defaults to a comma."
483555
(if (equal string-tag "chn-stock")
484556
"_ntes_quote_callback(" "\\["))
485557

558+
(defun stock-tracker--subprocess-fetch-url (url)
559+
"Fetch URL using `url-retrieve-synchronously'."
560+
(ignore-errors
561+
(with-current-buffer
562+
(url-retrieve-synchronously url t nil 5)
563+
(set-buffer-multibyte t)
564+
(goto-char (point-min))
565+
(let (body)
566+
(when (string-match "200 OK" (buffer-string))
567+
(re-search-forward "\n\n" nil t)
568+
(setq body (buffer-substring-no-properties (point) (point-max))))
569+
(kill-current-buffer)
570+
body))))
571+
572+
(defun stock-tracker--subprocess-fetch-curl (url)
573+
"Fetch URL using curl."
574+
(ignore-errors
575+
(with-temp-buffer
576+
(let* ((args (list "--silent" "--show-error"
577+
"--max-time" (number-to-string subprocess-curl-timeout)
578+
"--location"
579+
"-H" "User-Agent: Mozilla/5.0"
580+
url))
581+
(status (apply #'call-process subprocess-curl-program nil t nil args)))
582+
(if (and (numberp status) (zerop status))
583+
(buffer-string)
584+
nil)))))
585+
586+
(defun stock-tracker--subprocess-fetch (url)
587+
"Fetch URL using the configured backend."
588+
(if (eq subprocess-fetch-backend 'curl)
589+
(or (stock-tracker--subprocess-fetch-curl url)
590+
(stock-tracker--subprocess-fetch-url url))
591+
(stock-tracker--subprocess-fetch-url url)))
592+
486593
(defun stock-tracker--subprocess-request-synchronously (stock string-tag)
487594
"Get stock data synchronously, return a list of JSON each as alist."
488595
(let* (jsons response)
489596
(ignore-errors
490-
(with-current-buffer
491-
(url-retrieve-synchronously
492-
(format (stock-tracker--subprocess-api-url string-tag) (url-hexify-string stock)) t nil 5)
493-
(set-buffer-multibyte t)
494-
(goto-char (point-min))
495-
(when (string-match "200 OK" (buffer-string))
496-
(re-search-forward (stock-tracker--subprocess-result-prefix string-tag) nil 'move)
497-
(setq response (buffer-substring-no-properties (point) (point-max)))
498-
(setq jsons (json-read-from-string (replace-regexp-in-string "[\\[\\]]" "" response))))
499-
(kill-current-buffer)))
597+
(let ((body (stock-tracker--subprocess-fetch
598+
(format (stock-tracker--subprocess-api-url string-tag) (url-hexify-string stock)))))
599+
(when body
600+
(with-temp-buffer
601+
(insert body)
602+
(goto-char (point-min))
603+
(re-search-forward (stock-tracker--subprocess-result-prefix string-tag) nil 'move)
604+
(setq response (buffer-substring-no-properties (point) (point-max)))
605+
(setq jsons (json-read-from-string (replace-regexp-in-string "[\\[\\]]" "" response)))))))
500606
jsons))
501607

502608
;; make sure subprocess can exit successfully

0 commit comments

Comments
 (0)