GithubHelp home page GithubHelp logo

domsson / succade Goto Github PK

View Code? Open in Web Editor NEW
55.0 5.0 3.0 831 KB

Run, feed and style your Lemonbar with ease

License: Creative Commons Zero v1.0 Universal

C 97.87% Objective-C 1.92% Shell 0.20%
lemonbar linux c rice ricing status-bar bar wrapper manager

succade's Introduction

succade - a Lemonbar manager

Feed your Lemonbar with succade! It repeatedly runs blocks, piping their output to Lemonbar. Every block is a script or program that provides text to be displayed on your bar. Configuration is done with a simple ini file.

succade lemonbar example 1 succade lemonbar example 2 succade lemonbar example 3 succade lemonbar example 4

How it works

  • Starts lemonbar for you
  • Reads the config file (default is ~/.config/succade/succaderc)
  • Loads blocks (programs or scripts defined in the config file)
  • Updates Lemonbar based on the blocks' output

The config needs to have one section for lemonbar (bar) and one per block, plus an optional section for common styles (default) that will apply to all blocks. The bar's section lists the blocks that should be displayed on the bar (and where), and can set some lemonbar properties like its size and position. Block sections define the styling of individual blocks, as well as how often the blocks should be reloaded. Alternatively, trigger commands can be defined. Once a trigger produces output, the associated block will be run (optionally with the trigger's output as command line argument).

Notable features

  • Define labels for your blocks. E.g., for a volume block, have it return 35 % and define the label Vol. in the config.
  • Define a prefix and suffix for every block. Want to wrap all blocks in square brackets? That's 2 lines in the main config.
  • Define a minimum width for your blocks to achieve a uniform look when using fixed-width fonts.
  • Prefix, suffix, label and actual block content can have different foreground and background colors.
  • Most settings can be set once for all blocks, then overwritten for individual blocks, if need be.

Dependencies

  • inih (libinih-dev in Debian, but also included in this repo, see below)

Portability

succade uses libkita to manage child processes. libkita uses epoll, which is Linux only. I've attempted to port libkita to BSD using kqueue, but couldn't get it to work reliably (yet).

Installation

Make sure you have lemonbar (obviously), gcc (for compiling the source code) and all dependencies, as listed above, installed. If inih is not available in your distribution, just replace ./build with ./build-inih below and you should be good to go.

  1. Clone succade and change into its directory:
    git clone https://github.com/domsson/succade.git
    cd succade
  2. Make the build script executable, then run it:
    chmod +x ./build
    ./build
  3. Create the config directory (assuming .config as your config dir):
    mkdir ~/.config/succade
  4. Copy the example config:
    cp ./cfg/example1.ini ~/.config/succade/succaderc
  5. Make succade executable and put it somewhere that's included in your path:
    chmod +x ./bin/succade
    cp ./bin/succade ~/.local/bin/

Configuration

Take a look at the example configurations in this repository and refer to the following documentation.

Possible property values, based on their types as listed in the tables below, are:

  • string: Text within quotes, for example "Hello World"
  • number: A number, for example 34
  • boolean: Either true or false
  • color: RGB hex string, for example #F3BD70

Commands in config options

The config options command, trigger, mouse-left, mouse-middle, mouse-right, scroll-up, scroll-down expect a script or binary to execute. For performance reasons, succade does not invoke a shell to run the commands. This means that shell built-in functionality, like echo, pipes or redirection, will not work (as expected). If you want to use those, wrap those commands in a simple shell script and give succade the path to that script in these config options. You also don't need (and should not) background commands via &, succade will take care of that for you already.

You can, however, use variable substituion, . and ~, as succade internally uses wordexp. Also see the following paragraph from the wordexp man page:

