Git Product home page Git Product logo

xdg-ninja's Introduction

xdg-ninja

Because you wouldn't let just anyone into your $HOME

A shell script that checks your $HOME for unwanted files and directories.

xdg-ninja command output

When xdg-ninja encounters a file or directory it knows about, it will tell you whether it's possible to move it to the appropriate location, and how to do it.

The configurations are from the arch wiki page on XDG_BASE_DIR, antidot (thanks to Scr0nch for writing a conversion tool), and crowdsourced by other users.

Installing

Manual Installation

Clone the repository, then run the ./xdg-ninja.sh script.

git clone https://github.com/b3nj5m1n/xdg-ninja
cd xdg-ninja
./xdg-ninja.sh

This will run every test in the default configuration.

Turn on flakes, then run the following command:

nix run github:b3nj5m1n/xdg-ninja

Note

Due to how xdg-ninja is developed, releases are not cut, so Homebrew ships a stale version, therefore you have to install and upgrade xdg-ninja from the git HEAD. ref: #204

Homebrew will not upgrade xdg-ninja when running a generic brew upgrade, you must specifically upgrade xdg-ninja from the git HEAD, see below

Install:

brew install xdg-ninja --HEAD

Upgrade:

brew upgrade xdg-ninja --fetch-HEAD

Other Package Managers

xdg-ninja is avaliable in many other package managers.

The full list is available on the repology page.

Follow the instructions for your package manager to install xdg-ninja.

Contributing

Dependencies

  • Your favorite POSIX-compliant shell (bash, zsh, dash, etc.)
  • jq for parsing the json files
  • find

