Skip to content

alvarocsm91/adaptive-theme

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 

Repository files navigation

adaptive-theme

\newpage

1 adaptive-theme

This file contains package function explanation, all use cases and description with examples. For more information, please contact me.

2 Installation

Easy to install, clone this project into your/.emacs.d/ folder and include next code at the end of your init.el file.

(load-file "./adaptive-theme/adaptive-theme.el")

3 Requirements

To use adaptive-theme-location scraping require org-web-tools package, installed when use the function thet require it. This package require Emacs 25.1 or later varsion. This functions has been tested with my init.el configuration and Emacs 28.0.50.

  • Emacs 28.0.50
  • org-web-tools package (intalled when use)

4 How to use

You can use different functions included in adaptive-theme.el file, in this section is explained how to use.

4.1 adaptive-theme

This is most simply function, only compare current hour with default or customized hour and load selected theme. This is faster function.

Default time

(adaptive-theme 'gruvbox-light-soft 'gruvbox-dark-hard)

Customized time, am time 08:01:03, pm time 20:02:04.

(adaptive-theme 'gruvbox-light-soft 'gruvbox-dark-hard 08 20 01 02 03 04)

4.2 adaptive-theme-location

This function allow to get am and pm time from the city that you want, thought to set your current city and country. Is slower than adaptive-theme because require to access to internet to get data time.

Please, search your city in https://www.timeanddate.com/sun to execute this function successfuly. As before function, have two methods, becouse have optional arguments.

Default location. Madrid, Spain.

(adaptive-theme-location 'gruvbox-light-soft 'gruvbox-dark-hard)

Customized location, in munich city and germany as country.