The expansion done consists of the following stages: tilde expansion (replacing ~user by user's home directory), variable substitution (replacing $FOO by the value of the environment variable FOO), command substitution (replacing $(command) or command by the output of command), arithmetic expansion, field splitting, wildcard expansion, quote removal.

lemonbar

The special section bar configures Lemonbar itself and can define common formatting for all blocks. It is required for succade to run, but the only mandatory property is blocks.

Parameter Type Description
command string The command to start the bar; defaults to lemonbar
blocks string Specifies the blocks to display on the bar. Example: desktop | title | volume time
width number Width of the bar in pixel - omit this value for a full-width bar.
height number Height of the bar in pixel - omit to get the minimum required height.
left number x-position of the bar - omit to have it sit at the edge of your screen.
top number y-position of the bar - omit to have it sit at the edge of your screen.
bottom boolean Dock the bar at the bottom instead of the top of the screen.
force boolean Set to true if you want to force docking of Lemonbar; default is false.
foreground color Default foreground (font) color for all blocks.
background color Default background color for the entire bar.
font string Font to use for all blocks.
label-font string Font to use for all block's labels, if any.
affix-font string Font to use for all block's prefixes / suffixes, if any.
line-color color Color for all underlines / overlines, if any.
line-width number Thickness of all underlines / overlines, if any, in pixels.
separator string String to place in between any two blocks of the same alignment.

blocks

Every block that has been named in blocks needs its own config section. Most of these values can also be specified in the special default section, which will apply to all blocks.

Parameter Type Description
command string The command to run the block; defaults to the section name.
interval number Run the block every interval seconds; 0 (default) means the block will only be run once.
trigger string Run the block whenever the command given here prints something to stdout.
consume boolean Use the trigger's output as command line argument when running the block.
live boolean The block is supposed to keep running; succade will monitor it for new output on stdout.
raw boolean If true, succade will not escape '%' characters, allowing you to use format strings directly.
prefix string Shown before the block's main text and label.
suffix string Shown after the block's main text and unit, if any.
label string Shown before the block's main text; useful to display icons when using fonts like Siji.
min-width number Minimum width of the block's main text, which will be left-padded with spaces if neccessary.
foreground color Font color for the whole block (including label and affixes).
background color Background color for the whole block (including label and affixes).
label-foreground color Font color for the block's label, if any.
label-background color Background color for the block's label, if any.
affix-foreground color Font color for the block's prefix and suffix, if any.
affix-background color Background color for the block's prefix and suffix, if any.
line-color color Overline / underline color for the block.
overline boolean Whether or not to draw an overline for the block.
underline boolean Whether or not to draw an underline for the block.
margin number Distance to the next block (or edge of bar) on the left and right, in pixels.
margin-left number Distance to the next block (or edge of bar) on the left, in pixels.
margin-right number Distance to the next block (or edge of bar) on the right, in pixels.
padding number Number of spaces that will be added around a block's output on the left and right.
padding-left number Number of spaces that will be added on the left side of a block's output.
padding-right number Number of spaces that will be added on the right side of a block's output.
mouse-left string Command to run when you left-click the block.
mouse-middle string Command to run when you middle-click the block.
mouse-right string Command to run when you right-click the block.
scroll-up string Command to run when you scroll your mouse wheel up while hovering over the block.
scroll-down string Command to run when you scroll your mouse whell down while hovering over the block.

Usage and command line arguments

Usage:

succade [OPTIONS...]

Options:

  • c CONFIG: config file to use
  • e: run bar even if it is empty (no blocks defined or loaded)
  • h: print help text and exit
  • s SECTION: config section name for the bar (default is "bar")
  • V: print version information and exit

Support

ko-fi

Blocks - Fetching System Information

Looking for scripts, programs or code that can fetch information to display on your bar? Check out fetch-all-the-things.

License

succade is public domain software, do with it whatever you want. However, succade uses inih, which is under the New BSD license.

Motivation

With projects like polybar, the question for the relevance of succade is justified. Personally, I prefer succade - and similar solutions, like Captain - because they enforce the separation of concerns as described by the UNIX philosophy.

For example, imagine someone created a fork of Lemonbar that works with Wayland. As long as they would keep the same interface (same format specifiers supported as with Lemonbar), you can immediately switch to that new bar, without changing anything else. You can still use the same blocks, because they are not tied to the bar or succade.

Additionally, I like minimalistic setups. I don't want most of the additional features of other bars, like true type fonts or rounded borders. Hence, I might as well save a bit of RAM by going with more minimalistic solutions.

Fun fact: polybar started out as a lemonbar wrapper just like succade, but eventually the project maintainers decided to include lemonbar's functionality right into the project. Therefore, in a way, succade is what polybar used to be.

succade's People

Contributors

domsson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

succade's Issues

Replace inih with a header-only, public domain alternative?

Should we replace inih with an alternative INI parser? Motivation is as follows:

  • inih uses a New BSD license, but I'd prefer if this project was 100% public domain
  • a header-only alternative would make succade dependency free and installation easier for some users

Of course, there is also a drawback to the header-only approach: succade will increase in size. With inih, succade doesn't need to include any INI parsing code and instead uses the shared library. Seeing, however, how some distributions do not have a package for inih (example, Arch Linux, see #33), I'm pretty tempted to trade a larger binary for easier installation.

Not quite sure about this one yet, let's see what alternatives are out there. A quick scan brings up simple-ini-reader and ini.h as possible candidates, but there are more.

Resolve libkita dependency

libkita could as well be a header only library. This would remove one of two external dependencies, with the remaining one (inih) being available in some (most?) package managers anyway. Therefore, installation would become a lot less tedious.

Implement raw mode

Currently, it isn't possible to feed pre-formatted strings to succade, as succade will escape all % characters. However, this means that we need to resort to multiple-block trickery to implement workspace indicators and other blocks where only part of the output should have a certain formatting.

In order to work around this, we could implement a raw (boolean) option, that would tell succade to not escape the output of a block. This should enable users to write blocks that output lemonbar-formatted strings.

Implement live blocks

Currently we have blocks that...

  • ...are being run at a set interval
  • ...are being run only once at startup
  • ...are being run as soon as a trigger program produces any output

However, it would be pretty neat to have live blocks that act as their own trigger. Meaning that we monitor them for output and update them in the bar whenever there is (new) output. Most of the required logic should already be in place, just not wired the right way.

This should be quite useful as it allows to build blocks that show little ASCII animations, marquee effects and the like, without being too taxing on the CPU.

Hot-reload config file

It would be nice if we had a way to reload the config without having to restart succade, for example on receiving a certain signal. Not high priority - let's only do this if it doesn't add too much complexity to the code.

suffix doesn't show?

Need to test a bit more, but doest the suffix option actually work? It's in the code, but not in the documentation (README), and it doesn't seem to show up. INVESTIGATE, PLZ!

The `bin` config option should not be mandatory

Currently, every block config needs to specify the bin option. However, if the block name is actually the same as the binary to run (this requires the binary to be in the search path, of course), then this option isn't technically required. In other words, if bin isn't specified, we should fall back to setting bin to the block's name.

This is especially important because having bin be mandatory breaks backwards compatibility with versions before the changes introduced with 9d5d7da.

Allow multiple triggers?

In his video, Brodie Robertson points out that it would be desirable to have the possibility to set multiple triggers for a block. Let's investigate how much work and additional code this would require, then consider adding such a feature.

Implement pipe mode?

Some people might prefer to not have succade run lemonbar for them. Instead, they might prefer to use succade just to generate the required string, then pipe that to lemonbar. We should implement an option to do this, for example via -p (pipe or print) command line option.

Settle for configuration parameter names

Now that most configuration parameters should be in place, it is time to check what comparable parameters are called in Captain as well as Polybar. If possible, we should then allow for both names to be used, in order to make for a high compatibility and easy migration between all three projects. Once that's done, all parameters should be properly documented, ideally in a Wiki here on GitHub.

Option to specify number of clickable areas

Lemonbar has a command line option to specify the number of clickable areas that are to be supported. It would be nice to add a config option to succade that allows to set this value more conveniently.

Compile warnings

Compile warnings from another user read as follows:

src/succade.c: In function ‘main’:
src/succade.c:1295:9: warning: variable ‘delta’ set but not used [-Wunused-but-set-variable]
 1295 |  double delta;
      |         ^~~~~
In function ‘barstr’,
    inlined from ‘feed_lemon’ at src/succade.c:877:16,
    inlined from ‘main’ at src/succade.c:1313:3:
src/succade.c:471:13: warning: writing 1 byte into a region of size 0 [-Wstringop-overflow=]
  471 |  bar_str[0] = '\0';
      |  ~~~~~~~~~~~^~~~~~
src/succade.c: In function ‘main’:
src/succade.c:470:18: note: at offset 0 to an object with size 0 allocated by ‘malloc’ here
  470 |  char *bar_str = malloc(bar_str_len);

I know about the unused delta, that's expected and okay for now, but the other two should be investigated and fixed sooner than later.

Some characters / glyphs don't show up on lemonbar

Some characters, even though 6x13 seems to be capable of displaying them, simply do not show up after piping them to lemonbar. For example: (flower punctuation mark (U+2055)).

A quick search didn't bring up much, apart from this comment on reddit from 2015:

Lemonbar, in any version, is unable to handle utf-8 sequences with more than 3 bytes right now (\ufffd is used instead). I will look into writing a patch to fix that.

However, I believe the example above (flower punctuation mark) uses 3 bytes, so even if this issue still holds true, it shouldn't cause the behavior I'm seeing? Also, I'm not seeing the replacement character (\ufffd), I'm simply not seeing the piped glyph at all.

This needs further investigation to figure out what is going on and if the issue lies within succade, lemonbar, my font or something entirely else.

Do TrueType fonts actually work?

Brodie Robertson reports that true type fonts don't work for him. Seeing how succade is simply passing the font name over to Lemonbar, this should work given that a true-type enabled fork of Lemonbar (like lemonbar-xfg) is being used.

This needs to be tested and then either better documented or fixed.

Support new Lemonbar feature: option -a dropped

As per this commit, the number of clickable areas is allocated dynamically and Lemonbar hence doesn't support the -a option anymore. Once this feature finds its way into repos, we should remove the areas option from succade as well. And until then, we need to make sure that succade does not set the -a option in case it isn't given in the config, so that we can run both versions of Lemonbar just fine.

Document the no-shell popen behavior

As became evident through this video, not having the block's commands being run through a shell (and therefore, not supporting pipes etc) does go against the expectations of users and makes it seem like this is a bug. Better documentation should help here. In the future, considering a new command line switch to enable a vanilla popen() behavior (spawning a shell) would be an option, but I'd rather stick with the minimalist solution.

Escape % automatically, instead of having to double it in block output

Lemonbar's format strings start with %, therefore we need to escape this character if we want to print a literal % by doubling it: %%. Currently, blocks have to make sure to do this escaping by outputting two percent signs if they want to print one. This, however, is inconvenient and messes with the padding option.

Instead, succade should take care of the escaping and account for it when applying padding.

Add "unit" option for blocks

A block can have a label, which is simply text that will be displayed before the actual output of the block. One example would be "Vol." for a volume block. It would be nice to also have the option to have a suffix for every block (currently we only have prefix and suffix for all blocks via the succade config file). This would allow to define units for blocks that output things like volume (%), temperature (°C) or similar data, while not requiring the block scripts/binaries to include them in their output.

As I currently only see this being used for units, we might as well call the option "unit". This way, we have prefix and suffix for all blocks, plus label and unit on a per-block basis.

Bar freezes under certain circumstances

According to a video by Brodie Robertson, there is an issue that can freeze up Lemonbar and/or succade when mouse actions (in quick succession) are being used on a block. I haven't been able to reproduce this bug, neither do I know what causes it. More information is required at this point, but we should definitely try and get to the bottom of this.

Reload blocks on signal

Other projects, like i3blocks and lemonblocks, offer the option to send a signal to request reloading of a certain block/module. It shouldn't be too hard to implement this in succade, so let's look into it.

In the config, blocks could then have a new option, signal, which could be assigned a signal number. When succade receives a user signal, it can check which block it matches and mark that block as "due". If the block is a live block, we should ignore the signal option with one exception: if the live block has died, we should restart it if there is a signal coming in.

Option to specify configuration file

Currently, succade is hardwired to load its configuration file from CONFIG_DIR/succade/succaderc, but we should have a -c command line argument that allows us to specify a config file. This would come in handy for testing as well.

Add config option for placeholder values

On startup, blocks are often not immediately displayed, because they are still going through their first invocation. Only once they have finished running, can we obtain their output and update the bar with it. This leads to a flash of blank blocks on startup. Additionally, it is possible that a block runs forever (hangs) and therefore never produces output, making for a permanently missing value in the bar.

Both of these issues could be prevented if there was a placeholder config option. The value given here would be immediately displayed where the block output would usually appear until the block has actually produced output itself.

Make consuming a trigger's output optional

Currently, blocks with a trigger will be run with the trigger's output, if any, as (additional) command line argument. However, sometimes this is not desired and the trigger should merely act as an activation for the block. This is a valid use-case, but is currently not possible.

We should introduce a config option for this, for example use-trigger-output or somesuch.

Detect X Server shutdown or document how to end succade

Currently, without taking proper precautions, succade might keep on running even if the X Server is being shut down. We should research if there is a way to detect when X (or alternatively succade's parent process) is gone. If that's not reliably possible, we should document this somehow (for example, add an example that shows how to modify the sxhkd hotkeys to also shut down succade properly.

Add support for conditional formatting

Currently, the biggest disadvantage of succade + lemonbar over other solutions, like polybar, is that it isn't possible to have conditional formatting. For example, you might want to have the CPU temperature string be displayed in blue for low temperatures, yellow for mid-range temperatures and red for high temperatures. We can't do this at the moment.

Here is an idea of how this could be implemented:

  • The configuration files for blocks (or a global config, doesn't matter) could make use of INI sections to list alternative settings. Each section would denote a mode of the block.
  • Blocks could print mode change requests to stderr, and succade would then make use of the appropriate config section of that block.

Example block config:

[temperature:default]
bin = "temps -m"
foreground = "#ffff00"

[temperature:low]
foreground = "#0000ff"

[temperature:high]
foreground = "#ff0000"

Example stderr output of temps -m when a certain temperature change is detected:

@mode:high

Offset behaves inconsistently

When defining an offset for all blocks, the result is different from what it is supposed to be like in that left, center and right aligned blocks all seem to have the offset on the left side, instead of in their alignment direction. I assume this is a bug in Lemonbar and not in succade, so for now we'll have to wait on feedback there.

Implement clickable area support

Lemonbar supports mouse actions (left button, middle button, right button, scroll up and scroll down) on a block. This feature should be supported by succade in one form or another.

Open lemonbar in read and write mode

Currently, the lemonbar process is being opened with popen() in write mode. Since pipes are unidirectional, and popen() does the pipe magic behind the scenes for us, there is no way to also read lemonbars output this way. However, in order to implement clickable areas, we need to fetch its output.

Therefore, popen() needs to be replaced with some more sophisticated code (fork(), dup2() etc) that will create an additional pipe for reading from, in addition to writing to lemonbar.

No cleanup on SIGPIPE

Sometimes (but not always), when ending succade via CTRL+C, we run into a SIGPIPE. This leads to some (theoretical) memory leaks because we don't get to the clean-up code. The OS takes care of it, so nothing too bad, but still not exactly nice.

We should either take the time to figure out what circumstances exactly lead to the SIGPIPE and how to prevent this, or simply catch the SIGPIPE with the existing signal handler, so that we can be sure clean-up will happen either way.

Lemonbar doesn't always show the input we feed it

Because of a bug in lemonbar, feeding new input to lemonbar in quick succession doesn't work, which can lead to lemonbar showing a previous state rather than the newest.

Example: Assume succade is configured with two blocks, both static, one that produces output ever so slightly faster than the other. This will cause succade to feed lemonbar two times in quick succession, the first time only containing the faster block, the second time containing both blocks. However, lemonbar will show only the first block, as it completely ignored the second input, as it came in too fast.

We can't really fix this in succade, so there are two options here:

  • fork lemonbar, fix it, send a PR (however, there are many unanswered issued and unmerged PRs already, not sure if the maintainer is still active on the project)
  • find some kind of workaround in succade that can optionally be activated via command line switch (for example, a minimum time between feeds)

Command line arguments for blocks

Some blocks might require command line arguments to operate. I think this is not currently possible with succade. Either the bin option needs to be changed to allow for this (unless it does already), or an additional option, like args should be introduced.

What does a reload value of 0 do and is that intended?

I need to go dig into my code to check what reload=0 would actually do. Then we can check if it makes sense to make this a value of special meaning, for example for static blocks that really only need to be executed once, then will show the same string for the rest of the bar's lifetime. That sounds useful to me.

How to build on FreeBSD

This requires the sys/epoll.h, while on FreeBSD we only have sys/poll.h. The port libepoll-shim does contain a epoll implementation for FreeBSD, but the linker does not recognize it in the object code.

How would I go about building succade on FreeBSD.

action command's output appears on stdout / stderr

When an action command is defined in the configuration of a block (for example, via mouse-left) and the block is clicked, the output of that command will display on the terminal. We should run action commands in a fire-and-forget manner and completely ignore their output.

Add option to specify the bar binary

Currently, the bar binary is hardcoded as lemonbar. It somewhat makes sense, as succade produces output tailored for lemonbar in particular, but it isn't unthinkable that the binary might have a different name on some user's system. Also, someone might come up with a fork of lemonbar that has the same interface (or is at least backwards compatible), but has a different name entirely. For example, someone might port lemonbar to Wayland.

Hence, we should have a command line option to hand in the name of the bar binary. I suggest -b (bar or binary).

Mouse actions sometimes trigger the wrong command

When there are many mouse actions in quick succession, it seems that the wrong events are fired. For example, when putting volume control on mouse wheel and mute/unmute on left mouse button, after scrolling a lot, a mouse click might change the volume or scrolling might mute/unmute. Need to investigate this further.

String handling and buffers are a buggy, fragile mess and will break eventually

Looking at the code after a while, I noticed that all the string handling is a hot mess. There is lots of "let's say this will never be longer than x chars" code that creates buffers of random, hardcoded size, then goes on to stuff more into those buffers than they can actually hold. It's a miracle we haven't seen any segfaults from this so far. Go ahead an refactor this before it blows up in someone's face!

Implement a "default" config section

Currently, the bar section has a lot of config options that are the exact same ones that exist for the block sections. It would be much more sensible to have the bar section to only support config options that actually pertain to the bar. The problem that arises then, however, is to how implement default config settings that apply to all blocks, without having to note them down for every single block. The obvious solution to this would be to have a default config section, that supports the exact same options as the block sections, and will be used as default for all blocks.

All-in-one configuration file

Originally I really liked the idea of having one config file per block, as this would allow block scripts to be distributed together with their configuration very easily. It also is, to some degree, separation of concerns.

However, in practice I found this quite cumbersome. It also makes backing up the configuration more tedious. Now that I've decided to move the block scripts out of the configuration directory, it seems like a reasonable thing to get rid of the block configuration directory entirely, which means to eliminate the need for block configurations.

Still, we need some way to configure blocks - the solution is obvious: go with the same approach as caption, where every block can have a configuration section in the main config file. The special section [bar] would then be reserved and used for the bar itself, while every block would be prefaced with a section named after the block, for example [datetime].

Move block binaries/scripts out of the config directory

As pointed out by a user, the actual block binaries and/or scripts should not reside in the config directory. I don't think this is actually required even now, as long as the scripts are in PATH, succade should be able to run them. Apart from that, can we currently specify the path to the bin/script in the config files? If not, we should have that option. In other words, decouple bin/script and config file, effectively making the blocks just a config file. It is then up to the user where the bin/script actually is. Let's look into this.

Only update Lemonbar if contents have actually changed

We're already remembering each block's result string. We should use this to check if the output of a block was different from the previous run. If all block's output happens to not have changed between two runs, we should not update Lemonbar. This should help to reduce CPU usage, both on succade as well as Lemonbar.

May I please retrieve an example version of succade?

Hey man how is it going @domsson ? I was just wondering if you had an example version of succade setup config file that I could use.

I am going to again attempt to write the program that I wanted which deals with workspaces but this time in a language called V rather than Rust (it honestly got way to complicated).

Separator config option

It would be nice if you could have the option to set an overall separator for the blocks.

For example:

[bar]
separator = "|"

Output:

date | cpu | memory | battery

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.