Optional

  • glow for rendering Markdown in the terminal (bat, pygmentize or highlight can be used as a fallback, but glow's output is clearer therefore glow is recommended)

Configuration

The configuration is done in the ./programs/ directory, which should be located in the same working directory as the xdg-ninja.sh script. This can be overridden with the XN_PROGRAMS_DIR environment variable.

You define a program, and then a list of files and directories which that program ruthlessly puts into your $HOME directory.

For each file/directory, you specify if it can be (re)moved.

If this is the case, you also specify instructions on how to accomplish this in Markdown.

Files in this directory can have any name, but using the name of the program is recommended.

Automatically Generating Configuration

For x86_64 Linux systems, you can download the xdgnj binary from the releases page.

Alternatively, you can build it from source using cabal or stack, use the nix flake or use the provided docker image.

To be clear, this is just a tool that will help you automatically generate the config files, you still only need your shell to run the tests

Available commands

xdgnj add # Adds a new configuration
xdgnj prev programs/FILE.json # Preview the configuration for a program
xdgnj edit programs/FILE.json # Edit the configuration for a program
xdgnj run # Mostly the same as running the shell script

Prebuilt Binaries

Important

The binaries only run on x86_64 Linux systems.

curl -fsSL -o xdgnj https://github.com/b3nj5m1n/xdg-ninja/releases/latest/download/xdgnj
chmod +x xdgnj

Building from source

You can use cabal build or stack build

Nix

nix run github:b3nj5m1n/xdg-ninja#xdgnj-bin ...

Docker

Use the provided dockerfile in ./haskell/build/.

Manually Creating Configuration

We're going to use git as an example.

By default, it puts the file .gitconfig into $HOME.

Luckily, the XDG spec is supported by git, so we can simply move the file to $XDG_CONFIG_HOME/git/config.

We can use that last sentence as our instructions. In this case, there are no newlines, so escaping this string for use in json is trivial, however, this is how you should generally approach it:

echo "Luckily, the XDG spec is supported by git, so we can simply move the file to _$XDG_CONFIG_HOME/git/config_." | jq -aRs .

Let's see what the output of this command looks like for something a little more sophisticated.

Here's an example file:

cat example.md
Currently not fixable.

_(But you can probably just delete the dir)_

Here's what catting this file into jq produces:

cat example.md | jq -aRs .
"Currently not fixable.\n\n_(But you can probably just delete the dir)_\n"

Now, we can assemble our final json file:

{
    "name": "git",
    "files": [
        {
            "path": "$HOME/.gitconfig",
            "movable": true,
            "help": "Luckily, the XDG spec is supported by git, so we can simply move the file to _$XDG_CONFIG_HOME/git/config_.\n"
        }
    ]
}

Saving this as git.json in the ./programs/ directory will result in the script picking it up and checking the file.

If you've created a configuration for a file that isn't in the official repository yet, make sure to create a pull request so that other people can benefit from it as well.

xdg-ninja's People

Contributors

akmadan23 avatar alsogamer avatar amtoine avatar andrewmustea avatar b3nj5m1n avatar ballasi avatar blueboxware avatar cantido avatar ccoveille avatar e-khurmamatov avatar flexagoon avatar franciscopombal avatar franzxaver avatar grigorenkopv avatar hanabishirecca avatar himdek avatar hizani avatar iagoleal avatar itaranto avatar jopejoe1 avatar layerex avatar midblyte avatar n-hebert avatar nicolas-graves avatar silejonu avatar supersandro2000 avatar tinfoilsubmarine avatar torjacob avatar willenbrink avatar zweihander-main 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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

xdg-ninja's Issues

`.cinnamon` (and `.themes` and `.icons`?)

This is a reminder for myself when I have the time to open a PR, but anyone can open it.

From Cinnamon version 5.6.0

These are backwards compatible (read from new and old dirs in this order, write to new dirs unless it's modifying an existing file). Moving them is the best action to take.
.cinnamon/panel-launchers/ -> $XDG_DATA_HOME/cinnamon/panel-launchers/
.cinnamon/backgrounds/ -> $XDG_DATA_HOME/cinnamon/backgrounds/
.cinnamon/configs/ -> $XDG_CONFIG_HOME/cinnamon/spices/

Bit unclear if anything else uses them as well, but if it's only Cinnamon then they can be safely moved to the new location. Cinnamon used them to store downloaded themes (and their icon/cursor themes).
.themes/ -> $XGD_DATA_HOME/themes/
.icons/ -> $XDG_DATA_HOME/icons/

These are recreated at the new paths. The ones in the old path can be safely deleted.
.cinnamon/glass.log -> $XDG_STATE_HOME/cinnamon/glass.log
.cinnamon/harvester.log -> $XDG_STATE_HOME/cinnamon/harvester.log

Cache, old path can be safely deleted.
.cinnamon/spices.cache/ -> $XDG_CACHE_HOME/cinnamon/spices/

Commits refs

linuxmint/cinnamon@74c26ab
linuxmint/cinnamon@c098674
linuxmint/cinnamon@0fc32e2
linuxmint/cinnamon@323b5aa
linuxmint/cinnamon@844493a
linuxmint/cinnamon@f372914

.java stays at home with recommended settings

With suggested edits, .userPrefs file is created correctly in specified location inside ~/.config/java/.java/.userPrefs but there is also a fcinfo-*-.properties file in ~/.java.

cabal is causing some problems

I was able to move .cabal to $XDG_CONFIG_HOME/cabal/config and $XDG_DATA_HOME/cabal and even have xdgnj running again, but after some extras steps 🤔

I had to replace every instance of $HOME/.cabal by either $XDG_CONFIG_HOME/cabal or $XDG_DATA_HOME/cabal in my new $XDG_CONFIG_HOME/cabal/config file.

I have the comment ready in a branch if you wish to open a PR for that 😋

.dotnet

There is no configurable way, but there is an active issue here - dotnet/sdk#8678

Until then, we cry.

feature request: launch from other folder (which isnt repo root)

Hi,
It would be nice to set a symlink to path/to/xdg-ninja.sh on my $PATH, such that the cwd / PWD from where its being launched from is different than the repo root folder.

This might involve doing some extra check within the launch script xdg-ninja.sh when it starts, in order to change directory first, before continuing to do anything further.

Currently the processing fails with the following msg(s)

[id:~/.bin] $ ln -s ~/.xdg-ninja/xdg-ninja.sh xdg-ninja
[id:~/.bin] $ cd
[id:~] $ xdg-ninja 
Markdown rendering will be done by bat. (Glow is recommended)
Install glow for easier reading & copy-paste.
The $XDG_DATA_HOME environment variable is not set, make sure to add it to your shell's configuration before setting any of the other environment variables!
    ⤷ The recommended value is: $HOME/.local/share
The $XDG_CONFIG_HOME environment variable is not set, make sure to add it to your shell's configuration before setting any of the other environment variables!
    ⤷ The recommended value is: $HOME/.config
The $XDG_STATE_HOME environment variable is not set, make sure to add it to your shell's configuration before setting any of the other environment variables!
    ⤷ The recommended value is: $HOME/.local/state
The $XDG_CACHE_HOME environment variable is not set, make sure to add it to your shell's configuration before setting any of the other environment variables!
    ⤷ The recommended value is: $HOME/.cache

Starting to check your $HOME.

jq: error: Could not open file /home/id/.bin/programs/*: No such file or directory
Done checking your $HOME.

If you have files in your $HOME that shouldn't be there, but weren't recognised by xdg-ninja, please consider creating a configuration file for it and opening a pull request on github.

θ61° [id:~] $ 

Wrapped links are invalid

Hi! When my terminal width is too narrow, text automatically wraps which ends up breaking long links. Below is a screenshot:
image

This means that only the first line of the hyperlink is active: https://github.com/ValveSoftware/steam-for- which gives a 404, obviously. I have attempted to read through glow's documentation and issues for anyone referencing this issue but have found no such mention of it.

Additionally, I notice that with glow, the markdown fails to make the text an actual hyperlink, instead just coloring it and underlining it.

Using escape sequences, it is possible to do this in terminal:
image

This acts as a valid hyperlink, and is additionally immune to this sort of text wrapping issue, as demonstrated here:
image

Not sure if this would best be solved with code changes to this repo, or if this may be better targeted towards glow.

Thanks!

Where to set required environment variables?

The $XDG_DATA_HOME environment variable is not set, make sure to add it to your shell's configuration before setting any of the other environment variables!
    ⤷ The recommended value is: $HOME/.local/share
The $XDG_CONFIG_HOME environment variable is not set, make sure to add it to your shell's configuration before setting any of the other environment variables!
    ⤷ The recommended value is: $HOME/.config
The $XDG_STATE_HOME environment variable is not set, make sure to add it to your shell's configuration before setting any of the other environment variables!
    ⤷ The recommended value is: $HOME/.local/state
The $XDG_CACHE_HOME environment variable is not set, make sure to add it to your shell's configuration before setting any of the other environment variables!
    ⤷ The recommended value is: $HOME/.cache

I want to fix this error, so I can set the rest of the variables required, but I don't know where to set them.

I tried in /etc/environment, ~/.bashrc, ~/.profile and ~/.bash_profile, but neither of them worked.

Would you please give me a hint as to where I should look for the answer?

Thanks in advance.

Edit: I'm using Arch Linux and the AUR version of the script.

missing entries

heres how my home looks:

drwxr-x---     - wael .android
drwx------     - wael .cache
drwxr-xr-x     - wael .config
drwx------     - wael .dbus
drwx------     - wael .irssi
drwxr-xr-x     - wael .local
drwxr-xr-x     - wael .mozilla
drwx------     - wael .pki
drwx------     - wael .ssh
drwx------     - wael .thunderbird
lrwxrwxrwx    40 wael .zshenv -> /home/wael/.local/share/dotfiles/.zshenv

xdg-ninja only reports the following:
.android, .mozilla, .irssi, .pki, .ssh, .zshenv

whilst the missing are:
.thunderbird, .dbus

also, the output is quite messy. can't it be more cleaner?

Invalid string error

The script runs flawlessly after switching the shebang to bash.

The error is caused by the read on line 173.

xdg-ninja/xdg-ninja.sh

Lines 170 to 176 in 91a0001

NAME=$(printf "%s" "$INPUT" | jq -r .name)
while IFS= read -r file; do
check_file "$file" "$NAME"
done <<EOF
$(echo "$INPUT" | jq -rc '.files[]')

Terminal with "invalid string" errors

Glow pager mode break output

If in settings glow is set to use pager mode, the output is scrambled… instruction or comments appear as separate "page" without clear indication about what .dotfile they are talking. and after closing all pages i can see the list of .dotfiles.
screnshot attached for clarifing…

screenshots

20220628T071225
20220628T071354
20220628T071232

recommend XDG default dirs via parameter expansion

Wherever you're using "$XDG_CONFIG_DIR", use "${XDG_CONFIG_DIR:-"$HOME/.config"}" (ditto for the others) - this is in POSIX so will work regardless of which shell, which you're already assuming by using export. An alternative to this would be to unconditionally recommend adding this to the shell rc:

: "${XDG_CONFIG_HOME:="$HOME/.config"}"

bat version compatibility

I have a small concern on bat compatibility on the program, the flag -f used in the bat call has been implemented in version 0.16.0 while some distributions downloads lower versions on their package manager (see https://repology.org/project/bat-cat/versions), for instance, Ubuntu 21.10 (on apt) installing version 0.12.1 of bat.

On my side, I don't see any difference with and without the -f flag, and I would believe it is the same. @pvonmoradi can you confirm?

If this is, in fact, useful, it would probably be best to check the availability of this flag.

Additional Programs + Steam bypass

Hi, here are some additional files and directories that aren't covered:

For the steam files, the flatpack version doesn't create the files in the home dir (because flatpack creates it's own container) so it could be used to remove these

Loving this project, my home directory has never been this clean

.trash on mac

Partly bug / partly question(?)
Don't crucify me, at the moment the output of .trash is:

  This is probably a custom directory on your system.
  You can move the file to XDG_DATA_HOME/trash.

My question is: If that is also relevant to macOS, or is it Linux only? If it's Linux only, it needs to be fixed.
On macOS, the .trash folder is the user's system-trash folder.

Thanks

Make the script POSIX-compliant

I am writing this here as this is being discussed in #6.

In all cases, it'll be up to b3nj5m1n to make the choice, but knowing that the script isn't so big, I think it shouldn't take much effort to make the script POSIX-compliant.

The main reasons why you'd want to have your script POSIX-compliant is that this is standardized and portable across all POSIX (i.e., all Linux, even though not officially POSIX) systems, meaning that shells like bash, or even zsh, dash would work as expected.

Modern desktops most likely uses bash so it shouldn't cause a problem using a hard dependency, but as stated before, the amount of effort that has to go into making the script POSIX-compliant isn't so big and could be done easily too. This would ensure everyone can run the script correctly.

Unable to parse list markdowns

Running xdgnj prev on the following config file gave an error.

{
   "name":"anacondaAUR",
   "files":[
      {
         "path":"~/.conda/.condarc",
         "movable":true,
         "help":"pkgs_dirs:\n  - ${XDG_CACHE_HOME}/conda/pkgs\n"
      }
   ]
}

xdgnj: /tmp/xdg-ninja.6817cde6-410a-4526-ba99-beab8e5f2e91.txt: hGetContents: invalid argument (invalid byte sequence)

> cat /tmp/xdg-ninja.795d8231-1933-48bc-8119-aa217a7ca776.txt

  • ${XDG_CACHE_HOME}/conda/pkgs

My guess is the binary is unable to parse this symbol.

Using pam_environment

Another kind of clutter is so much .profile .bashrc .bash_profile etc variables and not being able to switch shells easily. Adding environment variables to system pam env and home pam env reduces this clutter.
I've been using .pam_environment instead of .shellrcs for while but the devs want to discontinue it and depreciated it.
Edit: ~/.config/environment.d/*.conf for local variables and /etc/environment for system variables are good alternatives. ~/.config/env is loaded by systemd so don't know if few non-systemd systems support this.

Exit status dependent upon amount of fixable files in homedir

This would allow for slightly easier scripting using the program.

Example of a cronjob that could be created using this feature:

@weekly xdg-ninja.sh || notify-send "Unnecessary dot files in homedir" "You have $@ unnecessary files in your home directory."

Currently a user would have to create a rather-long wrapper program that finds the amount of red ansi codes which is not preferable.

.help json value request

for example:

{
    "name": "bash",
    "files": [
        {
            "path": "${HOME}/.bash_history",
            "movable": true,
            "help": "Export the following environment variables:\n\n```bash\nexport HISTFILE=\"${XDG_STATE_HOME}\"/bash/history\n```\n"
        }
    ]
}

currently, i am trying to compeletely declutter and optimize xdg-ninja.
please close this issue immediately if you do not agree with me.

i request to simply change the json files to have markdown rendering removed. so the output could be something along the lines of:

Export the following environment variables:
$ export HISTFILE=\"${XDG_STATE_HOME}\"/bash/history

instead of a very colorful and in my opinion, bloated output.
most programs follow the 'standard' i had said above and doing this could actually save me some time trying to optimize xdg-ninja.

npm no longer uses 'tmp' config

When running npm --help after applying xdg-ninja recommendations to npmrc, npm warns:

npm WARN config tmp This setting is no longer used. npm stores temporary files in a special
npm WARN config location in the cache, and they are managed by
npm WARN config cacache.

npm version: 8.9.0

Removing the recommendation tmp=${XDG_RUNTIME_DIR}/npm removes the warning, as npm now uses a subdirectory under the user supplied cache location cache=${XDG_CACHE_HOME}/npm.

List of missing programs

I really love that application 🤩
I was able to quickly clean a lot of my $HOME directory and my dotfiles repo, really cool stuff we have here 👍

I wanted to further clean my $HOME and noticed, and that's normal considering the number of existing applications, a lot of hidden files and directories still hanging around without explicit permissions 🤔

Below are lists of all the directories and files I still have in my $HOME, I only removed those already supported by xdg-ninja but without definitive solution and my personal files. There might be items not relevant to xdg-ninja, please tell me and i'll remove them 😉

If you prefer to split this issue into more little ones, this one can be closed and the job can be done.

I just thought a somewhat ""complete"" list of missing programs would be usefull 😋
And I would be more than happy to help if you accept external contributions 😋

Missing directories:

.arduino-create/
.arduino15/
.dwarffortress/
.eendroroy-coloschemes/
.emacs.d/
.gsutil/
.ipython/
.keras/
.kube/
.links/
.lyrics/
.moc/
.mujoco/
.oh-my-bash/
.oh-my-xonsh/
.pytest_cache/
.quickemu/
.screenlayout/
.slime/
.SpaceVim/
.SpaceVim.d/
.surf/
.telegram-cli/
.themes/
.thumbnails/
.trash/
.vim/ -> ~/.SpaceVim/
.vim_back/
.virtualbox/
.when/
.xmonad/
.zotero/

Missing files:

.bash_aliases
.bash_logout
.bash_profile
.bashrc
.bashrc.backup
.boto
.ctags
.dmrc
.gcalcli_cache
.gcalcli_oauth
.gnuplot_history
.grip-oggenc
.luahistory
.matho_history
.net.log
.notion-enhancer
.nwinkler_random_colors
.osh-update
.pdbhistory
.profile
.pulse-cookie
.python_history-83827.tmp
.rtorrent.rc
.sc_history
.sdirs
.viminfo
.vimrc_back
.xonshrc
.xscreensaver
.yarnrc
.zcompdump*
.zsh_history

Special cases:

  • gnupg: this program is supported but the ~/.gnupg file gets regenerated each time on my machine. This would be great to have a mention to that problem in the output of xdg-ninja

xdgnj doesn't work on arbitrary directory directly after download

The binary downloaded from the release page fails out of the box when trying to save a config with this error message:
xdgnj: ./programs/FreeCAD.json: openBinaryFile: does not exist (No such file or directory)
I guess it has to be run from within a clone of this repo but IMO when run from an arbitrary location (like my downloads folder) it should just save the file in that directory or create the programs folder so that work is not lost.

.steampid is missing

.steampid -> /home/wael/.steam/steam.pid

i should have told you to add this in #75, but at that time i wasn't using steam.

Add warning about potential GNUPGHOME pitfalls

On Archlinux the following additional changes are required when setting a non-default GNUPGHOME directory (archwiki):

If you use non-default GnuPG Home directory, you will need to edit all socket files to use the values of gpgconf --list-dirs.

If you set your SSH_AUTH_SOCK manually, keep in mind that your socket location may be different if you are using a custom GNUPGHOME

The effects of omitting these changes is actually kinda subtle, i.e. there are no obvious errors.
In my case, i noticed the gpg ssh-agent caching wasn't working anymore and the gpg-agent service was constantly restarting with socket is now serviced by another server - i assume due to conflicting sockets.

I think a word of caution here would be really helpful :)

Should shell history files be STATE, CACHE, or DATA?

For zsh/bash history files, currently:

ArchWiki suggests STATE.

antidot suggests CACHE.

xdg_ninja suggests CACHE for bash and DATA for zsh.

The XDG Base Directory Specification states:

The $XDG_STATE_HOME contains state data that should persist between (application) restarts, but that is not important or portable enough to the user that it should be stored in $XDG_DATA_HOME. It may contain:

actions history (logs, history, recently used files, …)

current state of the application that can be reused on a restart (view, layout, open files, undo history, …)

So I am leaning toward STATE, but figured I would ask first. :)

xonsh can be moved!

After some investigation, it appears that .xonshrc can be moved to $XDG_CONFIG_HOME/xonsh/xonshrc and xonsh can then be aliased to xonsh --rc "$XDG_CONFIG_HOME/xonsh/xonshrc"! 😋

isn't the description a bit misleading?

A shell script which checks your $HOME for unwanted files and directories.

doesn't the program need Haskell?
why not write the program entirely in POSIX sh/bash?

less history should be in $XDG_STATE_HOME, not CACHE_HOME

When finding the default .lesshst in home, xdg-ninja currently suggests:

[less]: ${HOME}/.lesshst

  Export the following environment variables:

    export LESSHISTFILE="$XDG_CACHE_HOME"/less/history

As discussed in #45 history files generally want to be in the new $XDG_STATE_HOME. I think less history similarly fits better as state than as cache, since it seems to clearly fit

  • actions history (logs, history, recently used files, …)
  • current state of the application that can be reused on a restart (view, layout, open files, undo history, …)

Your less search history obviously doesn't matter as much as your shell history, but it can't be regenerated if lost, so I don't think can be considered cache either. I propose instead suggesting

    export LESSHISTFILE="$XDG_STATE_HOME"/less/history

.android, .expo, .bubblewrap

There is no configuration for .android, which can be set using ANDROID_USER_HOME: https://developer.android.com/studio/command-line/variables

.expo is used by the expo-cli, which is An open-source platform for making universal native apps with React. Expo runs on Android, iOS, and the web.

.bubblewrap is used by bubblewrap-cli, which helps developers to create a Project for an Android application that launches an existing Progressive Web App (PWAs) using a Trusted Web Activity.

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.