branch: elpa/autothemer commit 97f7276eb1010387202214ccf2721f20a21012e7 Merge: 96ca493b9f 1bc52d721d Author: Jason Milkins <jason...@users.noreply.github.com> Commit: GitHub <nore...@github.com>
Merge Pull request #23 from develop - Add more tests / github actions CI - Add autothemer-colorize - Add more info to README - Add Bug and Feature issue templates - Update contribution notes --- .github/ISSUE_TEMPLATE/BUG-REPORT.yml | 90 +++++++++ .github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml | 55 ++++++ .github/ISSUE_TEMPLATE/config.yml | 1 + .github/workflows/test.yml | 33 ++++ CONTRIBUTING.md | 38 ++++ readme.md => README.md | 189 ++++++++++++------ autothemer.el | 295 +++++++++++++++++++++++------ bin/setup | 21 ++ bin/test | 40 ++++ tests/autothemer-tests.el | 214 +++++++++++++++++---- 10 files changed, 823 insertions(+), 153 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml new file mode 100644 index 0000000000..b05f938bc7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml @@ -0,0 +1,90 @@ +name: "🪲 Bug Report" +description: Create a new issue for a bug. +title: "🪲 [BUG] - <title>" +labels: [ + "bug" +] +body: + - type: textarea + id: description + attributes: + label: "Description" + description: Please enter a short/clear description of your issue + placeholder: Enter a short/clear description of your issue. + validations: + required: true + - type: textarea + id: reprod + attributes: + label: "Steps to Reproduce the issue. (Issues that cannot be reproduced will be closed.)" + description: Please enter accurate steps to reproduce the issue. + value: | + 1. Open foobar.rs '...' + 2. M-x doctor'....' + 3. Doctor says: What seems to be the problem? '....' + 4. Debugger opens with error 'void function foo-bae' + render: bash + validations: + required: true + - type: textarea + id: screenshot + attributes: + label: "Screenshots" + description: If applicable, add screenshots to help explain your problem. (link to or drag and drop an image.) + value: | +  + render: bash + validations: + required: false + - type: textarea + id: logs + attributes: + label: "Logs" + description: Please copy and paste any relevant log or debug output. This will be automatically formatted into code, so no need for backticks. + render: bash + validations: + required: false + - type: dropdown + id: browsers + attributes: + label: "Emacs version" + description: What Emacs version are you seeing the problem on ? + multiple: true + options: + - 26.1 + - 26.2 + - 26.3 + - 27.1 + - 27.2 + - 28.1 + - snapshot + validations: + required: false + - type: dropdown + id: display + attributes: + label: "Emacs running on GUI, Terminal or Daemon?" + description: How was Emacs being run? GUI, Terminal or Daemon? + multiple: true + options: + - "GUI" + - "Terminal COLORTERM=truecolor" + - "Terminal TERM=xterm256colors" + - "Terminal other (tell us about it in the description.)" + - "Daemon" + validations: + required: false - type: dropdown + id: os + attributes: + label: "OS" + description: What is the impacted environment ? + multiple: true + options: + - Windows + - Linux + - Mac + - Solaris + - AiX + - HP/UX + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml new file mode 100644 index 0000000000..0e179cc4a2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml @@ -0,0 +1,55 @@ +name: "🐣 Feature Request" +description: Create a new issue for a new feature request +title: "🐣 [REQUEST] - <title>" +labels: [ + "question" +] +body: + - type: textarea + id: implementation_pr + attributes: + label: "Implementation PR" + description: Pull request used + placeholder: "#Pull Request ID" + validations: + required: false + - type: textarea + id: reference_issues + attributes: + label: "Reference Issues" + description: Common issues + placeholder: "#Issues IDs" + validations: + required: false + - type: textarea + id: summary + attributes: + label: "Summary" + description: Provide a brief explanation of the feature + placeholder: Describe in a few lines your feature request + validations: + required: true + - type: textarea + id: basic_example + attributes: + label: "Basic Example" + description: Indicate here some basic examples of your feature. If the feature exists in another product, this is a good place to mention and link to it. + placeholder: A few specific words about your feature request. + validations: + required: true + - type: textarea + id: drawbacks + attributes: + label: "Drawbacks" + description: What are the drawbacks/impacts of your feature request ? + placeholder: Identify the drawbacks and impacts while being neutral on your feature request + validations: + required: true + - type: textarea + id: unresolved_question + attributes: + label: "Unresolved questions" + description: What questions still remain unresolved ? + placeholder: Any unresolved issues? + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..3ba13e0cec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..6a2f8ec3a2 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,33 @@ +name: Autothemer Tests + +on: + push: + branches: + - master + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + emacs_version: + - 26.1 + - 26.2 + - 26.3 + - 27.1 + - 27.2 + - 28.1 + - snapshot + fail-fast: false + steps: + - uses: actions/checkout@v3 + - name: Set up Emacs + uses: purcell/setup-emacs@v3.0 + with: + version: ${{ matrix.emacs_version }} + + - name: Test + run: | + bin/setup + bin/test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..dab95f0cbf --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,38 @@ +# Contributing + +Welcome to Emacs AutoThemer. You can contribute to the project in the following ways: + +- **Bug reports:** One liner reports are difficult to understand and review. + - Follow the bug reporting issue template and provide clear, concise descriptions and steps to reproduce the bug. + - Ensure that you have searched the existing issues to avoid duplicates. + - Maintainers may close unclear issues that lack enough information to reproduce a bug. [Report a bug here](https://github.com/jasonm23/autothemer/issues/new/choose). + - Maintainers WILL close issues if they cannot be reliably and repeatedly reproduced, without loading your config. + +- **Feature suggestions:** If you feel there is a nice enhancement or feature that can benefit many users, please open a feature request issue. + - Ensure that you have searched the existing issues to avoid duplicates. + - What makes sense for the project, what suits its scope and goals, and its future direction are at the discretion of the maintainers who put in the time, effort, and energy in building and maintaining the project for free. Please be respectful of this and keep discussions friendly and fruitful. + - It is the responsibility of the requester to clearly explain and justify why a change is warranted. It is not the responsibility of the maintainers to coax this information out of a requester. So, please post well researched, well thought out, and detailed feature requests saving everyone time. + - Maintainers may close unclear feature requests that lack enough information. [Suggest a feature here](https://github.com/jasonm23/autothemer/issues/new/choose). + +- **Pull requests** + This is a tricky one for many reasons. A PR, be it a new feature or a small enhancement, has to make sense to the project's overall scope, goals, and technical aspects. The quality, style, and conventions of the code have to conform to that of the project's. Performance, usability, stability and other kinds of impacts of a PR should be well understood. + + This makes reviewing PRs a difficult and time consuming task. The bigger a PR, the more difficult it is to understand. Reviewing a PR in detail, engaging in back and forth discussions to improve it, and deciding that it is meaningful and safe to merge can often require more time and effort than what has gone into creating a PR. Thus, ultimately, whether a PR gets accepted or not, for whatever reason, is at the discretion of the maintainers. Please be respectful of the fact that maintai [...] + + To keep the process smooth **send small PRs:** Whenever possible, send small PRs with well defined scopes. The smaller the PR, the easier it is to review and test. Bundling multiple features into a single PR is highly discouraged. + +- **Be respectful** + Remember, most FOSS projects are fruits of love and labour of maintainers who share them with the world for free with no expectations of any returns. Free as in freedom, and free as in beer too. Really, *some people just want to watch the world turn*. + +### So: + +- Please be respectful and refrain from using aggressive or snarky language. It wastes time, energy, cognitive bandwidth, and goodwill. +- Please refrain from demanding. How badly you want a feature has no bearing on whether it warrants a maintainer's time or attention. It is entirely up to the maintainers, if, how, and when they want to implement something. +- Please do not nitpick and generate unnecessary discussions that waste time. +- Please make sure you have searched the docs and issues before asking support questions. +- **Please remember, FOSS project maintainers owe you nothing** (unless you have an explicit agreement with them, of course) including their time in responding to your messages or providing free customer support. If you want to be heard, please be respectful and establish goodwill. +- If these are unacceptable to you + - You don't have to use the project + - You can always fork the project and change it to your liking while adhering to the terms of the license. That is the beauty of FOSS, afterall. + +Thank you diff --git a/readme.md b/README.md similarity index 64% rename from readme.md rename to README.md index 531324bb9f..59eaf548b6 100644 --- a/readme.md +++ b/README.md @@ -1,15 +1,34 @@ # Autothemer + +[](https://github.com/jasonm23/autothemer/actions/workflows/test.yml) + [](https://elpa.nongnu.org/nongnu/autothemer.html) +[](https://melpa.org/#/autothemer) +[](https://stable.melpa.org/#/autothemer) + +Autothemer provides `autothemer-deftheme` a macro wrapper for `deftheme` and +`custom-theme-set-faces` which creates a custom color theme. + +The package also includes some useful theme development features... read on. + +## News + +We've added new things to AutoThemer in recent weeks: -Autothemer provides a thin layer on top of `deftheme` and -`custom-theme-set-faces` that creates a new custom color theme. +- [Updates to the list of themes using autothemer](#themes-using-autothemer) (More to come...) +- [Theme Variant Architecture/TVA](#tva) A convention for developing themes with multiple versions. +- [Generate a cool SVG Palette image](#generate-a-svg-image-of-the-palette) Generate a cool SVG Palette image [like this one...](https://raw.githubusercontent.com/emacsfodder/emacs-theme-orangey-bits/master/palette.svg) +- [Select colors from the theme in development](#select-colors-from-the-palette) Select, and insert a palette color name, or it's color value. +- [Colorize palette color names](#colorize-color-names-from-the-palette) +- [`autothemer-let-palette`](#let-palette) +- [Generate missing specs, updated to allow filtering](#auto-generating-missing-specs) -## Usage +## Overview -Autothemer requires a set of color classes, a color palette and -simplified face specifications to be applied to Emacs. +`autothemer-deftheme` uses a color class(es)/palette(s) which simplify the `deftheme` style and +simplified face specifications to be applied to Emacs faces. -Take a look at the example below. +See the example below. ```lisp (autothemer-deftheme example-name "Autothemer example..." @@ -18,7 +37,7 @@ Take a look at the example below. ((((class color) (min-colors #xFFFFFF)) ((class color) (min-colors #xFF))) - ;; Specify the color palette for each of the classes above. + ;; Specify the color palette, color columns correspond to each of the classes above. (example-red "#781210" "#FF0000") (example-green "#22881F" "#00D700") (example-blue "#212288" "#0000FF") @@ -27,12 +46,15 @@ Take a look at the example below. (example-orange "#E06500" "#FF6600") (example-cyan "#22DDFF" "#00FFFF")) - ;; specifications for Emacs faces. + ;; Specifications for Emacs faces. + ;; Simpler than deftheme, just specify a face name and + ;; a plist of face definitions (nested for :underline, :box etc.) ((button (:underline t :weight 'bold :foreground example-yellow)) (error (:foreground example-red))) ;; Forms after the face specifications are evaluated. ;; (palette vars can be used, read below for details.) + (custom-theme-set-variables 'example-name `(ansi-color-names-vector [,example-red ,example-green @@ -45,22 +67,32 @@ Take a look at the example below. ## Faces and Color Classes -One of the things that makes writing themes for Emacs difficult is the syntax of `defface`, the macro used to configre Emacs `face` definitions. +One of the things that makes writing themes for Emacs painful is the syntax of `defface`, +the macro used to configre Emacs `face` definitions. -Because the syntax isn't particularly developer friendly, it usually results in themes with limited support for different color displays, usually GUI / 24bit themes are made, and the results in the terminal are often sub par. On occassion a theme does appear that provides better support for multiple display types, but due to the manual work involved in adding face specs, mode support is limited and development often stalls. +Because the syntax isn't developer friendly it usually results in themes with limited support. Especially for +different color displays. Usually GUI / 24bit themes are made, and the results in the terminal are often sub par. +On occassion a theme does appear that provides better support for multiple display types, but due to the manual work +involved in adding face specs, mode support is limited and development often stalls. -On the plus side the complexity of face specifcations means we can in theory design themes that support any display with any number of colors, we can support dark and light background modes. Until now it's been hard to fully exploit the potential. +On the plus side the complexity of face specifcations means we can in theory design themes that support any display +with any number of colors, we can support dark and light background modes. Until now it's been hard to fully +exploit the potential. -Autothemer solves most of the problems that a theme developer would face. +Autothemer attempts to solve the problems that a theme developer faces. By defining a simple set of +color class rules we can remove repetitive face specs. -By defining a simple set of color class rules we can remove swathes of repetitive face specs. Looking again at the example above. +Looking again at the example above. ```lisp (((class color) (min-colors #xFFFFFF)) ((class color) (min-colors #xFF))) ``` -Here we've setup a color class for 16.8million (0xFFFFFF) color display i.e. 24bit, which will be read from first column in the palette. We've then setup a color class for 256 (0xFF) color displays i.e. Xterm-256color, this will be read from the second column. +Here we've setup a color class for 16.8million (0xFFFFFF) color display i.e. 24bit, +which will be read from first column in the palette. Next we setup a color class for 256 (0xFF) color +displays i.e. `xterm-256color`, the color palette values for this will be read from +the corresponding second column. We can setup as many columns as we'd like to support, here's a few more examples. @@ -82,11 +114,14 @@ For a dark background 24bit ((class color) (min-colors #xFFFFFF) (background dark)) ``` -You can read more about defining faces in the Emacs manual, [display types and class color is covered here.](https://www.gnu.org/software/emacs/manual/html_node/elisp/Defining-Faces.html) +You can read more about defining faces in the Emacs manual, +[display types and class color is covered here.](https://www.gnu.org/software/emacs/manual/html_node/elisp/Defining-Faces.html) ### Palette -The palette definition is specified as a list of lists, each of the nested lists is a color name and then color values that correspond to each of the display/color classes defined above. +The palette definition is specified as a list of lists, each of the nested lists is a +color name and then color values that correspond to each of the display/color classes +defined above. You can set color values as nil and the first color to the left will be used. @@ -102,8 +137,8 @@ For example, if we have three display classes defined, 256, 24bit, 16 color: ``` Note we only specify 256 color mode's `my-red` value, and leave the -others as nil. Autothemer will set the others with the value -`#FF0000`. +others as nil. Autothemer will copy the value `#FF0000` to the other +color classes at the same paletee index if they are nil. ### Simplified face specs @@ -133,7 +168,8 @@ affected. - `:slant` - `:style` -(NOTE: there may be others I have missed!) +(NOTE: there may be others I have missed. Please open an [issue] if you find +another attribute that needs quoting.) ### Body / Evaluated Forms @@ -141,27 +177,27 @@ After defining the display specs, palette and simplified face specs, you can include other code to be evaluated. Be aware that colors named in the palette will need to be `,` -comma-ed. For example if you wanted to use the color `my-red` in a -form, you would refer to it as `,my-red`, so that it's evaluated -properly. - -(This section of the README will be updated as I find any other -gotchas.) +comma-ed so they evaluate correctly. For example if you wanted to use +the color `my-red` somewhere in the `body` section, you would refer to it +as `,my-red`, so that it's evaluated properly. ### Auto generating missing specs You can automatically generate specs for faces that are not in your theme using the command -`M-x autothemer-generate-templates` +``` +M-x autothemer-generate-templates +``` +There's an alternative command to use if you'd like to filter by regexp. -This will create a new buffer with simplified specs for all unthemed -faces. Colors will be selected from the theme palette based on the -nearest RGB distance to the un-themed color. +``` +M-x autothemer-generate-templates-filtered +``` -We recommend thoroughly reviewing the auto generated themes so that -you produce a high quality theme. Autothemer doesn't replace good -judgement and taste! +These commands will create a new buffer with simplified specs for all the +unthemed faces (or the subset you filtered by). Colors will be selected from the theme palette based on the +nearest RGB distance to the un-themed color. ### Re-using the color palette @@ -175,38 +211,69 @@ use, you can define simple advice on `autothemer-deftheme` to do so: (cadr e))) (cdr palette))) ``` + If you place the advice definition before the autothemer-generated theme is loaded, e.g. `my-red` from the example above will be available as a variable that can be used in other parts of your emacs configuration. +### Let palette + +Alternatively you can create a let-like block using the macro `autothemer-let-palette`. +You will need to load/eval the required autothemer theme source (not byte-compiled), before +executing it. + +The palette color values will autocomplete, and you can check the palette +with `M-x macrostep-expand`(place the cursor to the left of the macro call.) + + + +### Colorize color-names from the palette + +Color names in the palette can be colorized, in any buffer. +Make sure there's a current theme in `autothemer--current-theme` (eval your autothemer based theme from source, not byte-code) and use: + +``` +M-x autothemer-colorize +``` +For example, with [Soothe Theme](https://github.com/emacsfodder/emacs-soothe-theme) viewing `soothe-tva.el`: + + + +For even more feedback, install and use the excellet [Fontify-Face](https://github.com/Fuco1/fontify-face) so you +can see the current face definitions too. + + + +In these images `rainbow-mode` is also swiched on, so we can see hex colors and system palette names colorized. + +``` +M-x rainbow-mode +``` + +To edit colors interatively [Kurecolor](https://github.com/emacsfodder/kurecolor) will serve you well. + ### Select colors from the palette Since version 0.2.8 it is possible to select a color from the palette (using the `completing-read` style.) `autothemer-select-color` returns an `autothemer--color` struct (`name`,`value`) - + -You'd need to do something like this to insert a color name or color value: +There are also commands to insert a selected color name or it's value. -```lisp -(require 'autothemer) +``` +M-x autothemer-insert-color-name +``` +and... -(defun insert-autothemer-color-value () - "Select and insert a color value from `autothemer--current-theme`.") - (interactive) - (insert (autothemer--color-value (autothemer-select-color))) - -(defun insert-autothemer-color-name) - "Select and insert a color name from `autothemer--current-theme`.") - (interactive) - (insert (autothemer--color-name (autothemer-select-color))) +``` +M-x autothemer-insert-color ``` If `autothemer--current-theme` is `nil`, you'll need to eval an autothemer based theme before use. - ### Generate a SVG image of the palette Since version 0.2.8 you can generate a SVG image of a theme palette. (see this example for the [Sakura theme](https://raw.githubusercontent.com/emacsfodder/emacs-theme-sakura/master/sakura.svg)) @@ -422,26 +489,32 @@ Once this is done you test your theme. (I use `disable-theme` and `enable-theme` to test/use themes under development. Make sure you eval all the theme's elisp files before enabling the theme.) - - ### Themes using Autothemer -- [Gruvbox](https://github.com/greduan/emacs-theme-gruvbox) -- [Darktooth](https://github.com/emacsfodder/emacs-theme-darktooth) -- [Creamsody](https://github.com/emacsfodder/emacs-theme-creamsody) -- [Sakura](https://github.com/emacsfodder/emacs-theme-sakura) -- [Cyanometric](https://github.com/emacsfodder/emacs-theme-cyanometric) -- [Orangey Bits](https://github.com/emacsfodder/emacs-theme-orangey-bits) -- [Vegetative](https://github.com/emacsfodder/emacs-theme-vegetative) - -If you are creating themes with Autothemer, please let us know (you can email the maintainer.) +- [greduan/Gruvbox](https://github.com/greduan/emacs-theme-gruvbox) +- [thongpv87/Rose Pine](https://github.com/thongpv87/rose-pine-emacs) +- [ogdenwebb/Kaolin](https://github.com/ogdenwebb/emacs-kaolin-themes) +- [mtreca/Sorcery](https://github.com/mtreca/emacs-theme-sorcery) +- [ajgrf/Parchment](https://github.com/ajgrf/parchment/) +- [emacsfodder/Darktooth](https://github.com/emacsfodder/emacs-theme-darktooth) +- [emacsfodder/Soothe](https://github.com/emacsfodder/emacs-soothe-theme) +- [emacsfodder/Creamsody](https://github.com/emacsfodder/emacs-theme-creamsody) +- [emacsfodder/Sakura](https://github.com/emacsfodder/emacs-theme-sakura) +- [emacsfodder/Orangey Bits](https://github.com/emacsfodder/emacs-theme-orangey-bits) +- [emacsfodder/Cyanometric](https://github.com/emacsfodder/emacs-theme-cyanometric) +- [emacsfodder/Vegetative](https://github.com/emacsfodder/emacs-theme-vegetative) + +If you are creating themes with Autothemer, please let us know, you can add the +theme info to README and open a pull request. If you haven't released it as a +package, via a common source, open an [issue], we can help. ### Contributing -We welcome all issues and pull requests, for review by the project author. +See [CONTRIBUTING](CONTRIBUTING.md) ### Licence See [LICENCE](LICENCE) [Autothemer]: https://github.com/jasonm23/autothemer +[issue]: https://github.com/jasonm23/autothemer/issues/new/choose diff --git a/autothemer.el b/autothemer.el index 09660c5ded..a283e65389 100644 --- a/autothemer.el +++ b/autothemer.el @@ -7,7 +7,7 @@ ;; Maintainer: Jason Milkins <jason...@gmail.com> ;; ;; URL: https://github.com/jasonm23/autothemer -;; Version: 0.2.9 +;; Version: 0.2.10 ;; Package-Requires: ((dash "2.10.0") (emacs "26.1")) ;; ;;; License: @@ -36,9 +36,17 @@ (require 'lisp-mnt) (require 'subr-x) -(cl-defstruct autothemer--color name value) +(cl-defstruct + autothemer--color + name + value) -(cl-defstruct autothemer--theme colors defined-faces name description) +(cl-defstruct + autothemer--theme + colors + defined-faces + name + description) (defvar autothemer--current-theme nil "Internal variable of type `autothemer--theme' used by autothemer. @@ -128,8 +136,8 @@ bindings within both the REDUCED-SPECS and the BODY." "Return the distance in rgb space between COLOR and AUTOTHEMER-COLOR. Here, COLOR is an Emacs color specification and AUTOTHEMER-COLOR is of type `autothemer--color'." - (let ((rgb-1 (color-values color)) - (rgb-2 (color-values (autothemer--color-value autothemer-color)))) + (let ((rgb-1 (autothemer-hex-to-rgb color)) + (rgb-2 (autothemer-hex-to-rgb (autothemer--color-value autothemer-color)))) (-sum (--zip-with (abs (- it other)) rgb-1 rgb-2)))) (defun autothemer--find-closest-color (colors color) @@ -308,7 +316,9 @@ Otherwise, append NEW-COLUMN to every element of LISTS." Search the `autothemer--current-theme' color palette for COLOR-NAME and returns a color in the form of `autothemer--color' struct. -See also `autothemer--color-p', `autothemer--color-name', `autothemer--color-value'." +See also `autothemer--color-p', + `autothemer--color-name', + `autothemer--color-value'." (autothemer--current-theme-guard) (--find (eql (intern color-name) @@ -321,7 +331,9 @@ Current palette is read from `autothemer--current-theme'. The selected color will be in the form of a `autothemer--color' -See also `autothemer--color-p', `autothemer--color-name', `autothemer--color-value'." +See also `autothemer--color-p', + `autothemer--color-name', + `autothemer--color-value'." (autothemer--current-theme-guard) (let* ((selected @@ -345,7 +357,7 @@ See also `autothemer--color-p', `autothemer--color-name', `autothemer--color-val (autothemer--get-color color-name))) (defun autothemer-insert-color () - "Interactively select and insert a color from the current autotheme palette." + "Select and insert a color from the current autotheme palette." (interactive) (autothemer--current-theme-guard) (let ((color (autothemer--color-value @@ -353,7 +365,7 @@ See also `autothemer--color-p', `autothemer--color-name', `autothemer--color-val (insert color))) (defun autothemer-insert-color-name () - "Interactively select and insert a color name from the current autotheme palette." + "Select and insert a color name from the current autotheme palette." (interactive) (autothemer--current-theme-guard) (let ((color-name (autothemer--color-name @@ -379,12 +391,143 @@ If PLIST is nil, ARGS are bound to BODY nil values." "Unindent string S marked with | chars." (replace-regexp-in-string "^ *|" "" s)) +;;; let palette... +(defmacro autothemer-let-palette (&rest body) + "Provide a let block for BODY from `autothemer--current-theme'. + +Load/eval the required autothemer theme source (not +byte-compiled) to set `autothemer--current-theme'." + (autothemer--current-theme-guard) + `(let ,(--map (list (autothemer--color-name it) (autothemer--color-value it)) + (autothemer--theme-colors autothemer--current-theme)) + ,@body)) + +;;; Colorize alist for rainbow-mode +(defun autothemer-colorize-alist () + "Generate an alist for use with rainbow-mode. + +To colorize use: + + (rainbow-colorize-by-assoc (autothemer-colorize-alist)) + +Colors are from `autothemer--current-theme'." + (autothemer--current-theme-guard) + (--map (cons (format "%s" (autothemer--color-name it)) + (autothemer--color-value it)) + (autothemer--theme-colors autothemer--current-theme))) + +(defvar autothemer--colors-font-lock-keywords nil) + +(defun autothemer-colorize () + "Colorize using rainbow-mode." + (interactive) + (setq autothemer--colors-font-lock-keywords + `((,(regexp-opt (mapcar 'car (autothemer-colorize-alist)) 'words) + (0 (rainbow-colorize-by-assoc (autothemer-colorize-alist)))))) + (font-lock-add-keywords nil autothemer--colors-font-lock-keywords t)) + +(defun autothemer--color-to-hsv (rgb) + "Convert RGB, a list of `(r g b)' to list `(h s v)'. +The `r' `g' `b' values can range between `0..65535'. + +In `(h s v)' `h', `s' and `v' are `0.0..1.0'." + (cl-destructuring-bind + (r g b) rgb + (let* + ((bri (max r g b)) + (delta (- bri (min r g b))) + (sat (if (cl-plusp bri) + (/ delta bri) + 0.0)) + (normalize #'(lambda + (constant right left) + (let ((hue (+ constant (/ (* 60.0 (- right left)) delta)))) + (if (cl-minusp hue) + (+ hue 360.0) + hue))))) + (list (/ (cond + ((zerop sat) 0.0) + ((= r bri) (funcall normalize 0.0 g b)) ; dominant r + ((= g bri) (funcall normalize 120.0 b r)) ; dominant g + (t (funcall normalize 240.0 r g))) ; dominant b + 360.0) + sat + bri)))) + +(defun autothemer-hex-to-rgb (hex) + "Fast convert HEX to `(r g b)'. +(Perf equal to wx color values C function.) +`r', `g', `b' will be values `0..65535'" + (let ((rgb (string-to-number (substring hex 1) 16))) + (list + (* #x101 (ash (logand #xFF0000 rgb) -16)) + (* #x101 (ash (logand #xFF00 rgb) -8)) + (* #x101 (logand #xFF rgb))))) + +(defun autothemer-color-hue (hex-color) + "Return the HSV hue of HEX-COLOR." + (car (autothemer--color-to-hsv (autothemer-hex-to-rgb hex-color)))) + +(defun autothemer-color-sat (hex-color) + "Return the HSV sat of HEX-COLOR." + (cadr (autothemer--color-to-hsv (autothemer-hex-to-rgb hex-color)))) + +(defun autothemer-color-brightness (hex-color) + "Return the HSV brightness of HEX-COLOR." + (caddr (autothemer--color-to-hsv (autothemer-hex-to-rgb hex-color)))) + +(defun autothemer-darkest-order (a b) + "Return t if the darkness of A > B." + (let ((a (autothemer-color-brightness (autothemer--color-value a))) + (b (autothemer-color-brightness (autothemer--color-value b)))) + (> b a))) + +(defun autothemer-lightest-order (a b) + "Return t if the lightness of A > B." + (let ((a (autothemer-color-brightness (autothemer--color-value a))) + (b (autothemer-color-brightness (autothemer--color-value b)))) + (> a b))) + +(defun autothemer-saturated-order (a b) + "Return t if the saturation of A > B." + (let ((a (autothemer-color-sat (autothemer--color-value a))) + (b (autothemer-color-sat (autothemer--color-value b)))) + (> a b))) + +(defun autothemer-hue-order (a b) + "Return t if the hue of A > B." + (let ((a (autothemer-color-hue (autothemer--color-value a))) + (b (autothemer-color-hue (autothemer--color-value b)))) + (> a b))) + +(defun autothemer-hue-sat-order (a b) + "Return t if the hue and sat of A > B." + (let ((a-hue (autothemer-color-hue (autothemer--color-value a))) + (b-hue (autothemer-color-hue (autothemer--color-value b))) + (a-sat (autothemer-color-sat (autothemer--color-value a))) + (b-sat (autothemer-color-sat (autothemer--color-value b))) + (sort-hash-fmt "%016s-%016s")) + (string> (format sort-hash-fmt a-hue a-sat) + (format sort-hash-fmt b-hue b-sat)))) + +(defun autothemer-sort-palette (theme-colors &optional fn) + "Produce a list of sorted THEME-COLORS using FN. + +If FN is nil, sort by default FN `autothemer-darkest-order'. + +`autothemer-lightest-order' is available to balance the force. + +There are also `autothemer-hue-order' and `autothemer-saturated-order'" + (let ((fn (or fn 'autothemer-darkest-order))) + (-sort fn theme-colors))) + ;;; SVG Palette generator... (defun autothemer-generate-palette-svg (&optional options) "Create an SVG palette image for a theme. -Optionally supply OPTIONS, a plist (all keys are optional): +Optionally supply a plist of OPTIONS (all keys are optional, +required values will default or prompt interactively.): :theme-file - theme filename :theme-name - override the title found in :theme-file @@ -392,14 +535,23 @@ Optionally supply OPTIONS, a plist (all keys are optional): :theme-url - override the url found in :theme-file :swatch-width - px spacing width of a color swatch (default: 100) :swatch-height - px spacing height of a color swatch (default: 150) + :swatch-rotate - degrees of rotation for swatch (default: 45) :columns - number of columns for each palette row (default: 6) :page-template - see page-template below + :page-top-margin - (default 120) + :page-right-margin - (default 30) + :page-bottom-margin - (default 60) + :page-left-margin - (default 30) + :h-space - (default 10) + :v-space - (default 10) :swatch-template - see swatch-template below :font-family - font name to use in the generated SVG - :bg-color - background color - :text-color - text color - :text-accent-color - text color - :svg-out-file - SVG output filename + :bg-color + :text-color + :text-accent-color + :swatch-border-color + :sort-palette + :svg-out-file For advanced customization the :page-template and :swatch-template can be used to provide customize the SVG templates. @@ -423,15 +575,40 @@ Swatch Template parameters: %1$s - x %2$s - y - %3$s - swatch-color - %4$s - text-color - %5$s - swatch-color-name" + %3$s - swatch-border-color + %4$s - swatch-color + %5$s - text-accent-color + %6$s - swatch-color-name" (interactive) (autothemer--plist-bind - (theme-file theme-name theme-description theme-url - swatch-width swatch-height columns page-template - swatch-template font-family bg-color - text-color text-accent-color svg-out-file) + (theme-file + theme-name + theme-description + theme-url + + sort-palette + swatch-width + swatch-height + swatch-rotate + columns + + page-top-margin + page-right-margin + page-bottom-margin + page-left-margin + + page-template + swatch-template + + font-family + + bg-color + text-color + text-accent-color + swatch-border-color + h-space + v-space + svg-out-file) options (let ((theme-file (or theme-file (read-file-name "Select autothemer theme .el file: ")))) (load-file theme-file) ;; make it the current-theme @@ -459,50 +636,54 @@ Swatch Template parameters: | </a> | </g> | <g transform=\"translate(70,-40)\"> - | %10$s + | %10$s | </g> |</svg> |"))) (swatch-template (or swatch-template - (autothemer--unindent "<g transform=\"translate(%1$s,%2$s),rotate(45)\"> + (autothemer--unindent "<g transform=\"translate(%1$s,%2$s),rotate(%9$s)\"> | <ellipse cx=\"70\" cy=\"70\" rx=\"45\" ry=\"45\" id=\"background-color\" fill=\"%3$s\"/> | <ellipse cx=\"70\" cy=\"70\" rx=\"42\" ry=\"42\" id=\"color\" fill=\"%4$s\"/> | <text style=\"font-size:7pt\" font-weight=\"bold\" x=\"52\" y=\"125\" id=\"color-name\">%6$s</text> | <text style=\"font-size:7pt; fill:%5$s;\" font-weight=\"bold\" x=\"52\" y=\"134\" id=\"color\">%4$s</text> + | <!-- Rect below is for debug set stroke width to be visible --> + | <rect x=\"0\" y=\"0\" width=\"%7$spx\" height=\"%8$spx\" class=\"debug-rect\" fill-opacity=\"0.0\" stroke-width=\"0.0mm\" stroke=\"#FF8000\"/> |</g> |"))) (autotheme-name (autothemer--theme-name autothemer--current-theme)) - (theme-name (or theme-name - (autothemer--theme-name autothemer--current-theme))) - (theme-description (or theme-description - (autothemer--theme-description autothemer--current-theme))) - (theme-url (or theme-url - (lm-homepage theme-file) - (read-string "Enter theme URL: " "https://github.com/"))) (colors (autothemer--theme-colors autothemer--current-theme)) - (font-family (or font-family - (read-string "Font family name: " "Helvetica Neue"))) - (swatch-width (or swatch-width - 100)) - (swatch-height (or swatch-height - 150)) - (columns (or columns - 6)) - ;; TODO: Width and Height padding (55, 120) parameterized? - (width (+ 55 (* columns swatch-width))) - (height (+ 120 (* (/ (length colors) columns) swatch-height))) - (background-color (or bg-color - (autothemer--color-value - (autothemer--select-color "Select Background color: ")))) - (text-color (or text-color - (autothemer--color-value - (autothemer--select-color "Select Text color: ")))) - (text-accent-color (or text-accent-color - (autothemer--color-value - (autothemer--select-color "Select Text accent color: ")))) + (theme-name (or theme-name (autothemer--theme-name autothemer--current-theme))) + (theme-description (or theme-description (autothemer--theme-description autothemer--current-theme))) + (theme-url (or theme-url (lm-homepage theme-file) (read-string "Enter theme URL: " "https://github.com/"))) + + (font-family (or font-family (read-string "Font family name: " "Helvetica Neue"))) + (swatch-width (or swatch-width (read-number "Swatch width: " 100))) + (swatch-height (or swatch-height (read-number "Swatch height: " 150))) + (swatch-rotate (or swatch-rotate (read-number "Swatch rotate: " 45))) + (columns (or columns (read-number "Number or columns: " 6))) + (page-top-margin (or page-top-margin (read-number "Page Top margin: " 120))) + (page-bottom-margin (or page-bottom-margin (read-number "Page Bottom margin: " 60))) + (page-left-margin (or page-left-margin (read-number "Page Left margin: " 30))) + (page-right-margin (or page-right-margin (read-number "Page Right margin: " 30))) + (h-space (or h-space (read-number "Swatch horiztonal spacing: " 10))) + (v-space (or v-space (read-number "Swatch vertical spacing: " 10))) + + (rows (/ (length colors) columns)) + (width (+ page-right-margin page-left-margin + (* h-space columns) + (* swatch-width columns))) + (height (+ page-top-margin page-bottom-margin + (* v-space rows) + (* swatch-height (+ 1 rows)))) + + (background-color (or bg-color (autothemer--color-value (autothemer--select-color "Select Background color: ")))) + (text-color (or text-color (autothemer--color-value (autothemer--select-color "Select Text color: ")))) + (text-accent-color (or text-accent-color (autothemer--color-value (autothemer--select-color "Select Text accent color: ")))) + (swatch-border-color (or swatch-border-color (autothemer--color-value (autothemer--select-color "Select swatch border color: ")))) + (svg-out-file (or svg-out-file (read-file-name (format "Enter a Filename to save SVG palette for %s." theme-name)))) (svg-swatches (string-join (-map-indexed @@ -512,16 +693,20 @@ Swatch Template parameters: (replace-regexp-in-string (concat autotheme-name "-") "" (format "%s" (autothemer--color-name it))))) - (x (+ 20 (* swatch-width (% index columns)))) - (y (+ 90 (* swatch-height (/ index columns))))) + (x (+ page-left-margin (* (+ h-space swatch-width) (% index columns)))) + (y (+ page-top-margin (* (+ v-space swatch-height) (/ index columns))))) (format swatch-template x y - background-color + swatch-border-color color text-accent-color - name))) - colors) + name swatch-width swatch-height swatch-rotate))) + (if sort-palette + (if (eql t sort-palette) + (autothemer-sort-palette colors) + (autothemer-sort-palette colors (intern sort-palette))) + colors)) "\n"))) (with-temp-file svg-out-file (insert diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000000..7f5fec1a76 --- /dev/null +++ b/bin/setup @@ -0,0 +1,21 @@ +#!/bin/sh -e +# For CI / Github Actions + +EMACS="${EMACS:=emacs}" + +NEEDED_PACKAGES="dash" + +INIT_PACKAGE_EL="(progn \ + (require 'package) \ + (push '(\"melpa\" . \"https://melpa.org/packages/\") package-archives) \ + (package-initialize) \ + (unless package-archive-contents \ + (package-refresh-contents)) \ + (dolist (pkg '(${NEEDED_PACKAGES})) \ + (unless (package-installed-p pkg) \ + (package-install pkg))))" + +# Refresh package archives, because the test suite needs to see at least +# package-lint and cl-lib. +"$EMACS" -batch \ + --eval "$INIT_PACKAGE_EL" diff --git a/bin/test b/bin/test new file mode 100755 index 0000000000..a2ff50bf3b --- /dev/null +++ b/bin/test @@ -0,0 +1,40 @@ +#!/bin/sh +dash="$(dirname "$(find ~/.emacs.d | grep "/dash.el$" | tail -1)")" + +EMACS="${EMACS:=emacs}" + +cat <<INFO +════════════════════════════════════════════════════════════════════════════════ + _ _ _____ _ + / \ _ _| |_ __|_ _| |__ ___ _ __ ___ ___ _ __ + / _ \| | | | __/ _ \| | | '_ \ / _ \ '_ ' _ \ / _ \ '__| + / ___ \ |_| | || (_) | | | | | | __/ | | | | | __/ | +/_/ \_\__,_|\__\___/|_| |_| |_|\___|_| |_| |_|\___|_| + _____ _ +|_ _|__ ___| |_ ___ + | |/ _ \/ __| __/ __| + | | __/\__ \ |_\__ \\ + |_|\___||___/\__|___/ + +──────────────────────────────────────────────────────────────────────────────── +Required packages present: +Dash: $dash +════════════════════════════════════════════════════════════════════════════════ +$("$EMACS" --version) +════════════════════════════════════════════════════════════════════════════════ +INFO + +"$EMACS" --batch \ + -eval "(setq load-prefer-newer t)" \ + -eval "(add-to-list 'load-path \"${dash}\")" \ + -eval "(add-to-list 'load-path \".\")" \ + -l ert \ + -l dash \ + -l autothemer.el \ + -l tests/autothemer-tests.el \ + -f ert-run-tests-batch-and-exit +exitcode=$? + +echo " +════════════════════════════════════════════════════════════════════════════════" +exit ${exitcode} diff --git a/tests/autothemer-tests.el b/tests/autothemer-tests.el index 5699122967..03ab60faac 100644 --- a/tests/autothemer-tests.el +++ b/tests/autothemer-tests.el @@ -1,43 +1,177 @@ -;; theme-example.el +;; autothemer-tests.el ;;; Code: -(load-file "autothemer.el") - -(autothemer-deftheme - theme-example - "Autothemer example..." - - ;; Specify the color classes used by the theme - ((((class color) (min-colors #xFFFFFF)) - ((class color) (min-colors #xFF))) - - ;; Specify the color palette for each of the classes above. - (example-red "#781210" "#FF0000") - (example-green "#22881F" "#00D700") - (example-blue "#212288" "#0000FF") - (example-purple "#812FFF" "#Af00FF") - (example-yellow "#EFFE00" "#FFFF00") - (example-orange "#E06500" "#FF6600") - (example-cyan "#22DDFF" "#00FFFF")) - - ;; specifications for Emacs faces. - ((button (:underline t :weight 'bold :foreground example-yellow)) - (error (:foreground example-red))) - - ;; Forms after the face specifications are evaluated. - ;; (palette vars can be used, read below for details.) - (custom-theme-set-variables - 'theme-example - `(ansi-color-names-vector - [,example-red - ,example-green - ,example-blue - ,example-purple - ,example-yellow - ,example-orange - ,example-cyan]))) - -;; Eval buffer and use as a test sandox. - -;;; theme-example.el ends here +(require 'autothemer) + +(progn "Test autothemer-deftheme" + (autothemer-deftheme theme-example + "Autothemer example..." + + ;; Specify the color classes used by the theme + ((((class color) (min-colors #xFFFFFF)) + ((class color) (min-colors #xFF))) + + ;; Specify the color palette for each of the classes above. + (example-red "#781210" "#FF0000") + (example-green "#22881F" "#00D700") + (example-blue "#212288" "#0000FF") + (example-purple "#812FFF" "#Af00FF") + (example-yellow "#EFFE00" "#FFFF00") + (example-orange "#E06500" "#FF6600") + (example-cyan "#22DDFF" "#00FFFF")) + + ;; specifications for Emacs faces. + ((button (:underline t :weight 'bold :foreground example-yellow)) + (error (:foreground example-red))) + + ;; Forms after the face specifications are evaluated. + ;; (palette vars can be used, read below for details.) + (custom-theme-set-variables + 'theme-example + `(ansi-color-names-vector + [,example-red + ,example-green + ,example-blue + ,example-purple + ,example-yellow + ,example-orange + ,example-cyan]))) + + (ert-deftest current-theme () + "Test current theme is available." + (should (not (null + autothemer--current-theme)))) + + (ert-deftest theme-has-colors () + "Check theme has colors." + (should (eql 7 (length (autothemer--theme-colors + autothemer--current-theme))))) + + (ert-deftest theme-has-face-specs () + "Check theme has face specs." + (should (eql 2 (length (autothemer--theme-defined-faces + autothemer--current-theme))))) + + (ert-deftest color-value () + "Check color value." + (should (string= "#781210" + (autothemer--color-value + (car (autothemer--theme-colors + autothemer--current-theme)))))) + + (ert-deftest color-name () + "Check color name." + (should (string= "example-red" + (autothemer--color-name + (car (autothemer--theme-colors + autothemer--current-theme)))))) + + (ert-deftest spec-name () + "Check spec name." + (should (equal 'button + (car (autothemer--theme-defined-faces + autothemer--current-theme))))) + + (ert-deftest theme-has-description () + "Check theme description." + (should (string= + "Autothemer example..." + (autothemer--theme-description + autothemer--current-theme)))) + + (ert-deftest theme-has-name () + "Check theme name." + (should (string= + "theme-example" + (autothemer--theme-name + autothemer--current-theme)))) + + (ert-deftest let-palette () + "Check autothemer-let-palette" + (should (string= + "#781210" + (autothemer-let-palette example-red)))) + + (ert-deftest unindent () + "Test unindent." + (should + (string= + (autothemer--unindent "|Hello world + | Foo bar + | Indent + |") + "Hello world\n Foo bar\n Indent\n"))) + + (ert-deftest autothemer-plist-bind () + "Test plist-bind." + (autothemer--plist-bind (a b) '(:a 1 :b 2) + (should (eql a 1)) + (should (eql b 2)))) + + (ert-deftest autothemer-color-hue () + "Test get hue of hex-color." + (= (autothemer-color-hue "#FF0000") 0) + (= (autothemer-color-hue "#FFFF00") 0.16666666666666666) + (= (autothemer-color-hue "#00FF00") 0.33333333333333333) + (= (autothemer-color-hue "#0000FF") 0.66666666666666666)) + + (ert-deftest autothemer-color-sat () + "Test get sat of hex-color." + (= (autothemer-color-sat "#0000FF") 1.0) + (= (autothemer-color-sat "#FF00FF") 1.0) + (= (autothemer-color-sat "#778822") 0.75) + (= (autothemer-color-sat "#772288") 0.75) + (= (autothemer-color-sat "#112233") 0.6666666666666667)) + + (ert-deftest autothemer-color-brightness () + "Test get brightness of hex-color." + (= (autothemer-color-brightness "#0000FF") 1.0) + (= (autothemer-color-brightness "#00FF00") 1.0) + (= (autothemer-color-brightness "#FF00FF") 1.0) + (= (autothemer-color-brightness "#333333") 0.2) + (= (autothemer-color-brightness "#555555") 0.3333333333333333)) + + (ert-deftest autothemer--color-distance () + "Test color distance." + (let ((color-struct (make-autothemer--color :name "Test" :value "#100000"))) + (should (eql (autothemer--color-distance "#100000" color-struct) 0)) + (should (eql (autothemer--color-distance "#100001" color-struct) 257)) + (should (eql (autothemer--color-distance "#000001" color-struct) 4369)) + (should (eql (autothemer--color-distance "#FF0000" color-struct) 61423)))) + + (ert-deftest autothemer-hex-to-rgb () + "Test hex to rgb." + (should (equal '(0 0 0) (autothemer-hex-to-rgb "#000000"))) + (should (equal '(65535 65535 65535) (autothemer-hex-to-rgb "#FFFFFF"))) + (should (equal '(65535 0 0) (autothemer-hex-to-rgb "#FF0000"))) + (should (equal '(65535 65535 0) (autothemer-hex-to-rgb "#FFFF00"))) + (should (equal '(0 65535 0) (autothemer-hex-to-rgb "#00FF00"))) + (should (equal '(0 65535 65535) (autothemer-hex-to-rgb "#00FFFF"))) + (should (equal '(0 0 65535) (autothemer-hex-to-rgb "#0000FF"))) + (should (equal '(32896 32896 32896) (autothemer-hex-to-rgb "#808080")))) + + (ert-deftest autothemer-colorize-alist () + "Check autothemer-colorize-alist." + (should (equal '(("example-red" . "#781210") + ("example-green" . "#22881F") + ("example-blue" . "#212288") + ("example-purple" . "#812FFF") + ("example-yellow" . "#EFFE00") + ("example-orange" . "#E06500") + ("example-cyan" . "#22DDFF")) + (autothemer-colorize-alist))))) + +;;; Example theme in memory: +'(#s(autothemer--theme + (#s(autothemer--color example-red "#781210") + #s(autothemer--color example-green "#22881F") + #s(autothemer--color example-blue "#212288") + #s(autothemer--color example-purple "#812FFF") + #s(autothemer--color example-yellow "#EFFE00") + #s(autothemer--color example-orange "#E06500") + #s(autothemer--color example-cyan "#22DDFF")) + (button error) + "theme-example" "Autothemer example...")) + +;;; autothemer-tests.el ends here