CI/CD & Formatting (#6)

* Improve autoformatting guides, add editorconfig and add CI/CD
This commit is contained in:
Safin Singh 2020-10-13 09:57:50 -07:00 committed by elkowar
parent 4d6b38f3e1
commit c57713ca9a
17 changed files with 209 additions and 98 deletions

8
.editorconfig Normal file
View File

@ -0,0 +1,8 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

26
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: build
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Set up
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
components: rustfmt
- uses: actions/checkout@v2
- name: Format
run: cargo build

26
.github/workflows/fmt.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: format
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
jobs:
fmt:
runs-on: ubuntu-latest
steps:
- name: Set up
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
components: rustfmt
- uses: actions/checkout@v2
- name: Format
run: cargo fmt -- --check

26
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: test
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Set up
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
components: rustfmt
- uses: actions/checkout@v2
- name: Test
run: cargo test --verbose

View File

@ -4,8 +4,6 @@ version = "0.1.0"
authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
gtk = { version = "0.9", features = [ "v3_16" ] }
gdk = { version = "", features = ["v3_16"] }
@ -13,7 +11,6 @@ gio = { version = "", features = ["v2_44"] }
glib = { version = "", features = ["v2_44"] }
gdk-pixbuf = "0.9"
regex = "1"
try_match = "0.2.2"
anyhow = "1.0"
@ -39,8 +36,5 @@ lazy_static = "1.4.0"
libc = "0.2"
ref-cast = "1.0"
#thiserror = "1.0"
[dev-dependencies]
pretty_assertions = "0.6.1"

View File

@ -1,15 +1,12 @@
# Table of Contents
* [About](#org4ab08b6)
* [Configuration](#org581ca61)
* [Example Config](#orgb769597)
* [Building](#orgbf66ce2)
* [Prerequisites](#org727b3da)
* [Installation](#orgdd31739)
* [Usage](#org4a9b3c6)
- [About](#org4ab08b6)
- [Configuration](#org581ca61)
- [Example Config](#orgb769597)
- [Building](#orgbf66ce2)
- [Prerequisites](#org727b3da)
- [Installation](#orgdd31739)
- [Usage](#org4a9b3c6)
<a id="org4ab08b6"></a>
@ -17,17 +14,16 @@
Elkowar&rsquo;s Wacky Widgets is a standalone Widget System made in rust to add AwesomeWM like widgets to any WM
<a id="org581ca61"></a>
# Configuration
Eww&rsquo;s configuration should be placed in `~/.config/eww/eww.xml` and any `scss` styles you want to add should be put into `~/.config/eww/eww.scss`.
<a id="orgb769597"></a>
## Example Config
```xml
<eww>
<definitions>
@ -66,17 +62,16 @@ Eww&rsquo;s configuration should be placed in `~/.config/eww/eww.xml` and any `s
# Building
<a id="org727b3da"></a>
## Prerequisites
- cargo with nightly toolchain
- rustc
- cargo (nightly toolchain)
Rather than with your system package manager, I recommend installing it using [rustup](https://rustup.rs/),
as this makes it easy to use the nightly toolchain, which is necessary to build eww.
<a id="orgdd31739"></a>
## Installation
@ -89,10 +84,8 @@ Build the Binary using -:
then copy the built binary from `./target/release` to anywhere in `$PATH` (example - `~/.local/bin`)
<a id="org4a9b3c6"></a>
# Usage
Create a Config and then just do `eww`!

View File

@ -1,4 +1,13 @@
unstable_features=true
fn_single_line=false
max_width=130
unstable_features = true
fn_single_line = false
max_width = 130
reorder_impl_items = true
merge_imports = true
normalize_comments = true
use_field_init_shorthand = true
wrap_comments = true
combine_control_expr = false
condense_wildcard_suffixes = true
format_code_in_doc_comments = true
format_macro_matchers = true
format_strings = true

View File

@ -3,9 +3,10 @@ use lazy_static::lazy_static;
use regex::Regex;
use std::ops::Range;
use crate::value::AttrValue;
use crate::value::VarName;
use crate::with_text_pos_context;
use crate::{
value::{AttrValue, VarName},
with_text_pos_context,
};
use maplit::hashmap;
use std::collections::HashMap;

View File

@ -1,12 +1,12 @@
use crate::util;
use crate::value::PrimitiveValue;
use crate::value::VarName;
use crate::{
util,
value::{PrimitiveValue, VarName},
};
use anyhow::*;
use derive_more;
use element::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use std::{collections::HashMap, fmt};
use util::Coords;
use xml_ext::*;
@ -134,12 +134,15 @@ impl EwwConfig {
pub fn get_widgets(&self) -> &HashMap<String, WidgetDefinition> {
&self.widgets
}
pub fn get_windows(&self) -> &HashMap<WindowName, EwwWindowDefinition> {
&self.windows
}
pub fn get_default_vars(&self) -> &HashMap<VarName, PrimitiveValue> {
&self.initial_variables
}
pub fn get_script_vars(&self) -> &Vec<ScriptVar> {
&self.script_vars
}
@ -211,6 +214,7 @@ impl Default for WindowStacking {
impl std::str::FromStr for WindowStacking {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
let s = s.to_lowercase();
match s.as_str() {

View File

@ -141,6 +141,7 @@ impl<'a, 'b> XmlElement<'a, 'b> {
format!("<{} {}>", self.tag_name(), attrs)
}
pub fn tag_name(&self) -> &str {
self.0.tag_name().name()
}
@ -159,6 +160,7 @@ impl<'a, 'b> XmlElement<'a, 'b> {
.filter(|child| child.is_element() || (child.is_text() && !child.text().unwrap_or_default().is_blank()))
.map(XmlNode::from)
}
pub fn child_elements(&self) -> impl Iterator<Item = XmlElement> {
self.0.children().filter(|child| child.is_element()).map(XmlElement)
}

View File

@ -1,14 +1,11 @@
use crate::config::WindowName;
use crate::value::VarName;
use crate::{config::WindowName, value::VarName};
use anyhow::*;
use std::collections::HashMap;
use std::process::Command;
use std::sync::Arc;
use std::{collections::HashMap, process::Command, sync::Arc};
use crate::value::{AttrValue, PrimitiveValue};
/// Handler that get's executed to apply the necessary parts of the eww state to a gtk widget.
/// These are created and initialized in EwwState::resolve.
/// Handler that get's executed to apply the necessary parts of the eww state to
/// a gtk widget. These are created and initialized in EwwState::resolve.
pub struct StateChangeHandler {
func: Box<dyn Fn(HashMap<String, PrimitiveValue>) -> Result<()> + 'static>,
constant_values: HashMap<String, PrimitiveValue>,
@ -57,7 +54,8 @@ impl EwwWindowState {
}
}
/// Stores the actual state of eww, including the variable state and the window-specific state-change handlers.
/// Stores the actual state of eww, including the variable state and the
/// window-specific state-change handlers.
#[derive(Default)]
pub struct EwwState {
windows: HashMap<WindowName, EwwWindowState>,
@ -88,7 +86,8 @@ impl EwwState {
self.windows.clear();
}
/// Update the value of a variable, running all registered [StateChangeHandler]s.
/// Update the value of a variable, running all registered
/// [StateChangeHandler]s.
pub fn update_variable(&mut self, key: VarName, value: PrimitiveValue) -> Result<()> {
if !self.variables_state.contains_key(&key) {
bail!("Tried to set unknown variable '{}'", key);
@ -111,7 +110,8 @@ impl EwwState {
/// resolves a value if possible, using the current eww_state
/// Expects there to be at max one level of nesting var_refs from local-env.
/// This means that no elements in the local_env may be var-refs into the local_env again, but only into the global state.
/// This means that no elements in the local_env may be var-refs into the
/// local_env again, but only into the global state.
pub fn resolve_once<'a>(
&'a self,
local_env: &'a HashMap<VarName, AttrValue>,
@ -134,9 +134,11 @@ impl EwwState {
}
}
/// Resolve takes a function that applies a set of fully resolved attribute values to it's gtk widget.
/// Expects there to be at max one level of nesting var_refs from local-env.
/// This means that no elements in the local_env may be var-refs into the local_env again, but only into the global state.
/// Resolve takes a function that applies a set of fully resolved attribute
/// values to it's gtk widget. Expects there to be at max one level of
/// nesting var_refs from local-env. This means that no elements in the
/// local_env may be var-refs into the local_env again, but only into the
/// global state.
pub fn resolve<F: Fn(HashMap<String, PrimitiveValue>) -> Result<()> + 'static + Clone>(
&mut self,
window_name: &WindowName,
@ -144,9 +146,11 @@ impl EwwState {
mut needed_attributes: HashMap<String, AttrValue>,
set_value: F,
) {
// Resolve first collects all variable references and creates a set of unresolved attribute -> VarName pairs.
// additionally, all constant values are looked up and collected, including the values from the local environment
// These are then used to generate a StateChangeHandler, which is then executed and registered in the windows state.
// Resolve first collects all variable references and creates a set of
// unresolved attribute -> VarName pairs. additionally, all constant values are
// looked up and collected, including the values from the local environment
// These are then used to generate a StateChangeHandler, which is then executed
// and registered in the windows state.
let result: Result<_> = try {
let window_state = self

View File

@ -16,11 +16,12 @@ use ipc_channel::ipc;
use log;
use pretty_env_logger;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use structopt::StructOpt;
use value::PrimitiveValue;
use value::VarName;
use value::{PrimitiveValue, VarName};
pub mod app;
pub mod config;
@ -221,7 +222,8 @@ fn run_filewatch_thread<P: AsRef<Path>>(
Ok(hotwatch)
}
/// detach the process from the terminal, also closing stdout and redirecting stderr into /dev/null
/// detach the process from the terminal, also closing stdout and redirecting
/// stderr into /dev/null
fn do_detach() {
// detach from terminal
let pid = unsafe { libc::fork() };
@ -238,7 +240,7 @@ fn do_detach() {
libc::close(1);
}
}
//close stderr to not spam output
// close stderr to not spam output
if unsafe { libc::isatty(2) } != 0 {
unsafe {
let fd = libc::open(std::ffi::CString::new("/dev/null").unwrap().as_ptr(), libc::O_RDWR);

View File

@ -19,7 +19,8 @@ impl ScriptVarHandler {
})
}
/// clears and stops the currently running poll handles, then opens the new ones as configured
/// clears and stops the currently running poll handles, then opens the new
/// ones as configured
pub fn setup_command_poll_tasks(&mut self, config: &config::EwwConfig) -> Result<()> {
log::info!("reloading handler for poll script vars");
self.poll_handles.iter().for_each(|handle| handle.stop());

View File

@ -3,8 +3,7 @@ use extend::ext;
use grass;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::path::Path;
use std::{fmt, path::Path};
pub fn parse_scss_from_file<P: AsRef<Path>>(path: P) -> Result<String> {
let scss_content = std::fs::read_to_string(path)?;
@ -14,7 +13,8 @@ pub fn parse_scss_from_file<P: AsRef<Path>>(path: P) -> Result<String> {
#[ext(pub, name = StringExt)]
impl<T: AsRef<str>> T {
/// check if the string is empty after removing all linebreaks and trimming whitespace
/// check if the string is empty after removing all linebreaks and trimming
/// whitespace
fn is_blank(self) -> bool {
self.as_ref().replace('\n', "").trim().is_empty()
}
@ -51,6 +51,7 @@ impl fmt::Display for Coords {
impl std::str::FromStr for Coords {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
let (x, y) = s.split_once('x').ok_or_else(|| anyhow!("must be formatted like 200x500"))?;
Ok(Coords(x.parse()?, y.parse()?))

View File

@ -4,8 +4,7 @@ use lazy_static::lazy_static;
use ref_cast::RefCast;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::fmt;
use std::{convert::TryFrom, fmt};
#[derive(Clone, PartialEq, Deserialize, Serialize, derive_more::From)]
pub enum PrimitiveValue {
@ -32,7 +31,8 @@ impl fmt::Debug for PrimitiveValue {
impl std::str::FromStr for PrimitiveValue {
type Err = anyhow::Error;
/// parses the value, trying to turn it into a number and a boolean first, before deciding that it is a string.
/// parses the value, trying to turn it into a number and a boolean first,
/// before deciding that it is a string.
fn from_str(s: &str) -> Result<Self> {
Ok(PrimitiveValue::parse_string(s))
}
@ -44,6 +44,7 @@ fn remove_surrounding(s: &str, surround: char) -> &str {
impl TryFrom<PrimitiveValue> for String {
type Error = anyhow::Error;
fn try_from(x: PrimitiveValue) -> Result<Self> {
x.as_string()
}
@ -51,6 +52,7 @@ impl TryFrom<PrimitiveValue> for String {
impl TryFrom<PrimitiveValue> for f64 {
type Error = anyhow::Error;
fn try_from(x: PrimitiveValue) -> Result<Self> {
x.as_f64()
}
@ -58,6 +60,7 @@ impl TryFrom<PrimitiveValue> for f64 {
impl TryFrom<PrimitiveValue> for bool {
type Error = anyhow::Error;
fn try_from(x: PrimitiveValue) -> Result<Self> {
x.as_bool()
}
@ -70,13 +73,15 @@ impl From<&str> for PrimitiveValue {
}
impl PrimitiveValue {
/// parses the value, trying to turn it into a number and a boolean first, before deciding that it is a string.
/// parses the value, trying to turn it into a number and a boolean first,
/// before deciding that it is a string.
pub fn parse_string(s: &str) -> Self {
s.parse()
.map(PrimitiveValue::Number)
.or_else(|_| s.parse().map(PrimitiveValue::Boolean))
.unwrap_or_else(|_| PrimitiveValue::String(remove_surrounding(s, '\'').to_string()))
}
pub fn as_string(&self) -> Result<String> {
match self {
PrimitiveValue::String(x) => Ok(x.clone()),
@ -84,6 +89,7 @@ impl PrimitiveValue {
PrimitiveValue::Boolean(x) => Ok(format!("{}", x)),
}
}
pub fn as_f64(&self) -> Result<f64> {
match self {
PrimitiveValue::Number(x) => Ok(*x),
@ -93,6 +99,7 @@ impl PrimitiveValue {
_ => Err(anyhow!("{:?} is not an f64", &self)),
}
}
pub fn as_bool(&self) -> Result<bool> {
match self {
PrimitiveValue::Boolean(x) => Ok(*x),
@ -145,12 +152,14 @@ impl AttrValue {
_ => Err(anyhow!("{:?} is not a string", self)),
}
}
pub fn as_f64(&self) -> Result<f64> {
match self {
AttrValue::Concrete(x) => Ok(x.as_f64()?),
_ => Err(anyhow!("{:?} is not an f64", self)),
}
}
pub fn as_bool(&self) -> Result<bool> {
match self {
AttrValue::Concrete(x) => Ok(x.as_bool()?),

View File

@ -1,6 +1,8 @@
use crate::config::{element, WindowName};
use crate::eww_state::*;
use crate::value::{AttrValue, VarName};
use crate::{
config::{element, WindowName},
eww_state::*,
value::{AttrValue, VarName},
};
use anyhow::*;
use gtk::prelude::*;
@ -11,8 +13,8 @@ pub mod widget_definitions;
const CMD_STRING_PLACEHODLER: &str = "{}";
/// Run a command that was provided as an attribute. This command may use a placeholder ('{}')
/// which will be replaced by the value provided as [arg]
/// Run a command that was provided as an attribute. This command may use a
/// placeholder ('{}') which will be replaced by the value provided as [arg]
pub fn run_command<T: std::fmt::Display>(cmd: &str, arg: T) {
let cmd = cmd.replace(CMD_STRING_PLACEHODLER, &format!("{}", arg));
if let Err(e) = Command::new("bash").arg("-c").arg(cmd).output() {
@ -30,12 +32,14 @@ struct BuilderArgs<'a, 'b, 'c, 'd, 'e> {
}
/// Generate a [gtk::Widget] from a [element::WidgetUse].
/// The widget_use may be using a builtin widget, or a custom [element::WidgetDefinition].
/// The widget_use may be using a builtin widget, or a custom
/// [element::WidgetDefinition].
///
/// Also registers all the necessary state-change handlers in the eww_state.
///
/// This may return `Err` in case there was an actual error while parsing or resolving the widget,
/// Or `Ok(None)` if the widget_use just didn't match any widget name.
/// This may return `Err` in case there was an actual error while parsing or
/// resolving the widget, Or `Ok(None)` if the widget_use just didn't match any
/// widget name.
pub fn widget_use_to_gtk_widget(
widget_definitions: &HashMap<String, element::WidgetDefinition>,
eww_state: &mut EwwState,
@ -48,18 +52,20 @@ pub fn widget_use_to_gtk_widget(
let gtk_widget = if let Some(builtin_gtk_widget) = builtin_gtk_widget {
builtin_gtk_widget
} else if let Some(def) = widget_definitions.get(widget.name.as_str()) {
//let mut local_env = local_env.clone();
// let mut local_env = local_env.clone();
// the attributes that are set on the widget need to be resolved as far as possible.
// If an attribute is a variable reference, it must either reference a variable in the current local_env, or in the global state.
// As we are building widgets from the outer most to the most nested, we can resolve attributes at every step.
// This way, any definition that is affected by changes in the eww_state will be directly linked to the eww_state's value.
// Example:
// foo="{{in_eww_state}}" => attr_in_child="{{foo}}" => attr_in_nested_child="{{attr_in_child}}"
// will be resolved step by step. This code will first resolve attr_in_child to directly be attr_in_child={{in_eww_state}}.
// then, in the widget_use_to_gtk_widget call of that child element,
// attr_in_nested_child will again be resolved to point to the value of attr_in_child,
// and thus: attr_in_nested_child="{{in_eww_state}}"
// the attributes that are set on the widget need to be resolved as far as
// possible. If an attribute is a variable reference, it must either reference a
// variable in the current local_env, or in the global state. As we are building
// widgets from the outer most to the most nested, we can resolve attributes at
// every step. This way, any definition that is affected by changes in the
// eww_state will be directly linked to the eww_state's value. Example:
// foo="{{in_eww_state}}" => attr_in_child="{{foo}}" =>
// attr_in_nested_child="{{attr_in_child}}" will be resolved step by step. This
// code will first resolve attr_in_child to directly be
// attr_in_child={{in_eww_state}}. then, in the widget_use_to_gtk_widget call of
// that child element, attr_in_nested_child will again be resolved to point to
// the value of attr_in_child, and thus: attr_in_nested_child="{{in_eww_state}}"
let resolved_widget_attr_env = widget
.attrs
.clone()
@ -93,13 +99,14 @@ pub fn widget_use_to_gtk_widget(
Ok(gtk_widget)
}
/// build a [gtk::Widget] out of a [element::WidgetUse] that uses a **builtin widget**.
/// User defined widgets are handled by [widget_use_to_gtk_widget].
/// build a [gtk::Widget] out of a [element::WidgetUse] that uses a **builtin
/// widget**. User defined widgets are handled by [widget_use_to_gtk_widget].
///
/// Also registers all the necessary handlers in the `eww_state`.
///
/// This may return `Err` in case there was an actual error while parsing or resolving the widget,
/// Or `Ok(None)` if the widget_use just didn't match any widget name.
/// This may return `Err` in case there was an actual error while parsing or
/// resolving the widget, Or `Ok(None)` if the widget_use just didn't match any
/// widget name.
fn build_builtin_gtk_widget(
widget_definitions: &HashMap<String, element::WidgetDefinition>,
eww_state: &mut EwwState,

View File

@ -1,15 +1,12 @@
use super::{run_command, BuilderArgs};
use crate::config;
use crate::eww_state;
use crate::resolve_block;
use crate::value::{AttrValue, PrimitiveValue};
use crate::{
config, eww_state, resolve_block,
value::{AttrValue, PrimitiveValue},
};
use anyhow::*;
use gtk::prelude::*;
use gtk::ImageExt;
use gtk::{prelude::*, ImageExt};
use maplit::hashmap;
use std::cell::RefCell;
use std::path::Path;
use std::rc::Rc;
use std::{cell::RefCell, path::Path, rc::Rc};
use gdk_pixbuf;
@ -259,7 +256,8 @@ fn parse_align(o: &str) -> Result<gtk::Align> {
}
fn connect_first_map<W: IsA<gtk::Widget>, F: Fn(&W) + 'static>(widget: &W, func: F) {
// TODO it would be better to actually remove the connect_map after first map, but that would be highly annoying to implement...
// TODO it would be better to actually remove the connect_map after first map,
// but that would be highly annoying to implement...
let is_first_map = std::rc::Rc::new(std::cell::RefCell::new(true));
widget.connect_map(move |w| {
if is_first_map.replace(false) {