(adaptive-theme-location 'gruvbox-light-soft
'gruvbox-dark-hard "germany" "munich")

4.3 adaptive-theme-autolocation

This function allow to get am and pm time from the city that you are. Is slower than adaptive-theme adaptive-theme-location because require to access to internet to get data time and location.

(adaptive-theme-autolocation 'gruvbox-light-soft 'gruvbox-dark-hard)

5 Change log. Ver/Rev.

5.1 A/0

First version include operative adaptive-theme without errors and adaptive-theme-location in debug stage.

5.2 A/1

Stable version adaptive-theme, adaptive-theme-location and adaptive-theme-autolocation.

6 Development

All contributions and suggersion are welcome, only thinking to improve.

6.1 adaptive-theme

At first we found different arguments, two needed, light-theme that is loaded at day hours and datk-theme, that is loaded at night. The optional arguments are am hour and pm hour, you can choose both hours.

;;; Adaptive theme function
(defun adaptive-theme
  (light-theme dark-theme &optional am-hour pm-hour am-min pm-min am-sec pm-sec)
  " Adaptive theme function:
@Brief:   This function allow to configure different themes depending on the
          emacs initialization time.

@Author:  acsm

@Version: A/1

@Args:    light-theme: Theme loaded in sun hours.
          dark-theme:  Theme loaded in dark hours.
          &-am-hour: Custom dawn hour (0-23) (optional, default 07)
          &-pm-hour: Custom sunset hour (0-23) (optional, default 20)
          &-am-min:  Custom dawn min (0-59) (optional, default 00)
          &-pm-min:  Custom sunset min (0-59) (optional, default 00)
          &-am-sec:  Custom dawn sec (0-59) (optional, default 00)
          &-pm-sec:  Custom sunset sec (0-59) (optional, default 00)

@Links:
"

As it have optional values is important to define default values if this optional arguments are nil. I choose as default 7 as dawn hour and 20 as sunset hour.

;;;; Set default values
  ;; Set dawn time
  (unless (eval am-hour)
    (set 'am-hour 7))
  (unless (eval am-min)
    (set 'am-min 00))
  (unless (eval am-sec)
    (set 'am-sec 00))

  ;; Set sundown time
  (unless (eval pm-hour)
    (set 'pm-hour 20))
  (unless (eval pm-min)
    (set 'pm-min 00))
  (unless (eval pm-sec)
    (set 'pm-sec 00))

  ;; Set dawn time aux
  (setq am-hour-init am-hour)
  (setq am-min-init am-min)
  (setq am-sec-init am-sec)
  ;; Set sundown time
  (setq pm-hour-init pm-hour)
  (setq pm-min-init pm-min)
  (setq pm-sec-init pm-sec)

First of all, we need to get the initialization time to compare with the limits, we can get it with the descomposition of the current date in substrings, later we need to transform those substrings to integer to compare with inpus or default arguments.

;;;; Get time
  (set 'init-time (current-time-string))

;;;; Get integer hour
  ;; Get hour
  (set 'init-hour-str (substring init-time 11 13))
  (set 'init-hour-int (string-to-number init-hour-str 10))
  ;; Get minute
  (set 'init-min-str (substring init-time 14 16))
  (set 'init-min-int (string-to-number init-min-str 10))
  ;; Get Second
  (set 'init-sec-str (substring init-time 17 19))
  (set 'init-sec-int (string-to-number init-sec-str 10))

Start thinking that is day, first, if current hour is lower than am hour, sure, is night, else if same hour compare minutes. As with hours if is the same hour and current minute is less than am minutes is night. Same with the seconds

          ,-----------------------------------------------,
          | Hour ,------------------------------,         |
Night     |      | Minute      <-+->            |         |            Day
----------+------+---------------+--------------+---------+--------------
          |      |                Second        |         |
          |      '------------------------------'         |
          '-----------------------------------------------'

With the pm hour is the same but in the opposite.

;;;; Detect if is day
  (setq is-day t)
  (setq is-morning nil)
  (setq is-night nil)

;;;;; Compare with am
;;;;;; Hour
  (if (< init-hour-int am-hour)
      ;; true if init hour <  dawn hour is night
      (set 'is-day nil)
    ;; Evaluate minutes if is the same hour
    (if (= init-hour-int am-hour)
        ;; evaluate minutes
        (if (< init-min-int am-min)
            ;; true if init min <  dawn min is night
            (set 'is-day nil)
          ;; Evaluate seconds if is the same minute
          (if (= init-sec-int am-sec)
              ;; evaluate seconds
              (if (< init-sec-int am-sec)
                  ;; true if init sec <  dawn sec is night
                  (set 'is-day nil)
                  )))
          )
      )

  ;; Is is before day is morning
  (if (null is-day)
      (setq is-morning t))

;;;;; Compare with pm
;;;;;; Hour
  (if (> init-hour-int pm-hour)
      ;; true if init hour >  sunset hour is night
      (set 'is-day nil)
    ;; Evaluate minutes if is the same hour
    (if (= init-hour-int pm-hour)
        ;; evaluate minutes
        (if (> init-min-int pm-min)
            ;; true if init min >  sunset min is night
            (set 'is-day nil)
          ;; Evaluate seconds if is the same minute
          (if (= init-sec-int pm-sec)
              ;; evaluate seconds
              (if (> init-sec-int pm-sec)
                  ;; true if init sec >  sunset sec is night
                  (set 'is-day nil)))
          )
      )
    )

  ;; If is not day and no morning is night
  (if (null is-day)
      (if (null is-morning)
          (setq is-night t)))

Load theme depending on the point of the day, if is day light theme and if is night dark theme.

;;;; Load theme
  (if is-day
      ;; Load ligth theme if is day
      (load-theme light-theme t)
    ;;(load-theme 'gruvbox-light-soft t)
    ;; Load dark theme if is not day
    (load-theme dark-theme t))
  ;;(load-theme 'gruvbox-dark-hard t))

This is the basic function, but if you are coding all day or al night it should change at the time, becouse of that this function will be evaluate one min after the next am or pm time.

To make it we will use org timers, but first we should get next hour, the timers accept the hour as string “hh:mm”.

;;;; Program nex theme change
  ;; Timer example
  ;;(run-at-time "5 sec" nil #'message "Prueba timer")
  ;;(run-at-time "20:30" nil #'kill-emacs)
  ;;(run-at-time "5 sec" nil #'adaptive-theme 'gruvbox-light-soft 'gruvbox-dark-hard)

  ;;;;; Calculate time before change day - night
  (if (eval is-day)
      (if (> pm-min 58)
          (lambda ()
            (if (equal pm-hour 23)
              (setq pm-hour 0)
              (setq pm-hour (+ pm-hour 1))
              )
            (setq pm-hour (+ pm-hour 1))
            (setq pm-min 0))
        (setq pm-min (+ pm-min 1))
        )
    ;; If is not day
    (if (equal am-min 59)
        (lambda ()
          (if (equal am-hour 23)
            (setq am-hour 0)
            (setq am-hour (+ am-hour 1))
            )
          (setq am-min 0))
      (setq am-min (+ am-min 1))
      )
    )

  ;; Calculate next hour as str
  (if (< pm-hour 10)
      (setq pm-hour-str (concat "0" (number-to-string pm-hour)))
    (setq pm-hour-str (number-to-string pm-hour))
    )

  ;; Calculate next min asl str
  (if (< pm-min 10)
      (setq pm-min-str (concat "0" (number-to-string pm-min)))
    (setq pm-min-str (number-to-string pm-min))
    )

  ;; Define pm hour
  (setq pm-str (concat pm-hour-str ":" pm-min-str))

  ;; Calculate next hour as str
  (if (< am-hour 10)
      (setq am-hour-str (concat "0" (number-to-string am-hour)))
    (setq am-hour-str (number-to-string am-hour))
    )

  ;; Calculate next min asl str
  (if (< am-min 10)
      (setq am-min-str (concat "0" (number-to-string am-min)))
    (setq am-min-str (number-to-string am-min))
    )

  ;; Define am hour
  (setq am-str (concat am-hour-str ":" am-min-str))

  ;; If is night use timer in seconds
  (if (eval is-night)
      (lambda ()
        (setq am-str-int (+ (* (- 23 init-hour-int) 3600)
                            (* (- 59 init-min-int) 60)
                            (- 59 init-sec-int)))
        (setq am-str (concat (number-to-string am-str-int) " sec")))
    )

Set timers to evaluate this function after next change hour, if is day evalute the function after pm time and if not program before am time. If i setup the timer at time before current it will evaluate the function all time blocking emacs. I comment this timer because I close emacs all night.

;;;;; Program
;; Cancel timer if exist
(if (eval is-day)
    (if (boundp 'am-timer)
        (cancel-timer am-timer)
      )
  (if (boundp 'pm-timer)
      (cancel-timer pm-timer)
    )
  )

;; Reset timer
(if (eval is-day)
    (setq pm-timer (run-at-time pm-str nil #'adaptive-theme 'gruvbox-light-soft 'gruvbox-dark-hard (eval am-hour-init) (eval pm-hour-init) (eval am-min-init) (eval pm-min-init) (eval am-sec-init) (eval pm-sec-init))))

(if (eval is-morning)
    (setq am-timer (run-at-time am-str nil #'adaptive-theme 'gruvbox-light-soft 'gruvbox-dark-hard (eval am-hour-init) (eval pm-hour-init) (eval am-min-init) (eval pm-min-init) (eval am-sec-init) (eval pm-sec-init))))

;;(if (eval is-night)
;;    (setq am-timer (run-at-time am-str nil #'adaptive-theme 'gruvbox-light-soft 'gruvbox-dark-hard (eval am-hour-init) (eval pm-hour-init) (eval am-min-init) (eval pm-min-init) (eval am-sec-init) (eval pm-sec-init))))

    )

6.2 adaptive-theme-location

This function have two arguments, light-theme and dark-themen, as optional arguments it have two, country and city to setect in which city you are and in function of this search am hour and pm hour to detect dawn and sunset time.

;;; Adaptive theme location
(defun adaptive-theme-location (ligth-theme dark-theme &optional country city)
  "  Adaptive theme location function:
@Brief:   This function allow to configure different themes depending on your
          location when work emacs.

@Author:  acsm

@Version: A/1

@Args:    light-theme: Theme loaded in sun hours.
          dark-theme:  Theme loaded in dark hours.
          &country:    Custom Country location (str) (optional, default spain)
          &city:       Custom City or capital location (str) (optional, default madrid)

@Links:   https://www.timeanddate.com/sun where look for your country and city names.
"

First of all, we should check if we can access to internet to download packages and access web to scrap the information.

;;;; Detect internet connection
  ;; Change "www.google.es" with your proxy server if you need it. "my_proxy.es"
  (if (null (boundp 'host))
      (setq internet-external-host "www.google.com"))
  (setq is-internet-up (call-process "ping" nil nil nil "-c" "1" "-w" "1" internet-external-host))

  (if (/= is-internet-up 0)
      ;; If internet is not connected
      (lambda ()
        (adaptive-theme(dark-theme light-theme))
        (progn (message "No network detected") nil)
        (return))
    )

IF dont fill this arguments, you are going to have the Madrid, Spain hour. To make the scrapping this function require org-web-tools, this package is installed when use it.

The web site where take the hours information is https://www.timeanddate.com/sun, is recomended to search your city in the web to avoid errors.

;;;; Load basic requieres
(require 'org-web-tools)

;;;; Web scraping
;; URL base to get am and pm data
(setq url "https://www.timeanddate.com/sun")
;; Set default county
(unless (eval country)
  (setq country "spain"))

;; Set default city
(unless (eval city)
  (setq city "madrid"))

First is create the url to search the information, after that, is importart to download the web first to create the regular expression, i recomend to get the html and later create the regex in a web page, at the last, make it with emacs special syntaxis.

Later get in which point os the html stris is the match, and get a subtring filtering all the web.

;; Compose url
(setq web_to_scrap (concat url "/" country "/" city))
(setq webDataHtml (org-web-tools--get-url web_to_scrap))
;; web string to search
;; <div class=\"h1 dn-mob\">Daylight</div><p class=dn-mob>7:18 &#8211; 21:06<br>13 hours, 48 minutes</p></div>
;; first regex model
;;>Daylight<\/div><p class=dn-mob>[0-9]{1,2}:[0-9]{1,2} &#8211; [0-9]{1,2}:[0-9]{1,2}<br>[0-9]{1,2} hours, [0-9]{1,2} minutes<\/p><\/div>
;; second regex model
;;\WDaylight\W{1,}div\W{1,}p\sclass\Wdn\Wmob\W[0-9]{1,2}\W[0-9]{1,2} \W{1,}[0-9]{1,}\W [0-9]{1,2}\W[0-9]{1,2}\Wbr\W[0-9]{1,2}\shours\W\s[0-9]{1,2}\sminutes\W{1,}p\W{1,}div\W
;; Helm regex model
;;\\s_Daylight\\s_\\{2\\}div\\s_\\{2\\}p\\s-class\\s_dn\\s_mob\\s_[0-9]\\{1,2\\}:[0-9]\\{1,2\\}\\s-\\s_\\{1,\\}
;;;; Web regex model
(setq webRegexModel "\\s_Daylight\\s_\\{2\\}div\\s_\\{2\\}p\\s-class\\s_dn\\s_mob\\s_[0-9]\\{1,2\\}:[0-9]\\{1,2\\}\\s-\\s_\\{1,\\}")

;;;; Extract regex value
(setq daylight-regex
      (string-match webRegexModel webDataHtml))

;;;; Extract substring
(setq subWebStr
      (substring webDataHtml daylight-regex (+ daylight-regex 100)))

Later regenerate the regex to extract am hour and pm hour from the html substring.

;;;; Create regex group time
(setq timeGroupRegex "[0-9]\\{1,2\\}:[0-9]\\{1,2\\}")

;;;; Extract time value as string
(setq timeStr (string-match timeGroupRegex subWebStr))

;;;; Extract AM hour
(setq amTime (substring subWebStr timeStr (+ timeStr 5)))

;;;; Get substring pm time
(setq timeStr (string-match timeGroupRegex subWebStr (+ timeStr 6)))

;;;; Extract PM time
(setq pmTime (substring subWebStr timeStr (+ timeStr 5)))

;;;; Regenerate time regext to get hour and minutes
(setq timeGroupRegex ":")

;;;; Get AM Hour
(setq amSeparator (string-match timeGroupRegex amTime))
(setq amHourStr (substring amTime 0 amSeparator))
(setq amHourInt (string-to-number amHourStr))

;;;; Get AM Min
(setq amSeparator (string-match timeGroupRegex amTime))
(setq amMinStr (substring amTime (+ amSeparator 1) (+ amSeparator 3)))
(setq amMinInt (string-to-number amMinStr))

;;;; Get PM Hour
(setq pmSeparator (string-match timeGroupRegex pmTime))
(setq pmHourStr (substring pmTime 0 pmSeparator))
(setq pmHourInt (string-to-number pmHourStr))

;;;; Get PM Min
(setq pmSeparator (string-match timeGroupRegex pmTime))
(setq pmMinStr (substring pmTime (+ pmSeparator 1) (+ pmSeparator 3)))
(setq pmMinInt (string-to-number pmMinStr))

Execute adaptive theme function with the hour got from web.

;;;; Execute adaptive theme function
(adaptive-theme ligth-theme dark-theme amHourInt pmHourInt amMinInt pmMinInt))

6.3 adaptive-theme-autolocation

This function allow to get your location and execute adaptive-theme-location with data extracted from internet.

;;; Auto location adaptive theme
(defun adaptive-theme-autolocation (light-theme dark-theme)
  "  Adaptive theme auto-location function:
     @Brief:   This function allow to configure different themes depending on your
               location when work emacs, it get your location from internet.

     @Author:  acsm

     @Version: A/1

     @Args:    light-theme: Theme loaded in sun hours.
               dark-theme:  Theme loaded in dark hours.

     @Links:   https://www.timeanddate.com where look for your country and city names.
     "

First of all, we should check if we can access to internet to download packages and access web to scrap the information.

;;;; Detect internet connection
  ;; Change "www.google.es" with your proxy server if you need it. "my_proxy.es"
  (if (null (boundp 'host))
      (setq internet-external-host "www.google.com"))
  (setq is-internet-up (call-process "ping" nil nil nil "-c" "1" "-w" "1" internet-external-host))

  (if (/= is-internet-up 0)
      ;; If internet is not connected
      (lambda ()
        (adaptive-theme(dark-theme light-theme))
        (progn (message "No network detected") nil)
        (return))
    )

Require org-web-tools and get main web where we can saw our country and city.

;;;; Load basic requieres
  (require 'org-web-tools)

     ;;;; Web scraping
  ;; URL base to get am and pm data
  (setq url-location "https://www.timeanddate.com")

  ;; Get main web where display your location
  (setq webDataHtml (org-web-tools--get-url url-location))
  ;; web string to search
  ;;title=\"The World Clock / Time Zones\">Current Time</a></h2><a href=\"/worldclock/spain/madrid\" id=clk_box
  ;; Fist regex model
  ;;title=\W{2}The World Clock \W Time Zones\W{2}>Current Time<\Wa><\Wh2><a href=\W{3}worldclock\Wspain\Wmadrid\W{2} id=clk_box
  ;; Helm regex with regex-builder
  ;;"the world clock"

Identify keys to extrat the information.

;;;; Extract regex value
  (setq location-regex
        (string-match "The World Clock" webDataHtml))
     ;;;; Extract substring
  (setq subLocStr
        (substring webDataHtml location-regex (+ location-regex 120)))

Filter this data from first value detected with special characters.

;;;; Extract regex value
  (setq init-regex
        (string-match "worldclock/" subLocStr))

  (setq end-regex
        (string-match "id=" subLocStr))

  ;;;; Extract substring
  (setq subLocStr
        (substring subLocStr (+ init-regex 11) (+ end-regex 3)))

  ;;;; Extract country
  (setq end-regex
        (string-match "/" subLocStr))
  (setq myCountryLoc
        (substring subLocStr 0 end-regex))

  ;;;; Extract city
  (setq init-regex
        (string-match "/" subLocStr))

  (setq end-regex
        (string-match "id=" subLocStr))

  (setq myCityLoc
        (substring subLocStr (+ init-regex 1) (- end-regex 2)))

Execute adaptive theme location with data extracted.

;; Execute location function with data extracted.
(adaptive-theme-location light-theme dark-theme myCountryLoc myCityLoc)
)

7 Liscence

GPLv3