diff --git a/.gitignore b/.gitignore index 5cb52bd0..614ebe89 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ -elpa -*.elc -hybridline.el -loading.el -presentation2.org -talk.org \ No newline at end of file +.DS_Store +*.pem +config/direnv +config/Fritzing +config/fish/fish_history +config/fish/fishd.* +config/xbuild \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..181dcdbb --- /dev/null +++ b/.gitmodules @@ -0,0 +1,8 @@ +[submodule "emacs.d"] + path = emacs.d + url = git@github.com:hlissner/doom-emacs.git + branch = develop +[submodule "doom.d"] + path = doom.d + url = git@github.com:marten/doom-emacs-private.git + update = merge diff --git a/.spacemacs b/.spacemacs new file mode 100644 index 00000000..c57b4deb --- /dev/null +++ b/.spacemacs @@ -0,0 +1,306 @@ +;; -*- mode: emacs-lisp -*- +;; This file is loaded by Spacemacs at startup. +;; It must be stored in your home directory. + +(defun dotspacemacs/layers () + "Configuration Layers declaration. +You should not put any user code in this function besides modifying the variable +values." + (setq-default + ;; Base distribution to use. This is a layer contained in the directory + ;; `+distribution'. For now available distributions are `spacemacs-base' + ;; or `spacemacs'. (default 'spacemacs) + dotspacemacs-distribution 'spacemacs + ;; Lazy installation of layers (i.e. layers are installed only when a file + ;; with a supported type is opened). Possible values are `all', `unused' + ;; and `nil'. `unused' will lazy install only unused layers (i.e. layers + ;; not listed in variable `dotspacemacs-configuration-layers'), `all' will + ;; lazy install any layer that support lazy installation even the layers + ;; listed in `dotspacemacs-configuration-layers'. `nil' disable the lazy + ;; installation feature and you have to explicitly list a layer in the + ;; variable `dotspacemacs-configuration-layers' to install it. + ;; (default 'unused) + dotspacemacs-enable-lazy-installation 'unused + ;; If non-nil then Spacemacs will ask for confirmation before installing + ;; a layer lazily. (default t) + dotspacemacs-ask-for-lazy-installation t + ;; If non-nil layers with lazy install support are lazy installed. + ;; List of additional paths where to look for configuration layers. + ;; Paths must have a trailing slash (i.e. `~/.mycontribs/') + dotspacemacs-configuration-layer-path '() + ;; List of configuration layers to load. + dotspacemacs-configuration-layers + '( + elixir + ;; ---------------------------------------------------------------- + ;; Example of useful layers you may want to use right away. + ;; Uncomment some layer names and press (Vim style) or + ;; (Emacs style) to install them. + ;; ---------------------------------------------------------------- + helm + ;; auto-completion + ;; better-defaults + emacs-lisp + ;; git + ;; markdown + ;; org + ;; (shell :variables + ;; shell-default-height 30 + ;; shell-default-position 'bottom) + ;; spell-checking + ;; syntax-checking + ;; version-control + ) + ;; List of additional packages that will be installed without being + ;; wrapped in a layer. If you need some configuration for these + ;; packages, then consider creating a layer. You can also put the + ;; configuration in `dotspacemacs/user-config'. + dotspacemacs-additional-packages '() + ;; A list of packages that cannot be updated. + dotspacemacs-frozen-packages '() + ;; A list of packages that will not be installed and loaded. + dotspacemacs-excluded-packages '() + ;; Defines the behaviour of Spacemacs when installing packages. + ;; Possible values are `used-only', `used-but-keep-unused' and `all'. + ;; `used-only' installs only explicitly used packages and uninstall any + ;; unused packages as well as their unused dependencies. + ;; `used-but-keep-unused' installs only the used packages but won't uninstall + ;; them if they become unused. `all' installs *all* packages supported by + ;; Spacemacs and never uninstall them. (default is `used-only') + dotspacemacs-install-packages 'used-only)) + +(defun dotspacemacs/init () + "Initialization function. +This function is called at the very startup of Spacemacs initialization +before layers configuration. +You should not put any user code in there besides modifying the variable +values." + ;; This setq-default sexp is an exhaustive list of all the supported + ;; spacemacs settings. + (setq-default + ;; If non nil ELPA repositories are contacted via HTTPS whenever it's + ;; possible. Set it to nil if you have no way to use HTTPS in your + ;; environment, otherwise it is strongly recommended to let it set to t. + ;; This variable has no effect if Emacs is launched with the parameter + ;; `--insecure' which forces the value of this variable to nil. + ;; (default t) + dotspacemacs-elpa-https t + ;; Maximum allowed time in seconds to contact an ELPA repository. + dotspacemacs-elpa-timeout 5 + ;; If non nil then spacemacs will check for updates at startup + ;; when the current branch is not `develop'. Note that checking for + ;; new versions works via git commands, thus it calls GitHub services + ;; whenever you start Emacs. (default nil) + dotspacemacs-check-for-update nil + ;; If non-nil, a form that evaluates to a package directory. For example, to + ;; use different package directories for different Emacs versions, set this + ;; to `emacs-version'. + dotspacemacs-elpa-subdirectory nil + ;; One of `vim', `emacs' or `hybrid'. + ;; `hybrid' is like `vim' except that `insert state' is replaced by the + ;; `hybrid state' with `emacs' key bindings. The value can also be a list + ;; with `:variables' keyword (similar to layers). Check the editing styles + ;; section of the documentation for details on available variables. + ;; (default 'vim) + dotspacemacs-editing-style 'vim + ;; If non nil output loading progress in `*Messages*' buffer. (default nil) + dotspacemacs-verbose-loading nil + ;; Specify the startup banner. Default value is `official', it displays + ;; the official spacemacs logo. An integer value is the index of text + ;; banner, `random' chooses a random text banner in `core/banners' + ;; directory. A string value must be a path to an image format supported + ;; by your Emacs build. + ;; If the value is nil then no banner is displayed. (default 'official) + dotspacemacs-startup-banner 'official + ;; List of items to show in startup buffer or an association list of + ;; the form `(list-type . list-size)`. If nil then it is disabled. + ;; Possible values for list-type are: + ;; `recents' `bookmarks' `projects' `agenda' `todos'." + ;; List sizes may be nil, in which case + ;; `spacemacs-buffer-startup-lists-length' takes effect. + dotspacemacs-startup-lists '((recents . 5) + (projects . 7)) + ;; True if the home buffer should respond to resize events. + dotspacemacs-startup-buffer-responsive t + ;; Default major mode of the scratch buffer (default `text-mode') + dotspacemacs-scratch-mode 'text-mode + ;; List of themes, the first of the list is loaded when spacemacs starts. + ;; Press T n to cycle to the next theme in the list (works great + ;; with 2 themes variants, one dark and one light) + dotspacemacs-themes '(spacemacs-dark + spacemacs-light) + ;; If non nil the cursor color matches the state color in GUI Emacs. + dotspacemacs-colorize-cursor-according-to-state t + ;; Default font, or prioritized list of fonts. `powerline-scale' allows to + ;; quickly tweak the mode-line size to make separators look not too crappy. + dotspacemacs-default-font '("Source Code Pro" + :size 13 + :weight normal + :width normal + :powerline-scale 1.1) + ;; The leader key + dotspacemacs-leader-key "SPC" + ;; The key used for Emacs commands (M-x) (after pressing on the leader key). + ;; (default "SPC") + dotspacemacs-emacs-command-key "SPC" + ;; The key used for Vim Ex commands (default ":") + dotspacemacs-ex-command-key ":" + ;; The leader key accessible in `emacs state' and `insert state' + ;; (default "M-m") + dotspacemacs-emacs-leader-key "M-m" + ;; Major mode leader key is a shortcut key which is the equivalent of + ;; pressing ` m`. Set it to `nil` to disable it. (default ",") + dotspacemacs-major-mode-leader-key "," + ;; Major mode leader key accessible in `emacs state' and `insert state'. + ;; (default "C-M-m") + dotspacemacs-major-mode-emacs-leader-key "C-M-m" + ;; These variables control whether separate commands are bound in the GUI to + ;; the key pairs C-i, TAB and C-m, RET. + ;; Setting it to a non-nil value, allows for separate commands under + ;; and TAB or and RET. + ;; In the terminal, these pairs are generally indistinguishable, so this only + ;; works in the GUI. (default nil) + dotspacemacs-distinguish-gui-tab nil + ;; If non nil `Y' is remapped to `y$' in Evil states. (default nil) + dotspacemacs-remap-Y-to-y$ nil + ;; If non-nil, the shift mappings `<' and `>' retain visual state if used + ;; there. (default t) + dotspacemacs-retain-visual-state-on-shift t + ;; If non-nil, J and K move lines up and down when in visual mode. + ;; (default nil) + dotspacemacs-visual-line-move-text nil + ;; If non nil, inverse the meaning of `g' in `:substitute' Evil ex-command. + ;; (default nil) + dotspacemacs-ex-substitute-global nil + ;; Name of the default layout (default "Default") + dotspacemacs-default-layout-name "Default" + ;; If non nil the default layout name is displayed in the mode-line. + ;; (default nil) + dotspacemacs-display-default-layout nil + ;; If non nil then the last auto saved layouts are resume automatically upon + ;; start. (default nil) + dotspacemacs-auto-resume-layouts nil + ;; Size (in MB) above which spacemacs will prompt to open the large file + ;; literally to avoid performance issues. Opening a file literally means that + ;; no major mode or minor modes are active. (default is 1) + dotspacemacs-large-file-size 1 + ;; Location where to auto-save files. Possible values are `original' to + ;; auto-save the file in-place, `cache' to auto-save the file to another + ;; file stored in the cache directory and `nil' to disable auto-saving. + ;; (default 'cache) + dotspacemacs-auto-save-file-location 'cache + ;; Maximum number of rollback slots to keep in the cache. (default 5) + dotspacemacs-max-rollback-slots 5 + ;; If non nil, `helm' will try to minimize the space it uses. (default nil) + dotspacemacs-helm-resize nil + ;; if non nil, the helm header is hidden when there is only one source. + ;; (default nil) + dotspacemacs-helm-no-header nil + ;; define the position to display `helm', options are `bottom', `top', + ;; `left', or `right'. (default 'bottom) + dotspacemacs-helm-position 'bottom + ;; Controls fuzzy matching in helm. If set to `always', force fuzzy matching + ;; in all non-asynchronous sources. If set to `source', preserve individual + ;; source settings. Else, disable fuzzy matching in all sources. + ;; (default 'always) + dotspacemacs-helm-use-fuzzy 'always + ;; If non nil the paste micro-state is enabled. When enabled pressing `p` + ;; several times cycle between the kill ring content. (default nil) + dotspacemacs-enable-paste-transient-state nil + ;; Which-key delay in seconds. The which-key buffer is the popup listing + ;; the commands bound to the current keystroke sequence. (default 0.4) + dotspacemacs-which-key-delay 0.4 + ;; Which-key frame position. Possible values are `right', `bottom' and + ;; `right-then-bottom'. right-then-bottom tries to display the frame to the + ;; right; if there is insufficient space it displays it at the bottom. + ;; (default 'bottom) + dotspacemacs-which-key-position 'bottom + ;; If non nil a progress bar is displayed when spacemacs is loading. This + ;; may increase the boot time on some systems and emacs builds, set it to + ;; nil to boost the loading time. (default t) + dotspacemacs-loading-progress-bar t + ;; If non nil the frame is fullscreen when Emacs starts up. (default nil) + ;; (Emacs 24.4+ only) + dotspacemacs-fullscreen-at-startup nil + ;; If non nil `spacemacs/toggle-fullscreen' will not use native fullscreen. + ;; Use to disable fullscreen animations in OSX. (default nil) + dotspacemacs-fullscreen-use-non-native nil + ;; If non nil the frame is maximized when Emacs starts up. + ;; Takes effect only if `dotspacemacs-fullscreen-at-startup' is nil. + ;; (default nil) (Emacs 24.4+ only) + dotspacemacs-maximized-at-startup nil + ;; A value from the range (0..100), in increasing opacity, which describes + ;; the transparency level of a frame when it's active or selected. + ;; Transparency can be toggled through `toggle-transparency'. (default 90) + dotspacemacs-active-transparency 90 + ;; A value from the range (0..100), in increasing opacity, which describes + ;; the transparency level of a frame when it's inactive or deselected. + ;; Transparency can be toggled through `toggle-transparency'. (default 90) + dotspacemacs-inactive-transparency 90 + ;; If non nil show the titles of transient states. (default t) + dotspacemacs-show-transient-state-title t + ;; If non nil show the color guide hint for transient state keys. (default t) + dotspacemacs-show-transient-state-color-guide t + ;; If non nil unicode symbols are displayed in the mode line. (default t) + dotspacemacs-mode-line-unicode-symbols t + ;; If non nil smooth scrolling (native-scrolling) is enabled. Smooth + ;; scrolling overrides the default behavior of Emacs which recenters point + ;; when it reaches the top or bottom of the screen. (default t) + dotspacemacs-smooth-scrolling t + ;; If non nil line numbers are turned on in all `prog-mode' and `text-mode' + ;; derivatives. If set to `relative', also turns on relative line numbers. + ;; (default nil) + dotspacemacs-line-numbers nil + ;; Code folding method. Possible values are `evil' and `origami'. + ;; (default 'evil) + dotspacemacs-folding-method 'evil + ;; If non-nil smartparens-strict-mode will be enabled in programming modes. + ;; (default nil) + dotspacemacs-smartparens-strict-mode nil + ;; If non-nil pressing the closing parenthesis `)' key in insert mode passes + ;; over any automatically added closing parenthesis, bracket, quote, etc… + ;; This can be temporary disabled by pressing `C-q' before `)'. (default nil) + dotspacemacs-smart-closing-parenthesis nil + ;; Select a scope to highlight delimiters. Possible values are `any', + ;; `current', `all' or `nil'. Default is `all' (highlight any scope and + ;; emphasis the current one). (default 'all) + dotspacemacs-highlight-delimiters 'all + ;; If non nil, advise quit functions to keep server open when quitting. + ;; (default nil) + dotspacemacs-persistent-server nil + ;; List of search tool executable names. Spacemacs uses the first installed + ;; tool of the list. Supported tools are `ag', `pt', `ack' and `grep'. + ;; (default '("ag" "pt" "ack" "grep")) + dotspacemacs-search-tools '("ag" "pt" "ack" "grep") + ;; The default package repository used if no explicit repository has been + ;; specified with an installed package. + ;; Not used for now. (default nil) + dotspacemacs-default-package-repository nil + ;; Delete whitespace while saving buffer. Possible values are `all' + ;; to aggressively delete empty line and long sequences of whitespace, + ;; `trailing' to delete only the whitespace at end of lines, `changed'to + ;; delete only whitespace for changed lines or `nil' to disable cleanup. + ;; (default nil) + dotspacemacs-whitespace-cleanup nil + )) + +(defun dotspacemacs/user-init () + "Initialization function for user code. +It is called immediately after `dotspacemacs/init', before layer configuration +executes. + This function is mostly useful for variables that need to be set +before packages are loaded. If you are unsure, you should try in setting them in +`dotspacemacs/user-config' first." + ) + +(defun dotspacemacs/user-config () + "Configuration function for user code. +This function is called at the very end of Spacemacs initialization after +layers configuration. +This is the place where most of your configurations should be done. Unless it is +explicitly specified that a variable should be set before a package is loaded, +you should place your code here." + ) + +;; Do not write anything past this comment. This is where Emacs will +;; auto-generate custom variable definitions. diff --git a/Brewfile b/Brewfile new file mode 100644 index 00000000..fea261c4 --- /dev/null +++ b/Brewfile @@ -0,0 +1,13 @@ +brew "libyaml" +brew "libxml2" +brew "libxslt" +brew "readline" +brew "openssl" +brew "openssl-osx-ca" +brew "vim" +brew "ssh-copy-id" +brew "the_silver_searcher" +brew "git" +brew "postgresql" +brew "mr" +brew "autojump" diff --git a/README.md b/README.md index b576d322..25e10d7a 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,19 @@ -# Smartparens [![Build Status](https://travis-ci.org/Fuco1/smartparens.png?branch=master)](https://travis-ci.org/Fuco1/smartparens) [![Paypal logo](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CEYP5YVHDRX8C) +dotfiles +======== -Smartparens is minor mode for Emacs that *deals with parens pairs and tries to be smart about it*. It started as a unification effort to combine functionality of several existing packages in a single, compatible and extensible way to deal with parentheses, delimiters, tags and the like. Some of these packages include [autopair](https://github.com/capitaomorte/autopair), [textmate](http://code.google.com/p/emacs-textmate/), [wrap-region](https://github.com/rejeep/wrap-region), [electric-pair-mode](http://www.emacswiki.org/emacs/ElectricPair), [paredit](http://emacswiki.org/emacs/ParEdit) and others. With the basic features found in other packages it also brings many improvements as well as completely new features. [Here's][wiki-what] a highlight of some features, for a complete list and detailed documentation look in the [manual][wiki-new]. +These are my dotfiles. There are many like them, but these ones are mine. -For the complete documentation visit the [documentation wiki][wiki]. +## Installation -[wiki]: https://github.com/Fuco1/smartparens/wiki -[wiki-what]: https://github.com/Fuco1/smartparens/wiki#wiki-what-is-this-package-about? -[wiki-new]: https://github.com/Fuco1/smartparens/wiki#wiki-information-for-new-users + brew tap thoughtbot/formulae + brew install rcm + rcup -d ~/dotfiles -# Default configuration -The default configuration was moved into a separate file. If you wish to use the default configuration as a basis for your own additional customization, add: -```scheme -(require 'smartparens-config) -``` + git clone git://github.com/marten/dotfiles ~/dotfiles + cd ~/dotfiles -in your configuration files (e.g. init.el) to load it. There are also files with additional configuration for specific modes, such as `smartparens-latex.el`. The usage is similar as with the default config. Note however that the `smartparens-config.el` file will auto-load all the mode-speceific customizations. +## Inspired by -# Support the project - -If you want to support this project, you can do it in the following ways: - -* Contribute code. If you have an idea that is not yet implemented and will benefit smartparens, feel free to implement it and submit a pull request. If you have any concerns whether your contribution will be accepted, ask beforehand. You can email the author or [start an issue](https://github.com/Fuco1/smartparens/issues/new) on the tracker. -* Contribute ideas. Even if you can't code Emacs LISP, you can still contribute valuable ideas for other programmers to implement. Simply [start new issue](https://github.com/Fuco1/smartparens/issues/new) on the tracker and submit your suggestion. -* You can make a [financial donation](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CEYP5YVHDRX8C) through PayPal. I currently major in mathematics, having no full-time job as the school (and emacs :) eats most my day time. If you like smartparens or want a specific feature to be implemented and can spare a modest amount on a donation, feel free to do so. Regardless of the donations, smartparens will always be free both as in beer and as in speech. +* https://github.com/holman/dotfiles diff --git a/SHARPEN.md b/SHARPEN.md new file mode 100644 index 00000000..fa6699fe --- /dev/null +++ b/SHARPEN.md @@ -0,0 +1,6 @@ +* Emacs smartparens dont work nicely. +* Emacs shortcut key for opening SHARPEN.md +* Emacs shortcut key for opening init.el +* Emacs shortcut key for reloading config +* Can emacs persist open buffers between launches? +* Dotfiles should auto-sync or have hotkey for sync diff --git a/ackrc b/ackrc new file mode 100644 index 00000000..2785cce8 --- /dev/null +++ b/ackrc @@ -0,0 +1,11 @@ +--color +--pager=LESS=FSRX less -r +--type-add=ruby=.haml,.rake,.rsel +--type-set=haml=.haml +--type-set=sass=.sass +--type-set=erb=.erb +--type-set=yml=.yml +--type-set=coffeescript=.coffee +--type-set=markdown=.md,.markdown,.mdown +--nosql +--ignore-dir="i18n" \ No newline at end of file diff --git a/bin/ack b/bin/ack new file mode 100755 index 00000000..9047fc08 --- /dev/null +++ b/bin/ack @@ -0,0 +1,2484 @@ +#!/usr/bin/env perl +# +# This file, ack, is generated code. +# Please DO NOT EDIT or send patches for it. +# +# Please take a look at the source from +# http://code.google.com/p/ack/source +# and submit patches against the individual files +# that build ack. +# + +use warnings; +use strict; + +our $VERSION = '1.88'; +# Check http://petdance.com/ack/ for updates + +# These are all our globals. + + +MAIN: { + if ( $App::Ack::VERSION ne $main::VERSION ) { + App::Ack::die( "Program/library version mismatch\n\t$0 is $main::VERSION\n\t$INC{'App/Ack.pm'} is $App::Ack::VERSION" ); + } + + # Do preliminary arg checking; + my $env_is_usable = 1; + for ( @ARGV ) { + last if ( $_ eq '--' ); + + # Priorities! Get the --thpppt checking out of the way. + /^--th[pt]+t+$/ && App::Ack::_thpppt($_); + + # See if we want to ignore the environment. (Don't tell Al Gore.) + if ( $_ eq '--noenv' ) { + my @keys = ( 'ACKRC', grep { /^ACK_/ } keys %ENV ); + delete @ENV{@keys}; + $env_is_usable = 0; + } + } + unshift( @ARGV, App::Ack::read_ackrc() ) if $env_is_usable; + App::Ack::load_colors(); + + if ( exists $ENV{ACK_SWITCHES} ) { + App::Ack::warn( 'ACK_SWITCHES is no longer supported. Use ACK_OPTIONS.' ); + } + + if ( !@ARGV ) { + App::Ack::show_help(); + exit 1; + } + + main(); +} + +sub main { + my $opt = App::Ack::get_command_line_options(); + + $| = 1 if $opt->{flush}; # Unbuffer the output if flush mode + + if ( -p STDIN ) { # Check to see if it's a pipe + # We're going into filter mode + for ( qw( f g l ) ) { + $opt->{$_} and App::Ack::die( "Can't use -$_ when acting as a filter." ); + } + $opt->{show_filename} = 0; + $opt->{regex} = App::Ack::build_regex( defined $opt->{regex} ? $opt->{regex} : shift @ARGV, $opt ); + if ( my $nargs = @ARGV ) { + my $s = $nargs == 1 ? '' : 's'; + App::Ack::warn( "Ignoring $nargs argument$s on the command-line while acting as a filter." ); + } + my $res = App::Ack::Resource::Basic->new( '-' ); + App::Ack::search_resource( $res, $opt ); + $res->close(); + exit 0; + } + + my $file_matching = $opt->{f} || $opt->{lines}; + if ( !$file_matching ) { + @ARGV or App::Ack::die( 'No regular expression found.' ); + $opt->{regex} = App::Ack::build_regex( defined $opt->{regex} ? $opt->{regex} : shift @ARGV, $opt ); + } + + # check that all regexes do compile fine + App::Ack::check_regex( $_ ) for ( $opt->{regex}, $opt->{G} ); + + my $what = App::Ack::get_starting_points( \@ARGV, $opt ); + my $iter = App::Ack::get_iterator( $what, $opt ); + App::Ack::filetype_setup(); + + my $nmatches = 0; + + App::Ack::set_up_pager( $opt->{pager} ) if defined $opt->{pager}; + if ( $opt->{f} ) { + App::Ack::print_files( $iter, $opt ); + } + elsif ( $opt->{l} || $opt->{count} ) { + $nmatches = App::Ack::print_files_with_matches( $iter, $opt ); + } + else { + $nmatches = App::Ack::print_matches( $iter, $opt ); + } + close $App::Ack::fh; + exit ($nmatches ? 0 : 1); +} + +=head1 NAME + +ack - grep-like text finder + +=head1 SYNOPSIS + + ack [options] PATTERN [FILE...] + ack -f [options] [DIRECTORY...] + +=head1 DESCRIPTION + +Ack is designed as a replacement for 99% of the uses of F. + +Ack searches the named input FILEs (or standard input if no files are +named, or the file name - is given) for lines containing a match to the +given PATTERN. By default, ack prints the matching lines. + +Ack can also list files that would be searched, without actually searching +them, to let you take advantage of ack's file-type filtering capabilities. + +=head1 FILE SELECTION + +I is intelligent about the files it searches. It knows about +certain file types, based on both the extension on the file and, +in some cases, the contents of the file. These selections can be +made with the B<--type> option. + +With no file selections, I only searches files of types that +it recognizes. If you have a file called F, and I +doesn't know what a .wango file is, I won't search it. + +The B<-a> option tells I to select all files, regardless of +type. + +Some files will never be selected by I, even with B<-a>, +including: + +=over 4 + +=item * Backup files: Files ending with F<~>, or F<#*#> + +=item * Coredumps: Files matching F + +=back + +However, I always searches the files given on the command line, +no matter what type. Furthermore, by specifying the B<-u> option all +files will be searched. + +=head1 DIRECTORY SELECTION + +I descends through the directory tree of the starting directories +specified. However, it will ignore the shadow directories used by +many version control systems, and the build directories used by the +Perl MakeMaker system. You may add or remove a directory from this +list with the B<--[no]ignore-dir> option. The option may be repeated +to add/remove multiple directories from the ignore list. + +For a complete list of directories that do not get searched, run +F. + +=head1 WHEN TO USE GREP + +I trumps I as an everyday tool 99% of the time, but don't +throw I away, because there are times you'll still need it. + +E.g., searching through huge files looking for regexes that can be +expressed with I syntax should be quicker with I. + +If your script or parent program uses I C<--quiet> or +C<--silent> or needs exit 2 on IO error, use I. + +=head1 OPTIONS + +=over 4 + +=item B<-a>, B<--all> + +Operate on all files, regardless of type (but still skip directories +like F, F, etc.) + +=item B<-A I>, B<--after-context=I> + +Print I lines of trailing context after matching lines. + +=item B<-B I>, B<--before-context=I> + +Print I lines of leading context before matching lines. + +=item B<-C [I]>, B<--context[=I]> + +Print I lines (default 2) of context around matching lines. + +=item B<-c>, B<--count> + +Suppress normal output; instead print a count of matching lines for +each input file. If B<-l> is in effect, it will only show the +number of lines for each file that has lines matching. Without +B<-l>, some line counts may be zeroes. + +=item B<--color>, B<--nocolor> + +B<--color> highlights the matching text. B<--nocolor> supresses +the color. This is on by default unless the output is redirected. + +On Windows, this option is off by default unless the +L module is installed or the C +environment variable is used. + +=item B<--env>, B<--noenv> + +B<--noenv> disables all environment processing. No F<.ackrc> is read +and all environment variables are ignored. By default, F considers +F<.ackrc> and settings in the environment. + +=item B<--flush> + +B<--flush> flushes output immediately. This is off by default +unless ack is running interactively (when output goes to a pipe +or file). + +=item B<-f> + +Only print the files that would be searched, without actually doing +any searching. PATTERN must not be specified, or it will be taken as +a path to search. + +=item B<--follow>, B<--nofollow> + +Follow or don't follow symlinks, other than whatever starting files +or directories were specified on the command line. + +This is off by default. + +=item B<-G I> + +Only paths matching I are included in the search. The entire +path and filename are matched against I, and I is a +Perl regular expression, not a shell glob. + +The options B<-i>, B<-w>, B<-v>, and B<-Q> do not apply to this I. + +=item B<-g I> + +Print files where the relative path + filename matches I. This option is +a convenience shortcut for B<-f> B<-G I>. + +The options B<-i>, B<-w>, B<-v>, and B<-Q> do not apply to this I. + +=item B<--group>, B<--nogroup> + +B<--group> groups matches by file name with. This is the default when +used interactively. + +B<--nogroup> prints one result per line, like grep. This is the default +when output is redirected. + +=item B<-H>, B<--with-filename> + +Print the filename for each match. + +=item B<-h>, B<--no-filename> + +Suppress the prefixing of filenames on output when multiple files are +searched. + +=item B<--help> + +Print a short help statement. + +=item B<-i>, B<--ignore-case> + +Ignore case in the search strings. + +This applies only to the PATTERN, not to the regexes given for the B<-g> +and B<-G> options. + +=item B<--[no]ignore-dir=DIRNAME> + +Ignore directory (as CVS, .svn, etc are ignored). May be used multiple times +to ignore multiple directories. For example, mason users may wish to include +B<--ignore-dir=data>. The B<--noignore-dir> option allows users to search +directories which would normally be ignored (perhaps to research the contents +of F<.svn/props> directories). + +=item B<--line=I> + +Only print line I of each file. Multiple lines can be given with multiple +B<--line> options or as a comma separated list (B<--line=3,5,7>). B<--line=4-7> +also works. The lines are always output in ascending order, no matter the +order given on the command line. + +=item B<-l>, B<--files-with-matches> + +Only print the filenames of matching files, instead of the matching text. + +=item B<-L>, B<--files-without-matches> + +Only print the filenames of files that do I match. This is equivalent +to specifying B<-l> and B<-v>. + +=item B<--match I> + +Specify the I explicitly. This is helpful if you don't want to put the +regex as your first argument, e.g. when executing multiple searches over the +same set of files. + + # search for foo and bar in given files + ack file1 t/file* --match foo + ack file1 t/file* --match bar + +=item B<-m=I>, B<--max-count=I> + +Stop reading a file after I matches. + +=item B<--man> + +Print this manual page. + +=item B<-n> + +No descending into subdirectories. + +=item B<-o> + +Show only the part of each line matching PATTERN (turns off text +highlighting) + +=item B<--output=I> + +Output the evaluation of I for each line (turns off text +highlighting) + +=item B<--pager=I> + +Direct ack's output through I. This can also be specified +via the C and C environment variables. + +Using --pager does not suppress grouping and coloring like piping +output on the command-line does. + +=item B<--passthru> + +Prints all lines, whether or not they match the expression. Highlighting +will still work, though, so it can be used to highlight matches while +still seeing the entire file, as in: + + # Watch a log file, and highlight a certain IP address + $ tail -f ~/access.log | ack --passthru 123.45.67.89 + +=item B<--print0> + +Only works in conjunction with -f, -g, -l or -c (filename output). The filenames +are output separated with a null byte instead of the usual newline. This is +helpful when dealing with filenames that contain whitespace, e.g. + + # remove all files of type html + ack -f --html --print0 | xargs -0 rm -f + +=item B<-Q>, B<--literal> + +Quote all metacharacters in PATTERN, it is treated as a literal. + +This applies only to the PATTERN, not to the regexes given for the B<-g> +and B<-G> options. + +=item B<--smart-case>, B<--no-smart-case> + +Ignore case in the search strings if PATTERN contains no uppercase +characters. This is similar to C in vim. This option is +off by default. + +B<-i> always overrides this option. + +This applies only to the PATTERN, not to the regexes given for the +B<-g> and B<-G> options. + +=item B<--sort-files> + +Sorts the found files lexically. Use this if you want your file +listings to be deterministic between runs of I. + +=item B<--thpppt> + +Display the all-important Bill The Cat logo. Note that the exact +spelling of B<--thpppppt> is not important. It's checked against +a regular expression. + +=item B<--type=TYPE>, B<--type=noTYPE> + +Specify the types of files to include or exclude from a search. +TYPE is a filetype, like I or I. B<--type=perl> can +also be specified as B<--perl>, and B<--type=noperl> can be done +as B<--noperl>. + +If a file is of both type "foo" and "bar", specifying --foo and +--nobar will exclude the file, because an exclusion takes precedence +over an inclusion. + +Type specifications can be repeated and are ORed together. + +See I for a list of valid types. + +=item B<--type-add I=I<.EXTENSION>[,I<.EXT2>[,...]]> + +Files with the given EXTENSION(s) are recognized as being of (the +existing) type TYPE. See also L. + + +=item B<--type-set I=I<.EXTENSION>[,I<.EXT2>[,...]]> + +Files with the given EXTENSION(s) are recognized as being of type +TYPE. This replaces an existing definition for type TYPE. See also +L. + +=item B<-u>, B<--unrestricted> + +All files and directories (including blib/, core.*, ...) are searched, +nothing is skipped. When both B<-u> and B<--ignore-dir> are used, the +B<--ignore-dir> option has no effect. + +=item B<-v>, B<--invert-match> + +Invert match: select non-matching lines + +This applies only to the PATTERN, not to the regexes given for the B<-g> +and B<-G> options. + +=item B<--version> + +Display version and copyright information. + +=item B<-w>, B<--word-regexp> + +Force PATTERN to match only whole words. The PATTERN is wrapped with +C<\b> metacharacters. + +This applies only to the PATTERN, not to the regexes given for the B<-g> +and B<-G> options. + +=item B<-1> + +Stops after reporting first match of any kind. This is different +from B<--max-count=1> or B<-m1>, where only one match per file is +shown. Also, B<-1> works with B<-f> and B<-g>, where B<-m> does +not. + +=back + +=head1 THE .ackrc FILE + +The F<.ackrc> file contains command-line options that are prepended +to the command line before processing. Multiple options may live +on multiple lines. Lines beginning with a # are ignored. A F<.ackrc> +might look like this: + + # Always sort the files + --sort-files + + # Always color, even if piping to a another program + --color + + # Use "less -r" as my pager + --pager=less -r + +Note that arguments with spaces in them do not need to be quoted, +as they are not interpreted by the shell. Basically, each I +in the F<.ackrc> file is interpreted as one element of C<@ARGV>. + +F looks in your home directory for the F<.ackrc>. You can +specify another location with the F variable, below. + +If B<--noenv> is specified on the command line, the F<.ackrc> file +is ignored. + +=head1 Defining your own types + +ack allows you to define your own types in addition to the predefined +types. This is done with command line options that are best put into +an F<.ackrc> file - then you do not have to define your types over and +over again. In the following examples the options will always be shown +on one command line so that they can be easily copy & pasted. + +I searches for foo in all perl files. I +tells you, that perl files are files ending +in .pl, .pm, .pod or .t. So what if you would like to include .xs +files as well when searching for --perl files? I +does this for you. B<--type-add> appends +additional extensions to an existing type. + +If you want to define a new type, or completely redefine an existing +type, then use B<--type-set>. I defines the type I to include files with +the extensions .e or .eiffel. So to search for all eiffel files +containing the word Bertrand use I. +As usual, you can also write B<--type=eiffel> +instead of B<--eiffel>. Negation also works, so B<--noeiffel> excludes +all eiffel files from a search. Redefining also works: I +and I<.xs> files no longer belong to the type I. + +When defining your own types in the F<.ackrc> file you have to use +the following: + + --type-set=eiffel=.e,.eiffel + +or writing on separate lines + + --type-set + eiffel=.e,.eiffel + +The following does B work in the F<.ackrc> file: + + --type-set eiffel=.e,.eiffel + + +In order to see all currently defined types, use I<--help types>, e.g. +I + +Restrictions: + +=over 4 + +=item + +The types 'skipped', 'make', 'binary' and 'text' are considered "builtin" and +cannot be altered. + +=item + +The shebang line recognition of the types 'perl', 'ruby', 'php', 'python', +'shell' and 'xml' cannot be redefined by I<--type-set>, it is always +active. However, the shebang line is only examined for files where the +extension is not recognised. Therefore it is possible to say +I and +only find your shiny new I<.perl> files (and all files with unrecognized extension +and perl on the shebang line). + +=back + +=head1 ENVIRONMENT VARIABLES + +For commonly-used ack options, environment variables can make life much easier. +These variables are ignored if B<--noenv> is specified on the command line. + +=over 4 + +=item ACKRC + +Specifies the location of the F<.ackrc> file. If this file doesn't +exist, F looks in the default location. + +=item ACK_OPTIONS + +This variable specifies default options to be placed in front of +any explicit options on the command line. + +=item ACK_COLOR_FILENAME + +Specifies the color of the filename when it's printed in B<--group> +mode. By default, it's "bold green". + +The recognized attributes are clear, reset, dark, bold, underline, +underscore, blink, reverse, concealed black, red, green, yellow, +blue, magenta, on_black, on_red, on_green, on_yellow, on_blue, +on_magenta, on_cyan, and on_white. Case is not significant. +Underline and underscore are equivalent, as are clear and reset. +The color alone sets the foreground color, and on_color sets the +background color. + +=item ACK_COLOR_MATCH + +Specifies the color of the matching text when printed in B<--color> +mode. By default, it's "black on_yellow". + +See B for the color specifications. + +=item ACK_PAGER + +Specifies a pager program, such as C, C or C, to which +ack will send its output. + +Using C does not suppress grouping and coloring like +piping output on the command-line does, except that on Windows +ack will assume that C does not support color. + +C overrides C if both are specified. + +=item ACK_PAGER_COLOR + +Specifies a pager program that understands ANSI color sequences. +Using C does not suppress grouping and coloring +like piping output on the command-line does. + +If you are not on Windows, you never need to use C. + +=back + +=head1 ACK & OTHER TOOLS + +=head2 Vim integration + +F integrates easily with the Vim text editor. Set this in your +F<.vimrc> to use F instead of F: + + set grepprg=ack\ -a + +That examples uses C<-a> to search through all files, but you may +use other default flags. Now you can search with F and easily +step through the results in Vim: + + :grep Dumper perllib + +=head2 Emacs integration + +Phil Jackson put together an F extension that "provides a +simple compilation mode ... has the ability to guess what files you +want to search for based on the major-mode." + +L + +=head2 TextMate integration + +Pedro Melo is a TextMate user who writes "I spend my day mostly +inside TextMate, and the built-in find-in-project sucks with large +projects. So I hacked a TextMate command that was using find + +grep to use ack. The result is the Search in Project with ack, and +you can find it here: +L" + +=head2 Shell and Return Code + +For greater compatibility with I, I in normal use returns +shell return or exit code of 0 only if something is found and 1 if +no match is found. + +(Shell exit code 1 is C<$?=256> in perl with C or backticks.) + +The I code 2 for errors is not used. + +0 is returned if C<-f> or C<-g> are specified, irrespective of +number of files found. + +=cut + +=head1 DEBUGGING ACK PROBLEMS + +If ack gives you output you're not expecting, start with a few simple steps. + +=head2 Use B<--noenv> + +Your environment variables and F<.ackrc> may be doing things you're +not expecting, or forgotten you specified. Use B<--noenv> to ignore +your environment and F<.ackrc>. + +=head2 Use B<-f> to see what files you're scanning + +The reason I created B<-f> in the first place was as a debugging +tool. If ack is not finding matches you think it should find, run +F to see what files are being checked. + +=head1 TIPS + +=head2 Use the F<.ackrc> file. + +The F<.ackrc> is the place to put all your options you use most of +the time but don't want to remember. Put all your --type-add and +--type-set definitions in it. If you like --smart-case, set it +there, too. I also set --sort-files there. + +=head2 Use F<-f> for working with big codesets + +Ack does more than search files. C will create a +list of all the Perl files in a tree, ideal for sending into F. +For example: + + # Change all "this" to "that" in all Perl files in a tree. + ack -f --perl | perl -p -i -e's/this/that/g' + +=head2 Use F<-Q> when in doubt about metacharacters + +If you're searching for something with a regular expression +metacharacter, most often a period in a filename or IP address, add +the -Q to avoid false positives without all the backslashing. See +the following example for more... + +=head2 Use ack to watch log files + +Here's one I used the other day to find trouble spots for a website +visitor. The user had a problem loading F, so I +took the access log and scanned it with ack twice. + + ack -Q aa.bb.cc.dd /path/to/access.log | ack -Q -B5 troublesome.gif + +The first ack finds only the lines in the Apache log for the given +IP. The second finds the match on my troublesome GIF, and shows +the previous five lines from the log in each case. + +=head2 Share your knowledge + +Join the ack-users mailing list. Send me your tips and I may add +them here. + +=head1 AUTHOR + +Andy Lester, C<< >> + +=head1 BUGS + +Please report any bugs or feature requests to the issues list at +Google Code: L + +=head1 ENHANCEMENTS + +All enhancement requests MUST first be posted to the ack-users +mailing list at L. I +will not consider a request without it first getting seen by other +ack users. + +There is a list of enhancements I want to make to F in the ack +issues list at Google Code: L + +Patches are always welcome, but patches with tests get the most +attention. + +=head1 SUPPORT + +Support for and information about F can be found at: + +=over 4 + +=item * The ack homepage + +L + +=item * The ack issues list at Google Code + +L + +=item * AnnoCPAN: Annotated CPAN documentation + +L + +=item * CPAN Ratings + +L + +=item * Search CPAN + +L + +=item * Subversion repository + +L + +=back + +=head1 ACKNOWLEDGEMENTS + +How appropriate to have Inowledgements! + +Thanks to everyone who has contributed to ack in any way, including +Sitaram Chamarty, +Adam James, +Richard Carlsson, +Pedro Melo, +AJ Schuster, +Phil Jackson, +Michael Schwern, +Jan Dubois, +Christopher J. Madsen, +Matthew Wickline, +David Dyck, +Jason Porritt, +Jjgod Jiang, +Thomas Klausner, +Uri Guttman, +Peter Lewis, +Kevin Riggle, +Ori Avtalion, +Torsten Blix, +Nigel Metheringham, +Gábor Szabó, +Tod Hagan, +Michael Hendricks, +Ævar Arnfjörð Bjarmason, +Piers Cawley, +Stephen Steneker, +Elias Lutfallah, +Mark Leighton Fisher, +Matt Diephouse, +Christian Jaeger, +Bill Sully, +Bill Ricker, +David Golden, +Nilson Santos F. Jr, +Elliot Shank, +Merijn Broeren, +Uwe Voelker, +Rick Scott, +Ask Bjørn Hansen, +Jerry Gay, +Will Coleda, +Mike O'Regan, +Slaven Rezić, +Mark Stosberg, +David Alan Pisoni, +Adriano Ferreira, +James Keenan, +Leland Johnson, +Ricardo Signes +and Pete Krawczyk. + +=head1 COPYRIGHT & LICENSE + +Copyright 2005-2009 Andy Lester, all rights reserved. + +This program is free software; you can redistribute it and/or modify +it under the terms of either: + +=over 4 + +=item * the GNU General Public License as published by the Free +Software Foundation; either version 1, or (at your option) any later +version, or + +=item * the "Artistic License" which comes with Perl 5. + +=back + +=cut +package File::Next; + +use strict; +use warnings; + + +our $VERSION = '1.02'; + + + +use File::Spec (); + + +our $name; # name of the current file +our $dir; # dir of the current file + +our %files_defaults; +our %skip_dirs; + +BEGIN { + %files_defaults = ( + file_filter => undef, + descend_filter => undef, + error_handler => sub { CORE::die @_ }, + sort_files => undef, + follow_symlinks => 1, + ); + %skip_dirs = map {($_,1)} (File::Spec->curdir, File::Spec->updir); +} + + +sub files { + my ($parms,@queue) = _setup( \%files_defaults, @_ ); + my $filter = $parms->{file_filter}; + + return sub { + while (@queue) { + my ($dir,$file,$fullpath) = splice( @queue, 0, 3 ); + if ( -f $fullpath ) { + if ( $filter ) { + local $_ = $file; + local $File::Next::dir = $dir; + local $File::Next::name = $fullpath; + next if not $filter->(); + } + return wantarray ? ($dir,$file,$fullpath) : $fullpath; + } + elsif ( -d _ ) { + unshift( @queue, _candidate_files( $parms, $fullpath ) ); + } + } # while + + return; + }; # iterator +} + + + + + + + +sub sort_standard($$) { return $_[0]->[1] cmp $_[1]->[1] }; +sub sort_reverse($$) { return $_[1]->[1] cmp $_[0]->[1] }; + +sub reslash { + my $path = shift; + + my @parts = split( /\//, $path ); + + return $path if @parts < 2; + + return File::Spec->catfile( @parts ); +} + + + +sub _setup { + my $defaults = shift; + my $passed_parms = ref $_[0] eq 'HASH' ? {%{+shift}} : {}; # copy parm hash + + my %passed_parms = %{$passed_parms}; + + my $parms = {}; + for my $key ( keys %{$defaults} ) { + $parms->{$key} = + exists $passed_parms{$key} + ? delete $passed_parms{$key} + : $defaults->{$key}; + } + + # Any leftover keys are bogus + for my $badkey ( keys %passed_parms ) { + my $sub = (caller(1))[3]; + $parms->{error_handler}->( "Invalid option passed to $sub(): $badkey" ); + } + + # If it's not a code ref, assume standard sort + if ( $parms->{sort_files} && ( ref($parms->{sort_files}) ne 'CODE' ) ) { + $parms->{sort_files} = \&sort_standard; + } + my @queue; + + for ( @_ ) { + my $start = reslash( $_ ); + if (-d $start) { + push @queue, ($start,undef,$start); + } + else { + push @queue, (undef,$start,$start); + } + } + + return ($parms,@queue); +} + + +sub _candidate_files { + my $parms = shift; + my $dir = shift; + + my $dh; + if ( !opendir $dh, $dir ) { + $parms->{error_handler}->( "$dir: $!" ); + return; + } + + my @newfiles; + my $descend_filter = $parms->{descend_filter}; + my $follow_symlinks = $parms->{follow_symlinks}; + my $sort_sub = $parms->{sort_files}; + + while ( defined ( my $file = readdir $dh ) ) { + next if $skip_dirs{$file}; + my $has_stat; + + # Only do directory checking if we have a descend_filter + my $fullpath = File::Spec->catdir( $dir, $file ); + if ( !$follow_symlinks ) { + next if -l $fullpath; + $has_stat = 1; + } + + if ( $descend_filter ) { + if ( $has_stat ? (-d _) : (-d $fullpath) ) { + local $File::Next::dir = $fullpath; + local $_ = $file; + next if not $descend_filter->(); + } + } + if ( $sort_sub ) { + push( @newfiles, [ $dir, $file, $fullpath ] ); + } + else { + push( @newfiles, $dir, $file, $fullpath ); + } + } + closedir $dh; + + if ( $sort_sub ) { + return map { @{$_} } sort $sort_sub @newfiles; + } + + return @newfiles; +} + + +1; # End of File::Next +package App::Ack; + +use warnings; +use strict; + + + + +our $VERSION; +our $COPYRIGHT; +BEGIN { + $VERSION = '1.88'; + $COPYRIGHT = 'Copyright 2005-2009 Andy Lester, all rights reserved.'; +} + +our $fh; + +BEGIN { + $fh = *STDOUT; +} + + +our %types; +our %type_wanted; +our %mappings; +our %ignore_dirs; + +our $dir_sep_chars; +our $is_cygwin; +our $is_windows; +our $to_screen; + +use File::Spec (); +use File::Glob ':glob'; +use Getopt::Long (); + +BEGIN { + %ignore_dirs = ( + '.bzr' => 'Bazaar', + '.cdv' => 'Codeville', + '~.dep' => 'Interface Builder', + '~.dot' => 'Interface Builder', + '~.nib' => 'Interface Builder', + '~.plst' => 'Interface Builder', + '.git' => 'Git', + '.hg' => 'Mercurial', + '.pc' => 'quilt', + '.svn' => 'Subversion', + blib => 'Perl module building', + CVS => 'CVS', + RCS => 'RCS', + SCCS => 'SCCS', + _darcs => 'darcs', + _sgbak => 'Vault/Fortress', + 'autom4te.cache' => 'autoconf', + 'cover_db' => 'Devel::Cover', + _build => 'Module::Build', + ); + + %mappings = ( + actionscript => [qw( as mxml )], + asm => [qw( asm s )], + batch => [qw( bat cmd )], + binary => q{Binary files, as defined by Perl's -B op (default: off)}, + cc => [qw( c h xs )], + cfmx => [qw( cfc cfm cfml )], + cpp => [qw( cpp cc cxx m hpp hh h hxx )], + csharp => [qw( cs )], + css => [qw( css )], + elisp => [qw( el )], + erlang => [qw( erl hrl )], + fortran => [qw( f f77 f90 f95 f03 for ftn fpp )], + haskell => [qw( hs lhs )], + hh => [qw( h )], + html => [qw( htm html shtml xhtml )], + java => [qw( java properties )], + js => [qw( js )], + jsp => [qw( jsp jspx jhtm jhtml )], + lisp => [qw( lisp lsp )], + lua => [qw( lua )], + make => q{Makefiles}, + mason => [qw( mas mhtml mpl mtxt )], + objc => [qw( m h )], + objcpp => [qw( mm h )], + ocaml => [qw( ml mli )], + parrot => [qw( pir pasm pmc ops pod pg tg )], + perl => [qw( pl pm pod t )], + php => [qw( php phpt php3 php4 php5 )], + plone => [qw( pt cpt metadata cpy py )], + python => [qw( py )], + rake => q{Rakefiles}, + ruby => [qw( rb rhtml rjs rxml erb rake )], + scheme => [qw( scm )], + shell => [qw( sh bash csh tcsh ksh zsh )], + skipped => q{Files, but not directories, normally skipped by ack (default: off)}, + smalltalk => [qw( st )], + sql => [qw( sql ctl )], + tcl => [qw( tcl itcl itk )], + tex => [qw( tex cls sty )], + text => q{Text files, as defined by Perl's -T op (default: off)}, + tt => [qw( tt tt2 ttml )], + vb => [qw( bas cls frm ctl vb resx )], + vim => [qw( vim )], + yaml => [qw( yaml yml )], + xml => [qw( xml dtd xslt ent )], + ); + + while ( my ($type,$exts) = each %mappings ) { + if ( ref $exts ) { + for my $ext ( @{$exts} ) { + push( @{$types{$ext}}, $type ); + } + } + } + + $is_cygwin = ($^O eq 'cygwin'); + $is_windows = ($^O =~ /MSWin32/); + $to_screen = -t *STDOUT; + $dir_sep_chars = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) ); +} + + +sub read_ackrc { + my @files = ( $ENV{ACKRC} ); + my @dirs = + $is_windows + ? ( $ENV{HOME}, $ENV{USERPROFILE} ) + : ( '~', $ENV{HOME} ); + for my $dir ( grep { defined } @dirs ) { + for my $file ( '.ackrc', '_ackrc' ) { + push( @files, bsd_glob( "$dir/$file", GLOB_TILDE ) ); + } + } + for my $filename ( @files ) { + if ( defined $filename && -e $filename ) { + open( my $fh, '<', $filename ) or App::Ack::die( "$filename: $!\n" ); + my @lines = grep { /./ && !/^\s*#/ } <$fh>; + chomp @lines; + close $fh or App::Ack::die( "$filename: $!\n" ); + + return @lines; + } + } + + return; +} + + +sub get_command_line_options { + my %opt = ( + pager => $ENV{ACK_PAGER_COLOR} || $ENV{ACK_PAGER}, + ); + + my $getopt_specs = { + 1 => sub { $opt{1} = $opt{m} = 1 }, + 'A|after-context=i' => \$opt{after_context}, + 'B|before-context=i' => \$opt{before_context}, + 'C|context:i' => sub { shift; my $val = shift; $opt{before_context} = $opt{after_context} = ($val || 2) }, + 'a|all-types' => \$opt{all}, + 'break!' => \$opt{break}, + c => \$opt{count}, + 'color|colour!' => \$opt{color}, + count => \$opt{count}, + 'env!' => sub { }, # ignore this option, it is handled beforehand + f => \$opt{f}, + flush => \$opt{flush}, + 'follow!' => \$opt{follow}, + 'g=s' => sub { shift; $opt{G} = shift; $opt{f} = 1 }, + 'G=s' => \$opt{G}, + 'group!' => sub { shift; $opt{heading} = $opt{break} = shift }, + 'heading!' => \$opt{heading}, + 'h|no-filename' => \$opt{h}, + 'H|with-filename' => \$opt{H}, + 'i|ignore-case' => \$opt{i}, + 'lines=s' => sub { shift; my $val = shift; push @{$opt{lines}}, $val }, + 'l|files-with-matches' => \$opt{l}, + 'L|files-without-match' => sub { $opt{l} = $opt{v} = 1 }, + 'm|max-count=i' => \$opt{m}, + 'match=s' => \$opt{regex}, + n => \$opt{n}, + o => sub { $opt{output} = '$&' }, + 'output=s' => \$opt{output}, + 'pager=s' => \$opt{pager}, + 'nopager' => sub { $opt{pager} = undef }, + 'passthru' => \$opt{passthru}, + 'print0' => \$opt{print0}, + 'Q|literal' => \$opt{Q}, + 'smart-case!' => \$opt{smart_case}, + 'sort-files' => \$opt{sort_files}, + 'u|unrestricted' => \$opt{u}, + 'v|invert-match' => \$opt{v}, + 'w|word-regexp' => \$opt{w}, + + 'ignore-dirs=s' => sub { shift; my $dir = remove_dir_sep( shift ); $ignore_dirs{$dir} = '--ignore-dirs' }, + 'noignore-dirs=s' => sub { shift; my $dir = remove_dir_sep( shift ); delete $ignore_dirs{$dir} }, + + 'version' => sub { print_version_statement(); exit 1; }, + 'help|?:s' => sub { shift; show_help(@_); exit; }, + 'help-types'=> sub { show_help_types(); exit; }, + 'man' => sub { require Pod::Usage; Pod::Usage::pod2usage({-verbose => 2}); exit; }, + + 'type=s' => sub { + # Whatever --type=xxx they specify, set it manually in the hash + my $dummy = shift; + my $type = shift; + my $wanted = ($type =~ s/^no//) ? 0 : 1; # must not be undef later + + if ( exists $type_wanted{ $type } ) { + $type_wanted{ $type } = $wanted; + } + else { + App::Ack::die( qq{Unknown --type "$type"} ); + } + }, # type sub + }; + + # Stick any default switches at the beginning, so they can be overridden + # by the command line switches. + unshift @ARGV, split( ' ', $ENV{ACK_OPTIONS} ) if defined $ENV{ACK_OPTIONS}; + + # first pass through options, looking for type definitions + def_types_from_ARGV(); + + for my $i ( filetypes_supported() ) { + $getopt_specs->{ "$i!" } = \$type_wanted{ $i }; + } + + + my $parser = Getopt::Long::Parser->new(); + $parser->configure( 'bundling', 'no_ignore_case', ); + $parser->getoptions( %{$getopt_specs} ) or + App::Ack::die( 'See ack --help or ack --man for options.' ); + + my %defaults = ( + all => 0, + color => $to_screen, + follow => 0, + break => $to_screen, + heading => $to_screen, + before_context => 0, + after_context => 0, + ); + if ( $is_windows && $defaults{color} && not $ENV{ACK_PAGER_COLOR} ) { + if ( $ENV{ACK_PAGER} || not eval { require Win32::Console::ANSI } ) { + $defaults{color} = 0; + } + } + if ( $to_screen && $ENV{ACK_PAGER_COLOR} ) { + $defaults{color} = 1; + } + + while ( my ($key,$value) = each %defaults ) { + if ( not defined $opt{$key} ) { + $opt{$key} = $value; + } + } + + if ( defined $opt{m} && $opt{m} <= 0 ) { + App::Ack::die( '-m must be greater than zero' ); + } + + for ( qw( before_context after_context ) ) { + if ( defined $opt{$_} && $opt{$_} < 0 ) { + App::Ack::die( "--$_ may not be negative" ); + } + } + + if ( defined( my $val = $opt{output} ) ) { + $opt{output} = eval qq[ sub { "$val" } ]; + } + if ( defined( my $l = $opt{lines} ) ) { + # --line=1 --line=5 is equivalent to --line=1,5 + my @lines = split( /,/, join( ',', @{$l} ) ); + + # --line=1-3 is equivalent to --line=1,2,3 + @lines = map { + my @ret; + if ( /-/ ) { + my ($from, $to) = split /-/, $_; + if ( $from > $to ) { + App::Ack::warn( "ignoring --line=$from-$to" ); + @ret = (); + } + else { + @ret = ( $from .. $to ); + } + } + else { + @ret = ( $_ ); + }; + @ret + } @lines; + + if ( @lines ) { + my %uniq; + @uniq{ @lines } = (); + $opt{lines} = [ sort { $a <=> $b } keys %uniq ]; # numerical sort and each line occurs only once! + } + else { + # happens if there are only ignored --line directives + App::Ack::die( 'All --line options are invalid.' ); + } + } + + return \%opt; +} + + +sub def_types_from_ARGV { + my @typedef; + + my $parser = Getopt::Long::Parser->new(); + # pass_through => leave unrecognized command line arguments alone + # no_auto_abbrev => otherwise -c is expanded and not left alone + $parser->configure( 'no_ignore_case', 'pass_through', 'no_auto_abbrev' ); + $parser->getoptions( + 'type-set=s' => sub { shift; push @typedef, ['c', shift] }, + 'type-add=s' => sub { shift; push @typedef, ['a', shift] }, + ) or App::Ack::die( 'See ack --help or ack --man for options.' ); + + for my $td (@typedef) { + my ($type, $ext) = split /=/, $td->[1]; + + if ( $td->[0] eq 'c' ) { + # type-set + if ( exists $mappings{$type} ) { + # can't redefine types 'make', 'skipped', 'text' and 'binary' + App::Ack::die( qq{--type-set: Builtin type "$type" cannot be changed.} ) + if ref $mappings{$type} ne 'ARRAY'; + + delete_type($type); + } + } + else { + # type-add + + # can't append to types 'make', 'skipped', 'text' and 'binary' + App::Ack::die( qq{--type-add: Builtin type "$type" cannot be changed.} ) + if exists $mappings{$type} && ref $mappings{$type} ne 'ARRAY'; + + App::Ack::warn( qq{--type-add: Type "$type" does not exist, creating with "$ext" ...} ) + unless exists $mappings{$type}; + } + + my @exts = split /,/, $ext; + s/^\.// for @exts; + + if ( !exists $mappings{$type} || ref($mappings{$type}) eq 'ARRAY' ) { + push @{$mappings{$type}}, @exts; + for my $e ( @exts ) { + push @{$types{$e}}, $type; + } + } + else { + App::Ack::die( qq{Cannot append to type "$type".} ); + } + } + + return; +} + + +sub delete_type { + my $type = shift; + + App::Ack::die( qq{Internal error: Cannot delete builtin type "$type".} ) + unless ref $mappings{$type} eq 'ARRAY'; + + delete $mappings{$type}; + delete $type_wanted{$type}; + for my $ext ( keys %types ) { + $types{$ext} = [ grep { $_ ne $type } @{$types{$ext}} ]; + } +} + + +sub ignoredir_filter { + return !exists $ignore_dirs{$_}; +} + + +sub remove_dir_sep { + my $path = shift; + $path =~ s/[$dir_sep_chars]$//; + + return $path; +} + + +use constant TEXT => 'text'; + +sub filetypes { + my $filename = shift; + + return 'skipped' unless is_searchable( $filename ); + + my $basename = $filename; + $basename =~ s{.*[$dir_sep_chars]}{}; + + return ('make',TEXT) if lc $basename eq 'makefile'; + return ('rake','ruby',TEXT) if lc $basename eq 'rakefile'; + + # If there's an extension, look it up + if ( $filename =~ m{\.([^\.$dir_sep_chars]+)$}o ) { + my $ref = $types{lc $1}; + return (@{$ref},TEXT) if $ref; + } + + # At this point, we can't tell from just the name. Now we have to + # open it and look inside. + + return unless -e $filename; + # From Elliot Shank: + # I can't see any reason that -r would fail on these-- the ACLs look + # fine, and no program has any of them open, so the busted Windows + # file locking model isn't getting in there. If I comment the if + # statement out, everything works fine + # So, for cygwin, don't bother trying to check for readability. + if ( !$is_cygwin ) { + if ( !-r $filename ) { + App::Ack::warn( "$filename: Permission denied" ); + return; + } + } + + return 'binary' if -B $filename; + + # If there's no extension, or we don't recognize it, check the shebang line + my $fh; + if ( !open( $fh, '<', $filename ) ) { + App::Ack::warn( "$filename: $!" ); + return; + } + my $header = <$fh>; + close $fh; + + if ( $header =~ /^#!/ ) { + return ($1,TEXT) if $header =~ /\b(ruby|p(?:erl|hp|ython))\b/; + return ('shell',TEXT) if $header =~ /\b(?:ba|t?c|k|z)?sh\b/; + } + else { + return ('xml',TEXT) if $header =~ /\Q{Q}; + if ( $opt->{w} ) { + $str = "\\b$str" if $str =~ /^\w/; + $str = "$str\\b" if $str =~ /\w$/; + } + + my $regex_is_lc = $str eq lc $str; + if ( $opt->{i} || ($opt->{smart_case} && $regex_is_lc) ) { + $str = "(?i)$str"; + } + + return $str; +} + + +sub check_regex { + my $regex = shift; + + return unless defined $regex; + + eval { qr/$regex/ }; + if ($@) { + (my $error = $@) =~ s/ at \S+ line \d+.*//; + chomp($error); + App::Ack::die( "Invalid regex '$regex':\n $error" ); + } + + return; +} + + + + +sub warn { + return CORE::warn( _my_program(), ': ', @_, "\n" ); +} + + +sub die { + return CORE::die( _my_program(), ': ', @_, "\n" ); +} + +sub _my_program { + require File::Basename; + return File::Basename::basename( $0 ); +} + + + +sub filetypes_supported { + return keys %mappings; +} + +sub _get_thpppt { + my $y = q{_ /|,\\'!.x',=(www)=, U }; + $y =~ tr/,x!w/\nOo_/; + return $y; +} + +sub _thpppt { + my $y = _get_thpppt(); + App::Ack::print( "$y ack $_[0]!\n" ); + exit 0; +} + +sub _key { + my $str = lc shift; + $str =~ s/[^a-z]//g; + + return $str; +} + + +sub show_help { + my $help_arg = shift || 0; + + return show_help_types() if $help_arg =~ /^types?/; + + my $ignore_dirs = _listify( sort { _key($a) cmp _key($b) } keys %ignore_dirs ); + + App::Ack::print( <<"END_OF_HELP" ); +Usage: ack [OPTION]... PATTERN [FILE] + +Search for PATTERN in each source file in the tree from cwd on down. +If [FILES] is specified, then only those files/directories are checked. +ack may also search STDIN, but only if no FILE are specified, or if +one of FILES is "-". + +Default switches may be specified in ACK_OPTIONS environment variable or +an .ackrc file. If you want no dependency on the environment, turn it +off with --noenv. + +Example: ack -i select + +Searching: + -i, --ignore-case Ignore case distinctions in PATTERN + --[no]smart-case Ignore case distinctions in PATTERN, + only if PATTERN contains no upper case + Ignored if -i is specified + -v, --invert-match Invert match: select non-matching lines + -w, --word-regexp Force PATTERN to match only whole words + -Q, --literal Quote all metacharacters; PATTERN is literal + +Search output: + --line=NUM Only print line(s) NUM of each file + -l, --files-with-matches + Only print filenames containing matches + -L, --files-without-match + Only print filenames with no match + -o Show only the part of a line matching PATTERN + (turns off text highlighting) + --passthru Print all lines, whether matching or not + --output=expr Output the evaluation of expr for each line + (turns off text highlighting) + --match PATTERN Specify PATTERN explicitly. + -m, --max-count=NUM Stop searching in each file after NUM matches + -1 Stop searching after one match of any kind + -H, --with-filename Print the filename for each match + -h, --no-filename Suppress the prefixing filename on output + -c, --count Show number of lines matching per file + + -A NUM, --after-context=NUM + Print NUM lines of trailing context after matching + lines. + -B NUM, --before-context=NUM + Print NUM lines of leading context before matching + lines. + -C [NUM], --context[=NUM] + Print NUM lines (default 2) of output context. + + --print0 Print null byte as separator between filenames, + only works with -f, -g, -l, -L or -c. + +File presentation: + --pager=COMMAND Pipes all ack output through COMMAND. + Ignored if output is redirected. + --nopager Do not send output through a pager. Cancels any + setting in ~/.ackrc, ACK_PAGER or ACK_PAGER_COLOR. + --[no]heading Print a filename heading above each file's results. + (default: on when used interactively) + --[no]break Print a break between results from different files. + (default: on when used interactively) + --group Same as --heading --break + --nogroup Same as --noheading --nobreak + --[no]color Highlight the matching text (default: on unless + output is redirected, or on Windows) + --[no]colour Same as --[no]color + --flush Flush output immediately, even when ack is used + non-interactively (when output goes to a pipe or + file). + +File finding: + -f Only print the files found, without searching. + The PATTERN must not be specified. + -g REGEX Same as -f, but only print files matching REGEX. + --sort-files Sort the found files lexically. + +File inclusion/exclusion: + -a, --all-types All file types searched; + Ignores CVS, .svn and other ignored directories + -u, --unrestricted All files and directories searched + --[no]ignore-dir=name Add/Remove directory from the list of ignored dirs + -n No descending into subdirectories + -G REGEX Only search files that match REGEX + + --perl Include only Perl files. + --type=perl Include only Perl files. + --noperl Exclude Perl files. + --type=noperl Exclude Perl files. + See "ack --help type" for supported filetypes. + + --type-set TYPE=.EXTENSION[,.EXT2[,...]] + Files with the given EXTENSION(s) are recognized as + being of type TYPE. This replaces an existing + definition for type TYPE. + --type-add TYPE=.EXTENSION[,.EXT2[,...]] + Files with the given EXTENSION(s) are recognized as + being of (the existing) type TYPE + + --[no]follow Follow symlinks. Default is off. + + Directories ignored by default: + $ignore_dirs + + Files not checked for type: + /~\$/ - Unix backup files + /#.+#\$/ - Emacs swap files + /[._].*\\.swp\$/ - Vi(m) swap files + /core\\.\\d+\$/ - core dumps + +Miscellaneous: + --noenv Ignore environment variables and ~/.ackrc + --help This help + --man Man page + --version Display version & copyright + --thpppt Bill the Cat + +Exit status is 0 if match, 1 if no match. + +This is version $VERSION of ack. +END_OF_HELP + + return; + } + + + +sub show_help_types { + App::Ack::print( <<'END_OF_HELP' ); +Usage: ack [OPTION]... PATTERN [FILES] + +The following is the list of filetypes supported by ack. You can +specify a file type with the --type=TYPE format, or the --TYPE +format. For example, both --type=perl and --perl work. + +Note that some extensions may appear in multiple types. For example, +.pod files are both Perl and Parrot. + +END_OF_HELP + + my @types = filetypes_supported(); + my $maxlen = 0; + for ( @types ) { + $maxlen = length if $maxlen < length; + } + for my $type ( sort @types ) { + next if $type =~ /^-/; # Stuff to not show + my $ext_list = $mappings{$type}; + + if ( ref $ext_list ) { + $ext_list = join( ' ', map { ".$_" } @{$ext_list} ); + } + App::Ack::print( sprintf( " --[no]%-*.*s %s\n", $maxlen, $maxlen, $type, $ext_list ) ); + } + + return; +} + +sub _listify { + my @whats = @_; + + return '' if !@whats; + + my $end = pop @whats; + my $str = @whats ? join( ', ', @whats ) . " and $end" : $end; + + no warnings 'once'; + require Text::Wrap; + $Text::Wrap::columns = 75; + return Text::Wrap::wrap( '', ' ', $str ); +} + + +sub get_version_statement { + my $copyright = get_copyright(); + return <<"END_OF_VERSION"; +ack $VERSION + +$copyright + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. +END_OF_VERSION +} + + +sub print_version_statement { + App::Ack::print( get_version_statement() ); + + return; +} + + +sub get_copyright { + return $COPYRIGHT; +} + + +sub load_colors { + eval 'use Term::ANSIColor ()'; + + $ENV{ACK_COLOR_MATCH} ||= 'black on_yellow'; + $ENV{ACK_COLOR_FILENAME} ||= 'bold green'; + + return; +} + + +sub is_interesting { + return if /^\./; + + my $include; + + for my $type ( filetypes( $File::Next::name ) ) { + if ( defined $type_wanted{$type} ) { + if ( $type_wanted{$type} ) { + $include = 1; + } + else { + return; + } + } + } + + return $include; +} + + + +# print subs added in order to make it easy for a third party +# module (such as App::Wack) to redefine the display methods +# and show the results in a different way. +sub print { print {$fh} @_ } +sub print_first_filename { App::Ack::print( $_[0], "\n" ) } +sub print_blank_line { App::Ack::print( "\n" ) } +sub print_separator { App::Ack::print( "--\n" ) } +sub print_filename { App::Ack::print( $_[0], $_[1] ) } +sub print_line_no { App::Ack::print( $_[0], $_[1] ) } +sub print_count { + my $filename = shift; + my $nmatches = shift; + my $ors = shift; + my $count = shift; + + App::Ack::print( $filename ); + App::Ack::print( ':', $nmatches ) if $count; + App::Ack::print( $ors ); +} + +sub print_count0 { + my $filename = shift; + my $ors = shift; + + App::Ack::print( $filename, ':0', $ors ); +} + + + +{ + my $filename; + my $regex; + my $display_filename; + + my $keep_context; + + my $last_output_line; # number of the last line that has been output + my $any_output; # has there been any output for the current file yet + my $context_overall_output_count; # has there been any output at all + +sub search_resource { + my $res = shift; + my $opt = shift; + + $filename = $res->name(); + + my $v = $opt->{v}; + my $passthru = $opt->{passthru}; + my $max = $opt->{m}; + my $nmatches = 0; + + $display_filename = undef; + + # for --line processing + my $has_lines = 0; + my @lines; + if ( defined $opt->{lines} ) { + $has_lines = 1; + @lines = ( @{$opt->{lines}}, -1 ); + undef $regex; # Don't match when printing matching line + } + else { + $regex = qr/$opt->{regex}/; + } + + # for context processing + $last_output_line = -1; + $any_output = 0; + my $before_context = $opt->{before_context}; + my $after_context = $opt->{after_context}; + + $keep_context = ($before_context || $after_context) && !$passthru; + + my @before; + my $before_starts_at_line; + my $after = 0; # number of lines still to print after a match + + while ( $res->next_text ) { + # XXX Optimize away the case when there are no more @lines to find. + # XXX $has_lines, $passthru and $v never change. Optimize. + if ( $has_lines + ? $. != $lines[0] # $lines[0] should be a scalar + : $v ? m/$regex/ : !m/$regex/ ) { + if ( $passthru ) { + App::Ack::print( $_ ); + next; + } + + if ( $keep_context ) { + if ( $after ) { + print_match_or_context( $opt, 0, $., $_ ); + $after--; + } + elsif ( $before_context ) { + if ( @before ) { + if ( @before >= $before_context ) { + shift @before; + ++$before_starts_at_line; + } + } + else { + $before_starts_at_line = $.; + } + push @before, $_; + } + last if $max && ( $nmatches >= $max ) && !$after; + } + next; + } # not a match + + ++$nmatches; + + # print an empty line as a divider before first line in each file (not before the first file) + if ( !$any_output && $opt->{show_filename} && $opt->{break} && defined( $context_overall_output_count ) ) { + App::Ack::print_blank_line(); + } + + shift @lines if $has_lines; + + if ( $res->is_binary ) { + App::Ack::print( "Binary file $filename matches\n" ); + last; + } + if ( $keep_context ) { + if ( @before ) { + print_match_or_context( $opt, 0, $before_starts_at_line, @before ); + @before = (); + $before_starts_at_line = 0; + } + if ( $max && $nmatches > $max ) { + --$after; + } + else { + $after = $after_context; + } + } + print_match_or_context( $opt, 1, $., $_ ); + + last if $max && ( $nmatches >= $max ) && !$after; + } # while + + return $nmatches; +} # search_resource() + + + +sub print_match_or_context { + my $opt = shift; # opts array + my $is_match = shift; # is there a match on the line? + my $line_no = shift; + + my $color = $opt->{color}; + my $heading = $opt->{heading}; + my $show_filename = $opt->{show_filename}; + + if ( $show_filename ) { + if ( not defined $display_filename ) { + $display_filename = + $color + ? Term::ANSIColor::colored( $filename, $ENV{ACK_COLOR_FILENAME} ) + : $filename; + if ( $heading && !$any_output ) { + App::Ack::print_first_filename($display_filename); + } + } + } + + my $sep = $is_match ? ':' : '-'; + my $output_func = $opt->{output}; + for ( @_ ) { + if ( $keep_context && !$output_func ) { + if ( ( $last_output_line != $line_no - 1 ) && + ( $any_output || ( !$heading && defined( $context_overall_output_count ) ) ) ) { + App::Ack::print_separator(); + } + # to ensure separators between different files when --noheading + + $last_output_line = $line_no; + } + + if ( $show_filename ) { + App::Ack::print_filename($display_filename, $sep) if not $heading; + App::Ack::print_line_no($line_no, $sep); + } + + if ( $output_func ) { + while ( /$regex/go ) { + App::Ack::print( $output_func->() . "\n" ); + } + } + else { + if ( $color && $is_match && $regex && + s/$regex/Term::ANSIColor::colored( substr($_, $-[0], $+[0] - $-[0]), $ENV{ACK_COLOR_MATCH} )/eg ) { + # At the end of the line reset the color and remove newline + s/[\r\n]*\z/\e[0m\e[K/; + } + else { + # remove any kind of newline at the end of the line + s/[\r\n]*\z//; + } + App::Ack::print($_ . "\n"); + } + $any_output = 1; + ++$context_overall_output_count; + ++$line_no; + } + + return; +} # print_match_or_context() + +} # scope around search_resource() and print_match_or_context() + + + +sub search_and_list { + my $res = shift; + my $opt = shift; + + my $nmatches = 0; + my $count = $opt->{count}; + my $ors = $opt->{print0} ? "\0" : "\n"; # output record separator + + my $regex = qr/$opt->{regex}/; + + if ( $opt->{v} ) { + while ( $res->next_text ) { + if ( /$regex/ ) { + return 0 unless $count; + } + else { + ++$nmatches; + } + } + } + else { + while ( $res->next_text ) { + if ( /$regex/ ) { + ++$nmatches; + last unless $count; + } + } + } + + if ( $nmatches ) { + App::Ack::print_count( $res->name, $nmatches, $ors, $count ); + } + elsif ( $count && !$opt->{l} ) { + App::Ack::print_count0( $res->name, $ors ); + } + + return $nmatches ? 1 : 0; +} # search_and_list() + + + +sub filetypes_supported_set { + return grep { defined $type_wanted{$_} && ($type_wanted{$_} == 1) } filetypes_supported(); +} + + + +sub print_files { + my $iter = shift; + my $opt = shift; + + my $ors = $opt->{print0} ? "\0" : "\n"; + + while ( defined ( my $file = $iter->() ) ) { + App::Ack::print $file, $ors; + last if $opt->{1}; + } + + return; +} + + +sub print_files_with_matches { + my $iter = shift; + my $opt = shift; + + my $nmatches = 0; + while ( defined ( my $filename = $iter->() ) ) { + my $repo = App::Ack::Repository::Basic->new( $filename ); + my $res; + while ( $res = $repo->next_resource() ) { + $nmatches += search_and_list( $res, $opt ); + $res->close(); + last if $nmatches && $opt->{1}; + } + $repo->close(); + } + + return $nmatches; +} + + +sub print_matches { + my $iter = shift; + my $opt = shift; + + $opt->{show_filename} = 0 if $opt->{h}; + $opt->{show_filename} = 1 if $opt->{H}; + + my $nmatches = 0; + while ( defined ( my $filename = $iter->() ) ) { + my $repo; + if ( $filename =~ /\.tar\.gz$/ ) { + App::Ack::die( 'Not working here yet' ); + require App::Ack::Repository::Tar; # XXX Error checking + $repo = App::Ack::Repository::Tar->new( $filename ); + } + else { + $repo = App::Ack::Repository::Basic->new( $filename ); + } + $repo or next; + + while ( my $res = $repo->next_resource() ) { + my $needs_line_scan; + if ( $opt->{regex} && !$opt->{passthru} ) { + $needs_line_scan = $res->needs_line_scan( $opt ); + if ( $needs_line_scan ) { + $res->reset(); + } + } + else { + $needs_line_scan = 1; + } + if ( $needs_line_scan ) { + $nmatches += search_resource( $res, $opt ); + } + $res->close(); + } + last if $nmatches && $opt->{1}; + $repo->close(); + } + return $nmatches; +} + + +sub filetype_setup { + my $filetypes_supported_set = filetypes_supported_set(); + # If anyone says --no-whatever, we assume all other types must be on. + if ( !$filetypes_supported_set ) { + for my $i ( keys %type_wanted ) { + $type_wanted{$i} = 1 unless ( defined( $type_wanted{$i} ) || $i eq 'binary' || $i eq 'text' || $i eq 'skipped' ); + } + } + return; +} + + +EXPAND_FILENAMES_SCOPE: { + my $filter; + + sub expand_filenames { + my $argv = shift; + + my $attr; + my @files; + + foreach my $pattern ( @{$argv} ) { + my @results = bsd_glob( $pattern ); + + if (@results == 0) { + @results = $pattern; # Glob didn't match, pass it thru unchanged + } + elsif ( (@results > 1) or ($results[0] ne $pattern) ) { + if (not defined $filter) { + eval 'require Win32::File;'; + if ($@) { + $filter = 0; + } + else { + $filter = Win32::File::HIDDEN()|Win32::File::SYSTEM(); + } + } # end unless we've tried to load Win32::File + if ( $filter ) { + # Filter out hidden and system files: + @results = grep { not(Win32::File::GetAttributes($_, $attr) and $attr & $filter) } @results; + App::Ack::warn( "$pattern: Matched only hidden files" ) unless @results; + } # end if we can filter by file attributes + } # end elsif this pattern got expanded + + push @files, @results; + } # end foreach pattern + + return \@files; + } # end expand_filenames +} # EXPAND_FILENAMES_SCOPE + + + +sub get_starting_points { + my $argv = shift; + my $opt = shift; + + my @what; + + if ( @{$argv} ) { + @what = @{ $is_windows ? expand_filenames($argv) : $argv }; + $_ = File::Next::reslash( $_ ) for @what; + + # Show filenames unless we've specified one single file + $opt->{show_filename} = (@what > 1) || (!-f $what[0]); + } + else { + @what = '.'; # Assume current directory + $opt->{show_filename} = 1; + } + + for my $start_point (@what) { + App::Ack::warn( "$start_point: No such file or directory" ) unless -e $start_point; + } + return \@what; +} + + + +sub get_iterator { + my $what = shift; + my $opt = shift; + + # Starting points are always searched, no matter what + my %starting_point = map { ($_ => 1) } @{$what}; + + my $g_regex = defined $opt->{G} ? qr/$opt->{G}/ : undef; + my $file_filter; + + if ( $g_regex ) { + $file_filter + = $opt->{u} ? sub { $File::Next::name =~ /$g_regex/ } # XXX Maybe this should be a 1, no? + : $opt->{all} ? sub { $starting_point{ $File::Next::name } || ( $File::Next::name =~ /$g_regex/ && is_searchable( $File::Next::name ) ) } + : sub { $starting_point{ $File::Next::name } || ( $File::Next::name =~ /$g_regex/ && is_interesting( @_ ) ) } + ; + } + else { + $file_filter + = $opt->{u} ? sub {1} + : $opt->{all} ? sub { $starting_point{ $File::Next::name } || is_searchable( $File::Next::name ) } + : sub { $starting_point{ $File::Next::name } || is_interesting( @_ ) } + ; + } + + my $descend_filter + = $opt->{n} ? sub {0} + : $opt->{u} ? sub {1} + : \&ignoredir_filter; + + my $iter = + File::Next::files( { + file_filter => $file_filter, + descend_filter => $descend_filter, + error_handler => sub { my $msg = shift; App::Ack::warn( $msg ) }, + sort_files => $opt->{sort_files}, + follow_symlinks => $opt->{follow}, + }, @{$what} ); + return $iter; +} + + +sub set_up_pager { + my $command = shift; + + return unless $to_screen; + + my $pager; + if ( not open( $pager, '|-', $command ) ) { + App::Ack::die( qq{Unable to pipe to pager "$command": $!} ); + } + $fh = $pager; + + return; +} + + +1; # End of App::Ack +package App::Ack::Repository; + + +use warnings; +use strict; + +sub FAIL { + require Carp; + Carp::confess( 'Must be overloaded' ); +} + + +sub new { + FAIL(); +} + + +sub next_resource { + FAIL(); +} + + +sub close { + FAIL(); +} + +1; +package App::Ack::Resource; + + +use warnings; +use strict; + +sub FAIL { + require Carp; + Carp::confess( 'Must be overloaded' ); +} + + +sub new { + FAIL(); +} + + +sub name { + FAIL(); +} + + +sub is_binary { + FAIL(); +} + + + +sub needs_line_scan { + FAIL(); +} + + +sub reset { + FAIL(); +} + + +sub next_text { + FAIL(); +} + + +sub close { + FAIL(); +} + +1; +package App::Ack::Plugin::Basic; + + + +package App::Ack::Resource::Basic; + + +use warnings; +use strict; + + +our @ISA = qw( App::Ack::Resource ); + + +sub new { + my $class = shift; + my $filename = shift; + + my $self = bless { + filename => $filename, + fh => undef, + could_be_binary => undef, + opened => undef, + id => undef, + }, $class; + + if ( $self->{filename} eq '-' ) { + $self->{fh} = *STDIN; + $self->{could_be_binary} = 0; + } + else { + if ( !open( $self->{fh}, '<', $self->{filename} ) ) { + App::Ack::warn( "$self->{filename}: $!" ); + return; + } + $self->{could_be_binary} = 1; + } + + return $self; +} + + +sub name { + my $self = shift; + + return $self->{filename}; +} + + +sub is_binary { + my $self = shift; + + if ( $self->{could_be_binary} ) { + return -B $self->{filename}; + } + + return 0; +} + + + +sub needs_line_scan { + my $self = shift; + my $opt = shift; + + return 1 if $opt->{v}; + + my $size = -s $self->{fh}; + if ( $size == 0 ) { + return 0; + } + elsif ( $size > 100_000 ) { + return 1; + } + + my $buffer; + my $rc = sysread( $self->{fh}, $buffer, $size ); + if ( not defined $rc ) { + App::Ack::warn( "$self->{filename}: $!" ); + return 1; + } + return 0 unless $rc && ( $rc == $size ); + + my $regex = $opt->{regex}; + return $buffer =~ /$regex/m; +} + + +sub reset { + my $self = shift; + + seek( $self->{fh}, 0, 0 ) + or App::Ack::warn( "$self->{filename}: $!" ); + + return; +} + + +sub next_text { + if ( defined ($_ = readline $_[0]->{fh}) ) { + $. = ++$_[0]->{line}; + return 1; + } + + return; +} + + +sub close { + my $self = shift; + + if ( not close $self->{fh} ) { + App::Ack::warn( $self->name() . ": $!" ); + } + + return; +} + +package App::Ack::Repository::Basic; + + +our @ISA = qw( App::Ack::Repository ); + + +use warnings; +use strict; + +sub new { + my $class = shift; + my $filename = shift; + + my $self = bless { + filename => $filename, + nexted => 0, + }, $class; + + return $self; +} + + +sub next_resource { + my $self = shift; + + return if $self->{nexted}; + $self->{nexted} = 1; + + return App::Ack::Resource::Basic->new( $self->{filename} ); +} + + +sub close { +} + + + +1; diff --git a/bin/autotag b/bin/autotag new file mode 100755 index 00000000..512bde25 --- /dev/null +++ b/bin/autotag @@ -0,0 +1,32 @@ +#!/bin/sh + +# Tag mail sent specifically to me (not through a list or bcc) +notmuch tag +to-me to:marten@veldthuis.com and not tag:to-me +notmuch tag +to-me to:m.veldthuis@rug.nl and not tag:to-me +notmuch tag +to-me to:m.veldthuis@med.umcg.nl and not tag:to-me + +# Tag mail sent by me +notmuch tag +sent from:marten@veldthuis.com and not tag:sent +notmuch tag +sent from:marten@fmf.nl and not tag:sent +notmuch tag +sent from:m.veldthuis@rug.nl and not tag:sent +notmuch tag +sent from:m.veldthuis@med.umcg.nl and not tag:sent +notmuch tag -inbox -unread tag:sent and tag:inbox and tag:unread + +# My various mail addresses +notmuch tag +rgoc \(to:med.umcg.nl or from:med.umcg.nl\) and not tag:rgoc +notmuch tag +perio to:perio@fmf.nl and not tag:perio +notmuch tag -inbox +roqua +exception from:exception and to:roqua and tag:inbox and not tag:roqua and not tag:exception + +# Keep ignored threads out of my inbox +notmuch tag -inbox tag:ignore and tag:inbox + +# Mailing lists +notmuch tag +list +github to:github@googlegroups.com and not tag:github +notmuch tag +list +notmuch to:notmuch@notmuchmail.org and not tag:notmuch +notmuch tag +list +emacs -inbox to:help-gnu-emacs@gnu.org and not tag:emacs +notmuch tag +list +jekyll to:jekyll-rb@googlegroups.com and not tag:jekyll +notmuch tag +list +sup -inbox to:sup-talk@rubyforge.org or \ + to:sup-devel@rubyforge.org and not tag:sup +notmuch tag +list +railsbridge to:railsbridge@googlegroups.com and not tag:railsbridge +notmuch tag +list +formtastic to:formtastic@googlegroups.com and not tag:formtastic +notmuch tag +list +vcshome to:vcs-home@lists.madduck.net and not tag:vcshome diff --git a/bin/awssh b/bin/awssh new file mode 100755 index 00000000..206aa609 --- /dev/null +++ b/bin/awssh @@ -0,0 +1,43 @@ +#!/usr/bin/env ruby +require 'bundler/inline' +gemfile do + source 'https://rubygems.org' + gem 'aws-sdk' + gem 'tty-command' + gem 'pry' +end + +`command -v fzf >/dev/null 2>&1` +if $? != 0 + STDERR.puts "This script needs fzf but it is not installed." + STDERR.puts " brew install fzf" + exit 1 +end + +puts "Getting instances" + +ec2 = Aws::EC2::Client.new(region: "us-east-1") +response = ec2.describe_instances +reservations = response.reservations +instances = reservations.flat_map(&:instances).select { |instance| instance.state.name != "terminated" } + +lines = instances.map do |instance| + "#{instance.instance_id} :: #{instance.instance_type} [#{instance.tags.map(&:value).join(", ")}]" +end + +tempfile = Tempfile.new('awssh') +tempfile.write(lines.join("\n")) +tempfile.close +line = `cat #{tempfile.path} | fzf` +tempfile.unlink + +if $? == 0 && line.strip.size > 0 + chosen_instance_id = line.strip.split(" ").first + chosen_instance = instances.find { |instance| instance.instance_id == chosen_instance_id } + + puts "ssh #{chosen_instance.public_dns_name}" + exec "ssh", chosen_instance.public_dns_name.to_s +else + puts "Aborting." + exit 1 +end diff --git a/bin/commit_messages.txt b/bin/commit_messages.txt new file mode 100644 index 00000000..c6322c3e --- /dev/null +++ b/bin/commit_messages.txt @@ -0,0 +1,183 @@ +de-misunderestimating +XNAMEX, WE WENT OVER THIS. EXPANDTAB. +XNAMEX, WE WENT OVER THIS. C++ IO SUCKS. +Some shit. +add actual words +I CAN HAZ COMMENTZ. +giggle. +Whatever. +Finished fondling. +FONDLED THE CODE +this is how we generate our shit. +unh +don't be retarded. +It works! +unionfind is no longer being molested. +Well, it's doing something. +I'M PUSHING. +Whee. +Whee, good night. +It'd be nice if type errors caused the compiler to issue a type error +Fucking templates. +I hate this fucking language. +marks +that coulda been bad +hoo boy +It was the best of times, it was the worst of times +Fucking egotistical bastard. adds expandtab to vimrc +if you're not using et, fuck off +WHO THE FUCK CAME UP WITH MAKE? +This is a basic implementation that works. +By works, I meant 'doesnt work'. Works now.. +Last time I said it works? I was kidding. Try this. +Just stop reading these for a while, ok.. +Give me a break, it's 2am. But it works now. +Make that it works in 90% of the cases. 3:30. +Ok, 5am, it works. For real. +FOR REAL. +I don't know what these changes are supposed to accomplish but somebody told me to make them. +I don't get paid enough for this shit. +fix some fucking errors +first blush +So my boss wanted this button ... +uhhhhhh +forgot we're not using a smart language +include shit +To those I leave behind, good luck! +things occurred +i dunno, maybe this works +8==========D +No changes made +whooooooooooooooooooooooooooo +clarify further the brokenness of C++. why the fuck are we using C++? +(\ /)
(O.o)
(> <) Bunny approves these changes. +. +Friday 5pm +changes +A fix I believe, not like I tested or anything +Useful text +pgsql is being a pain +pgsql is more strict, increase the hackiness up to 11 +c&p fail +syntax +XNAMEX sucks +XNAMEX SUCKS +fix +someone fails and it isn't me +Gross hack because XNAMEX doesn't know how to code +totally more readable +better grepping +fix +fix bug, for realz +fix /sigh +Does this work +MOAR BIFURCATION +bifurcation +REALLY FUCKING FIXED +FIX +better ignores +More ignore +more ignores +more ignores +more ignores +more ignores +more ignores +more ignored words +more fixes +really ignore ignored worsd +fixes +/sigh +fix +fail +pointless limitation +eppic fail XNAMEX +omg what have I done? +added super-widget 2.0. +tagging release w.t.f. +I can't believe it took so long to fix this. +I must have been drunk. +This is why the cat shouldn't sit on my keyboard. +This is why git rebase is a horrible horrible thing. +ajax-loader hotness, oh yeah +XNAMEX broke the regex, lame +small is a real HTML tag, who knew. +WTF is this. +Do things better, faster, stronger +Fixed a bug cause XNAMEX said to +Use a real JS construct, WTF knows why this works in chromium. +Added a banner to the default admin page. Please have mercy on me =( +needs more cow bell +Switched off unit test X because the build had to go out now and there was no time to fix it properly. +Updated +I must sleep... it's working... in just three hours... +I was wrong... +Completed with no bugs... +Fixed a little bug... +Fixed a bug in NoteLineCount... not seriously... +woa!! this one was really HARD! +Made it to compile... +changed things... +touched... +i think i fixed a bug... +perfect... +Moved something to somewhere... goodnight... +oops, forgot to add the file +Corrected mistakes +oops +oops! +put code that worked where the code that didn't used to be +Nothing to see here, move along +I am even stupider than I thought +I don't know what the hell I was thinking. +fixed errors in the previous commit +Committed some changes +Some bugs fixed +Minor updates +Added missing file in previous commit +bug fix +typo +bara bra grejjor +Continued development... +Does anyone read this? I'll be at the coffee shop accross the street. +That's just how I roll +work in progress +minor changes +some brief changes +assorted changes +lots and lots of changes +another big bag of changes +lots of changes after a lot of time +LOTS of changes. period +XNAMEX made me do it +Test commit. Please ignore +I'm just a grunt. Don't blame me for this awful PoS. +I did it for the lulz! +I'll explain this when I'm sober .. or revert it +Obligatory placeholder commit message +A long time ago, in a galaxy far far away... +Fixed the build. +Fixing XNAMEX's bug. +Fixing XNAMEX's bugs. +various changes +One more time, but with feeling. +Handled a particular error. +Fixed unnecessary bug. +Removed code. +Added translation. +Updated build targets. +Refactored configuration. +Locating the required gigapixels to render... +Spinning up the hamster... +Shovelling coal into the server... +Programming the flux capacitor +The last time I tried this the monkey didn't survive. Let's hope it works better this time. +I should have had a V8 this morning. +640K ought to be enough for anybody +pay no attention to the man behind the curtain +a few bits tried to escape, but we caught them +This is the last time we let XNAMEX commit ascii porn in the comments. +Who has two thumbs and remembers the rudiments of his linear algebra courses? Apparently, this guy. +workaround for ant being a pile of fail +Don't push this commit +rats +squash me \ No newline at end of file diff --git a/bin/crop b/bin/crop new file mode 100755 index 00000000..5051140f --- /dev/null +++ b/bin/crop @@ -0,0 +1,3 @@ +#!/bin/bash + +pdfcrop $1 $1 \ No newline at end of file diff --git a/bin/diff-highlight b/bin/diff-highlight new file mode 100755 index 00000000..ffefc31a --- /dev/null +++ b/bin/diff-highlight @@ -0,0 +1,218 @@ +#!/usr/bin/perl + +use 5.008; +use warnings FATAL => 'all'; +use strict; + +# Highlight by reversing foreground and background. You could do +# other things like bold or underline if you prefer. +my @OLD_HIGHLIGHT = ( + color_config('color.diff-highlight.oldnormal'), + color_config('color.diff-highlight.oldhighlight', "\x1b[7m"), + color_config('color.diff-highlight.oldreset', "\x1b[27m") +); +my @NEW_HIGHLIGHT = ( + color_config('color.diff-highlight.newnormal', $OLD_HIGHLIGHT[0]), + color_config('color.diff-highlight.newhighlight', $OLD_HIGHLIGHT[1]), + color_config('color.diff-highlight.newreset', $OLD_HIGHLIGHT[2]) +); + +my $RESET = "\x1b[m"; +my $COLOR = qr/\x1b\[[0-9;]*m/; +my $BORING = qr/$COLOR|\s/; + +my @removed; +my @added; +my $in_hunk; + +# Some scripts may not realize that SIGPIPE is being ignored when launching the +# pager--for instance scripts written in Python. +$SIG{PIPE} = 'DEFAULT'; + +while (<>) { + if (!$in_hunk) { + print; + $in_hunk = /^$COLOR*\@/; + } + elsif (/^$COLOR*-/) { + push @removed, $_; + } + elsif (/^$COLOR*\+/) { + push @added, $_; + } + else { + show_hunk(\@removed, \@added); + @removed = (); + @added = (); + + print; + $in_hunk = /^$COLOR*[\@ ]/; + } + + # Most of the time there is enough output to keep things streaming, + # but for something like "git log -Sfoo", you can get one early + # commit and then many seconds of nothing. We want to show + # that one commit as soon as possible. + # + # Since we can receive arbitrary input, there's no optimal + # place to flush. Flushing on a blank line is a heuristic that + # happens to match git-log output. + if (!length) { + local $| = 1; + } +} + +# Flush any queued hunk (this can happen when there is no trailing context in +# the final diff of the input). +show_hunk(\@removed, \@added); + +exit 0; + +# Ideally we would feed the default as a human-readable color to +# git-config as the fallback value. But diff-highlight does +# not otherwise depend on git at all, and there are reports +# of it being used in other settings. Let's handle our own +# fallback, which means we will work even if git can't be run. +sub color_config { + my ($key, $default) = @_; + my $s = `git config --get-color $key 2>/dev/null`; + return length($s) ? $s : $default; +} + +sub show_hunk { + my ($a, $b) = @_; + + # If one side is empty, then there is nothing to compare or highlight. + if (!@$a || !@$b) { + print @$a, @$b; + return; + } + + # If we have mismatched numbers of lines on each side, we could try to + # be clever and match up similar lines. But for now we are simple and + # stupid, and only handle multi-line hunks that remove and add the same + # number of lines. + if (@$a != @$b) { + print @$a, @$b; + return; + } + + my @queue; + for (my $i = 0; $i < @$a; $i++) { + my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]); + print $rm; + push @queue, $add; + } + print @queue; +} + +sub highlight_pair { + my @a = split_line(shift); + my @b = split_line(shift); + + # Find common prefix, taking care to skip any ansi + # color codes. + my $seen_plusminus; + my ($pa, $pb) = (0, 0); + while ($pa < @a && $pb < @b) { + if ($a[$pa] =~ /$COLOR/) { + $pa++; + } + elsif ($b[$pb] =~ /$COLOR/) { + $pb++; + } + elsif ($a[$pa] eq $b[$pb]) { + $pa++; + $pb++; + } + elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') { + $seen_plusminus = 1; + $pa++; + $pb++; + } + else { + last; + } + } + + # Find common suffix, ignoring colors. + my ($sa, $sb) = ($#a, $#b); + while ($sa >= $pa && $sb >= $pb) { + if ($a[$sa] =~ /$COLOR/) { + $sa--; + } + elsif ($b[$sb] =~ /$COLOR/) { + $sb--; + } + elsif ($a[$sa] eq $b[$sb]) { + $sa--; + $sb--; + } + else { + last; + } + } + + if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) { + return highlight_line(\@a, $pa, $sa, \@OLD_HIGHLIGHT), + highlight_line(\@b, $pb, $sb, \@NEW_HIGHLIGHT); + } + else { + return join('', @a), + join('', @b); + } +} + +sub split_line { + local $_ = shift; + return utf8::decode($_) ? + map { utf8::encode($_); $_ } + map { /$COLOR/ ? $_ : (split //) } + split /($COLOR+)/ : + map { /$COLOR/ ? $_ : (split //) } + split /($COLOR+)/; +} + +sub highlight_line { + my ($line, $prefix, $suffix, $theme) = @_; + + my $start = join('', @{$line}[0..($prefix-1)]); + my $mid = join('', @{$line}[$prefix..$suffix]); + my $end = join('', @{$line}[($suffix+1)..$#$line]); + + # If we have a "normal" color specified, then take over the whole line. + # Otherwise, we try to just manipulate the highlighted bits. + if (defined $theme->[0]) { + s/$COLOR//g for ($start, $mid, $end); + chomp $end; + return join('', + $theme->[0], $start, $RESET, + $theme->[1], $mid, $RESET, + $theme->[0], $end, $RESET, + "\n" + ); + } else { + return join('', + $start, + $theme->[1], $mid, $theme->[2], + $end + ); + } +} + +# Pairs are interesting to highlight only if we are going to end up +# highlighting a subset (i.e., not the whole line). Otherwise, the highlighting +# is just useless noise. We can detect this by finding either a matching prefix +# or suffix (disregarding boring bits like whitespace and colorization). +sub is_pair_interesting { + my ($a, $pa, $sa, $b, $pb, $sb) = @_; + my $prefix_a = join('', @$a[0..($pa-1)]); + my $prefix_b = join('', @$b[0..($pb-1)]); + my $suffix_a = join('', @$a[($sa+1)..$#$a]); + my $suffix_b = join('', @$b[($sb+1)..$#$b]); + + return $prefix_a !~ /^$COLOR*-$BORING*$/ || + $prefix_b !~ /^$COLOR*\+$BORING*$/ || + $suffix_a !~ /^$BORING*$/ || + $suffix_b !~ /^$BORING*$/; +} diff --git a/bin/ghpr b/bin/ghpr new file mode 100755 index 00000000..aac87766 --- /dev/null +++ b/bin/ghpr @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby + +MATCHER = /^\* (\S+)\s+([0-9a-f]+) \[(\w+)\/(.+)\] .*$/ + +match = `git branch -vv`.split("\n").find { |line| line =~ MATCHER } + +if match + local_branch = $1 + sha = $2 + github_organization = "zooniverse" + remote_repo_name = "Panoptes" + remote_origin = $3 + remote_branch = $4 + + puts "https://github.com/#{github_organization}/#{remote_repo_name}/compare/master...#{remote_origin}:#{remote_branch}?expand=1" +else + STDERR.puts "Couldn't find configured tracking branch." + exit 1 +end + diff --git a/bin/git-divergence b/bin/git-divergence new file mode 100755 index 00000000..88fabf97 --- /dev/null +++ b/bin/git-divergence @@ -0,0 +1,60 @@ +#!/bin/bash + +set -e + +( + function branch() { + git branch 2>/dev/null | grep -e '^*' | tr -d '\* ' + } + + function ensure_valid_ref() { + ref=$1 + ( + set +e + git show-ref $ref > /dev/null + if [[ $? == 1 ]]; then + echo "$0: bad ref: $ref" + exit 1 + fi + ) + } + + function show_rev() { + rev=$1 + git log -1 $rev --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative + echo + git di $rev^..$rev | diffstat + echo + } + + if [[ $# == 2 ]]; then + LOCAL=$1 + REMOTE=$2 + elif [[ $# == 1 ]]; then + LOCAL=`branch` + REMOTE=$1 + else + LOCAL=`branch` + REMOTE=origin/$LOCAL + fi + + ensure_valid_ref $LOCAL + ensure_valid_ref $REMOTE + + echo "changes from local ${LOCAL} to remote ${REMOTE}:" + echo + + echo incoming: + echo + for rev in `git rev-list $LOCAL..$REMOTE`; do + show_rev $rev + done + + echo + echo outgoing: + echo + for rev in `git rev-list $REMOTE..$LOCAL`; do + show_rev $rev + done +) | less -r + diff --git a/bin/git-foreach b/bin/git-foreach new file mode 100755 index 00000000..8f6f49f9 --- /dev/null +++ b/bin/git-foreach @@ -0,0 +1,54 @@ +#!/bin/bash +# +# This script runs a given command over a range of Git revisions. Note that it +# will check past revisions out! Exercise caution if there are important +# untracked files in your working tree. +# +# This came from Gary Bernhardt's dotfiles: +# https://github.com/garybernhardt/dotfiles +# +# Example usage: +# $ run-command-on-git-revisions origin/master master 'python runtests.py' + +set -e + +start_ref=$1 +end_ref=$2 +test_command=$3 + +main() { + enforce_usage + run_tests +} + +enforce_usage() { + if [ -z "$test_command" ]; then + usage + exit $E_BADARGS + fi +} + +usage() { + echo "usage: `basename $0` start_ref end_ref test_command (start_ref is exclusive)" +} + +run_tests() { + revs=`log_command git rev-list --reverse ${start_ref}..${end_ref}` + echo $revs + + for rev in $revs; do + echo "Checking out: $(git log --oneline -1 $rev)" + log_command git checkout --quiet $rev + log_command $test_command + done + log_command git checkout $end_ref + echo "OK for all revisions!" +} + +log_command() { + echo "=> $*" >&2 + eval $* +} + +main + diff --git a/bin/git-prompt.sh b/bin/git-prompt.sh new file mode 100644 index 00000000..044252d8 --- /dev/null +++ b/bin/git-prompt.sh @@ -0,0 +1,603 @@ + + # don't set prompt if this is not interactive shell + [[ $- != *i* ]] && return + +################################################################### CONFIG + + ##### read config file if any. + + unset dir_color rc_color user_id_color root_id_color init_vcs_color clean_vcs_color + unset modified_vcs_color added_vcs_color addmoded_vcs_color untracked_vcs_color op_vcs_color detached_vcs_color + + conf=git-prompt.conf; [[ -r $conf ]] && . $conf + conf=/etc/git-prompt.conf; [[ -r $conf ]] && . $conf + conf=~/.git-prompt.conf; [[ -r $conf ]] && . $conf + unset conf + + ##### set defaults if not set + + git_module=${git_module:-on} + svn_module=${svn_module:-off} + hg_module=${hg_module:-on} + vim_module=${vim_module:-on} + error_bell=${error_bell:-off} + cwd_cmd=${cwd_cmd:-\\w} + + + #### dir, rc, root color + if [ 0`tput colors` -ge 8 ]; then # if terminal supports colors + dir_color=${dir_color:-CYAN} + rc_color=${rc_color:-red} + user_id_color=${user_id_color:-blue} + root_id_color=${root_id_color:-magenta} + else # only B/W + dir_color=${dir_color:-bw_bold} + rc_color=${rc_color:-bw_bold} + fi + + #### vcs state colors + init_vcs_color=${init_vcs_color:-WHITE} # initial + clean_vcs_color=${clean_vcs_color:-blue} # nothing to commit (working directory clean) + modified_vcs_color=${modified_vcs_color:-red} # Changed but not updated: + added_vcs_color=${added_vcs_color:-green} # Changes to be committed: + addmoded_vcs_color=${addmoded_vcs_color:-yellow} + untracked_vcs_color=${untracked_vcs_color:-BLUE} # Untracked files: + op_vcs_color=${op_vcs_color:-MAGENTA} + detached_vcs_color=${detached_vcs_color:-RED} + + max_file_list_length=${max_file_list_length:-100} + upcase_hostname=${upcase_hostname:-on} + + +##################################################################### post config + + ################# make PARSE_VCS_STATUS + PARSE_VCS_STATUS="" + [[ $git_module = "on" ]] && type git >&/dev/null && PARSE_VCS_STATUS="parse_git_status" + [[ $svn_module = "on" ]] && type svn >&/dev/null && PARSE_VCS_STATUS+="||parse_svn_status" + [[ $hg_module = "on" ]] && type hg >&/dev/null && PARSE_VCS_STATUS+="||parse_hg_status" + PARSE_VCS_STATUS+="||return" + ################# terminfo colors-16 + # + # black? 0 8 + # red 1 9 + # green 2 10 + # yellow 3 11 + # blue 4 12 + # magenta 5 13 + # cyan 6 14 + # white 7 15 + # + # terminfo setaf/setab - sets ansi foreground/background + # terminfo sgr0 - resets all attributes + # terminfo colors - number of colors + # + ################# Colors-256 + # To use foreground and background colors from the extension, you only + # have to remember two escape codes: + # Set the foreground color to index N: \033[38;5;${N}m + # Set the background color to index M: \033[48;5;${M}m + # To make vim aware of a present 256 color extension, you can either set + # the $TERM environment variable to xterm-256color or use vim's -T option + # to set the terminal. I'm using an alias in my bashrc to do this. At the + # moment I only know of two color schemes which is made for multi-color + # terminals like urxvt (88 colors) or xterm: inkpot and desert256, + + ### if term support colors, then use color prompt, else bold + + black='\['`tput sgr0; tput setaf 0`'\]' + red='\['`tput sgr0; tput setaf 1`'\]' + green='\['`tput sgr0; tput setaf 2`'\]' + yellow='\['`tput sgr0; tput setaf 3`'\]' + blue='\['`tput sgr0; tput setaf 4`'\]' + magenta='\['`tput sgr0; tput setaf 5`'\]' + cyan='\['`tput sgr0; tput setaf 6`'\]' + white='\['`tput sgr0; tput setaf 7`'\]' + + BLACK='\['`tput setaf 0; tput bold`'\]' + RED='\['`tput setaf 1; tput bold`'\]' + GREEN='\['`tput setaf 2; tput bold`'\]' + YELLOW='\['`tput setaf 3; tput bold`'\]' + BLUE='\['`tput setaf 4; tput bold`'\]' + MAGENTA='\['`tput setaf 5; tput bold`'\]' + CYAN='\['`tput setaf 6; tput bold`'\]' # why 14 doesn't work? + WHITE='\['`tput setaf 7; tput bold`'\]' + + bw_bold='\['`tput bold`'\]' + + on='' + off=': ' + + bell="\[`eval ${!error_bell} tput bel`\]" + + colors_reset='\['`tput sgr0`'\]' + + # Workaround for UTF readline(?) bug. Disable bell when UTF + #locale |grep -qi UTF && bell='' + + + # replace symbolic colors names to raw treminfo strings + init_vcs_color=${!init_vcs_color} + modified_vcs_color=${!modified_vcs_color} + untracked_vcs_color=${!untracked_vcs_color} + clean_vcs_color=${!clean_vcs_color} + added_vcs_color=${!added_vcs_color} + op_vcs_color=${!op_vcs_color} + addmoded_vcs_color=${!addmoded_vcs_color} + detached_vcs_color=${!detached_vcs_color} + + + unset PROMPT_COMMAND + + ####### work around for MC bug + if [ -z "$TERM" -o "$TERM" = "dumb" -o -n "$MC_SID" ]; then + unset PROMPT_COMMAND + PS1='\w> ' + return 0 + fi + + #################################################################### MARKERS + screen_marker="sCRn" + if [[ $LANG =~ "UTF" && $TERM != "linux" ]]; then + elipses_marker="…" + else + elipses_marker="..." + fi + + export who_where + + +cwd_truncate() { + # based on: https://www.blog.montgomerie.net/pwd-in-the-title-bar-or-a-regex-adventure-in-bash + # TODO: never abbrivate last dir + + # arg1: max path lenght + # returns abbrivated $PWD in public "cwd" var + + cwd=${PWD/$HOME/\~} # substitute "~" + + case $1 in + full) + return + ;; + last) + cwd=${PWD##/*/} + [[ $PWD == $HOME ]] && cwd="~" + return + ;; + *) + if [[ ${BASH_VERSINFO[0]} -ge 3 && ${BASH_VERSINFO[1]} -ge 2 || ${BASH_VERSINFO[0]} -gt 3 ]] ; then + local cwd_max_length=$1 + else + # if bash < v3.2 then don't truncate + return + fi + ;; + esac + + # split path into: head='~/', truncapable middle, last_dir + if [[ "$cwd" =~ (~?/)(.*/)([^/]*)$ ]] ; then # only valid if path have more then 1 dir + local path_head=${BASH_REMATCH[1]} + local path_middle=${BASH_REMATCH[2]} + local path_last_dir=${BASH_REMATCH[3]} + + local cwd_middle_max=$(( $cwd_max_length - ${#path_last_dir} )) + [[ $cwd_middle_max < 0 ]] && cwd_middle_max=0 + + + # trunc middle if over limit + if [[ ${#path_middle} -gt $(( $cwd_middle_max + ${#elipses_marker} + 5 )) ]]; then + + # truncate + middle_tail=${path_middle:${#path_middle}-${cwd_middle_max}} + + # trunc on dir boundary (trunc 1st, probably tuncated dir) + [[ $middle_tail =~ [^/]*/(.*)$ ]] + middle_tail=${BASH_REMATCH[1]} + + # use truncated only if we cut at least 4 chars + if [[ $(( ${#path_middle} - ${#middle_tail})) -gt 4 ]]; then + cwd=$path_head$elipses_marker$middle_tail$path_last_dir + fi + fi + fi + return + } + + +set_shell_label() { + + xterm_label() { echo -n "]2;${@}" ; } # FIXME: replace hardcodes with terminfo codes + + screen_label() { + # FIXME: run this only if screen is in xterm (how to test for this?) + xterm_label "$screen_marker $plain_who_where $@" + + # FIXME $STY not inherited though "su -" + [ "$STY" ] && screen -S $STY -X title "$*" + } + + case $TERM in + + screen*) + screen_label "$*" + ;; + + xterm* | rxvt* | gnome-terminal | konsole | eterm | wterm ) + # is there a capability which we can to test + # for "set term title-bar" and its escapes? + xterm_label "$plain_who_where $@" + ;; + + *) + ;; + esac + } + + export -f set_shell_label + +###################################################### ID (user name) + id=`id -un` + id=${id#$default_user} + +########################################################### TTY + tty=`tty` + tty=`echo $tty | sed "s:/dev/pts/:p:; s:/dev/tty::" ` # RH tty devs + tty=`echo $tty | sed "s:/dev/vc/:vc:" ` # gentoo tty devs + + if [[ "$TERM" = "screen" ]] ; then + + # [ "$WINDOW" = "" ] && WINDOW="?" + # + # # if under screen then make tty name look like s1-p2 + # # tty="${WINDOW:+s}$WINDOW${WINDOW:+-}$tty" + # tty="${WINDOW:+s}$WINDOW" # replace tty name with screen number + tty="$WINDOW" # replace tty name with screen number + fi + + # we don't need tty name under X11 + case $TERM in + xterm* | rxvt* | gnome-terminal | konsole | eterm | wterm ) unset tty ;; + *);; + esac + + dir_color=${!dir_color} + rc_color=${!rc_color} + user_id_color=${!user_id_color} + root_id_color=${!root_id_color} + + ########################################################### HOST + ### we don't display home host/domain $SSH_* set by SSHD or keychain + + # How to find out if session is local or remote? Working with "su -", ssh-agent, and so on ? + ## is sshd our parent? + # if { for ((pid=$$; $pid != 1 ; pid=`ps h -o pid --ppid $pid`)); do ps h -o command -p $pid; done | grep -q sshd && echo == REMOTE ==; } + #then + host=${HOSTNAME} + #host=`hostname --short` + host=${host#$default_host} + uphost=`echo ${host} | tr a-z A-Z` + if [[ $upcase_hostname = "on" ]]; then + host=${uphost} + fi + + host_color=${uphost}_host_color + host_color=${!host_color} + if [[ -z $host_color && -x /usr/bin/cksum ]] ; then + cksum_color_no=`echo $uphost | cksum | awk '{print $1%7}'` + color_index=(green yellow blue magenta cyan white) # FIXME: bw, color-256 + host_color=${color_index[cksum_color_no]} + fi + + host_color=${!host_color} + + # we already should have short host name, but just in case + host=${host%.$localdomain} + host=${host%.$default_domain} + + +#################################################################### WHO_WHERE + # [[user@]host[-tty]] + + if [[ -n $id || -n $host ]] ; then + [[ -n $id && -n $host ]] && at='@' || at='' + color_who_where="${id}${host:+$host_color$at$host}${tty:+ $tty}" + plain_who_where="${id}$at$host" + + # add trailing " " + color_who_where="$color_who_where " + plain_who_where="$plain_who_where " + + # if root then make it root_color + if [ "$id" == "root" ] ; then + user_id_color=$root_id_color + fi + color_who_where="$user_id_color$color_who_where$colors_reset" + else + color_who_where='' + fi + + +parse_svn_status() { + + [[ -d .svn ]] || return 1 + + vcs=svn + + ### get rev + eval ` + svn info | + sed -n " + s@^URL[^/]*//@repo_dir=@p + s/^Revision: /rev=/p + " + ` + ### get status + + unset status modified added clean init added mixed untracked op detached + eval `svn status 2>/dev/null | + sed -n ' + s/^A \([^.].*\)/modified=modified; modified_files[${#modified_files[@]}]=\"\1\";/p + s/^M \([^.].*\)/modified=modified; modified_files[${#modified_files[@]}]=\"\1\";/p + s/^\? \([^.].*\)/untracked=untracked; untracked_files[${#untracked_files[@]}]=\"\1\";/p + ' + ` + # TODO branch detection if standard repo layout + + [[ -z $modified ]] && [[ -z $untracked ]] && clean=clean + vcs_info=svn:r$rev + } + +parse_hg_status() { + + [[ -d ./.hg/ ]] || return 1 + + vcs=hg + + ### get status + unset status modified added clean init added mixed untracked op detached + + eval `hg status 2>/dev/null | + sed -n ' + s/^M \([^.].*\)/modified=modified; modified_files[${#modified_files[@]}]=\"\1\";/p + s/^A \([^.].*\)/added=added; added_files[${#added_files[@]}]=\"\1\";/p + s/^R \([^.].*\)/added=added;/p + s/^! \([^.].*\)/modified=modified;/p + s/^? \([^.].*\)/untracked=untracked; untracked_files[${#untracked_files[@]}]=\\"\1\\";/p + '` + + branch=`hg branch 2> /dev/null` + + [[ -z $modified ]] && [[ -z $untracked ]] && [[ -z $added ]] && clean=clean + vcs_info=${branch/default/D} + } + +parse_git_status() { + + # TODO add status: LOCKED (.git/index.lock) + + git_dir=`[[ $git_module = "on" ]] && git rev-parse --git-dir 2> /dev/null` + #git_dir=`eval \$$git_module git rev-parse --git-dir 2> /dev/null` + #git_dir=` git rev-parse --git-dir 2> /dev/null` + + [[ -n ${git_dir/./} ]] || return 1 + + vcs=git + + ########################################################## GIT STATUS + file_regex='\([^/]*\/\?\).*' + added_files=() + modified_files=() + untracked_files=() + unset status modified added clean init added mixed untracked op detached + + # quoting hell + eval " $( + git status 2>/dev/null | + sed -n ' + s/^# On branch /branch=/p + s/^nothing to commit (working directory clean)/clean=clean/p + s/^# Initial commit/init=init/p + + /^# Changes to be committed:/,/^# [A-Z]/ { + s/^# Changes to be committed:/added=added;/p + + s/^# modified: '"$file_regex"'/ [[ \" ${added_files[*]} \" =~ \" \1 \" ]] || added_files[${#added_files[@]}]=\"\1\"/p + s/^# new file: '"$file_regex"'/ [[ \" ${added_files[*]} \" =~ \" \1 \" ]] || added_files[${#added_files[@]}]=\"\1\"/p + s/^# renamed:[^>]*> '"$file_regex"'/ [[ \" ${added_files[*]} \" =~ \" \1 \" ]] || added_files[${#added_files[@]}]=\"\1\"/p + s/^# copied:[^>]*> '"$file_regex"'/ [[ \" ${added_files[*]} \" =~ \" \1 \" ]] || added_files[${#added_files[@]}]=\"\1\"/p + } + + /^# Changed but not updated:/,/^# [A-Z]/ { + s/^# Changed but not updated:/modified=modified;/p + s/^# modified: '"$file_regex"'/ [[ \" ${modified_files[*]} \" =~ \" \1 \" ]] || modified_files[${#modified_files[@]}]=\"\1\"/p + s/^# unmerged: '"$file_regex"'/ [[ \" ${modified_files[*]} \" =~ \" \1 \" ]] || modified_files[${#modified_files[@]}]=\"\1\"/p + } + + /^# Untracked files:/,/^[^#]/{ + s/^# Untracked files:/untracked=untracked;/p + s/^# '"$file_regex"'/ [[ \" ${untracked_files[*]} ${modified_files[*]} ${added_files[*]} \" =~ \" \1 \" ]] || untracked_files[${#untracked_files[@]}]=\"\1\"/p + } + ' + )" + + if ! grep -q "^ref:" $git_dir/HEAD 2>/dev/null; then + detached=detached + fi + + + ################# GET GIT OP + + unset op + + if [[ -d "$git_dir/.dotest" ]] ; then + + if [[ -f "$git_dir/.dotest/rebasing" ]] ; then + op="rebase" + + elif [[ -f "$git_dir/.dotest/applying" ]] ; then + op="am" + + else + op="am/rebase" + + fi + + elif [[ -f "$git_dir/.dotest-merge/interactive" ]] ; then + op="rebase -i" + # ??? branch="$(cat "$git_dir/.dotest-merge/head-name")" + + elif [[ -d "$git_dir/.dotest-merge" ]] ; then + op="rebase -m" + # ??? branch="$(cat "$git_dir/.dotest-merge/head-name")" + + # lvv: not always works. Should ./.dotest be used instead? + elif [[ -f "$git_dir/MERGE_HEAD" ]] ; then + op="merge" + # ??? branch="$(git symbolic-ref HEAD 2>/dev/null)" + + else + [[ -f "$git_dir/BISECT_LOG" ]] && op="bisect" + # ??? branch="$(git symbolic-ref HEAD 2>/dev/null)" || \ + # branch="$(git describe --exact-match HEAD 2>/dev/null)" || \ + # branch="$(cut -c1-7 "$git_dir/HEAD")..." + fi + + + #### GET GIT HEX-REVISION + rawhex=`git rev-parse HEAD 2>/dev/null` + rawhex=${rawhex/HEAD/} + rawhex=${rawhex:0:6} + + #### branch + branch=${branch/master/M} + + # another method of above: + # branch=$(git symbolic-ref -q HEAD || { echo -n "detached:" ; git name-rev --name-only HEAD 2>/dev/null; } ) + # branch=${branch#refs/heads/} + + ### compose vcs_info + + if [[ $init ]]; then + vcs_info=M$white=init + + else + if [[ "$detached" ]] ; then + branch="/dev/null`" + + + elif [[ "$op" ]]; then + branch="$op:$branch" + if [[ "$op" == "merge" ]] ; then + branch+="<--$(git name-rev --name-only $(<$git_dir/MERGE_HEAD))" + fi + #branch="<$branch>" + fi + vcs_info="$branch$white=$rawhex" + + fi + } + + +parse_vcs_status() { + + unset file_list modified_files untracked_files added_files + unset vcs vcs_info + unset status modified untracked added init detached + unset file_list modified_files untracked_files added_files + + eval '[[ $HOME != $PWD ]]&&' $PARSE_VCS_STATUS + + + ### status: choose primary (for branch color) + unset status + status=${op:+op} + status=${status:-$detached} + status=${status:-$clean} + status=${status:-$modified} + status=${status:-$added} + status=${status:-$untracked} + status=${status:-$init} + # at least one should be set + : ${status?prompt internal error: git status} + eval vcs_color="\${${status}_vcs_color}" + # no def: vcs_color=${vcs_color:-$WHITE} # default + + + ### VIM + + if [[ $vim_module = "on" ]] ; then + unset vim_glob vim_file vim_files + old_nullglob=`shopt -p nullglob` + shopt -s nullglob + vim_glob=`echo .*.swp` + eval $old_nullglob + + if [[ $vim_glob ]]; then + vim_file=${vim_glob#.} + vim_file=${vim_file%.swp} + # if swap is newer, then unsaved vim session + [[ .${vim_file}.swp -nt $vim_file ]] && vim_files=$vim_file + fi + fi + + + ### file list + unset file_list + [[ ${added_files[0]} ]] && file_list+=" "$added_vcs_color${added_files[@]} + [[ ${modified_files[0]} ]] && file_list+=" "$modified_vcs_color${modified_files[@]} + [[ ${untracked_files[0]} ]] && file_list+=" "$untracked_vcs_color${untracked_files[@]} + [[ ${vim_files} ]] && file_list+=" "${RED}VIM:${vim_files} + file_list=${file_list:+:$file_list} + + if [[ ${#file_list} -gt $max_file_list_length ]] ; then + file_list=${file_list:0:$max_file_list_length} + if [[ $max_file_list_length -gt 0 ]] ; then + file_list="${file_list% *} $elipses_marker" + fi + fi + + + head_local="(${vcs_info}$vcs_color${file_list}$vcs_color)" + + ### fringes + head_local="${head_local+$vcs_color$head_local }" + #above_local="${head_local+$vcs_color$head_local\n}" + #tail_local="${tail_local+$vcs_color $tail_local}${dir_color}" + } + + + # currently executed comman display in label + trap - DEBUG >& /dev/null + trap '[[ $BASH_COMMAND != prompt_command_function ]] && set_shell_label $BASH_COMMAND' DEBUG >& /dev/null + +###################################################################### PROMPT_COMMAND + +prompt_command_function() { + rc="$?" + + if [[ "$rc" == "0" ]]; then + rc="" + else + rc="$rc_color$rc$colors_reset$bell " + fi + + cwd=${PWD/$HOME/\~} # substitute "~" + set_shell_label "${cwd##[/~]*/}/" # default label - path last dir + + parse_vcs_status + + # if cwd_cmd have back-slash, then assign it value to cwd + # else eval cmd_cmd, cwd should have path after exection + eval "${cwd_cmd/\\/cwd=\\\\}" + + PS1="$colors_reset$rc$head_local$color_who_where$dir_color$cwd$tail_local$dir_color> $colors_reset" + + unset head_local tail_local pwd + } + + PROMPT_COMMAND=prompt_command_function + + unset rc id tty modified_files file_list + +# vim: set ft=sh ts=8 sw=8: diff --git a/bin/git-up b/bin/git-up new file mode 100644 index 00000000..8d1618c7 --- /dev/null +++ b/bin/git-up @@ -0,0 +1,2 @@ +#!/bin/sh +# Shows log and diffstat of pulled commits diff --git a/bin/git-vain b/bin/git-vain new file mode 100755 index 00000000..28134741 --- /dev/null +++ b/bin/git-vain @@ -0,0 +1,96 @@ +#!/usr/bin/env ruby + +require 'digest/sha1' + +def get_message + pattern = /\A[a-f0-9]{2,10}\z/ + if ARGV.first && ARGV.first =~ pattern + msg = ARGV.first + else + msg = `git config vain.default`.chomp + end + + unless msg =~ pattern + abort "ERROR: vain.default not set, or not lowercase hex\nhint: git config --global vain.default " + end + msg +end + +def parse_commit + original = `git cat-file -p HEAD` + parts = original.split(/(^author.*> |committer.*> )(\d+)(.*$)/, 3) + [ parts[0..1].join, + parts[2 ].to_i, + parts[3..5].join, + parts[6 ].to_i, + parts[7..-1].join ] +end + +def format_progress(ad, cd, hashes, rewrite="\r") + print "∆author: %5d, ∆committer: %5d, khash: %d#{rewrite}" % [ad,cd,hashes/1000] +end + +def spiral_pair(n) + # http://2000clicks.com/mathhelp/CountingRationalsSquareSpiral1.aspx + s = ((Math.sqrt(n)+1)/2).to_i + l = ((n-((2*s)-1)**2)/(2*s)).to_i + e = (n-((2*s)-1)**2)-(2*s*l)-s+1 + + case l + when 0 then [s, e] + when 1 then [-e, s] + when 2 then [-s,-e] + else [e, -s] + end +end + +def spiral(max_side) + total = (max_side*2+1)**2-1 + (1..total).lazy.map {|n| spiral_pair(n) } +end + + +def search(message, parsed_commit) + puts "searching for: #{message}" + head, orig_auth_t, middle, orig_comm_t, rest = parsed_commit + counter = 0 + + spiral(3600).each do |(ad,cd)| + new_auth_t = orig_auth_t + ad + new_comm_t = orig_comm_t + cd + + content = [head, new_auth_t, middle, new_comm_t, rest].join + store = "commit #{content.length}\0" + content + sha1 = Digest::SHA1.hexdigest(store) + + if sha1.start_with?(message) + format_progress(ad,cd,counter,"\n") + return [content,sha1] + end + + counter += 1 + format_progress(ad,cd,counter) if (counter%100000).zero? + end +end + +content, sha1 = search(get_message, parse_commit) + +if ARGV.include? "--dry-run" + puts sha1 + exit +end + +File.open("/tmp/commit", 'w') {|f| f.write(content) } +sha2=`git hash-object -t commit /tmp/commit`.strip + +if sha1==sha2 + system("git reset --soft HEAD^") + system("git hash-object -t commit -w /tmp/commit") + system("git reset --soft #{sha1}") +else + puts "failed, git doesn't agree:" + puts sha1 + puts sha2 + exit 1 +end + diff --git a/bin/hub b/bin/hub new file mode 100755 index 00000000..64c81e3b --- /dev/null +++ b/bin/hub @@ -0,0 +1,2171 @@ +#!/usr/bin/env ruby +# +# This file, hub, is generated code. +# Please DO NOT EDIT or send patches for it. +# +# Please take a look at the source from +# https://github.com/defunkt/hub +# and submit patches against the individual files +# that build hub. +# + +require 'shellwords' +require 'forwardable' +require 'uri' + +module Hub + module Context + extend Forwardable + + NULL = defined?(File::NULL) ? File::NULL : File.exist?('/dev/null') ? '/dev/null' : 'NUL' + + class GitReader + attr_reader :executable + + def initialize(executable = nil, &read_proc) + @executable = executable || 'git' + read_proc ||= lambda { |cache, cmd| + result = %x{#{command_to_string(cmd)} 2>#{NULL}}.chomp + cache[cmd] = $?.success? && !result.empty? ? result : nil + } + @cache = Hash.new(&read_proc) + end + + def add_exec_flags(flags) + @executable = Array(executable).concat(flags) + end + + def read_config(cmd, all = false) + config_cmd = ['config', (all ? '--get-all' : '--get'), *cmd] + config_cmd = config_cmd.join(' ') unless cmd.respond_to? :join + read config_cmd + end + + def read(cmd) + @cache[cmd] + end + + def stub_config_value(key, value, get = '--get') + stub_command_output "config #{get} #{key}", value + end + + def stub_command_output(cmd, value) + @cache[cmd] = value.nil? ? nil : value.to_s + end + + def stub!(values) + @cache.update values + end + + private + + def to_exec(args) + args = Shellwords.shellwords(args) if args.respond_to? :to_str + Array(executable) + Array(args) + end + + def command_to_string(cmd) + full_cmd = to_exec(cmd) + full_cmd.respond_to?(:shelljoin) ? full_cmd.shelljoin : full_cmd.join(' ') + end + end + + module GitReaderMethods + extend Forwardable + + def_delegator :git_reader, :read_config, :git_config + def_delegator :git_reader, :read, :git_command + + def self.extended(base) + base.extend Forwardable + base.def_delegators :'self.class', :git_config, :git_command + end + end + + private + + def git_reader + @git_reader ||= GitReader.new ENV['GIT'] + end + + include GitReaderMethods + private :git_config, :git_command + + def local_repo(fatal = true) + @local_repo ||= begin + if is_repo? + LocalRepo.new git_reader, current_dir + elsif fatal + abort "fatal: Not a git repository" + end + end + end + + repo_methods = [ + :current_branch, :master_branch, + :current_project, :upstream_project, + :repo_owner, :repo_host, + :remotes, :remotes_group, :origin_remote + ] + def_delegator :local_repo, :name, :repo_name + def_delegators :local_repo, *repo_methods + private :repo_name, *repo_methods + + class LocalRepo < Struct.new(:git_reader, :dir) + include GitReaderMethods + + def name + if project = main_project + project.name + else + File.basename(dir) + end + end + + def repo_owner + if project = main_project + project.owner + end + end + + def repo_host + project = main_project and project.host + end + + def main_project + remote = origin_remote and remote.project + end + + def upstream_project + if branch = current_branch and upstream = branch.upstream and upstream.remote? + remote = remote_by_name upstream.remote_name + remote.project + end + end + + def current_project + upstream_project || main_project + end + + def current_branch + if branch = git_command('symbolic-ref -q HEAD') + Branch.new self, branch + end + end + + def master_branch + Branch.new self, 'refs/heads/master' + end + + def remotes + @remotes ||= begin + list = git_command('remote').to_s.split("\n") + main = list.delete('origin') and list.unshift(main) + list.map { |name| Remote.new self, name } + end + end + + def remotes_group(name) + git_config "remotes.#{name}" + end + + def origin_remote + remotes.first + end + + def remote_by_name(remote_name) + remotes.find {|r| r.name == remote_name } + end + + def known_hosts + git_config('hub.host', :all).to_s.split("\n") + [default_host] + end + + def default_host + ENV['GITHUB_HOST'] || main_host + end + + def main_host + 'github.com' + end + + def ssh_config + @ssh_config ||= SshConfig.new + end + end + + class GithubProject < Struct.new(:local_repo, :owner, :name, :host) + def self.from_url(url, local_repo) + if local_repo.known_hosts.include? url.host + _, owner, name = url.path.split('/', 4) + GithubProject.new(local_repo, owner, name.sub(/\.git$/, ''), url.host) + end + end + + def initialize(*args) + super + self.host ||= local_repo.default_host + end + + def private? + local_repo and host != local_repo.main_host + end + + def owned_by(new_owner) + new_project = dup + new_project.owner = new_owner + new_project + end + + def name_with_owner + "#{owner}/#{name}" + end + + def ==(other) + name_with_owner == other.name_with_owner + end + + def remote + local_repo.remotes.find { |r| r.project == self } + end + + def web_url(path = nil) + project_name = name_with_owner + if project_name.sub!(/\.wiki$/, '') + unless '/wiki' == path + path = if path =~ %r{^/commits/} then '/_history' + else path.to_s.sub(/\w+/, '_\0') + end + path = '/wiki' + path + end + end + "https://#{host}/" + project_name + path.to_s + end + + def git_url(options = {}) + if options[:https] then "https://#{host}/" + elsif options[:private] or private? then "git@#{host}:" + else "git://#{host}/" + end + name_with_owner + '.git' + end + + def api_url(type, resource, action) + URI("https://#{host}/api/v2/#{type}/#{resource}/#{action}") + end + + def api_show_url(type) + api_url(type, 'repos', "show/#{owner}/#{name}") + end + + def api_fork_url(type) + api_url(type, 'repos', "fork/#{owner}/#{name}") + end + + def api_create_url(type) + api_url(type, 'repos', 'create') + end + + def api_pullrequest_url(id, type) + api_url(type, 'pulls', "#{owner}/#{name}/#{id}") + end + + def api_create_pullrequest_url(type) + api_url(type, 'pulls', "#{owner}/#{name}") + end + end + + class GithubURL < URI::HTTPS + extend Forwardable + + attr_reader :project + def_delegator :project, :name, :project_name + def_delegator :project, :owner, :project_owner + + def self.resolve(url, local_repo) + u = URI(url) + if %[http https].include? u.scheme and project = GithubProject.from_url(u, local_repo) + self.new(u.scheme, u.userinfo, u.host, u.port, u.registry, + u.path, u.opaque, u.query, u.fragment, project) + end + rescue URI::InvalidURIError + nil + end + + def initialize(*args) + @project = args.pop + super(*args) + end + + def project_path + path.split('/', 4)[3] + end + end + + class Branch < Struct.new(:local_repo, :name) + alias to_s name + + def short_name + name.sub(%r{^refs/(remotes/)?.+?/}, '') + end + + def master? + short_name == 'master' + end + + def upstream + if branch = local_repo.git_command("rev-parse --symbolic-full-name #{short_name}@{upstream}") + Branch.new local_repo, branch + end + end + + def remote? + name.index('refs/remotes/') == 0 + end + + def remote_name + name =~ %r{^refs/remotes/([^/]+)} and $1 or + raise "can't get remote name from #{name.inspect}" + end + end + + class Remote < Struct.new(:local_repo, :name) + alias to_s name + + def ==(other) + other.respond_to?(:to_str) ? name == other.to_str : super + end + + def project + urls.each { |url| + if valid = GithubProject.from_url(url, local_repo) + return valid + end + } + nil + end + + def urls + @urls ||= local_repo.git_config("remote.#{name}.url", :all).to_s.split("\n").map { |uri| + begin + if uri =~ %r{^[\w-]+://} then uri_parse(uri) + elsif uri =~ %r{^([^/]+?):} then uri_parse("ssh://#{$1}/#{$'}") # scp-like syntax + end + rescue URI::InvalidURIError + nil + end + }.compact + end + + def uri_parse uri + uri = URI.parse uri + uri.host = local_repo.ssh_config.get_value(uri.host, 'hostname') { uri.host } + uri.user = local_repo.ssh_config.get_value(uri.host, 'user') { uri.user } + uri + end + end + + + def github_project(name, owner = nil) + if owner and owner.index('/') + owner, name = owner.split('/', 2) + elsif name and name.index('/') + owner, name = name.split('/', 2) + else + name ||= repo_name + owner ||= github_user + end + + if local_repo(false) and main_project = local_repo.main_project + project = main_project.dup + project.owner = owner + project.name = name + project + else + GithubProject.new(local_repo, owner, name) + end + end + + def git_url(owner = nil, name = nil, options = {}) + project = github_project(name, owner) + project.git_url({:https => https_protocol?}.update(options)) + end + + def resolve_github_url(url) + GithubURL.resolve(url, local_repo) if url =~ /^https?:/ + end + + LGHCONF = "http://help.github.com/set-your-user-name-email-and-github-token/" + + def github_user(fatal = true, host = nil) + if local = local_repo(false) + host ||= local.default_host + host = nil if host == local.main_host + end + host = %(."#{host}") if host + if user = ENV['GITHUB_USER'] || git_config("github#{host}.user") + user + elsif fatal + if host.nil? + abort("** No GitHub user set. See #{LGHCONF}") + else + abort("** No user set for github#{host}") + end + end + end + + def github_token(fatal = true, host = nil) + if local = local_repo(false) + host ||= local.default_host + host = nil if host == local.main_host + end + host = %(."#{host}") if host + if token = ENV['GITHUB_TOKEN'] || git_config("github#{host}.token") + token + elsif fatal + if host.nil? + abort("** No GitHub token set. See #{LGHCONF}") + else + abort("** No token set for github#{host}") + end + end + end + + def http_clone? + git_config('--bool hub.http-clone') == 'true' + end + + def https_protocol? + git_config('hub.protocol') == 'https' or http_clone? + end + + def git_alias_for(name) + git_config "alias.#{name}" + end + + def rev_list(a, b) + git_command("rev-list --cherry-pick --right-only --no-merges #{a}...#{b}") + end + + PWD = Dir.pwd + + def current_dir + PWD + end + + def git_dir + git_command 'rev-parse -q --git-dir' + end + + def is_repo? + !!git_dir + end + + def git_editor + editor = git_command 'var GIT_EDITOR' + editor = ENV[$1] if editor =~ /^\$(\w+)$/ + editor = File.expand_path editor if (editor =~ /^[~.]/ or editor.index('/')) and editor !~ /["']/ + editor.shellsplit + end + + def browser_launcher + browser = ENV['BROWSER'] || ( + osx? ? 'open' : windows? ? 'start' : + %w[xdg-open cygstart x-www-browser firefox opera mozilla netscape].find { |comm| which comm } + ) + + abort "Please set $BROWSER to a web launcher to use this command." unless browser + Array(browser) + end + + def osx? + require 'rbconfig' + RbConfig::CONFIG['host_os'].to_s.include?('darwin') + end + + def windows? + require 'rbconfig' + RbConfig::CONFIG['host_os'] =~ /msdos|mswin|djgpp|mingw|windows/ + end + + def which(cmd) + exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : [''] + ENV['PATH'].split(File::PATH_SEPARATOR).each do |path| + exts.each { |ext| + exe = "#{path}/#{cmd}#{ext}" + return exe if File.executable? exe + } + end + return nil + end + + def command?(name) + !which(name).nil? + end + + class SshConfig + CONFIG_FILES = %w(~/.ssh/config /etc/ssh_config /etc/ssh/ssh_config) + + def initialize files = nil + @settings = Hash.new {|h,k| h[k] = {} } + Array(files || CONFIG_FILES).each do |path| + file = File.expand_path path + parse_file file if File.exist? file + end + end + + def get_value hostname, key + key = key.to_s.downcase + @settings.each do |pattern, settings| + if pattern.match? hostname and found = settings[key] + return found + end + end + yield + end + + class HostPattern + def initialize pattern + @pattern = pattern.to_s.downcase + end + + def to_s() @pattern end + def ==(other) other.to_s == self.to_s end + + def matcher + @matcher ||= + if '*' == @pattern + Proc.new { true } + elsif @pattern !~ /[?*]/ + lambda { |hostname| hostname.to_s.downcase == @pattern } + else + re = self.class.pattern_to_regexp @pattern + lambda { |hostname| re =~ hostname } + end + end + + def match? hostname + matcher.call hostname + end + + def self.pattern_to_regexp pattern + escaped = Regexp.escape(pattern) + escaped.gsub!('\*', '.*') + escaped.gsub!('\?', '.') + /^#{escaped}$/i + end + end + + def parse_file file + host_patterns = [HostPattern.new('*')] + + IO.foreach(file) do |line| + case line + when /^\s*(#|$)/ then next + when /^\s*(\S+)\s*=/ + key, value = $1, $' + else + key, value = line.strip.split(/\s+/, 2) + end + + next if value.nil? + key.downcase! + value = $1 if value =~ /^"(.*)"$/ + value.chomp! + + if 'host' == key + host_patterns = value.split(/\s+/).map {|p| HostPattern.new p } + else + record_setting key, value, host_patterns + end + end + end + + def record_setting key, value, patterns + patterns.each do |pattern| + @settings[pattern][key] ||= value + end + end + end + end +end +module Hub + class Args < Array + attr_accessor :executable + + def initialize(*args) + super + @executable = ENV["GIT"] || "git" + @after = nil + @skip = @noop = false + @original_args = args.first + @chain = [nil] + end + + def after(cmd_or_args = nil, args = nil, &block) + @chain.insert(-1, normalize_callback(cmd_or_args, args, block)) + end + + def before(cmd_or_args = nil, args = nil, &block) + @chain.insert(@chain.index(nil), normalize_callback(cmd_or_args, args, block)) + end + + def chained? + @chain.size > 1 + end + + def commands + chain = @chain.dup + chain[chain.index(nil)] = self.to_exec + chain + end + + def skip! + @skip = true + end + + def skip? + @skip + end + + def noop! + @noop = true + end + + def noop? + @noop + end + + def to_exec(args = self) + Array(executable) + args + end + + def add_exec_flags(flags) + self.executable = Array(executable).concat(flags) + end + + def words + reject { |arg| arg.index('-') == 0 } + end + + def flags + self - words + end + + def changed? + chained? or self != @original_args + end + + def has_flag?(*flags) + pattern = flags.flatten.map { |f| Regexp.escape(f) }.join('|') + !grep(/^#{pattern}(?:=|$)/).empty? + end + + private + + def normalize_callback(cmd_or_args, args, block) + if block + block + elsif args + [cmd_or_args].concat args + elsif Array === cmd_or_args + self.to_exec cmd_or_args + elsif cmd_or_args + cmd_or_args + else + raise ArgumentError, "command or block required" + end + end + end +end +module Hub + module Commands + instance_methods.each { |m| undef_method(m) unless m =~ /(^__|send|to\?$)/ } + extend self + + extend Context + + NAME_RE = /\w[\w.-]*/ + OWNER_RE = /[a-zA-Z0-9-]+/ + NAME_WITH_OWNER_RE = /^(?:#{NAME_RE}|#{OWNER_RE}\/#{NAME_RE})$/ + + CUSTOM_COMMANDS = %w[alias create browse compare fork pull-request] + + def run(args) + slurp_global_flags(args) + + args.unshift 'help' if args.empty? + + cmd = args[0] + if expanded_args = expand_alias(cmd) + cmd = expanded_args[0] + expanded_args.concat args[1..-1] + end + + respect_help_flags(expanded_args || args) if custom_command? cmd + + cmd = cmd.sub(/(\w)-/, '\1_') + if method_defined?(cmd) and cmd != 'run' + args.replace expanded_args if expanded_args + send(cmd, args) + end + rescue Errno::ENOENT + if $!.message.include? "No such file or directory - git" + abort "Error: `git` command not found" + else + raise + end + end + + def pull_request(args) + args.shift + options = { } + force = explicit_owner = false + base_project = local_repo.main_project + head_project = local_repo.current_project + + unless base_project + abort "Aborted: the origin remote doesn't point to a GitHub repository." + end + + from_github_ref = lambda do |ref, context_project| + if ref.index(':') + owner, ref = ref.split(':', 2) + project = github_project(context_project.name, owner) + end + [project || context_project, ref] + end + + while arg = args.shift + case arg + when '-f' + force = true + when '-b' + base_project, options[:base] = from_github_ref.call(args.shift, base_project) + when '-h' + head = args.shift + explicit_owner = !!head.index(':') + head_project, options[:head] = from_github_ref.call(head, head_project) + when '-i' + options[:issue] = args.shift + else + if url = resolve_github_url(arg) and url.project_path =~ /^issues\/(\d+)/ + options[:issue] = $1 + base_project = url.project + elsif !options[:title] then options[:title] = arg + else + abort "invalid argument: #{arg}" + end + end + end + + options[:project] = base_project + options[:base] ||= master_branch.short_name + + if tracked_branch = options[:head].nil? && current_branch.upstream + if !tracked_branch.remote? + tracked_branch = nil + elsif base_project == head_project and tracked_branch.short_name == options[:base] + $stderr.puts "Aborted: head branch is the same as base (#{options[:base].inspect})" + warn "(use `-h ` to specify an explicit pull request head)" + abort + end + end + options[:head] ||= (tracked_branch || current_branch).short_name + + user = github_user(true, head_project.host) + if head_project.owner != user and !tracked_branch and !explicit_owner + head_project = head_project.owned_by(user) + end + + remote_branch = "#{head_project.remote}/#{options[:head]}" + options[:head] = "#{head_project.owner}:#{options[:head]}" + + if !force and tracked_branch and local_commits = rev_list(remote_branch, nil) + $stderr.puts "Aborted: #{local_commits.split("\n").size} commits are not yet pushed to #{remote_branch}" + warn "(use `-f` to force submit a pull request anyway)" + abort + end + + if args.noop? + puts "Would request a pull to #{base_project.owner}:#{options[:base]} from #{options[:head]}" + exit + end + + unless options[:title] or options[:issue] + base_branch = "#{base_project.remote}/#{options[:base]}" + commits = rev_list(base_branch, remote_branch).to_s.split("\n") + + case commits.size + when 0 + default_message = commit_summary = nil + when 1 + format = '%w(78,0,0)%s%n%+b' + default_message = git_command "show -s --format='#{format}' #{commits.first}" + commit_summary = nil + else + format = '%h (%aN, %ar)%n%w(78,3,3)%s%n%+b' + default_message = nil + commit_summary = git_command "log --no-color --format='%s' --cherry %s...%s" % + [format, base_branch, remote_branch] + end + + options[:title], options[:body] = pullrequest_editmsg(commit_summary) { |msg| + msg.puts default_message if default_message + msg.puts "" + msg.puts "# Requesting a pull to #{base_project.owner}:#{options[:base]} from #{options[:head]}" + msg.puts "#" + msg.puts "# Write a message for this pull request. The first block" + msg.puts "# of text is the title and the rest is description." + } + end + + pull = create_pullrequest(options) + + args.executable = 'echo' + args.replace [pull['html_url']] + rescue HTTPExceptions + display_http_exception("creating pull request", $!.response) + exit 1 + end + + def clone(args) + ssh = args.delete('-p') + has_values = /^(--(upload-pack|template|depth|origin|branch|reference)|-[ubo])$/ + + idx = 1 + while idx < args.length + arg = args[idx] + if arg.index('-') == 0 + idx += 1 if arg =~ has_values + else + if arg =~ NAME_WITH_OWNER_RE and !File.directory?(arg) + name, owner = arg, nil + owner, name = name.split('/', 2) if name.index('/') + host = ENV['GITHUB_HOST'] + project = Context::GithubProject.new(nil, owner || github_user(true, host), name, host || 'github.com') + ssh ||= args[0] != 'submodule' && project.owner == github_user(false, host) || host + args[idx] = project.git_url(:private => ssh, :https => https_protocol?) + end + break + end + idx += 1 + end + end + + def submodule(args) + return unless index = args.index('add') + args.delete_at index + + branch = args.index('-b') || args.index('--branch') + if branch + args.delete_at branch + branch_name = args.delete_at branch + end + + clone(args) + + if branch_name + args.insert branch, '-b', branch_name + end + args.insert index, 'add' + end + + def remote(args) + if %w[add set-url].include?(args[1]) + name = args.last + if name =~ /^(#{OWNER_RE})$/ || name =~ /^(#{OWNER_RE})\/(#{NAME_RE})$/ + user, repo = $1, $2 || repo_name + end + end + return unless user # do not touch arguments + + ssh = args.delete('-p') + + if args.words[2] == 'origin' && args.words[3].nil? + user, repo = github_user, repo_name + elsif args.words[-2] == args.words[1] + idx = args.index( args.words[-1] ) + args[idx] = user + else + args.pop + end + + args << git_url(user, repo, :private => ssh) + end + + def fetch(args) + if args.include?('--multiple') + names = args.words[1..-1] + elsif remote_name = args.words[1] + if remote_name =~ /^\w+(,\w+)+$/ + index = args.index(remote_name) + args.delete(remote_name) + names = remote_name.split(',') + args.insert(index, *names) + args.insert(index, '--multiple') + else + names = [remote_name] + end + else + names = [] + end + + projects = names.map { |name| + unless name =~ /\W/ or remotes.include?(name) or remotes_group(name) + project = github_project(nil, name) + project if repo_exists?(project) + end + }.compact + + if projects.any? + projects.each do |project| + args.before ['remote', 'add', project.owner, project.git_url(:https => https_protocol?)] + end + end + end + + def checkout(args) + _, url_arg, new_branch_name = args.words + if url = resolve_github_url(url_arg) and url.project_path =~ /^pull\/(\d+)/ + pull_id = $1 + + load_net_http + response = http_request(url.project.api_pullrequest_url(pull_id, 'json')) + pull_data = JSON.parse(response.body)['pull'] + + args.delete new_branch_name + user, branch = pull_data['head']['label'].split(':', 2) + abort "Error: #{user}'s fork is not available anymore" unless pull_data['head']['repository'] + new_branch_name ||= "#{user}-#{branch}" + + if remotes.include? user + args.before ['remote', 'set-branches', '--add', user, branch] + args.before ['fetch', user, "+refs/heads/#{branch}:refs/remotes/#{user}/#{branch}"] + else + url = github_project(url.project_name, user).git_url(:private => pull_data['head']['repository']['private'], + :https => https_protocol?) + args.before ['remote', 'add', '-f', '-t', branch, user, url] + end + idx = args.index url_arg + args.delete_at idx + args.insert idx, '--track', '-B', new_branch_name, "#{user}/#{branch}" + end + end + + def cherry_pick(args) + unless args.include?('-m') or args.include?('--mainline') + ref = args.words.last + if url = resolve_github_url(ref) and url.project_path =~ /^commit\/([a-f0-9]{7,40})/ + sha = $1 + project = url.project + elsif ref =~ /^(#{OWNER_RE})@([a-f0-9]{7,40})$/ + owner, sha = $1, $2 + project = local_repo.main_project.owned_by(owner) + end + + if project + args[args.index(ref)] = sha + + if remote = project.remote and remotes.include? remote + args.before ['fetch', remote.to_s] + else + args.before ['remote', 'add', '-f', project.owner, project.git_url(:https => https_protocol?)] + end + end + end + end + + def am(args) + if url = args.find { |a| a =~ %r{^https?://(gist\.)?github\.com/} } + idx = args.index(url) + gist = $1 == 'gist.' + url = url.sub(/#.+/, '') + url = url.sub(%r{(/pull/\d+)/\w*$}, '\1') unless gist + ext = gist ? '.txt' : '.patch' + url += ext unless File.extname(url) == ext + patch_file = File.join(ENV['TMPDIR'] || '/tmp', "#{gist ? 'gist-' : ''}#{File.basename(url)}") + args.before 'curl', ['-#LA', "hub #{Hub::Version}", url, '-o', patch_file] + args[idx] = patch_file + end + end + + alias_method :apply, :am + + def init(args) + if args.delete('-g') + host = ENV['GITHUB_HOST'] + project = Context::GithubProject.new(nil, github_user(true, host), File.basename(current_dir), host || 'github.com') + url = project.git_url(:private => true, :https => https_protocol?) + args.after ['remote', 'add', 'origin', url] + end + end + + def fork(args) + unless project = local_repo.main_project + abort "Error: repository under 'origin' remote is not a GitHub project" + end + forked_project = project.owned_by(github_user(true, project.host)) + + if repo_exists?(forked_project) + abort "Error creating fork: %s already exists on %s" % + [ forked_project.name_with_owner, forked_project.host ] + else + fork_repo(project) unless args.noop? + end + + if args.include?('--no-remote') + exit + else + url = forked_project.git_url(:private => true, :https => https_protocol?) + args.replace %W"remote add -f #{forked_project.owner} #{url}" + args.after 'echo', ['new remote:', forked_project.owner] + end + rescue HTTPExceptions + display_http_exception("creating fork", $!.response) + exit 1 + end + + def create(args) + if !is_repo? + abort "'create' must be run from inside a git repository" + elsif owner = github_user and github_token + args.shift + options = {} + options[:private] = true if args.delete('-p') + new_repo_name = nil + + until args.empty? + case arg = args.shift + when '-d' + options[:description] = args.shift + when '-h' + options[:homepage] = args.shift + else + if arg =~ /^[^-]/ and new_repo_name.nil? + new_repo_name = arg + owner, new_repo_name = new_repo_name.split('/', 2) if new_repo_name.index('/') + else + abort "invalid argument: #{arg}" + end + end + end + new_repo_name ||= repo_name + new_project = github_project(new_repo_name, owner) + + if repo_exists?(new_project) + warn "#{new_project.name_with_owner} already exists on #{new_project.host}" + action = "set remote origin" + else + action = "created repository" + create_repo(new_project, options) unless args.noop? + end + + url = new_project.git_url(:private => true, :https => https_protocol?) + + if remotes.first != 'origin' + args.replace %W"remote add -f origin #{url}" + else + args.replace %W"remote -v" + end + + args.after 'echo', ["#{action}:", new_project.name_with_owner] + end + rescue HTTPExceptions + display_http_exception("creating repository", $!.response) + exit 1 + end + + def push(args) + return if args[1].nil? || !args[1].index(',') + + branch = (args[2] ||= current_branch.short_name) + remotes = args[1].split(',') + args[1] = remotes.shift + + remotes.each do |name| + args.after ['push', name, branch] + end + end + + def browse(args) + args.shift + browse_command(args) do + dest = args.shift + dest = nil if dest == '--' + + if dest + project = github_project dest + branch = master_branch + else + project = current_project + branch = current_branch && current_branch.upstream || master_branch + end + + abort "Usage: hub browse [/]" unless project + + path = case subpage = args.shift + when 'commits' + "/commits/#{branch.short_name}" + when 'tree', NilClass + "/tree/#{branch.short_name}" if branch and !branch.master? + else + "/#{subpage}" + end + + project.web_url(path) + end + end + + def compare(args) + args.shift + browse_command(args) do + if args.empty? + branch = current_branch.upstream + if branch and not branch.master? + range = branch.short_name + project = current_project + else + abort "Usage: hub compare [USER] [...]" + end + else + sha_or_tag = /(\w{1,2}|\w[\w.-]+\w)/ + range = args.pop.sub(/^#{sha_or_tag}\.\.#{sha_or_tag}$/, '\1...\2') + project = if owner = args.pop then github_project(nil, owner) + else current_project + end + end + + project.web_url "/compare/#{range}" + end + end + + def hub(args) + return help(args) unless args[1] == 'standalone' + require 'hub/standalone' + $stdout.puts Hub::Standalone.build + exit + rescue LoadError + abort "hub is running in standalone mode." + end + + def alias(args) + shells = %w[bash zsh sh ksh csh fish] + + script = !!args.delete('-s') + shell = args[1] || ENV['SHELL'] + abort "hub alias: unknown shell" if shell.nil? or shell.empty? + shell = File.basename shell + + unless shells.include? shell + $stderr.puts "hub alias: unsupported shell" + warn "supported shells: #{shells.join(' ')}" + abort + end + + if script + puts "alias git=hub" + if 'zsh' == shell + puts "if type compdef >/dev/null; then" + puts " compdef hub=git" + puts "fi" + end + else + profile = case shell + when 'bash' then '~/.bash_profile' + when 'zsh' then '~/.zshrc' + when 'ksh' then '~/.profile' + else + 'your profile' + end + + puts "# Wrap git automatically by adding the following to #{profile}:" + puts + puts 'eval "$(hub alias -s)"' + end + + exit + end + + def version(args) + args.after 'echo', ['hub version', Version] + end + alias_method "--version", :version + + def help(args) + command = args.words[1] + + if command == 'hub' + puts hub_manpage + exit + elsif command.nil? && !args.has_flag?('-a', '--all') + ENV['GIT_PAGER'] = '' unless args.has_flag?('-p', '--paginate') # Use `cat`. + puts improved_help_text + exit + end + end + alias_method "--help", :help + + private + + def custom_command? cmd + CUSTOM_COMMANDS.include? cmd + end + + def respect_help_flags args + return if args.size > 2 + case args[1] + when '-h' + pattern = /(git|hub) #{Regexp.escape args[0].gsub('-', '\-')}/ + hub_raw_manpage.each_line { |line| + if line =~ pattern + $stderr.print "Usage: " + $stderr.puts line.gsub(/\\f./, '').gsub('\-', '-') + abort + end + } + abort "Error: couldn't find usage help for #{args[0]}" + when '--help' + puts hub_manpage + exit + end + end + + def improved_help_text + <<-help +usage: git [--version] [--exec-path[=]] [--html-path] [--man-path] [--info-path] + [-p|--paginate|--no-pager] [--no-replace-objects] [--bare] + [--git-dir=] [--work-tree=] [--namespace=] + [-c name=value] [--help] + [] + +Basic Commands: + init Create an empty git repository or reinitialize an existing one + add Add new or modified files to the staging area + rm Remove files from the working directory and staging area + mv Move or rename a file, a directory, or a symlink + status Show the status of the working directory and staging area + commit Record changes to the repository + +History Commands: + log Show the commit history log + diff Show changes between commits, commit and working tree, etc + show Show information about commits, tags or files + +Branching Commands: + branch List, create, or delete branches + checkout Switch the active branch to another branch + merge Join two or more development histories (branches) together + tag Create, list, delete, sign or verify a tag object + +Remote Commands: + clone Clone a remote repository into a new directory + fetch Download data, tags and branches from a remote repository + pull Fetch from and merge with another repository or a local branch + push Upload data, tags and branches to a remote repository + remote View and manage a set of remote repositories + +Advanced commands: + reset Reset your staging area or working directory to another point + rebase Re-apply a series of patches in one branch onto another + bisect Find by binary search the change that introduced a bug + grep Print files with lines matching a pattern in your codebase + +See 'git help ' for more information on a specific command. +help + end + + def slurp_global_flags(args) + flags = %w[ --noop -c -p --paginate --no-pager --no-replace-objects --bare --version --help ] + flags2 = %w[ --exec-path= --git-dir= --work-tree= ] + + globals = [] + locals = [] + + while args[0] && (flags.include?(args[0]) || flags2.any? {|f| args[0].index(f) == 0 }) + flag = args.shift + case flag + when '--noop' + args.noop! + when '--version', '--help' + args.unshift flag.sub('--', '') + when '-c' + config_pair = args.shift + key, value = config_pair.split('=', 2) + git_reader.stub_config_value(key, value) + + globals << flag << config_pair + when '-p', '--paginate', '--no-pager' + locals << flag + else + globals << flag + end + end + + git_reader.add_exec_flags(globals) + args.add_exec_flags(globals) + args.add_exec_flags(locals) + end + + def browse_command(args) + url_only = args.delete('-u') + warn "Warning: the `-p` flag has no effect anymore" if args.delete('-p') + url = yield + + args.executable = url_only ? 'echo' : browser_launcher + args.push url + end + + def hub_manpage + abort "** Can't find groff(1)" unless command?('groff') + + require 'open3' + out = nil + Open3.popen3(groff_command) do |stdin, stdout, _| + stdin.puts hub_raw_manpage + stdin.close + out = stdout.read.strip + end + out + end + + def groff_command + "groff -Wall -mtty-char -mandoc -Tascii" + end + + def hub_raw_manpage + if File.exists? file = File.dirname(__FILE__) + '/../../man/hub.1' + File.read(file) + else + DATA.read + end + end + + def puts(*args) + page_stdout + super + end + + def page_stdout + return if not $stdout.tty? or windows? + + read, write = IO.pipe + + if Kernel.fork + $stdin.reopen(read) + read.close + write.close + + ENV['LESS'] = 'FSRX' + + Kernel.select [STDIN] + + pager = ENV['GIT_PAGER'] || + `git config --get-all core.pager`.split.first || ENV['PAGER'] || + 'less -isr' + + pager = 'cat' if pager.empty? + + exec pager rescue exec "/bin/sh", "-c", pager + else + $stdout.reopen(write) + $stderr.reopen(write) if $stderr.tty? + read.close + write.close + end + end + + def repo_exists?(project) + load_net_http + Net::HTTPSuccess === http_request(project.api_show_url('yaml')) + end + + def fork_repo(project) + load_net_http + response = http_post project.api_fork_url('yaml') + response.error! unless Net::HTTPSuccess === response + end + + def create_repo(project, options = {}) + is_org = project.owner != github_user(true, project.host) + params = {'name' => is_org ? project.name_with_owner : project.name} + params['public'] = '0' if options[:private] + params['description'] = options[:description] if options[:description] + params['homepage'] = options[:homepage] if options[:homepage] + + load_net_http + response = http_post(project.api_create_url('json'), params) + response.error! unless Net::HTTPSuccess === response + end + + def create_pullrequest(options) + project = options.fetch(:project) + params = { + 'pull[base]' => options.fetch(:base), + 'pull[head]' => options.fetch(:head) + } + params['pull[issue]'] = options[:issue] if options[:issue] + params['pull[title]'] = options[:title] if options[:title] + params['pull[body]'] = options[:body] if options[:body] + + load_net_http + response = http_post(project.api_create_pullrequest_url('json'), params) + response.error! unless Net::HTTPSuccess === response + JSON.parse(response.body)['pull'] + end + + def pullrequest_editmsg(changes) + message_file = File.join(git_dir, 'PULLREQ_EDITMSG') + File.open(message_file, 'w') { |msg| + yield msg + if changes + msg.puts "#\n# Changes:\n#" + msg.puts changes.gsub(/^/, '# ').gsub(/ +$/, '') + end + } + edit_cmd = Array(git_editor).dup + edit_cmd << '-c' << 'set ft=gitcommit' if edit_cmd[0] =~ /^[mg]?vim$/ + edit_cmd << message_file + system(*edit_cmd) + abort "can't open text editor for pull request message" unless $?.success? + title, body = read_editmsg(message_file) + abort "Aborting due to empty pull request title" unless title + [title, body] + end + + def read_editmsg(file) + title, body = '', '' + File.open(file, 'r') { |msg| + msg.each_line do |line| + next if line.index('#') == 0 + ((body.empty? and line =~ /\S/) ? title : body) << line + end + } + title.tr!("\n", ' ') + title.strip! + body.strip! + + [title =~ /\S/ ? title : nil, body =~ /\S/ ? body : nil] + end + + def expand_alias(cmd) + if expanded = git_alias_for(cmd) + if expanded.index('!') != 0 + require 'shellwords' unless defined?(::Shellwords) + Shellwords.shellwords(expanded) + end + end + end + + def http_request(url, type = :Get) + url = URI(url) unless url.respond_to? :host + user, token = github_user(type != :Get, url.host), github_token(type != :Get, url.host) + + req = Net::HTTP.const_get(type).new(url.request_uri) + req.basic_auth "#{user}/token", token if user and token + + http = setup_http(url) + + yield req if block_given? + http.start { http.request(req) } + end + + def http_post(url, params = nil) + http_request(url, :Post) do |req| + req.set_form_data params if params + req['Content-Length'] = req.body ? req.body.length : 0 + end + end + + def setup_http(url) + port = url.port + if use_ssl = 'https' == url.scheme and not use_ssl? + use_ssl = false + port = 80 + end + + http_args = [url.host, port] + if proxy = proxy_url(use_ssl) + http_args.concat proxy.select(:host, :port) + if proxy.userinfo + require 'cgi' + http_args.concat proxy.userinfo.split(':', 2).map {|a| CGI.unescape a } + end + end + + http = Net::HTTP.new(*http_args) + + if http.use_ssl = use_ssl + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + return http + end + + def load_net_http + require 'net/https' + rescue LoadError + require 'net/http' + end + + def use_ssl? + defined? ::OpenSSL + end + + def proxy_url(use_ssl) + env_name = "HTTP#{use_ssl ? 'S' : ''}_PROXY" + if proxy = ENV[env_name] || ENV[env_name.downcase] + proxy = "http://#{proxy}" unless proxy.include? '://' + URI.parse(proxy) + end + end + + module HTTPExceptions + def self.===(exception) + exception.class.ancestors.map {|a| a.to_s }.include? 'Net::HTTPExceptions' + end + end + + def display_http_exception(action, response) + $stderr.puts "Error #{action}: #{response.message} (HTTP #{response.code})" + case response.code.to_i + when 401 then warn "Check your token configuration (`git config github.token`)" + when 422 + if response.content_type =~ /\bjson\b/ and data = JSON.parse(response.body) and data["error"] + $stderr.puts data["error"] + end + end + end + + end +end +require 'strscan' +require 'forwardable' + +class Hub::JSON + def self.parse(data) new(data).parse end + + WSP = /\s+/ + OBJ = /[{\[]/; HEN = /\}/; AEN = /\]/ + COL = /\s*:\s*/; KEY = /\s*,\s*/ + NUM = /-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/ + BOL = /true|false/; NUL = /null/ + + extend Forwardable + + attr_reader :scanner + alias_method :s, :scanner + def_delegators :scanner, :scan, :matched + private :s, :scan, :matched + + def initialize data + @scanner = StringScanner.new data.to_s + end + + def parse + space + object + end + + private + + def space() scan WSP end + + def endkey() scan(KEY) or space end + + def object + matched == '{' ? hash : array if scan(OBJ) + end + + def value + object or string or + scan(NUL) ? nil : + scan(BOL) ? matched.size == 4: + scan(NUM) ? eval(matched) : + error + end + + def hash + obj = {} + space + repeat_until(HEN) { k = string; scan(COL); obj[k] = value; endkey } + obj + end + + def array + ary = [] + space + repeat_until(AEN) { ary << value; endkey } + ary + end + + SPEC = {'b' => "\b", 'f' => "\f", 'n' => "\n", 'r' => "\r", 't' => "\t"} + UNI = 'u'; CODE = /[a-fA-F0-9]{4}/ + STR = /"/; STE = '"' + ESC = '\\' + + def string + if scan(STR) + str, esc = '', false + while c = s.getch + if esc + str << (c == UNI ? (s.scan(CODE) || error).to_i(16).chr : SPEC[c] || c) + esc = false + else + case c + when ESC then esc = true + when STE then break + else str << c + end + end + end + str + end + end + + def error + raise "parse error at: #{scan(/.{1,10}/m).inspect}" + end + + def repeat_until reg + until scan(reg) + pos = s.pos + yield + error unless s.pos > pos + end + end +end +module Hub + class Runner + attr_reader :args + + def initialize(*args) + @args = Args.new(args) + Commands.run(@args) + end + + def self.execute(*args) + new(*args).execute + end + + def command + if args.skip? + '' + else + commands.join('; ') + end + end + + def commands + args.commands.map do |cmd| + if cmd.respond_to?(:join) + cmd.map { |arg| arg = arg.to_s; (arg.index(' ') || arg.empty?) ? "'#{arg}'" : arg }.join(' ') + else + cmd.to_s + end + end + end + + def execute + if args.noop? + puts commands + elsif not args.skip? + if args.chained? + execute_command_chain + else + exec(*args.to_exec) + end + end + end + + def execute_command_chain + commands = args.commands + commands.each_with_index do |cmd, i| + if cmd.respond_to?(:call) then cmd.call + elsif i == commands.length - 1 + exec(*cmd) + else + exit($?.exitstatus) unless system(*cmd) + end + end + end + end +end +module Hub + Version = VERSION = '1.8.4' +end +Hub::Runner.execute(*ARGV) +__END__ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "HUB" "1" "March 2012" "DEFUNKT" "Git Manual" +. +.SH "NAME" +\fBhub\fR \- git + hub = github +. +.SH "SYNOPSIS" +\fBhub\fR [\fB\-\-noop\fR] \fICOMMAND\fR \fIOPTIONS\fR +. +.br +\fBhub alias\fR [\fB\-s\fR] [\fISHELL\fR] +. +.SS "Expanded git commands:" +\fBgit init \-g\fR \fIOPTIONS\fR +. +.br +\fBgit clone\fR [\fB\-p\fR] \fIOPTIONS\fR [\fIUSER\fR/]\fIREPOSITORY\fR \fIDIRECTORY\fR +. +.br +\fBgit remote add\fR [\fB\-p\fR] \fIOPTIONS\fR \fIUSER\fR[/\fIREPOSITORY\fR] +. +.br +\fBgit remote set\-url\fR [\fB\-p\fR] \fIOPTIONS\fR \fIREMOTE\-NAME\fR \fIUSER\fR[/\fIREPOSITORY\fR] +. +.br +\fBgit fetch\fR \fIUSER\-1\fR,[\fIUSER\-2\fR,\.\.\.] +. +.br +\fBgit checkout\fR \fIPULLREQ\-URL\fR [\fIBRANCH\fR] +. +.br +\fBgit cherry\-pick\fR \fIGITHUB\-REF\fR +. +.br +\fBgit am\fR \fIGITHUB\-URL\fR +. +.br +\fBgit apply\fR \fIGITHUB\-URL\fR +. +.br +\fBgit push\fR \fIREMOTE\-1\fR,\fIREMOTE\-2\fR,\.\.\.,\fIREMOTE\-N\fR [\fIREF\fR] +. +.br +\fBgit submodule add\fR [\fB\-p\fR] \fIOPTIONS\fR [\fIUSER\fR/]\fIREPOSITORY\fR \fIDIRECTORY\fR +. +.SS "Custom git commands:" +\fBgit create\fR [\fINAME\fR] [\fB\-p\fR] [\fB\-d\fR \fIDESCRIPTION\fR] [\fB\-h\fR \fIHOMEPAGE\fR] +. +.br +\fBgit browse\fR [\fB\-u\fR] [[\fIUSER\fR\fB/\fR]\fIREPOSITORY\fR] [SUBPAGE] +. +.br +\fBgit compare\fR [\fB\-u\fR] [\fIUSER\fR] [\fISTART\fR\.\.\.]\fIEND\fR +. +.br +\fBgit fork\fR [\fB\-\-no\-remote\fR] +. +.br +\fBgit pull\-request\fR [\fB\-f\fR] [\fITITLE\fR|\fB\-i\fR \fIISSUE\fR] [\fB\-b\fR \fIBASE\fR] [\fB\-h\fR \fIHEAD\fR] +. +.SH "DESCRIPTION" +hub enhances various git commands to ease most common workflows with GitHub\. +. +.TP +\fBhub \-\-noop\fR \fICOMMAND\fR +Shows which command(s) would be run as a result of the current command\. Doesn\'t perform anything\. +. +.TP +\fBhub alias\fR [\fB\-s\fR] [\fISHELL\fR] +Shows shell instructions for wrapping git\. If given, \fISHELL\fR specifies the type of shell; otherwise defaults to the value of SHELL environment variable\. With \fB\-s\fR, outputs shell script suitable for \fBeval\fR\. +. +.TP +\fBgit init\fR \fB\-g\fR \fIOPTIONS\fR +Create a git repository as with git\-init(1) and add remote \fBorigin\fR at "git@github\.com:\fIUSER\fR/\fIREPOSITORY\fR\.git"; \fIUSER\fR is your GitHub username and \fIREPOSITORY\fR is the current working directory\'s basename\. +. +.TP +\fBgit clone\fR [\fB\-p\fR] \fIOPTIONS\fR [\fIUSER\fR\fB/\fR]\fIREPOSITORY\fR \fIDIRECTORY\fR +Clone repository "git://github\.com/\fIUSER\fR/\fIREPOSITORY\fR\.git" into \fIDIRECTORY\fR as with git\-clone(1)\. When \fIUSER\fR/ is omitted, assumes your GitHub login\. With \fB\-p\fR, clone private repositories over SSH\. For repositories under your GitHub login, \fB\-p\fR is implicit\. +. +.TP +\fBgit remote add\fR [\fB\-p\fR] \fIOPTIONS\fR \fIUSER\fR[\fB/\fR\fIREPOSITORY\fR] +Add remote "git://github\.com/\fIUSER\fR/\fIREPOSITORY\fR\.git" as with git\-remote(1)\. When /\fIREPOSITORY\fR is omitted, the basename of the current working directory is used\. With \fB\-p\fR, use private remote "git@github\.com:\fIUSER\fR/\fIREPOSITORY\fR\.git"\. If \fIUSER\fR is "origin" then uses your GitHub login\. +. +.TP +\fBgit remote set\-url\fR [\fB\-p\fR] \fIOPTIONS\fR \fIREMOTE\-NAME\fR \fIUSER\fR[/\fIREPOSITORY\fR] +Sets the url of remote \fIREMOTE\-NAME\fR using the same rules as \fBgit remote add\fR\. +. +.TP +\fBgit fetch\fR \fIUSER\-1\fR,[\fIUSER\-2\fR,\.\.\.] +Adds missing remote(s) with \fBgit remote add\fR prior to fetching\. New remotes are only added if they correspond to valid forks on GitHub\. +. +.TP +\fBgit checkout\fR \fIPULLREQ\-URL\fR [\fIBRANCH\fR] +Checks out the head of the pull request as a local branch, to allow for reviewing, rebasing and otherwise cleaning up the commits in the pull request before merging\. The name of the local branch can explicitly be set with \fIBRANCH\fR\. +. +.TP +\fBgit cherry\-pick\fR \fIGITHUB\-REF\fR +Cherry\-pick a commit from a fork using either full URL to the commit or GitHub\-flavored Markdown notation, which is \fBuser@sha\fR\. If the remote doesn\'t yet exist, it will be added\. A \fBgit fetch \fR is issued prior to the cherry\-pick attempt\. +. +.TP +\fBgit [am|apply]\fR \fIGITHUB\-URL\fR +Downloads the patch file for the pull request or commit at the URL and applies that patch from disk with \fBgit am\fR or \fBgit apply\fR\. Similar to \fBcherry\-pick\fR, but doesn\'t add new remotes\. \fBgit am\fR creates commits while preserving authorship info while \fBapply\fR only applies the patch to the working copy\. +. +.TP +\fBgit push\fR \fIREMOTE\-1\fR,\fIREMOTE\-2\fR,\.\.\.,\fIREMOTE\-N\fR [\fIREF\fR] +Push \fIREF\fR to each of \fIREMOTE\-1\fR through \fIREMOTE\-N\fR by executing multiple \fBgit push\fR commands\. +. +.TP +\fBgit submodule add\fR [\fB\-p\fR] \fIOPTIONS\fR [\fIUSER\fR/]\fIREPOSITORY\fR \fIDIRECTORY\fR +Submodule repository "git://github\.com/\fIUSER\fR/\fIREPOSITORY\fR\.git" into \fIDIRECTORY\fR as with git\-submodule(1)\. When \fIUSER\fR/ is omitted, assumes your GitHub login\. With \fB\-p\fR, use private remote "git@github\.com:\fIUSER\fR/\fIREPOSITORY\fR\.git"\. +. +.TP +\fBgit help\fR +Display enhanced git\-help(1)\. +. +.P +hub also adds some custom commands that are otherwise not present in git: +. +.TP +\fBgit create\fR [\fINAME\fR] [\fB\-p\fR] [\fB\-d\fR \fIDESCRIPTION\fR] [\fB\-h\fR \fIHOMEPAGE\fR] +Create a new public GitHub repository from the current git repository and add remote \fBorigin\fR at "git@github\.com:\fIUSER\fR/\fIREPOSITORY\fR\.git"; \fIUSER\fR is your GitHub username and \fIREPOSITORY\fR is the current working directory name\. To explicitly name the new repository, pass in \fINAME\fR, optionally in \fIORGANIZATION\fR/\fINAME\fR form to create under an organization you\'re a member of\. With \fB\-p\fR, create a private repository, and with \fB\-d\fR and \fB\-h\fR set the repository\'s description and homepage URL, respectively\. +. +.TP +\fBgit browse\fR [\fB\-u\fR] [[\fIUSER\fR\fB/\fR]\fIREPOSITORY\fR] [SUBPAGE] +Open repository\'s GitHub page in the system\'s default web browser using \fBopen(1)\fR or the \fBBROWSER\fR env variable\. If the repository isn\'t specified, \fBbrowse\fR opens the page of the repository found in the current directory\. If SUBPAGE is specified, the browser will open on the specified subpage: one of "wiki", "commits", "issues" or other (the default is "tree")\. +. +.TP +\fBgit compare\fR [\fB\-u\fR] [\fIUSER\fR] [\fISTART\fR\.\.\.]\fIEND\fR +Open a GitHub compare view page in the system\'s default web browser\. \fISTART\fR to \fIEND\fR are branch names, tag names, or commit SHA1s specifying the range of history to compare\. If a range with two dots (\fBa\.\.b\fR) is given, it will be transformed into one with three dots\. If \fISTART\fR is omitted, GitHub will compare against the base branch (the default is "master")\. +. +.TP +\fBgit fork\fR [\fB\-\-no\-remote\fR] +Forks the original project (referenced by "origin" remote) on GitHub and adds a new remote for it under your username\. Requires \fBgithub\.token\fR to be set (see CONFIGURATION)\. +. +.TP +\fBgit pull\-request\fR [\fB\-f\fR] [\fITITLE\fR|\fB\-i\fR \fIISSUE\fR|\fIISSUE\-URL\fR] [\fB\-b\fR \fIBASE\fR] [\fB\-h\fR \fIHEAD\fR] +Opens a pull request on GitHub for the project that the "origin" remote points to\. The default head of the pull request is the current branch\. Both base and head of the pull request can be explicitly given in one of the following formats: "branch", "owner:branch", "owner/repo:branch"\. This command will abort operation if it detects that the current topic branch has local commits that are not yet pushed to its upstream branch on the remote\. To skip this check, use \fB\-f\fR\. +. +.IP +If \fITITLE\fR is omitted, a text editor will open in which title and body of the pull request can be entered in the same manner as git commit message\. +. +.IP +If instead of normal \fITITLE\fR an issue number is given with \fB\-i\fR, the pull request will be attached to an existing GitHub issue\. Alternatively, instead of title you can paste a full URL to an issue on GitHub\. +. +.SH "CONFIGURATION" +Use git\-config(1) to display the currently configured GitHub username: +. +.IP "" 4 +. +.nf + +$ git config \-\-global github\.user +. +.fi +. +.IP "" 0 +. +.P +Or, set the GitHub username and token with: +. +.IP "" 4 +. +.nf + +$ git config \-\-global github\.user +$ git config \-\-global github\.token +. +.fi +. +.IP "" 0 +. +.P +You can override these values with \fIGITHUB_USER\fR and \fIGITHUB_TOKEN\fR environment variables\. +. +.P +See \fIhttp://help\.github\.com/set\-your\-user\-name\-email\-and\-github\-token/\fR for more information\. +. +.P +If you prefer the HTTPS protocol for GitHub repositories, you can set "hub\.protocol" to "https"\. This will affect \fBclone\fR, \fBfork\fR, \fBremote add\fR and other operations that expand references to GitHub repositories as full URLs that otherwise use git and ssh protocols\. +. +.IP "" 4 +. +.nf + +$ git config \-\-global hub\.protocol https +. +.fi +. +.IP "" 0 +. +.SS "GitHub Enterprise" +By default, hub will only work with repositories that have remotes which point to github\.com\. GitHub Enterprise hosts need to be whitelisted to configure hub to treat such remotes same as github\.com: +. +.IP "" 4 +. +.nf + +$ git config \-\-global \-\-add hub\.host my\.git\.org +. +.fi +. +.IP "" 0 +. +.P +API username and token need also be configured for each Enterprise host: +. +.IP "" 4 +. +.nf + +$ git config \-\-global github\."my\.git\.org"\.user +$ git config \-\-global github\."my\.git\.org"\.token +. +.fi +. +.IP "" 0 +. +.P +The default host for commands like \fBinit\fR and \fBclone\fR is still github\.com, but this can be affected with the \fIGITHUB_HOST\fR environment variable: +. +.IP "" 4 +. +.nf + +$ GITHUB_HOST=my\.git\.org git clone myproject +. +.fi +. +.IP "" 0 +. +.SH "EXAMPLES" +. +.SS "git clone" +. +.nf + +$ git clone schacon/ticgit +> git clone git://github\.com/schacon/ticgit\.git + +$ git clone \-p schacon/ticgit +> git clone git@github\.com:schacon/ticgit\.git + +$ git clone resque +> git clone git@github\.com/YOUR_USER/resque\.git +. +.fi +. +.SS "git remote add" +. +.nf + +$ git remote add rtomayko +> git remote add rtomayko git://github\.com/rtomayko/CURRENT_REPO\.git + +$ git remote add \-p rtomayko +> git remote add rtomayko git@github\.com:rtomayko/CURRENT_REPO\.git + +$ git remote add origin +> git remote add origin git://github\.com/YOUR_USER/CURRENT_REPO\.git +. +.fi +. +.SS "git fetch" +. +.nf + +$ git fetch mislav +> git remote add mislav git://github\.com/mislav/REPO\.git +> git fetch mislav + +$ git fetch mislav,xoebus +> git remote add mislav \.\.\. +> git remote add xoebus \.\.\. +> git fetch \-\-multiple mislav xoebus +. +.fi +. +.SS "git cherry\-pick" +. +.nf + +$ git cherry\-pick http://github\.com/mislav/REPO/commit/SHA +> git remote add \-f mislav git://github\.com/mislav/REPO\.git +> git cherry\-pick SHA + +$ git cherry\-pick mislav@SHA +> git remote add \-f mislav git://github\.com/mislav/CURRENT_REPO\.git +> git cherry\-pick SHA + +$ git cherry\-pick mislav@SHA +> git fetch mislav +> git cherry\-pick SHA +. +.fi +. +.SS "git am, git apply" +. +.nf + +$ git am https://github\.com/defunkt/hub/pull/55 +> curl https://github\.com/defunkt/hub/pull/55\.patch \-o /tmp/55\.patch +> git am /tmp/55\.patch + +$ git am \-\-ignore\-whitespace https://github\.com/davidbalbert/hub/commit/fdb9921 +> curl https://github\.com/davidbalbert/hub/commit/fdb9921\.patch \-o /tmp/fdb9921\.patch +> git am \-\-ignore\-whitespace /tmp/fdb9921\.patch + +$ git apply https://gist\.github\.com/8da7fb575debd88c54cf +> curl https://gist\.github\.com/8da7fb575debd88c54cf\.txt \-o /tmp/gist\-8da7fb575debd88c54cf\.txt +> git apply /tmp/gist\-8da7fb575debd88c54cf\.txt +. +.fi +. +.SS "git fork" +. +.nf + +$ git fork +[ repo forked on GitHub ] +> git remote add \-f YOUR_USER git@github\.com:YOUR_USER/CURRENT_REPO\.git +. +.fi +. +.SS "git pull\-request" +. +.nf + +# while on a topic branch called "feature": +$ git pull\-request +[ opens text editor to edit title & body for the request ] +[ opened pull request on GitHub for "YOUR_USER:feature" ] + +# explicit title, pull base & head: +$ git pull\-request "I\'ve implemented feature X" \-b defunkt:master \-h mislav:feature + +$ git pull\-request \-i 123 +[ attached pull request to issue #123 ] +. +.fi +. +.SS "git checkout" +. +.nf + +# $ git checkout https://github\.com/defunkt/hub/pull/73 +# > git remote add \-f \-t feature git://github:com/mislav/hub\.git +# > git checkout \-\-track \-B mislav\-feature mislav/feature + +# $ git checkout https://github\.com/defunkt/hub/pull/73 custom\-branch\-name +. +.fi +. +.SS "git create" +. +.nf + +$ git create +[ repo created on GitHub ] +> git remote add origin git@github\.com:YOUR_USER/CURRENT_REPO\.git + +# with description: +$ git create \-d \'It shall be mine, all mine!\' + +$ git create recipes +[ repo created on GitHub ] +> git remote add origin git@github\.com:YOUR_USER/recipes\.git + +$ git create sinatra/recipes +[ repo created in GitHub organization ] +> git remote add origin git@github\.com:sinatra/recipes\.git +. +.fi +. +.SS "git init" +. +.nf + +$ git init \-g +> git init +> git remote add origin git@github\.com:YOUR_USER/REPO\.git +. +.fi +. +.SS "git push" +. +.nf + +$ git push origin,staging,qa bert_timeout +> git push origin bert_timeout +> git push staging bert_timeout +> git push qa bert_timeout +. +.fi +. +.SS "git browse" +. +.nf + +$ git browse +> open https://github\.com/YOUR_USER/CURRENT_REPO + +$ git browse \-\- commit/SHA +> open https://github\.com/YOUR_USER/CURRENT_REPO/commit/SHA + +$ git browse \-\- issues +> open https://github\.com/YOUR_USER/CURRENT_REPO/issues + +$ git browse schacon/ticgit +> open https://github\.com/schacon/ticgit + +$ git browse schacon/ticgit commit/SHA +> open https://github\.com/schacon/ticgit/commit/SHA + +$ git browse resque +> open https://github\.com/YOUR_USER/resque + +$ git browse resque network +> open https://github\.com/YOUR_USER/resque/network +. +.fi +. +.SS "git compare" +. +.nf + +$ git compare refactor +> open https://github\.com/CURRENT_REPO/compare/refactor + +$ git compare 1\.0\.\.1\.1 +> open https://github\.com/CURRENT_REPO/compare/1\.0\.\.\.1\.1 + +$ git compare \-u fix +> (https://github\.com/CURRENT_REPO/compare/fix) + +$ git compare other\-user patch +> open https://github\.com/other\-user/REPO/compare/patch +. +.fi +. +.SS "git submodule" +. +.nf + +$ hub submodule add wycats/bundler vendor/bundler +> git submodule add git://github\.com/wycats/bundler\.git vendor/bundler + +$ hub submodule add \-p wycats/bundler vendor/bundler +> git submodule add git@github\.com:wycats/bundler\.git vendor/bundler + +$ hub submodule add \-b ryppl ryppl/pip vendor/pip +> git submodule add \-b ryppl git://github\.com/ryppl/pip\.git vendor/pip +. +.fi +. +.SS "git help" +. +.nf + +$ git help +> (improved git help) +$ git help hub +> (hub man page) +. +.fi +. +.SH "BUGS" +\fIhttps://github\.com/defunkt/hub/issues\fR +. +.SH "AUTHORS" +\fIhttps://github\.com/defunkt/hub/contributors\fR +. +.SH "SEE ALSO" +git(1), git\-clone(1), git\-remote(1), git\-init(1), \fIhttp://github\.com\fR, \fIhttps://github\.com/defunkt/hub\fR diff --git a/bin/irccloud_tail b/bin/irccloud_tail new file mode 100755 index 00000000..47d3fc27 --- /dev/null +++ b/bin/irccloud_tail @@ -0,0 +1,74 @@ +#!/usr/bin/env ruby +require 'rbconfig' +require "rubygems" +require 'net/http' +require 'net/https' +require 'uri' +require 'json' +File.open("/home/marten/rbconf", "w") { |file| file.puts RbConfig::CONFIG.inspect } + +email = "marten@veldthuis.com" +pass = "rdam4we1" + +uri_login = URI.parse('https://irccloud.com/chat/login') +uri_stream = URI.parse('https://irccloud.com/chat/stream') + + +# do login to get session cookie: +puts 'Logging in...' +req = Net::HTTP::Post.new(uri_login.path) +req.set_form_data({'email' => email, 'password' => pass }) +http = Net::HTTP.new(uri_login.host, uri_login.port) +http.use_ssl = true +res = http.start {|http| http.request(req) } +case res +when Net::HTTPSuccess, Net::HTTPRedirection + session = res.response['set-cookie'].split(';')[0] +else + res.error! +end + +eob = {} +servers = {} +buffers = {} +buffer = '' +# start stream +http = Net::HTTP.new(uri_stream.host, uri_stream.port) +http.use_ssl = true +http.request_get(uri_stream.path, {'cookie'=>session}) {|response| + response.read_body do |str| + buffer += str + lines = buffer.split("\n") + lines.each { |line| + begin + STDOUT.sync = true + ev = JSON.parse line + STDERR.puts ev.inspect + # {"bid":83392, + # "eid":10, + # "type":"buffer_msg", + # "time":1314296055, + # "highlight":true, + # "from":"marten_", + # "msg":"marten: ping!", + # "chan":"#test", + # "cid":8643} + # + if ev['type'] == "buffer_msg" + next if ["#xkcd"].include?(ev['chan']) + important = ( ev['chan'] =~ /^#/ and ev['highlight'] == true) or + (not ev['chan'] =~ /^#/ and ev['from'] != "marten") + output = [] + output << "^fg(yellow)^bg(red)" if important + output << "#{ev['chan']} <#{ev['from']}> #{ev['msg']}" + output << '^fg()^bg()' if important + puts output.join("") + end + rescue JSON::JSONError => e + buffer = line + next + end + } + buffer = '' + end +} diff --git a/bin/iterm-emacsclient b/bin/iterm-emacsclient new file mode 100755 index 00000000..83e1a972 --- /dev/null +++ b/bin/iterm-emacsclient @@ -0,0 +1,18 @@ +#!/bin/zsh --login +# +# Extremely simple emacsclient wrapper for item2 "Semantic History" process running. +# +# In iTerm2 Session preferences pane setup Semantic History to: +# +# [Run command...] +# +# in the text input paste the line below: +# +# ~/.emacs.d/iterm-emacsclient +\2 \1 +# + +if [[ $1 == "+" ]]; then + shift +fi + +emacsclient $1 $2 -n -a /usr/local/Cellar/emacs-mac/emacs-24.5-z-mac-5.8/Emacs.app/Contents/MacOS/Emacs \ No newline at end of file diff --git a/bin/jslint b/bin/jslint new file mode 100755 index 00000000..3966c3d2 --- /dev/null +++ b/bin/jslint @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + + +target = File.expand_path(ARGV.first) + +if RUBY_PLATFORM =~ /win32/i + js_cmd = 'cscript' + runjslint_ext = 'wsf' + plugin_path = File.join(ENV['HOME'], 'vimfiles') +else + if ENV['JS_CMD'] + js_cmd = ENV['JS_CMD'] + else + js_cmd = 'js' + end + runjslint_ext = 'js' + plugin_path = File.join(ENV['HOME'], '.vim') +end + +Dir.chdir(File.join(ENV["HOME"], "bin", 'jslint_files')) do |dir| + IO.popen("#{js_cmd} runjslint.#{runjslint_ext}", 'r+') do |jslint| + jslint.write(File.open(target, 'r') { |f| f.read }) + jslint.close_write + $stdout.write(jslint.read) + end +end \ No newline at end of file diff --git a/bin/mail-sync b/bin/mail-sync new file mode 100755 index 00000000..29101814 --- /dev/null +++ b/bin/mail-sync @@ -0,0 +1,7 @@ +#!/bin/csh +while ( 1 ) + offlineimap |& tee -a ~/.offlineimaplog + echo "offlineimap exited; waiting 10 minutes before respawning." \ + |& tee -a ~/offlineimaplog + sleep 600 +end diff --git a/bin/mr b/bin/mr new file mode 100755 index 00000000..87618a22 --- /dev/null +++ b/bin/mr @@ -0,0 +1,1292 @@ +#!/usr/bin/perl + +#man{{{ + +=head1 NAME + +mr - a Multiple Repository management tool + +=head1 SYNOPSIS + +B [options] checkout + +B [options] update + +B [options] status + +B [options] commit [-m "message"] + +B [options] record [-m "message"] + +B [options] diff + +B [options] log + +B [options] register [repository] + +B [options] config section ["parameter=[value]" ...] + +B [options] action [params ...] + +=head1 DESCRIPTION + +B is a Multiple Repository management tool. It can checkout, update, or +perform other actions on a set of repositories as if they were one combined +repository. It supports any combination of subversion, git, cvs, mecurial, +bzr and darcs repositories, and support for other revision control systems can +easily be added. + +B cds into and operates on all registered repositories at or below your +working directory. Or, if you are in a subdirectory of a repository that +contains no other registered repositories, it will stay in that directory, +and work on only that repository, + +These predefined commands should be fairly familiar to users of any revision +control system: + +=over 4 + +=item checkout (or co) + +Checks out any repositories that are not already checked out. + +=item update + +Updates each repository from its configured remote repository. + +If a repository isn't checked out yet, it will first check it out. + +=item status + +Displays a status report for each repository, showing what +uncommitted changes are present in the repository. + +=item commit (or ci) + +Commits changes to each repository. (By default, changes are pushed to the +remote repository too, when using distributed systems like git. If you +don't like this default, you can change it in your .mrconfig, or use record +instead.) + +The optional -m parameter allows specifying a commit message. + +=item record + +Records changes to the local repository, but does not push them to the +remote repository. Only supported for distributed revision control systems. + +The optional -m parameter allows specifying a commit message. + +=item diff + +Show a diff of uncommitted changes. + +=item log + +Show the commit log. + +=back + +These commands are also available: + +=over 4 + +=item list (or ls) + +List the repositories that mr will act on. + +=item register + +Register an existing repository in a mrconfig file. By default, the +repository in the current directory is registered, or you can specify a +directory to register. + +The mrconfig file that is modified is chosen by either the -c option, or by +looking for the closest known one at or below the current directory. + +=item config + +Adds, modifies, removes, or prints a value from a mrconfig file. The next +parameter is the name of the section the value is in. To add or modify +values, use one or more instances of "parameter=value". Use "parameter=" to +remove a parameter. Use just "parameter" to get the value of a parameter. + +For example, to add (or edit) a repository in src/foo: + + mr config src/foo checkout="svn co svn://example.com/foo/trunk foo" + +To show the command that mr uses to update the repository in src/foo: + + mr config src/foo update + +To see the built-in library of shell functions contained in mr: + + mr config DEFAULT lib + +The ~/.mrconfig file is used by default. To use a different config file, +use the -c option. + +=item help + +Displays this help. + +=back + +Actions can be abbreviated to any unambiguous substring, so +"mr st" is equivalent to "mr status", and "mr up" is equivalent to "mr +update" + +Additional parameters can be passed to most commands, and are passed on +unchanged to the underlying revision control system. This is mostly useful +if the repositories mr will act on all use the same revision control +system. + +=head1 OPTIONS + +=over 4 + +=item -d directory + +Specifies the topmost directory that B should work in. The default is +the current working directory. + +=item -c mrconfig + +Use the specified mrconfig file. The default is B<~/.mrconfig> + +=item -v + +Be verbose. + +=item -q + +Be quiet. + +=item -s + +Expand the statistics line displayed at the end to include information +about exactly which repositories failed and were skipped, if any. + +=item -n [number] + +If no number if specified, just operate on the repository for the current +directory, do not recurse into deeper repositories. + +If a number is specified, will recurse into repositories at most that many +subdirectories deep. For example, with -n 2 it would recurse into ./src/foo, +but not ./src/packages/bar. + +=item -j [number] + +Run the specified number of jobs in parallel, or an unlimited number of jobs +with no number specified. This can greatly speed up operations such as updates. +It is not recommended for interactive operations. + +Note that running more than 10 jobs at a time is likely to run afoul of +ssh connection limits. Running between 3 and 5 jobs at a time will yeild +a good speedup in updates without loading the machine too much. + +=back + +=head1 FILES + +B is configured by .mrconfig files. It starts by reading the .mrconfig +file in your home directory, and this can in turn chain load .mrconfig files +from repositories. + +Here is an example .mrconfig file: + + [src] + checkout = svn co svn://svn.example.com/src/trunk src + chain = true + + [src/linux-2.6] + checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git && + cd linux-2.6 && + git checkout -b mybranch origin/master + +The .mrconfig file uses a variant of the INI file format. Lines starting with +"#" are comments. Values can be continued to the following line by +indenting the line with whitespace. + +The "DEFAULT" section allows setting default values for the sections that +come after it. + +The "ALIAS" section allows adding aliases for actions. Each parameter +is an alias, and its value is the action to use. + +All other sections add repositories. The section header specifies the +directory where the repository is located. This is relative to the directory +that contains the mrconfig file, but you can also choose to use absolute +paths. + +Within a section, each parameter defines a shell command to run to handle a +given action. mr contains default handlers for "update", "status", +"commit", and other standard actions. Normally you only need to specify what +to do for "checkout". + +Note that these shell commands are run in a "set -e" shell +environment, where any additional parameters you pass are available in +"$@". The "checkout" command is run in the parent of the repository +directory, since the repository isn't checked out yet. All other commands +are run inside the repository, though not necessarily at the top of it. + +The "MR_REPO" environment variable is set to the path to the top of the +repository. (For the "register" action, "MR_REPO" is instead set to the +basename of the directory that should be created when checking the +repository out.) + +The "MR_CONFIG" environment variable is set to the .mrconfig file +that defines the repo being acted on, or, if the repo is not yet in a config +file, the .mrconfig file that should be modified to register the repo. + +A few parameters have special meanings: + +=over 4 + +=item skip + +If the "skip" parameter is set and its command returns true, then B +will skip acting on that repository. The command is passed the action +name in $1. + +Here are two examples. The first skips the repo unless +mr is run by joey. The second uses the hours_since function +(included in mr's built-in library) to skip updating the repo unless it's +been at least 12 hours since the last update. + + skip = test `whoami` != joey + skip = [ "$1" = update ] && ! hours_since "$1" 12 + +=item order + +The "order" parameter can be used to override the default ordering of +repositories. The default order value is 10. Use smaller values to make +repositories be processed earlier, and larger values to make repositories +be processed later. + +Note that if a repository is located in a subdirectory of another +repository, ordering it to be processed earlier is not recommended. + +=item chain + +If the "chain" parameter is set and its command returns true, then B +will try to load a .mrconfig file from the root of the repository. (You +should avoid chaining from repositories with untrusted committers.) + +=item include + +If the "include" parameter is set, its command is ran, and should output +additional mrconfig file content. The content is included as if it were +part of the including file. + +Unlike all other parameters, this parameter does not need to be placed +within a section. + +=item lib + +The "lib" parameter can specify some shell code that will be run before each +command, this can be a useful way to define shell functions for other commands +to use. + +=back + +When looking for a command to run for a given action, mr first looks for +a parameter with the same name as the action. If that is not found, it +looks for a parameter named "rcs_action" (substituting in the name of the +revision control system and the action). The name of the revision control +system is itself determined by running each defined "rcs_test" action, +until one succeeds. + +Internally, mr has settings for "git_update", "svn_update", etc. To change +the action that is performed for a given revision control system, you can +override these rcs specific actions. To add a new revision control system, +you can just add rcs specific actions for it. + +=head1 AUTHOR + +Copyright 2007 Joey Hess + +Licensed under the GNU GPL version 2 or higher. + +http://kitenet.net/~joey/code/mr/ + +=cut + +#}}} + +use warnings; +use strict; +use Getopt::Long; +use Cwd qw(getcwd abs_path); + +# things that can happen when mr runs a command +use constant { + OK => 0, + FAILED => 1, + SKIPPED => 2, + ABORT => 3, +}; + +# configurables +my $config_overridden=0; +my $verbose=0; +my $quiet=0; +my $stats=0; +my $max_depth; +my $no_chdir=0; +my $jobs=1; +my $directory=getcwd(); +$ENV{MR_CONFIG}="$ENV{HOME}/.mrconfig"; + +# globals :-( +my %config; +my %configfiles; +my %knownactions; +my %alias; +my (@ok, @failed, @skipped); + +main(); + +my %rcs; +sub rcs_test { #{{{ + my ($action, $dir, $topdir, $subdir) = @_; + + if (exists $rcs{$dir}) { + return $rcs{$dir}; + } + + my $test="set -e\n"; + foreach my $rcs_test ( + sort { + length $a <=> length $b + || + $a cmp $b + } grep { /_test$/ } keys %{$config{$topdir}{$subdir}}) { + my ($rcs)=$rcs_test=~/(.*)_test/; + $test="my_$rcs_test() {\n$config{$topdir}{$subdir}{$rcs_test}\n}\n".$test; + $test.="if my_$rcs_test; then echo $rcs; fi\n"; + } + $test=$config{$topdir}{$subdir}{lib}."\n".$test + if exists $config{$topdir}{$subdir}{lib}; + + print "mr $action: running rcs test >>$test<<\n" if $verbose; + my $rcs=`$test`; + chomp $rcs; + if ($rcs=~/\n/s) { + $rcs=~s/\n/, /g; + print STDERR "mr $action: found multiple possible repository types ($rcs) for $topdir$subdir\n"; + return undef; + } + if (! length $rcs) { + return $rcs{$dir}=undef; + } + else { + return $rcs{$dir}=$rcs; + } +} #}}} + +sub findcommand { #{{{ + my ($action, $dir, $topdir, $subdir, $is_checkout) = @_; + + if (exists $config{$topdir}{$subdir}{$action}) { + return $config{$topdir}{$subdir}{$action}; + } + + if ($is_checkout) { + return undef; + } + + my $rcs=rcs_test(@_); + + if (defined $rcs && + exists $config{$topdir}{$subdir}{$rcs."_".$action}) { + return $config{$topdir}{$subdir}{$rcs."_".$action}; + } + else { + return undef; + } +} #}}} + +sub action { #{{{ + my ($action, $dir, $topdir, $subdir) = @_; + + $ENV{MR_CONFIG}=$configfiles{$topdir}; + my $lib=exists $config{$topdir}{$subdir}{lib} ? + $config{$topdir}{$subdir}{lib}."\n" : ""; + my $is_checkout=($action eq 'checkout'); + + if ($is_checkout) { + if (-d $dir) { + print "mr $action: $dir already exists, skipping checkout\n" if $verbose; + return SKIPPED; + } + + $dir=~s/^(.*)\/[^\/]+\/?$/$1/; + } + elsif ($action =~ /update/) { + if (! -d $dir) { + return action("checkout", $dir, $topdir, $subdir); + } + } + + $ENV{MR_REPO}=$dir; + + my $skiptest=findcommand("skip", $dir, $topdir, $subdir, $is_checkout); + my $command=findcommand($action, $dir, $topdir, $subdir, $is_checkout); + + if (defined $skiptest) { + my $test="set -e;".$lib. + "my_action(){ $skiptest\n }; my_action '$action'"; + print "mr $action: running skip test >>$test<<\n" if $verbose; + my $ret=system($test); + if ($ret != 0) { + if (($? & 127) == 2) { + print STDERR "mr $action: interrupted\n"; + return ABORT; + } + elsif ($? & 127) { + print STDERR "mr $action: skip test received signal ".($? & 127)."\n"; + return ABORT; + } + } + if ($ret >> 8 == 0) { + print "mr $action: $dir skipped per config file\n" if $verbose; + return SKIPPED; + } + } + + if ($is_checkout && ! -d $dir) { + print "mr $action: creating parent directory $dir\n" if $verbose; + system("mkdir", "-p", $dir); + } + + if (! $no_chdir && ! chdir($dir)) { + print STDERR "mr $action: failed to chdir to $dir: $!\n"; + return FAILED; + } + elsif (! defined $command) { + my $rcs=rcs_test(@_); + if (! defined $rcs) { + print STDERR "mr $action: unknown repository type and no defined $action command for $topdir$subdir\n"; + return FAILED; + } + else { + print STDERR "mr $action: no defined action for $rcs repository $topdir$subdir, skipping\n"; + return SKIPPED; + } + } + else { + if (! $no_chdir) { + print "mr $action: $topdir$subdir\n" unless $quiet; + } + else { + my $s=$directory; + $s=~s/^\Q$topdir$subdir\E\/?//; + print "mr $action: $topdir$subdir (in subdir $s)\n" unless $quiet; + } + $command="set -e; ".$lib. + "my_action(){ $command\n }; my_action ". + join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV); + print "mr $action: running >>$command<<\n" if $verbose; + my $ret=system($command); + if ($ret != 0) { + if (($? & 127) == 2) { + print STDERR "mr $action: interrupted\n"; + return ABORT; + } + elsif ($? & 127) { + print STDERR "mr $action: received signal ".($? & 127)."\n"; + return ABORT; + } + print STDERR "mr $action: failed ($ret)\n" if $verbose; + if ($ret >> 8 != 0) { + print STDERR "mr $action: command failed\n"; + } + elsif ($ret != 0) { + print STDERR "mr $action: command died ($ret)\n"; + } + return FAILED; + } + else { + if ($action eq 'checkout' && ! -d $dir) { + print STDERR "mr $action: $dir missing after checkout\n";; + return FAILED; + } + + return OK; + } + } +} #}}} + +# run actions on multiple repos, in parallel +sub mrs { #{{{ + my $action=shift; + my @repos=@_; + + $| = 1; + my @active; + my @fhs; + my @out; + my $running=0; + while (@fhs or @repos) { + while ((!$jobs || $running < $jobs) && @repos) { + $running++; + my $repo = shift @repos; + pipe(my $outfh, CHILD_STDOUT); + pipe(my $errfh, CHILD_STDERR); + my $pid; + unless ($pid = fork) { + die "mr $action: cannot fork: $!" unless defined $pid; + open(STDOUT, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!"; + open(STDERR, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!"; + close CHILD_STDOUT; + close CHILD_STDERR; + close $outfh; + close $errfh; + exit action($action, @$repo); + } + close CHILD_STDOUT; + close CHILD_STDERR; + push @active, [$pid, $repo]; + push @fhs, [$outfh, $errfh]; + push @out, ['', '']; + } + my ($rin, $rout) = ('',''); + my $nfound; + foreach my $fh (@fhs) { + next unless defined $fh; + vec($rin, fileno($fh->[0]), 1) = 1 if defined $fh->[0]; + vec($rin, fileno($fh->[1]), 1) = 1 if defined $fh->[1]; + } + $nfound = select($rout=$rin, undef, undef, 1); + foreach my $channel (0, 1) { + foreach my $i (0..$#fhs) { + next unless defined $fhs[$i]; + my $fh = $fhs[$i][$channel]; + next unless defined $fh; + if (vec($rout, fileno($fh), 1) == 1) { + my $r = ''; + if (sysread($fh, $r, 1024) == 0) { + close($fh); + $fhs[$i][$channel] = undef; + if (! defined $fhs[$i][0] && + ! defined $fhs[$i][1]) { + waitpid($active[$i][0], 0); + print STDOUT $out[$i][0]; + print STDERR $out[$i][1]; + record($active[$i][1], $? >> 8); + splice(@fhs, $i, 1); + splice(@active, $i, 1); + splice(@out, $i, 1); + $running--; + } + } + $out[$i][$channel] .= $r; + } + } + } + } +} #}}} + +sub record { #{{{ + my $dir=shift()->[0]; + my $ret=shift; + + if ($ret == OK) { + push @ok, $dir; + print "\n"; + } + elsif ($ret == FAILED) { + push @failed, $dir; + print "\n"; + } + elsif ($ret == SKIPPED) { + push @skipped, $dir; + } + elsif ($ret == ABORT) { + exit 1; + } + else { + die "unknown exit status $ret"; + } +} #}}} + +sub showstats { #{{{ + my $action=shift; + if (! @ok && ! @failed && ! @skipped) { + die "mr $action: no repositories found to work on\n"; + } + print "mr $action: finished (".join("; ", + showstat($#ok+1, "ok", "ok"), + showstat($#failed+1, "failed", "failed"), + showstat($#skipped+1, "skipped", "skipped"), + ).")\n" unless $quiet; + if ($stats) { + if (@skipped) { + print "mr $action: (skipped: ".join(" ", @skipped).")\n" unless $quiet; + } + if (@failed) { + print STDERR "mr $action: (failed: ".join(" ", @failed).")\n"; + } + } +} #}}} + +sub showstat { #{{{ + my $count=shift; + my $singular=shift; + my $plural=shift; + if ($count) { + return "$count ".($count > 1 ? $plural : $singular); + } + return; +} #}}} + +# an ordered list of repos +sub repolist { #{{{ + my @list; + foreach my $topdir (sort keys %config) { + foreach my $subdir (sort keys %{$config{$topdir}}) { + push @list, { + topdir => $topdir, + subdir => $subdir, + order => $config{$topdir}{$subdir}{order}, + }; + } + } + return sort { + $a->{order} <=> $b->{order} + || + $a->{topdir} cmp $b->{topdir} + || + $a->{subdir} cmp $b->{subdir} + } @list; +} #}}} + +# figure out which repos to act on +sub selectrepos { #{{{ + my @repos; + foreach my $repo (repolist()) { + my $topdir=$repo->{topdir}; + my $subdir=$repo->{subdir}; + + next if $subdir eq 'DEFAULT'; + my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir; + my $d=$directory; + $dir.="/" unless $dir=~/\/$/; + $d.="/" unless $d=~/\/$/; + next if $dir ne $d && $dir !~ /^\Q$d\E/; + if (defined $max_depth) { + my @a=split('/', $dir); + my @b=split('/', $d); + do { } while (@a && @b && shift(@a) eq shift(@b)); + next if @a > $max_depth || @b > $max_depth; + } + push @repos, [$dir, $topdir, $subdir]; + } + if (! @repos) { + # fallback to find a leaf repo + foreach my $repo (reverse repolist()) { + my $topdir=$repo->{topdir}; + my $subdir=$repo->{subdir}; + + next if $subdir eq 'DEFAULT'; + my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir; + my $d=$directory; + $dir.="/" unless $dir=~/\/$/; + $d.="/" unless $d=~/\/$/; + if ($d=~/^\Q$dir\E/) { + push @repos, [$dir, $topdir, $subdir]; + last; + } + } + $no_chdir=1; + } + return @repos; +} #}}} + +my %loaded; +sub loadconfig { #{{{ + my $f=shift; + + my @toload; + + my $in; + my $dir; + if (ref $f eq 'GLOB') { + $dir=""; + $in=$f; + } + else { + if (! -e $f) { + return; + } + + my $absf=abs_path($f); + if ($loaded{$absf}) { + return; + } + $loaded{$absf}=1; + + ($dir)=$f=~/^(.*\/)[^\/]+$/; + if (! defined $dir) { + $dir="."; + } + $dir=abs_path($dir)."/"; + + if (! exists $configfiles{$dir}) { + $configfiles{$dir}=$f; + } + + # copy in defaults from first parent + my $parent=$dir; + while ($parent=~s/^(.*\/)[^\/]+\/?$/$1/) { + if ($parent eq '/') { + $parent=""; + } + if (exists $config{$parent} && + exists $config{$parent}{DEFAULT}) { + $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} }; + last; + } + } + + print "mr: loading config $f\n" if $verbose; + open($in, "<", $f) || die "mr: open $f: $!\n"; + } + my @lines=<$in>; + close $in; + + my $section; + my $line=0; + while (@lines) { + $_=shift @lines; + $line++; + chomp; + next if /^\s*\#/ || /^\s*$/; + if (/^\[([^\]]*)\]\s*$/) { + $section=$1; + } + elsif (/^(\w+)\s*=\s*(.*)/) { + my $parameter=$1; + my $value=$2; + + # continued value + while (@lines && $lines[0]=~/^\s(.+)/) { + shift(@lines); + $line++; + $value.="\n$1"; + chomp $value; + } + + if ($parameter eq "include") { + print "mr: including output of \"$value\"\n" if $verbose; + unshift @lines, `$value`; + next; + } + + if (! defined $section) { + die "$f line $.: parameter ($parameter) not in section\n"; + } + if ($section ne 'ALIAS' && + ! exists $config{$dir}{$section} && + exists $config{$dir}{DEFAULT}) { + # copy in defaults + $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} }; + } + if ($section eq 'ALIAS') { + $alias{$parameter}=$value; + } + elsif ($parameter eq 'lib') { + $config{$dir}{$section}{lib}.=$value."\n"; + } + else { + $config{$dir}{$section}{$parameter}=$value; + if ($parameter =~ /.*_(.*)/) { + $knownactions{$1}=1; + } + else { + $knownactions{$parameter}=1; + } + if ($parameter eq 'chain' && + length $dir && $section ne "DEFAULT" && + -e $dir.$section."/.mrconfig") { + my $ret=system($value); + if ($ret != 0) { + if (($? & 127) == 2) { + print STDERR "mr: chain test interrupted\n"; + exit 2; + } + elsif ($? & 127) { + print STDERR "mr: chain test received signal ".($? & 127)."\n"; + } + } + else { + push @toload, $dir.$section."/.mrconfig"; + } + } + } + } + else { + die "$f line $line: parse error\n"; + } + } + + foreach (@toload) { + loadconfig($_); + } +} #}}} + +sub modifyconfig { #{{{ + my $f=shift; + # the section to modify or add + my $targetsection=shift; + # fields to change in the section + # To remove a field, set its value to "". + my %changefields=@_; + + my @lines; + my @out; + + if (-e $f) { + open(my $in, "<", $f) || die "mr: open $f: $!\n"; + @lines=<$in>; + close $in; + } + + my $formatfield=sub { + my $field=shift; + my @value=split(/\n/, shift); + + return "$field = ".shift(@value)."\n". + join("", map { "\t$_\n" } @value); + }; + my $addfields=sub { + my @blanks; + while ($out[$#out] =~ /^\s*$/) { + unshift @blanks, pop @out; + } + foreach my $field (sort keys %changefields) { + if (length $changefields{$field}) { + push @out, "$field = $changefields{$field}\n"; + delete $changefields{$field}; + } + } + push @out, @blanks; + }; + + my $section; + while (@lines) { + $_=shift(@lines); + + if (/^\s*\#/ || /^\s*$/) { + push @out, $_; + } + elsif (/^\[([^\]]*)\]\s*$/) { + if (defined $section && + $section eq $targetsection) { + $addfields->(); + } + + $section=$1; + + push @out, $_; + } + elsif (/^(\w+)\s*=\s(.*)/) { + my $parameter=$1; + my $value=$2; + + # continued value + while (@lines && $lines[0]=~/^\s(.+)/) { + shift(@lines); + $value.="\n$1"; + chomp $value; + } + + if ($section eq $targetsection) { + if (exists $changefields{$parameter}) { + if (length $changefields{$parameter}) { + $value=$changefields{$parameter}; + } + delete $changefields{$parameter}; + } + } + + push @out, $formatfield->($parameter, $value); + } + } + + if (defined $section && + $section eq $targetsection) { + $addfields->(); + } + elsif (%changefields) { + push @out, "\n[$targetsection]\n"; + foreach my $field (sort keys %changefields) { + if (length $changefields{$field}) { + push @out, $formatfield->($field, $changefields{$field}); + } + } + } + + open(my $out, ">", $f) || die "mr: write $f: $!\n"; + print $out @out; + close $out; +} #}}} + +sub dispatch { #{{{ + my $action=shift; + + # actions that do not operate on all repos + if ($action eq 'help') { + help(@ARGV); + } + elsif ($action eq 'config') { + config(@ARGV); + } + elsif ($action eq 'register') { + register(@ARGV); + } + + if (!$jobs || $jobs > 1) { + mrs($action, selectrepos()); + } + else { + foreach my $repo (selectrepos()) { + record($repo, action($action, @$repo)); + } + } +} #}}} + +sub help { #{{{ + exec($config{''}{DEFAULT}{help}) || die "exec: $!"; +} #}}} + +sub config { #{{{ + if (@_ < 2) { + die "mr config: not enough parameters\n"; + } + my $section=shift; + if ($section=~/^\//) { + # try to convert to a path relative to the config file + my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/; + $dir=abs_path($dir); + $dir.="/" unless $dir=~/\/$/; + if ($section=~/^\Q$dir\E(.*)/) { + $section=$1; + } + } + my %changefields; + foreach (@_) { + if (/^([^=]+)=(.*)$/) { + $changefields{$1}=$2; + } + else { + my $found=0; + foreach my $topdir (sort keys %config) { + if (exists $config{$topdir}{$section} && + exists $config{$topdir}{$section}{$_}) { + print $config{$topdir}{$section}{$_}."\n"; + $found=1; + last if $section eq 'DEFAULT'; + } + } + if (! $found) { + die "mr config: $section $_ not set\n"; + } + } + } + modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields; + exit 0; +} #}}} + +sub register { #{{{ + if (! $config_overridden) { + # Find the closest known mrconfig file to the current + # directory. + $directory.="/" unless $directory=~/\/$/; + my $foundconfig=0; + foreach my $topdir (reverse sort keys %config) { + next unless length $topdir; + if ($directory=~/^\Q$topdir\E/) { + $ENV{MR_CONFIG}=$configfiles{$topdir}; + $directory=$topdir; + $foundconfig=1; + last; + } + } + if (! $foundconfig) { + $directory=""; # no config file, use builtin + } + } + if (@ARGV) { + my $subdir=shift @ARGV; + if (! chdir($subdir)) { + print STDERR "mr register: failed to chdir to $subdir: $!\n"; + } + } + + $ENV{MR_REPO}=getcwd(); + my $command=findcommand("register", $ENV{MR_REPO}, $directory, 'DEFAULT', 0); + if (! defined $command) { + die "mr register: unknown repository type\n"; + } + + $ENV{MR_REPO}=~s/.*\/(.*)/$1/; + $command="set -e; ".$config{$directory}{DEFAULT}{lib}."\n". + "my_action(){ $command\n }; my_action ". + join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV); + print "mr register: running >>$command<<\n" if $verbose; + exec($command) || die "exec: $!"; +} #}}} + +# alias expansion and command stemming +sub expandaction { #{{{ + my $action=shift; + if (exists $alias{$action}) { + $action=$alias{$action}; + } + if (! exists $knownactions{$action}) { + my @matches = grep { /^\Q$action\E/ } + keys %knownactions, keys %alias; + if (@matches == 1) { + $action=$matches[0]; + } + elsif (@matches == 0) { + die "mr: unknown action \"$action\" (known actions: ". + join(", ", sort keys %knownactions).")\n"; + } + else { + die "mr: ambiguous action \"$action\" (matches: ". + join(", ", @matches).")\n"; + } + } + return $action; +} #}}} + +sub getopts { #{{{ + Getopt::Long::Configure("bundling", "no_permute"); + my $result=GetOptions( + "d|directory=s" => sub { $directory=abs_path($_[1]) }, + "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 }, + "v|verbose" => \$verbose, + "q|quiet" => \$quiet, + "s|stats" => \$stats, + "n|no-recurse:i" => \$max_depth, + "j|jobs:i" => \$jobs, + ); + if (! $result || @ARGV < 1) { + die("Usage: mr [-d directory] action [params ...]\n". + "(Use mr help for man page.)\n"); + } +} #}}} + +sub init { #{{{ + $SIG{INT}=sub { + print STDERR "mr: interrupted\n"; + exit 2; + }; + + # This can happen if it's run in a directory that was removed + # or other strangeness. + if (! defined $directory) { + die("mr: failed to determine working directory\n"); + } + # Make sure MR_CONFIG is an absolute path, but don't use abs_path since + # the config file might be a symlink to elsewhere, and the directory it's + # in is significant. + if ($ENV{MR_CONFIG} !~ /^\//) { + $ENV{MR_CONFIG}=getcwd()."/".$ENV{MR_CONFIG}; + } + # Try to set MR_PATH to the path to the program. + eval { + use FindBin qw($Bin $Script); + $ENV{MR_PATH}=$Bin."/".$Script; + }; +} #}}} + +sub main { #{{{ + getopts(); + init(); + loadconfig(\*DATA); + loadconfig($ENV{MR_CONFIG}); + #use Data::Dumper; print Dumper(\%config); + + my $action=expandaction(shift @ARGV); + dispatch($action); + showstats($action); + + if (@failed) { + exit 1; + } + elsif (! @ok && @skipped) { + exit 1; + } + else { + exit 0; + } +} #}}} + +# Finally, some useful actions that mr knows about by default. +# These can be overridden in ~/.mrconfig. +#DATA{{{ +__DATA__ +[ALIAS] +co = checkout +ci = commit +ls = list + +[DEFAULT] +order = 10 +lib = + error() { + echo "mr: $@" >&2 + exit 1 + } + warning() { + echo "mr (warning): $@" >&2 + } + info() { + echo "mr: $@" >&2 + } + hours_since() { + if [ -z "$1" ] || [ -z "$2" ]; then + error "mr: usage: hours_since action num" + fi + for dir in .git .svn .bzr CVS .hg _darcs; do + if [ -e "$MR_REPO/$dir" ]; then + flagfile="$MR_REPO/$dir/.mr_last$1" + break + fi + done + if [ -z "$flagfile" ]; then + error "cannot determine flag filename" + fi + delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"` + if [ "$delta" -lt "$2" ]; then + exit 0 + else + touch "$flagfile" + exit 1 + fi + } + +svn_test = test -d "$MR_REPO"/.svn +git_test = test -d "$MR_REPO"/.git +bzr_test = test -d "$MR_REPO"/.bzr +cvs_test = test -d "$MR_REPO"/CVS +hg_test = test -d "$MR_REPO"/.hg +darcs_test = test -d "$MR_REPO"/_darcs +git_bare_test = + test -d "$MR_REPO"/refs/heads && test -d "$MR_REPO"/refs/tags && + test -d "$MR_REPO"/objects && test -f "$MR_REPO"/config && + test "`GIT_CONFIG="$MR_REPO"/config git config --get core.bare`" = true + +svn_update = svn update "$@" +git_update = + if [ "$@" ]; then + git pull "$@" + else + git pull + fi +bzr_update = bzr merge "$@" +cvs_update = cvs update "$@" +hg_update = hg pull "$@" && hg update "$@" +darcs_update = darcs pull -a "$@" + +svn_status = svn status "$@" +git_status = git status "$@" || true +bzr_status = bzr status "$@" +cvs_status = cvs status "$@" +hg_status = hg status "$@" +darcs_status = darcs whatsnew -ls "$@" + +svn_commit = svn commit "$@" +git_commit = git commit -a "$@" && git push --all +bzr_commit = bzr commit "$@" && bzr push +cvs_commit = cvs commit "$@" +hg_commit = hg commit -m "$@" && hg push +darcs_commit = darcs record -a -m "$@" && darcs push -a + +git_record = git commit -a "$@" +bzr_record = bzr commit "$@" +hg_record = hg commit -m "$@" +darcs_record = darcs record -a -m "$@" + +svn_diff = svn diff "$@" +git_diff = git diff "$@" +bzr_diff = bzr diff "$@" +cvs_diff = cvs diff "$@" +hg_diff = hg diff "$@" +darcs_diff = darcs diff -u "$@" + +svn_log = svn log "$@" +git_log = git log "$@" +bzr_log = bzr log "$@" +cvs_log = cvs log "$@" +hg_log = hg log "$@" +darcs_log = darcs changes "$@" +git_bare_log = git log "$@" + +svn_register = + url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2` + if [ -z "$url" ]; then + error "cannot determine svn url" + fi + echo "Registering svn url: $url in $MR_CONFIG" + mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'" +git_register = + url="`LC_ALL=C git config --get remote.origin.url`" || true + if [ -z "$url" ]; then + error "cannot determine git url" + fi + echo "Registering git url: $url in $MR_CONFIG" + mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'" +bzr_register = + url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}'`" + if [ -z "$url" ]; then + error "cannot determine bzr url" + fi + echo "Registering bzr url: $url in $MR_CONFIG" + mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr clone '$url' '$MR_REPO'" +cvs_register = + repo=`cat CVS/Repository` + root=`cat CVS/Root` + if [ -z "$root" ]; then + error "cannot determine cvs root" + fi + echo "Registering cvs repository $repo at root $root" + mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'" +hg_register = + url=`hg showconfig paths.default` + echo "Registering mercurial repo url: $url in $MR_CONFIG" + mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'" +darcs_register = + url=`cat _darcs/prefs/defaultrepo` + echo "Registering darcs repository $url in $MR_CONFIG" + mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url' '$MR_REPO'" +git_bare_register = + url="`LC_ALL=C GIT_CONFIG=config git config --get remote.origin.url`" || true + if [ -z "$url" ]; then + error "cannot determine git url" + fi + echo "Registering git url: $url in $MR_CONFIG" + mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'" + +help = + if [ ! -e "$MR_PATH" ]; then + error "cannot find program path" + fi + tmp=$(mktemp -t mr.XXXXXXXXXX) || error "mktemp failed" + trap "rm -f $tmp" exit + pod2man -c mr "$MR_PATH" > "$tmp" || error "pod2man failed" + man -l "$tmp" || error "man failed" +list = true +config = + +ed = echo "A horse is a horse, of course, of course.." +T = echo "I pity the fool." +right = echo "Not found." +#}}} + +# vim:sw=8:sts=0:ts=8:noet diff --git a/bin/ngrok b/bin/ngrok new file mode 100755 index 00000000..f3cf76b9 Binary files /dev/null and b/bin/ngrok differ diff --git a/bin/panoptes-token b/bin/panoptes-token new file mode 100755 index 00000000..801a8158 --- /dev/null +++ b/bin/panoptes-token @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +require 'rubygems' +require 'panoptes-client' +require 'yaml' + +config = YAML.load(File.read(File.expand_path('~/.panoptes-secret.yml'))) +client = Panoptes::Client.new(auth: {client_id: config["id"], client_secret: config["secret"]}) +response = client.panoptes.connection.get('/me') +auth = response.env.request_headers["Authorization"] +token = auth.gsub(/Bearer (.*)/, "\\1") +puts token \ No newline at end of file diff --git a/bin/par b/bin/par new file mode 100755 index 00000000..b7950224 Binary files /dev/null and b/bin/par differ diff --git a/bin/pdf2pdf b/bin/pdf2pdf new file mode 100755 index 00000000..ce3feed8 --- /dev/null +++ b/bin/pdf2pdf @@ -0,0 +1,45 @@ +#!/bin/sh +# +# Convert a PDF to another PDF. This effectively strips out a lot of +# bogus stuff from most PDF files. + +gs=`which gs 2>/dev/null` +if [ ! -x "$gs" ]; then + echo "Error: install ghostscript first" >&2 + exit 1 +fi + +OPTS="" +pdfver="1.2" +while true; do + case "$1" in + --pdf-version) + shift + pdfver="$1" + ;; + -?*) OPTS="$OPTS $1";; + *) break;; + esac + shift +done + +if [ $# -eq 2 ]; then + outfile="$2" +elif [ $# -eq 1 ]; then + outfile="`basename \"$1\" .pdf`.new.pdf" +else + cat <&2 +Usage: pdf2pdf [--pdf-version (1.2|1.3|1.4)] [gs-options ...] [output.pdf|-] + +Converts a PDF from whatever PDF specification version it currently +exists as to the one specified by \`--pdf-version'. Default: 1.2 + +One side-effect of this conversion is the resulting document will have +the no-printing and no-copying flags removed in the output document if +they are set in the input document. +EOF + exit 1 +fi + +exec "$gs" $OPTIONS -q -dNOPAUSE -dBATCH -dSAFER -sDEVICE=pdfwrite \ + -dCompatibilityLevel="$pdfver" -sOutputFile="$outfile" -f "$1" diff --git a/bin/phttp b/bin/phttp new file mode 100755 index 00000000..f05e6cce --- /dev/null +++ b/bin/phttp @@ -0,0 +1,3 @@ +#!/bin/bash + +http $1 $2 "Accept:application/vnd.api+json; version=1" diff --git a/bin/printpdf b/bin/printpdf new file mode 100644 index 00000000..146b3c58 --- /dev/null +++ b/bin/printpdf @@ -0,0 +1,4 @@ +#!/bin/sh +a=$* +pdf2ps $a $a.ps +cat $a.ps | nc -q 3 129.125.183.99 9100 \ No newline at end of file diff --git a/bin/pvt b/bin/pvt new file mode 100755 index 00000000..fcdd5fbd --- /dev/null +++ b/bin/pvt @@ -0,0 +1,71 @@ +#!/usr/bin/env ruby + +require "rubygems" +require "bundler/setup" + +require "net/http" +require "uri" +require "cgi" +require "nokogiri" + +CONFIG_PATH = File.expand_path "~/.pvt" +FILTER_API_URL = "https://www.pivotaltracker.com:443/services/v3/projects/%s/stories?filter=%s" +FILTER_FORMAT = "mywork:%s state:started" + +# Check for config file +if not File.exists? CONFIG_PATH + $stderr.write "Could not find configuration at #{CONFIG_PATH}\n" + $stderr.write "See .pvt.sample for information on configuration\n" + exit 1 +end + +# Parse the configuration file +opts = {} + +open CONFIG_PATH do |config| + config.each_line do |line| + line.gsub!(/#.*$/, '') # Strip comments + line.strip! # Eliminate whitespace + + # Ignore empty lines + next if line.empty? + + name, value = line.split(':') + opts[name.to_sym] = value.strip + end +end + +# Construct the URL +filter = CGI.escape(FILTER_FORMAT % opts[:initials]) +filter_uri = URI.parse(FILTER_API_URL % [opts[:project_id], filter]) + +# Construct the client +client = Net::HTTP.new(filter_uri.host, filter_uri.port) +client.use_ssl = true + +# Grab the stories (XML) +begin + response = client.get("#{filter_uri.path}?#{filter_uri.query}", "X-TrackerToken" => opts[:token]) +rescue Exception => e + $stderr.write "Error while attempting to communicate with Pivotal\n" + $stderr.write "Details: #{e}\n" + exit 1 +end + +# Error handling +unless response.code.start_with? '2' + $stderr.write "Error response received from Pivotal\n" + $stderr.write "#{response.code}: #{response.message}\n" + exit 1 +end + +# Parse out the stories and write them to stdout +xml = Nokogiri::XML(response.body) +xml.css('story').each do |story| + id = story.css('id')[0].content + name = story.css('name')[0].content + + puts "#%-20s // %s" % [id, name] +end + +puts "" \ No newline at end of file diff --git a/bin/randomcommitmessage b/bin/randomcommitmessage new file mode 100755 index 00000000..c591d9d1 --- /dev/null +++ b/bin/randomcommitmessage @@ -0,0 +1,2 @@ +#!/bin/sh +cat $HOME/dotfiles/bin/commit_messages.txt | perl -MList::Util=shuffle -e 'print shuffle()' | head -1 diff --git a/bin/rsql-panoptes-production b/bin/rsql-panoptes-production new file mode 100755 index 00000000..6841334e --- /dev/null +++ b/bin/rsql-panoptes-production @@ -0,0 +1,12 @@ +#!/usr/bin/env ruby + +require 'pry' +require 'pry-byebug' +require 'sequel' + +DB = Sequel.connect(adapter: 'postgres', user: 'readonly', password: 'IXTLHks7Zy8>ACjT-zQKxCjmyOYgnNA>GSHXcfIyjcUgeGH5Wg', + host: 'panoptes-production.cezuuccr9cw6.us-east-1.rds.amazonaws.com', database: 'panoptes') +DB.extension :pg_json, :pg_array + +binding.pry +puts 'done' \ No newline at end of file diff --git a/bin/run-command-on-git-revisions b/bin/run-command-on-git-revisions new file mode 100755 index 00000000..be1c8f3b --- /dev/null +++ b/bin/run-command-on-git-revisions @@ -0,0 +1,56 @@ +#!/bin/bash +# +# This script runs a given command over a range of Git revisions. Note that it +# will check past revisions out! Exercise caution if there are important +# untracked files in your working tree. +# +# This came from Gary Bernhardt's dotfiles: +# https://github.com/garybernhardt/dotfiles +# +# Example usage: +# $ run-command-on-git-revisions origin/master master 'python runtests.py' + +set -e + +start_ref=$1 +end_ref=$2 +test_command=$3 + +main() { + enforce_usage + run_tests +} + +enforce_usage() { + if [ -z "$test_command" ]; then + usage + exit $E_BADARGS + fi +} + +usage() { + echo "usage: `basename $0` start_ref end_ref test_command" +} + +run_tests() { + revs=`log_command git rev-list --reverse ${start_ref}..${end_ref}` + + for rev in $revs; do + #echo "Checking out: $(git log --oneline -1 $rev)" + + echo -n `git log --pretty=format:'%ci' -1 $rev` + echo -n ',' + git checkout --quiet $rev + $test_command + git clean -fd + done + log_command git checkout $end_ref + echo "OK for all revisions!" +} + +log_command() { + echo "=> $*" >&2 + eval $* +} + +main \ No newline at end of file diff --git a/bin/s3cat b/bin/s3cat new file mode 100755 index 00000000..ec408f85 --- /dev/null +++ b/bin/s3cat @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +path=$1 + +if [[ "$path" == */ ]] +then + echo "Getting file listing for $path" + exec aws s3 ls $path +else + aws s3 cp $path - +fi diff --git a/bin/s3edit b/bin/s3edit new file mode 100755 index 00000000..abf1f78e --- /dev/null +++ b/bin/s3edit @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +path=$1 + +if [[ "$path" == */ ]] +then + echo "Getting file listing for $path" + exec aws s3 ls $path +else + tempfile=$(mktemp) + trap "rm -f $tempfile" INT TERM HUP EXIT + + echo "Downloading to $tempfile" + aws s3 cp $path $tempfile + + "${EDITOR:-vi}" $tempfile + + aws s3 cp --sse aws:kms $tempfile $path +fi diff --git a/bin/slurpmail b/bin/slurpmail new file mode 100755 index 00000000..e69db162 --- /dev/null +++ b/bin/slurpmail @@ -0,0 +1,19 @@ +#!/bin/sh + + +# First check for new mail. Store this to notify later on. +NOTMUCH_MESSAGES=`/usr/local/bin/notmuch new` +NOTMUCH_HEAD=`echo $NOTMUCH_MESSAGES | head -1` +NOTMUCH_REST=`echo $NOTMUCH_MESSAGES | sed '1d'` + +# Sync maildir flags with notmuch tags +/home/marten/bin/notmuchsync -r + +# Add/remove tags +/home/marten/bin/autotag + +# Sync notmuch tags to maildir flags +/usr/local/bin/notmuch new +/home/marten/bin/notmuchsync -s + +notify-send "$NOTMUCH_HEAD" "$NOTMUCH_REST" diff --git a/bin/switch_to b/bin/switch_to new file mode 100755 index 00000000..7037ab8e --- /dev/null +++ b/bin/switch_to @@ -0,0 +1,2 @@ +#!/bin/sh +$HOME/work/code/switch_to/bin/switch_to $@ \ No newline at end of file diff --git a/bin/tekkit b/bin/tekkit new file mode 100755 index 00000000..14717b97 --- /dev/null +++ b/bin/tekkit @@ -0,0 +1,3 @@ +#!/bin/sh +java -Xmx1024M -Xms1024M -jar $HOME/bin/Tekkit.jar +#java -jar $HOME/bin/MagicLauncher.jar \ No newline at end of file diff --git a/bin/youtubedown b/bin/youtubedown new file mode 100755 index 00000000..55427d9e --- /dev/null +++ b/bin/youtubedown @@ -0,0 +1,4734 @@ +#!/usr/bin/perl -w +# Copyright © 2007-2016 Jamie Zawinski +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation. No representations are made about the suitability of this +# software for any purpose. It is provided "as is" without express or +# implied warranty. +# +# Given a YouTube, Vimeo, Tumblr, Vine, Instagram or Twitter video URL, +# downloads the corresponding MP4 file. The name of the file will be derived +# from the title of the video. +# +# --title "STRING" Use this as the title instead. +# --prefix "STRING" Prepend the title with this. +# --suffix Append the video ID to each written file name. +# --progress Show a textual progress bar for downloads. +# --bwlimit Nkbps Throttle download speed. +# +# --size Instead of downloading it all, print video dimensions. +# This requires "ffmpeg". +# +# --list List the underlying URLs of a playlist. +# --list --list List IDs and titles of a playlist. +# --size --size List the sizes of each video of a playlist. +# +# --no-mux Only download pre-muxed videos, instead of sometimes +# downloading separate audio and video files, then combining +# them afterward with "ffmpeg". If you specify this option, +# you probably can't download anything higher resolution +# than 720p. +# +# Note: if you have ffmpeg < 2.2, upgrade to something less flaky. +# +# For playlists, it will download each video to its own file. +# +# You can also use this as a bookmarklet: put it somewhere on your web server +# as a .cgi, then bookmark this URL: +# +# javascript:location='http://YOUR_SITE/youtubedown.cgi?url='+location +# +# or, the same thing but using a small popup window, +# +# javascript:window.open('http://YOUR_SITE/youtubedown.cgi?url='+location.toString().replace(/%26/g,'%2526').replace(/%23/g,'%2523'),'youtubedown','width=400,height=50,top=0,left='+((screen.width-400)/2)) +# +# +# When you click on that bookmarklet in your toolbar, it will give you +# a link on which you can do "Save Link As..." and be offered a sensible +# file name by default. +# +# Make sure you host that script on your *local machine*, because the entire +# video content will be proxied through the server hosting the CGI, and you +# don't want to effectively download everything twice. +# +# Created: 25-Apr-2007. + +require 5; +use diagnostics; +use strict; +use IO::Socket; +use IO::Socket::SSL; +use IPC::Open3; +use HTML::Entities; + +my $progname0 = $0; +my $progname = $0; $progname =~ s@.*/@@g; +my ($version) = ('$Revision: 1.828 $' =~ m/\s(\d[.\d]+)\s/s); + +# Without this, [:alnum:] doesn't work on non-ASCII. +use locale; +use POSIX qw(locale_h strftime); +setlocale(LC_ALL, "en_US"); + +my $verbose = 1; +my $append_suffix_p = 0; + +my $http_proxy = undef; + +$ENV{PATH} = "/opt/local/bin:$ENV{PATH}"; # for macports ffmpeg + +my @video_extensions = ("mp4", "flv", "webm"); + + +my $html_head = + ("\n" . + "\n" . + " \n" . + " \n" . + " \n" . + " \n" . + " \n"); +my $html_tail = " \n\n"; + + +# Anything placed on this list gets unconditionally deleted when this +# script exits, even if abnormally. This is how CGI-mode cleans up +# after itself. +# +my @rm_f = (); +END { unlink @rm_f if (@rm_f); } + +sub signal_cleanup($) { + my ($s) = @_; + print STDERR "$progname: SIG$s\n" if ($verbose > 1); + exit (1); # This causes END{} to run. +} + +$SIG{HUP} = \&signal_cleanup; +$SIG{INT} = \&signal_cleanup; +$SIG{QUIT} = \&signal_cleanup; +$SIG{ABRT} = \&signal_cleanup; +$SIG{KILL} = \&signal_cleanup; +$SIG{TERM} = \&signal_cleanup; + + +my $noerror = 0; + +sub error($) { + my ($err) = @_; + + utf8::decode ($err); # Pack multi-byte UTF-8 back into wide chars. + + if (defined ($ENV{HTTP_HOST})) { + $err =~ s/&/&/gs; + $err =~ s//>/gs; + + # $error_whiteboard kludge + $err =~ s/^\t//gm; + $err =~ s@\n\n(.*)\n\n@
$1
@gs; + # $err =~ s/\n/
/gs; + + $err = $html_head . '

ERROR: ' . $err . + $html_tail; + $err =~ s@()[^<>]*@$1$progname: Error@gsi; + + print STDOUT ("Content-Type: text/html\n" . + "Status: 500\n" . + "\n" . + $err); + die "$err\n" if ($verbose > 2); # For debugging CGI. + exit 1; + } elsif ($noerror) { + die "$err\n"; + } else { + print STDERR "$progname: $err\n"; + exit 1; + } +} + + +# For internal errors. +my $errorI = ("\n" . + "\n\tPlease report this URL to jwz\@jwz.org!" . + "\n\tBut make sure you have the latest version first:" . + "\n\thttps://www.jwz.org/hacks/#youtubedown" . + "\n"); +my $error_whiteboard = ''; # for signature diagnostics + +sub errorI($) { + my ($err) = @_; + if ($error_whiteboard) { + $error_whiteboard =~ s/^/\t/gm; + $err .= "\n\n" . $error_whiteboard; + } + $err .= $errorI; + error ($err); +} + + +sub url_quote($) { + my ($u) = @_; + $u =~ s|([^-a-zA-Z0-9.\@/_\r\n])|sprintf("%%%02X", ord($1))|ge; + return $u; +} + +sub url_unquote($) { + my ($u) = @_; + $u =~ s/[+]/ /g; + $u =~ s/%([a-z0-9]{2})/chr(hex($1))/ige; + return $u; +} + +# Converts &, <, >, " and any UTF8 characters to HTML entities. +# Does not convert '. +# +sub html_quote($) { + my ($s) = @_; + return HTML::Entities::encode_entities ($s, + # Exclude "=042 &=046 <=074 >=076 + '^ \t\n\040\041\043-\045\047-\073\075\077-\176'); +} + +# Convert any HTML entities to Unicode characters. +# +sub html_unquote($) { + my ($s) = @_; + return HTML::Entities::decode_entities ($s); +} + + +sub fmt_size($) { + my ($size) = @_; + return ($size > 1024*1024 ? sprintf ("%.0f MB", $size/(1024*1024)) : + $size > 1024 ? sprintf ("%.0f KB", $size/1024) : + "$size bytes"); +} + +sub fmt_bps($) { # bits per sec, not bytes + my ($bps) = @_; + return ($bps > 1024*1024 ? sprintf ("%.1f Mbps", $bps/(1024*1024)) : + $bps > 1024 ? sprintf ("%.1f Kbps", $bps/1024) : + "$bps bps"); +} + + +my $progress_ticks = 0; +my $progress_time = 0; +my $progress_rubout = ''; +my $progress_last = 0; + +sub draw_progress($;$$$) { + my ($ratio, $bps, $eof, $cgi_p) = @_; # bits per sec, not bytes + + my $cols = ($cgi_p ? 100 : 64); + my $ticks = int($cols * $ratio); + my $cursep = !($verbose > 4) && (($ENV{TERM} || 'dumb') ne 'dumb'); + + my $now = time(); + + return if ($progress_time == $now && !$eof); + + if ($cgi_p) { # See comment on "X-Heartbeat" in do_cgi(). + while ($ticks > $progress_ticks) { + print STDOUT "."; + $progress_ticks++; + } + $progress_time = $now; + $progress_ticks = 0 if ($eof); + return; + } + + if ($now > $progress_last) { + $progress_last = $now; + my $pct = sprintf("%3d%% %s", 100 * $ratio, fmt_bps ($bps || 0)); + $pct =~ s/^ /. /s; + my $L = length($pct); + my $OL = length($progress_rubout); + print STDERR $progress_rubout if ($OL && $cursep); # erase previous pct + $progress_rubout = "\b" x $L; + while ($ticks > $progress_ticks) { + print STDERR "."; + $progress_ticks++; + } + print STDERR $pct; + my $L2 = $OL - $L; # If the current pct is shorter, clear to EOL + print STDERR ((' ' x $L2) . ("\b" x $L2)) + if ($L2 > 0 && $cursep); + print STDERR "\n" unless ($cursep); + } + print STDERR "\r" . (' ' x ($cols + 4)) . "\r" # erase line + if ($eof && $cursep); + $progress_time = $now; + $progress_ticks = 0 if ($eof || !$cursep); + $progress_rubout = '' if ($eof); + $progress_last = 0 if ($eof); +} + + + +# Like sysread() but timesout and return undef if no data received in N secs. +# The buffer argument is a reference, not a string. +# +sub sysread_timeout($$$$) { + my ($S, $buf, $bufsiz, $timeout) = @_; + my $read = undef; + my $err = "$progname: $timeout seconds with no data\n"; + eval { + local $SIG{ALRM} = sub { print STDERR $err; die ($err) }; + alarm ($timeout); + $read = sysread ($S, $$buf, $bufsiz); + alarm (0); + }; + if ($@) { + die unless ($@ eq $err); + } + return $read; +} + + +# Loads the given URL, returns: $http, $head, $body, +# $bytes_read, $content_length. +# +sub get_url_1($;$$$$$$) { + my ($url, $referer, $to_file, $bwlimit, $start_byte, $max_bytes, + $progress_p) = @_; + + error ("not an HTTP URL, try rtmpdump: $url") if ($url =~ m@^rtmp@i); + error ("not an HTTP URL: $url") unless ($url =~ m@^(https?|feed)://@i); + + my $sysread_timeout = 30; + + my ($proto, undef, $host, $path) = split(m@/@, $url, 4); + $path = "" unless defined ($path); + $path = "/$path"; + + my $port = ($host =~ s@:([^:/]*)$@@gs ? $1 : undef); + + $port = ($proto eq 'https:' ? 443 : 80) unless $port; + + my $oport = $port; + my $ohost = $host; + + # If we were just using LWP::UserAgent, we wouldn't have to do all of this + # proxy crap (that library already handles it) but we use byte-ranges, + # don't always read a URL to completion, and want to display progress bars. + # LWP::UserAgent doesn't provide easily-usable APIs for that case, so, we + # hack the TCP connections more-or-less directly. + + if ($http_proxy) { + (undef, undef, $host, undef) = split(m@/@, $http_proxy, 4); + $port = ($host =~ s@:([^:/]*)$@@gs ? $1 : undef); + + # RFC7230: Full url "absolute-form" works, but the "origin-form" of + # a path (e.g. "/foo.txt") hides proxy use when using SSL. + $path = $url unless ($proto eq 'https:'); + } + + # This is the connection to the proxy (if using one) or the target host. + # + my $S = IO::Socket::INET->new (PeerAddr => $host, + PeerPort => $port, + Proto => 'tcp', + Type => SOCK_STREAM, + ); + error ("connect: $host:$port: $!") unless $S; + + # If we are loading https through a proxy, put the proxy into tunnel mode. + # + # Note: this fails if the proxy *itself* is on https. In that case, we + # would need to bring up SSL on the connection to the proxy, then again + # on the interior CONNECT stream. + # + if ($http_proxy && $proto eq 'https:') { + my $hd = "CONNECT $ohost:$oport HTTP/1.0\r\n\r\n"; + my @ha = split(/\r?\n/, $hd); + + if ($verbose > 2) { + print STDERR " proxy send P " . length($hd) ." bytes\n"; + foreach (@ha) { print STDERR " ==> $_\n"; } + print STDERR " ==>\n"; + } + print $S $hd; + + my $bufsiz = 1024; + my $buf = ''; + $hd = ''; + + while (! $hd) { + if ($buf =~ m/^(.*?)\r?\n\r?\n(.*)$/s) { + ($hd, $buf) = ($1, $2); + last; + } + my $buf2 = ''; + my $size = sysread_timeout ($S, \$buf2, $bufsiz, $sysread_timeout); + print STDERR " proxy read P $size bytes\n" + if (defined($size) && $verbose > 2); + last if (!defined($size) || $size <= 0); + $buf .= $buf2; + } + @ha = split (/\r?\n/, $hd); + if ($verbose > 2) { + foreach (@ha) { print STDERR " <== $_\n"; } + print STDERR " <==\n"; + } + error ("HTTP proxy error: $ha[0]\n") + unless ($ha[0] =~ m@^HTTP/[0-9.]+ 20\d@si); + } + + # Some proxies suck, expect bad behavior like sending a body + $S->flush() || error ("Could not flush proxy socket: $!"); + + # Now we have a stream to the target host (which may be proxied or direct). + # Put that stream into SSL mode if the target host is https. + # + if ($proto eq 'https:') { + IO::Socket::SSL->start_SSL ($S, + # Ignore certificate errors + verify_hostname => 0, + SSL_verify_mode => 0, + SSL_verifycn_scheme => 'none', + # set hostname for SNI + SSL_hostname => $ohost, + ) + || error ("socket: SSL: $!"); + } + + $S->autoflush(1); + + my $user_agent = "$progname/$version"; + + # Finally we are in straight HTTP land (but $path may be either "absolute" + # or "origin" form, as above.) + # + my $hdrs = ("GET " . $path . " HTTP/1.0\r\n" . + "Host: $ohost\r\n" . + "User-Agent: $user_agent\r\n"); + + my @extra_headers = (); + push @extra_headers, "Referer: $referer" if ($referer); + + # If we're only reading the first N bytes, don't ask for more. + # + if ($start_byte || $max_bytes) { + # + # 0-0 means return the first byte. + # 0-1 means return the first two bytes. + # 0- is the same as 0-EOF. + # 1- is the same as 1-EOF. + # + $start_byte = 0 unless defined ($start_byte); + my $end_byte = ($max_bytes + ? $start_byte + $max_bytes - 1 + : ""); + push @extra_headers, "Range: bytes=$start_byte-$end_byte"; + } + + $hdrs .= join ("\r\n", @extra_headers, '') if (@extra_headers); + $hdrs .= "\r\n"; + + if ($verbose > 3) { + foreach (split('\r?\n', $hdrs)) { + print STDERR " ==> $_\n"; + } + } + print $S $hdrs; + + # Using max SSL frame sized (16384) chunks improves performance by + # avoiding SSL frame splitting on sysread() of IO::Socket::SSL. + my $bufsiz = 16384; + my $buf = ''; + + $bufsiz = int ($bwlimit / 8) + if ($bwlimit && int($bwlimit / 8) < $bufsiz); + + # Read network buffers until we have the HTTP response line. + my $http = ''; + while (! $http) { + if ($buf =~ m/^(.*?)\n(.*)$/s) { + ($http, $buf) = ($1, $2); + last; + } + my $buf2 = ''; + my $size = sysread_timeout ($S, \$buf2, $bufsiz, $sysread_timeout); + print STDERR " read A $size\n" if ($verbose > 5); + last if (!defined($size) || $size <= 0); + $buf .= $buf2; + } + + $_ = $http; + s/[\r\n]+$//s; + print STDERR " <== $_\n" if ($verbose > 3); + + # If the URL isn't there, don't write to the file. + $to_file = undef unless ($http =~ m@^HTTP/[0-9.]+ 20\d@si); + + # Read network buffers until we have the response header block. + my $head = ''; + while (! $head) { + if ($buf =~ m/^(.*?)\r?\n\r?\n(.*)$/s) { + ($head, $buf) = ($1, $2); + last; + } + my $buf2 = ''; + my $size = sysread_timeout ($S, \$buf2, $bufsiz, $sysread_timeout); + print STDERR " read B $size\n" if ($verbose > 5); + last if (!defined($size) || $size <= 0); + $buf .= $buf2; + } + + if ($verbose > 3) { + foreach (split(/\n/, $head)) { + s/\r$//gs; + print STDERR " <== $_\n"; + } + print STDERR " <== \n"; + } + + # Note that if we requested a byte range, this is the length of the range, + # not the length of the full document. + my ($cl) = ($head =~ m@^Content-Length: \s* (\d+) @mix); + + if ($start_byte) { + my ($s, $e, $cl2) = ($head =~ m@^Content-Range: + \s* bytes \s+ + (\d+) \s* - \s* + (\d+) \s* / \s* + (\d+) \s* $@mix); + error ("attempting to resume download failed: $url") unless defined($cl2); + error ("attempting to resume download failed: wrong start byte: $url") + unless ($s == $start_byte); + + # In byte-ranges mode, Content-Length is the length of the chunk being + # returned; the document content-length is in the Content-Range header. + $cl = $cl2; + } + + $cl = $start_byte + $max_bytes + if ($cl && $max_bytes && $start_byte + $max_bytes < $cl); + + $progress_p = 0 if (($progress_p || '') ne 'cgi' && ($cl || 0) <= 0); + + my $out; + + if ($to_file) { + + # No, don't do this. + # utf8::encode($to_file); # Unpack wide chars into multi-byte UTF-8. + + if ($to_file eq '-') { + open ($out, ">-"); + binmode ($out); + } elsif ($start_byte) { + open ($out, '>>:raw', $to_file) || error ("$to_file: $!"); + } else { + open ($out, '>:raw', $to_file) || error ("$to_file: $!"); + } + + # If we're proxying a download, also copy the document's headers. + # + if ($to_file eq '-') { + + # Maybe if we nuke the Content-Type, that will stop Safari from + # opening the file by default. Answer: nope. + # $head =~ s@^(Content-Type:)[^\r\n]+@$1 application/octet-stream@gmi; + # Ok, maybe if we mark it as an attachment? Answer: still nope. + # $head = "Content-Disposition: attachment\r\n" . $head; + + print $out $head . "\n\n"; + } + } + + my $bytes = 0; + my $body = ''; + + my $cgi_p = ($progress_p && $progress_p eq 'cgi'); + my $start_time = time(); + my $actual_bits_per_sec = 0; + + while (1) { + if ($buf eq '') { + + my $size = sysread_timeout ($S, \$buf, $bufsiz, $sysread_timeout); + + print STDERR " read C " . ($size || 'undef') . + " (" . ($start_byte + $bytes) . ")\n" + if ($verbose > 5); + last if (!defined($size) || $size <= 0); + } + + if ($to_file) { + print $out $buf; + } else { + $body .= $buf; + } + + $bytes += length($buf); + $buf = ''; + + my $now = time(); + my $elapsed = $now - $start_time; + $actual_bits_per_sec = $bytes * 8 / ($elapsed <= 0 ? 1 : $elapsed); + + draw_progress (($start_byte + $bytes) / $cl, + $actual_bits_per_sec, 0, $cgi_p) + if ($progress_p); + + # If we do a read while at EOF, sometimes Youtube hangs for ~30 seconds + # before sending back the EOF, so just stop reading as soon as we have + # reached the Content-Length or $max_bytes. + # + if ($cl && $start_byte + $bytes >= $cl) { + print STDERR " EOF (" . ($start_byte + $bytes) . " >= $cl)\n" + if ($verbose > 5); + last; + } + + # If we're throttling our download speed, and we went over, hang back. + # + if ($bwlimit) { + my $tick = 0.1; + my $paused = 0; + while (1) { + last if ($actual_bits_per_sec <= $bwlimit); + select (undef, undef, undef, $tick); + $paused += $tick; + $now = time(); + $elapsed = $now - $start_time; + + #### It would be better for this to be measured over the last few + #### seconds, rather than measured from the beginning of the download, + #### so that a network drop doesn't cause it to try and "catch up". + + $actual_bits_per_sec = $bytes * 8 / ($elapsed <= 0 ? 1 : $elapsed); + print STDERR "$progname: bwlimit: delay $paused\n" if ($verbose > 5); + } + } + } + draw_progress (($cl ? ($start_byte + $bytes) / $cl : 0), + $actual_bits_per_sec, 1, $cgi_p) + if ($progress_p); + + if ($to_file) { + close $out || error ("$to_file: $!"); + } + + if ($verbose > 3) { + if ($to_file) { + print STDERR " <== [ body ]: $bytes bytes to file \"$to_file\"\n"; + } else { + print STDERR " <== [ body ]: $bytes bytes\n"; + if ($verbose > 4 && + $head =~ m@^Content-Type: *(text/|application/(json|x-www-))@mi) { + foreach (split(/\n/, $body)) { + s/\r$//gs; + print STDERR " <== $_\n"; + } + } + } + } + + close $S; + + if (!$http) { + error ("null response: $url"); + } + + # Check to see if a network failure truncated the file and warn. + # Caller will then resume the download using byte ranges. + # + if ($to_file && + $cl && + $start_byte + $bytes < $cl-1) { + my $pct = int (100 * ($start_byte + $bytes) / $cl); + $pct = sprintf ("%.2f", 100 * $bytes / $cl) if ($pct == 100); + print STDERR "$progname: got only $pct% (" . + ($start_byte + $bytes) . " / $cl)" . + " of \"$to_file\", resuming...\n" + if ($verbose); + } + + return ($http, $head, $body, $bytes, $cl); +} + + +# Loads the given URL, returns: $http, $head, $body. +# If the connection dropped, try to resume it. +# +sub get_url_2($;$$$$$) { + my ($url, $referer, $to_file, $bwlimit, $max_bytes, $progress_p) = @_; + + my $total_bytes = 0; + my $start_byte = 0; + my $max_errors = 10; + my $errors = 0; + + while (1) { + my ($http, $head, $body, $bytes, $cl) = + get_url_1 ($url, $referer, $to_file, $bwlimit, $start_byte, $max_bytes, + $progress_p); + $total_bytes += $bytes; + $max_bytes -= $bytes if defined($max_bytes); + + if (!defined($cl) || $total_bytes >= $cl) { + return ($http, $head, $body); + } + + $start_byte = $total_bytes; + + # Avoid infinite loop: bail if we get 0 bytes back a few times in a row. + if ($bytes > 0) { + $errors = 0; + } else { + if ($errors++ >= $max_errors) { + unlink ($to_file) if $to_file; + error ("unable to resume download" . + ($to_file ? " of $to_file" : "") . + " (after $errors tries)"); + } + } + + } +} + + +# Loads the given URL, processes redirects. +# Returns: $http, $head, $body, $final_redirected_url. +# +sub get_url($;$$$$$) { + my ($url, $referer, $to_file, $bwlimit, $max_bytes, $progress_p) = @_; + + print STDERR "$progname: GET $url\n" if ($verbose > 2); + + my $orig_url = $url; + my $redirect_count = 0; + my $max_redirects = 20; + + do { + my ($http, $head, $body) = + get_url_2 ($url, $referer, $to_file, $bwlimit, $max_bytes, $progress_p); + + $http =~ s/[\r\n]+$//s; + + if ( $http =~ m@^HTTP/[0-9.]+ 30[123]@ ) { + $_ = $head; + + my ( $location ) = m@^location:[ \t]*(.*)$@im; + if ( $location ) { + $location =~ s/[\r\n]$//; + + print STDERR "$progname: redirect from $url to $location\n" + if ($verbose > 3); + + $referer = $url; + $url = $location; + + if ($url =~ m@^/@) { + $referer =~ m@^(https?://[^/]+)@i; + $url = $1 . $url; + } elsif (! ($url =~ m@^[a-z]+:@i)) { + $_ = $referer; + s@[^/]+$@@g if m@^https?://[^/]+/@i; + $_ .= "/" if m@^https?://[^/]+$@i; + $url = $_ . $url; + } + + } else { + error ("no Location with \"$http\""); + } + + error ("too many redirects ($max_redirects) from $orig_url") + if ($redirect_count++ > $max_redirects); + + } else { + return ($http, $head, $body, $url); + } + } while (1); +} + + +sub check_http_status($$$$) { + my ($id, $url, $http, $err_p) = @_; + return 1 if ($http =~ m@^HTTP/[0-9.]+ 20\d@si); + errorI ("$id: $http: $url") if ($err_p > 1 && $verbose > 0); + error ("$id: $http: $url") if ($err_p); + return 0; +} + + +# Runs ffmpeg to determine dimensions of the given video file. +# (We only do this in verbose mode, or with --size.) +# +sub video_file_size($) { + my ($file) = @_; + + # Sometimes ffmpeg gets stuck in a loop. + # Don't let it run for more than N CPU-seconds. + my $limit = "ulimit -t 10"; + + my $size = (stat($file))[7]; + + my @cmd = ("ffmpeg", + "-i", $file, + "-vframes", "0", + "-f", "null", + "/dev/null"); + print STDERR "\n$progname: exec: '" . join("' '", @cmd) . "'\n" + if ($verbose > 3); + my $result = ''; + { + my ($in, $out, $err); + $err = Symbol::gensym; + my $pid = eval { open3 ($in, $out, $err, @cmd) }; + + # If ffmpeg doesn't exist, or dumps core, just ignore it. + # There's nothing we can do about it anyway. + if ($pid) { + close ($in); + close ($out); + local $/ = undef; # read entire file + while (<$err>) { + $result .= $_; + } + waitpid ($pid, 0); + } + } + + print STDERR "\n$result\n" if ($verbose > 3); + + my ($w, $h, $abr) = (0, 0, 0); + + ($w, $h) = ($1, $2) + if ($result =~ m/^\s*Stream \#.* Video:.* (\d+)x(\d+),? /m); + $abr = $1 + if ($result =~ m@^\s*Duration:.* bitrate: ([\d.]+ *[kmb/s]+)@m); + + $abr =~ s@/s$@ps@si; + + # I don't understand why ffmpeg will say different things for the + # complete file, versus for the first 380 KB of the file, e.g.: + # + # Duration: 00:06:41.75, start: 0.000000, bitrate: 7 kb/s + # Duration: 00:06:41.75, start: 0.000000, bitrate: 133 kb/s + + return ($w, $h, $size, $abr); +} + + +sub which($) { + my ($cmd) = @_; + foreach my $dir (split (/:/, $ENV{PATH})) { + my $cmd2 = "$dir/$cmd"; + return $cmd2 if (-x "$cmd2"); + } + return undef; +} + +# When MacOS web browsers download a file, they write metadata into the +# file's extended attributes saying where and when it was downloaded, +# which can be seen in "Get Info" in the Finder. We do that too, to +# make it easier to figure out the original URL that a video file came +# from. +# +# To extract it: +# +# xattr -px com.apple.metadata:kMDItemWhereFroms FILE | +# xxd -r -p | plutil -convert xml1 - -o - +# +# On Linux systems, freedesktop.org proposes "user.xdg.origin.url". +# That's what "curl --xattr" does. So we write that too. +# +# xattr -p user.xdg.origin.url FILE +# +# Unfortunately, in CGI-mode, the file is actually being downloaded by +# the browser itself, so the metadata URL that gets written is the +# youtubedown.cgi URL. The original URL info is still buried in there, +# but it's messier. +# +sub write_file_metadata_url($$$) { + my ($file, $id, $url) = @_; + + my $now = time(); + + my $xattr = which ("xattr"); + my $plutil = which ("plutil"); + my $mp4tags = which ("mp4tags"); # port install mp4v2 + + my $added = 0; + my $ok = 1; + + if ($xattr) { + my $date = strftime ('%Y-%m-%dT%H:%M:%SZ', gmtime($now)); + + my $plhead = ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" . + "<!DOCTYPE plist PUBLIC" . + " \"-//Apple//DTD PLIST 1.0//EN\"" . + " \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" . + "<plist version=\"1.0\">\n"); + my $date_plist = ($plhead . + "<array>\n" . + "\t<date>$date</date>\n" . + "</array>\n" . + "</plist>"); + my $url_plist = ($plhead . + "<array>\n" . + "\t<string>" . html_quote($url) . "</string>\n" . + "</array>\n" . + "</plist>"); + + # Convert the plists to binary form if possible. Probably not strictly + # necessary. + # + if ($plutil) { + foreach my $s ($date_plist, $url_plist) { + my ($in, $out, $err); + $err = Symbol::gensym; + my $pid = eval { open3 ($in, $out, $err, + ($plutil, + '-convert', 'binary1', + '-', '-o', '-')) }; + # If there are errors converting the plist, just ignore them. + # It's not critical to convert. Though an error would be weird. + if (!$pid) { + print STDERR "$progname: $id: $plutil: $!\n"; + } else { + close ($err); + print $in $s; + close ($in); + + local $/ = undef; # read entire file + my $s2 = ''; + while (<$out>) { + $s2 .= $_; + } + $s = $s2 if $s2; + + waitpid ($pid, 0); + if ($?) { + my $exit_value = $? >> 8; + my $signal_num = $? & 127; + my $dumped_core = $? & 128; + print STDERR "$progname: $id: $plutil: core dumped!" + if ($dumped_core); + print STDERR "$progname: $id: $plutil: signal $signal_num!" + if ($signal_num); + print STDERR "$progname: $id: $plutil: exited with $exit_value!" + if ($exit_value); + } + } + } + } + + # I suppose setting the quarantine flag is also the proper thing to do. + # + my $quarantine = join (';', ('0002', # downloaded but never opened + sprintf("%08x", $now), + $progname, + "org.jwz.$progname")); + + # Convert the data to hex, to shield nulls from xattr. + # + my $hexurl = $url; + foreach ($date_plist, $url_plist, $quarantine, $hexurl) { + s/(.)/{ sprintf("%02X ", ord($1)); }/gsex; + } + + # Now run xattr for each attribute to dump it into the file. + # + error ("$file does not exist") unless (-f $file); + foreach ([$url_plist, 'com.apple.metadata:kMDItemWhereFroms'], + [$date_plist, 'com.apple.metadata:kMDItemDownloadedDate'], + [$quarantine, 'com.apple.quarantine'], + [$hexurl, 'user.xdg.origin.url']) { + my ($val, $key) = @$_; + my @cmd = ($xattr, "-w", "-x", $key, $val, $file); + print STDERR "\n$progname: exec: '" . join("' '", @cmd) . "'\n" + if ($verbose > 3); + system (@cmd); + $added = 1; + if ($?) { + $ok = 0; + my $exit_value = $? >> 8; + my $signal_num = $? & 127; + my $dumped_core = $? & 128; + print STDERR "$progname: $id: $cmd[0]: core dumped!\n" + if ($dumped_core); + print STDERR "$progname: $id: $cmd[0]: signal $signal_num!\n" + if ($signal_num); + print STDERR "$progname: $id: $cmd[0]: exited with $exit_value!\n" + if ($exit_value); + } + } + } elsif ($verbose > 1) { + print STDERR "$progname: $id: no metadata: xattr not found on \$PATH\n"; + } + + + # If we can, also store the URL inside the file's metadata tags. + # This shows up in the "Video / Description" field in iTunes + # rather than in "Info / Comments". + # + if ($mp4tags && $file =~ m/\.mp4$/si) { + my @cmd = ($mp4tags, "-m", $url, $file); + print STDERR "\n$progname: exec: '" . join("' '", @cmd) . "'\n" + if ($verbose > 3); + + my ($in, $out, $err); + $err = Symbol::gensym; + my $pid = eval { open3 ($in, $out, $err, @cmd) }; + $added = 1; + if (!$pid) { + print STDERR "$progname: $id: $cmd[0]: $!\n"; + $ok = 0; + } else { + close ($in); + close ($out); + close ($err); + + waitpid ($pid, 0); + if ($?) { + $ok = 0; + my $exit_value = $? >> 8; + my $signal_num = $? & 127; + my $dumped_core = $? & 128; + + if ($verbose) { + # mp4tags fucks up not-infrequently. Be quieter about it. + print STDERR "$progname: $id: $cmd[0]: core dumped!\n" + if ($dumped_core); + print STDERR "$progname: $id: $cmd[0]: signal $signal_num!\n" + if ($signal_num); + print STDERR "$progname: $id: $cmd[0]: exited with $exit_value!\n" + if ($exit_value); + } + } + } + } + + print STDERR "$progname: $id: added metadata\n" + if ($added && $ok && $verbose > 1); +} + + +# Downloads the first 380 KB of the URL, then runs ffmpeg to +# find out the dimensions of the video. +# +sub video_url_size($$;$$) { + my ($id, $url, $ct, $bwlimit) = @_; + + my $tmp = $ENV{TMPDIR} || "/tmp"; + my $ext = content_type_ext ($ct || ''); + my $file = sprintf("$tmp/$progname-%08x.$ext", rand(0xFFFFFFFF)); + unlink $file; + push @rm_f, $file; + + # Need a lot of data to get size from 1080p. + # + # This used to be 320 KB, but I see 640x360 140 MB videos where we can't + # get the size without 680 KB. + # + # And now I see a 624 x 352, 180 MB, 50 minute video that gets + # "error reading header: -541478725" unless we read 910 KB. + # + my $bytes = 910 * 1024; + + my ($http, $head, $body) = get_url ($url, undef, $file, $bwlimit, $bytes); + check_http_status ($id, $url, $http, 2); # internal error if still 403 + + ($ct) = ($head =~ m@^Content-Type: \s* ( [^\s;]+ ) @mix); + my ($size) = ($head =~ m@^Content-Range: \s* bytes \s+ [-\d]+ / (\d+) @mix); + ($size) = ($head =~ m@^Content-Length: \s* (\d+) @mix) + unless $size; + + errorI ("$id: expected audio or video, got \"$ct\" in $url") + if ($ct =~ m/text/i); + + $size = -1 unless defined($size); # WTF? + + my ($w, $h, undef, $abr) = video_file_size ($file); + unlink $file; + + return ($w, $h, $size, $abr); +} + + +# 24-Jun-2013: When use_cipher_signature=True, the signature must be +# translated from lengths ranging from 82 to 88 back down to the +# original, unciphered length of 81 (40.40). +# +# This is not crypto or a hash, just a character-rearrangement cipher. +# Total security through obscurity. Total dick move. +# +# The implementation of this cipher used by the Youtube HTML5 video +# player lives in a Javascript file with a name like: +# https://s.ytimg.com/yts/jsbin/html5player-VERSION.js +# or https://s.ytimg.com/yts/jsbin/player-VERSION/base.js +# where VERSION changes periodically. Sometimes the algorithm in the +# Javascript changes, also. So we name each algorithm according to +# the VERSION string, and dispatch off of that. Each time Youtube +# rolls out a new html5player file, we will need to update the +# algorithm accordingly. See guess_cipher(), below. Run this +# script with --guess if it has changed. Run --guess --guess from +# cron to have it tell you only when there's a new cipher. +# +# So far, only three commands are used in the ciphers, so we can represent +# them compactly: +# +# - r = reverse the string; +# - sN = slice from character N to the end; +# - wN = swap 0th and Nth character. +# +# The first number is the "sts" parameter from the html5player file, +# which is a timestamp or other ID code corresponding to this algorithm. +# Requesting get_video_info with that number will return URLs using the +# corresponding cipher algorithm. Except sometimes those old 'sts' values +# stop working! See below. +# +my %ciphers = ( + 'vflNzKG7n' => '135957536242 s3 r s2 r s1 r w67', # 30 Jan 2013 + 'vfllMCQWM' => '136089118952 s2 w46 r w27 s2 w43 s2 r', # 14 Feb 2013 + 'vflJv8FA8' => '136304655662 s1 w51 w52 r', # 11 Mar 2013 + 'vflR_cX32' => '1580 s2 w64 s3', # 11 Apr 2013 + 'vflveGye9' => '1582 w21 w3 s1 r w44 w36 r w41 s1', # 02 May 2013 + 'vflj7Fxxt' => '1583 r s3 w3 r w17 r w41 r s2', # 14 May 2013 + 'vfltM3odl' => '1584 w60 s1 w49 r s1 w7 r s2 r', # 23 May 2013 + 'vflDG7-a-' => '1586 w52 r s3 w21 r s3 r', # 06 Jun 2013 + 'vfl39KBj1' => '1586 w52 r s3 w21 r s3 r', # 12 Jun 2013 + 'vflmOfVEX' => '1586 w52 r s3 w21 r s3 r', # 21 Jun 2013 + 'vflJwJuHJ' => '1588 r s3 w19 r s2', # 25 Jun 2013 + 'vfl_ymO4Z' => '1588 r s3 w19 r s2', # 26 Jun 2013 + 'vfl26ng3K' => '15888 r s2 r', # 08 Jul 2013 + 'vflcaqGO8' => '15897 w24 w53 s2 w31 w4', # 11 Jul 2013 + 'vflQw-fB4' => '15902 s2 r s3 w9 s3 w43 s3 r w23', # 16 Jul 2013 + 'vflSAFCP9' => '15904 r s2 w17 w61 r s1 w7 s1', # 18 Jul 2013 + 'vflART1Nf' => '15908 s3 r w63 s2 r s1', # 22 Jul 2013 + 'vflLC8JvQ' => '15910 w34 w29 w9 r w39 w24', # 25 Jul 2013 + 'vflm_D8eE' => '15916 s2 r w39 w55 w49 s3 w56 w2', # 30 Jul 2013 + 'vflTWC9KW' => '15917 r s2 w65 r', # 31 Jul 2013 + 'vflRFcHMl' => '15921 s3 w24 r', # 04 Aug 2013 + 'vflM2EmfJ' => '15920 w10 r s1 w45 s2 r s3 w50 r', # 06 Aug 2013 + 'vflz8giW0' => '15919 s2 w18 s3', # 07 Aug 2013 + 'vfl_wGgYV' => '15923 w60 s1 r s1 w9 s3 r s3 r', # 08 Aug 2013 + 'vfl1HXdPb' => '15926 w52 r w18 r s1 w44 w51 r s1', # 12 Aug 2013 + 'vflkn6DAl' => '15932 w39 s2 w57 s2 w23 w35 s2', # 15 Aug 2013 + 'vfl2LOvBh' => '15933 w34 w19 r s1 r s3 w24 r', # 16 Aug 2013 + 'vfl-bxy_m' => '15936 w48 s3 w37 s2', # 20 Aug 2013 + 'vflZK4ZYR' => '15938 w19 w68 s1', # 21 Aug 2013 + 'vflh9ybst' => '15936 w48 s3 w37 s2', # 21 Aug 2013 + 'vflapUV9V' => '15943 s2 w53 r w59 r s2 w41 s3', # 27 Aug 2013 + 'vflg0g8PQ' => '15944 w36 s3 r s2', # 28 Aug 2013 + 'vflHOr_nV' => '15947 w58 r w50 s1 r s1 r w11 s3', # 30 Aug 2013 + 'vfluy6kdb' => '15953 r w12 w32 r w34 s3 w35 w42 s2', # 05 Sep 2013 + 'vflkuzxcs' => '15958 w22 w43 s3 r s1 w43', # 10 Sep 2013 + 'vflGNjMhJ' => '15956 w43 w2 w54 r w8 s1', # 12 Sep 2013 + 'vfldJ8xgI' => '15964 w11 r w29 s1 r s3', # 17 Sep 2013 + 'vfl79wBKW' => '15966 s3 r s1 r s3 r s3 w59 s2', # 19 Sep 2013 + 'vflg3FZfr' => '15969 r s3 w66 w10 w43 s2', # 24 Sep 2013 + 'vflUKrNpT' => '15973 r s2 r w63 r', # 25 Sep 2013 + 'vfldWnjUz' => '15976 r s1 w68', # 30 Sep 2013 + 'vflP7iCEe' => '15981 w7 w37 r s1', # 03 Oct 2013 + 'vflzVne63' => '15982 w59 s2 r', # 07 Oct 2013 + 'vflO-N-9M' => '15986 w9 s1 w67 r s3', # 09 Oct 2013 + 'vflZ4JlpT' => '15988 s3 r s1 r w28 s1', # 11 Oct 2013 + 'vflDgXSDS' => '15988 s3 r s1 r w28 s1', # 15 Oct 2013 + 'vflW444Sr' => '15995 r w9 r s1 w51 w27 r s1 r', # 17 Oct 2013 + 'vflK7RoTQ' => '15996 w44 r w36 r w45', # 21 Oct 2013 + 'vflKOCFq2' => '16 s1 r w41 r w41 s1 w15', # 23 Oct 2013 + 'vflcLL31E' => '16 s1 r w41 r w41 s1 w15', # 28 Oct 2013 + 'vflz9bT3N' => '16 s1 r w41 r w41 s1 w15', # 31 Oct 2013 + 'vfliZsE79' => '16010 r s3 w49 s3 r w58 s2 r s2', # 05 Nov 2013 + 'vfljOFtAt' => '16014 r s3 r s1 r w69 r', # 07 Nov 2013 + 'vflqSl9GX' => '16023 w32 r s2 w65 w26 w45 w24 w40 s2', # 14 Nov 2013 + 'vflFrKymJ' => '16023 w32 r s2 w65 w26 w45 w24 w40 s2', # 15 Nov 2013 + 'vflKz4WoM' => '16027 w50 w17 r w7 w65', # 19 Nov 2013 + 'vflhdWW8S' => '16030 s2 w55 w10 s3 w57 r w25 w41', # 21 Nov 2013 + 'vfl66X2C5' => '16031 r s2 w34 s2 w39', # 26 Nov 2013 + 'vflCXG8Sm' => '16031 r s2 w34 s2 w39', # 02 Dec 2013 + 'vfl_3Uag6' => '16034 w3 w7 r s2 w27 s2 w42 r', # 04 Dec 2013 + 'vflQdXVwM' => '16047 s1 r w66 s2 r w12', # 10 Dec 2013 + 'vflCtc3aO' => '16051 s2 r w11 r s3 w28', # 12 Dec 2013 + 'vflCt6YZX' => '16051 s2 r w11 r s3 w28', # 17 Dec 2013 + 'vflG49soT' => '16057 w32 r s3 r s1 r w19 w24 s3', # 18 Dec 2013 + 'vfl4cHApe' => '16059 w25 s1 r s1 w27 w21 s1 w39', # 06 Jan 2014 + 'vflwMrwdI' => '16058 w3 r w39 r w51 s1 w36 w14', # 06 Jan 2014 + 'vfl4AMHqP' => '16060 r s1 w1 r w43 r s1 r', # 09 Jan 2014 + 'vfln8xPyM' => '16080 w36 w14 s1 r s1 w54', # 10 Jan 2014 + 'vflVSLmnY' => '16081 s3 w56 w10 r s2 r w28 w35', # 13 Jan 2014 + 'vflkLvpg7' => '16084 w4 s3 w53 s2', # 15 Jan 2014 + 'vflbxes4n' => '16084 w4 s3 w53 s2', # 15 Jan 2014 + 'vflmXMtFI' => '16092 w57 s3 w62 w41 s3 r w60 r', # 23 Jan 2014 + 'vflYDqEW1' => '16094 w24 s1 r s2 w31 w4 w11 r', # 24 Jan 2014 + 'vflapGX6Q' => '16093 s3 w2 w59 s2 w68 r s3 r s1', # 28 Jan 2014 + 'vflLCYwkM' => '16093 s3 w2 w59 s2 w68 r s3 r s1', # 29 Jan 2014 + 'vflcY_8N0' => '16100 s2 w36 s1 r w18 r w19 r', # 30 Jan 2014 + 'vfl9qWoOL' => '16104 w68 w64 w28 r', # 03 Feb 2014 + 'vfle-mVwz' => '16103 s3 w7 r s3 r w14 w59 s3 r', # 04 Feb 2014 + 'vfltdb6U3' => '16106 w61 w5 r s2 w69 s2 r', # 05 Feb 2014 + 'vflLjFx3B' => '16107 w40 w62 r s2 w21 s3 r w7 s3', # 10 Feb 2014 + 'vfliqjKfF' => '16107 w40 w62 r s2 w21 s3 r w7 s3', # 13 Feb 2014 + 'ima-vflxBu-5R' => '16107 w40 w62 r s2 w21 s3 r w7 s3', # 13 Feb 2014 + 'ima-vflrGwWV9' => '16119 w36 w45 r s2 r', # 20 Feb 2014 + 'ima-vflCME3y0' => '16128 w8 s2 r w52', # 27 Feb 2014 + 'ima-vfl1LZyZ5' => '16128 w8 s2 r w52', # 27 Feb 2014 + 'ima-vfl4_saJa' => '16130 r s1 w19 w9 w57 w38 s3 r s2', # 01 Mar 2014 + 'ima-en_US-vflP9269H' => '16129 r w63 w37 s3 r w14 r', # 06 Mar 2014 + 'ima-en_US-vflkClbFb' => '16136 s1 w12 w24 s1 w52 w70 s2', # 07 Mar 2014 + 'ima-en_US-vflYhChiG' => '16137 w27 r s3', # 10 Mar 2014 + 'ima-en_US-vflWnCYSF' => '16142 r s1 r s3 w19 r w35 w61 s2', # 13 Mar 2014 + 'en_US-vflbT9-GA' => '16146 w51 w15 s1 w22 s1 w41 r w43 r', # 17 Mar 2014 + 'en_US-vflAYBrl7' => '16144 s2 r w39 w43', # 18 Mar 2014 + 'en_US-vflS1POwl' => '16145 w48 s2 r s1 w4 w35', # 19 Mar 2014 + 'en_US-vflLMtkhg' => '16149 w30 r w30 w39', # 20 Mar 2014 + 'en_US-vflbJnZqE' => '16151 w26 s1 w15 w3 w62 w54 w22', # 24 Mar 2014 + 'en_US-vflgd5txb' => '16151 w26 s1 w15 w3 w62 w54 w22', # 25 Mar 2014 + 'en_US-vflTm330y' => '16151 w26 s1 w15 w3 w62 w54 w22', # 26 Mar 2014 + 'en_US-vflnwMARr' => '16156 s3 r w24 s2', # 27 Mar 2014 + 'en_US-vflTq0XZu' => '16160 r w7 s3 w28 w52 r', # 31 Mar 2014 + 'en_US-vfl8s5-Vs' => '16158 w26 s1 w14 r s3 w8', # 01 Apr 2014 + 'en_US-vfl7i9w86' => '16158 w26 s1 w14 r s3 w8', # 02 Apr 2014 + 'en_US-vflA-1YdP' => '16158 w26 s1 w14 r s3 w8', # 03 Apr 2014 + 'en_US-vflZwcnOf' => '16164 w46 s2 w29 r s2 w51 w20 s1', # 07 Apr 2014 + 'en_US-vflFqBlmB' => '16164 w46 s2 w29 r s2 w51 w20 s1', # 08 Apr 2014 + 'en_US-vflG0UvOo' => '16164 w46 s2 w29 r s2 w51 w20 s1', # 09 Apr 2014 + 'en_US-vflS6PgfC' => '16170 w40 s2 w40 r w56 w26 r s2', # 10 Apr 2014 + 'en_US-vfl6Q1v_C' => '16172 w23 r s2 w55 s2', # 15 Apr 2014 + 'en_US-vflMYwWq8' => '16177 w51 w32 r s1 r s3', # 17 Apr 2014 + 'en_US-vflGC4r8Z' => '16184 w17 w34 w66 s3', # 24 Apr 2014 + 'en_US-vflyEvP6v' => '16189 s1 r w26', # 29 Apr 2014 + 'en_US-vflm397e5' => '16189 s1 r w26', # 01 May 2014 + 'en_US-vfldK8353' => '16192 r s3 w32', # 03 May 2014 + 'en_US-vflPTD6yH' => '16196 w59 s1 w66 s3 w10 r w55 w70 s1', # 06 May 2014 + 'en_US-vfl7KJl0G' => '16196 w59 s1 w66 s3 w10 r w55 w70 s1', # 07 May 2014 + 'en_US-vflhUwbGZ' => '16200 w49 r w60 s2 w61 s3', # 12 May 2014 + 'en_US-vflzEDYyE' => '16200 w49 r w60 s2 w61 s3', # 13 May 2014 + 'en_US-vflimfEzR' => '16205 r s2 w68 w28', # 15 May 2014 + 'en_US-vfl_nbW1R' => '16206 r w8 r s3', # 20 May 2014 + 'en_US-vfll7obaF' => '16212 w48 w17 s2', # 22 May 2014 + 'en_US-vfluBAJ91' => '16216 w13 s1 w39', # 27 May 2014 + 'en_US-vfldOnicU' => '16217 s2 r w7 w21 r', # 28 May 2014 + 'en_US-vflbbaSdm' => '16221 w46 r s3 w19 r s2 w15', # 03 Jun 2014 + 'en_US-vflIpxel5' => '16225 r w16 w35', # 04 Jun 2014 + 'en_US-vfloyxzv5' => '16232 r w30 s3 r s3 r', # 11 Jun 2014 + 'en_US-vflmY-xcZ' => '16230 w25 r s1 w49 w52', # 12 Jun 2014 + 'en_US-vflMVaJmz' => '16236 w12 s3 w56 r s2 r', # 17 Jun 2014 + 'en_US-vflgt97Vg' => '16240 r s1 r', # 19 Jun 2014 + 'en_US-vfl19qQQ_' => '16241 s2 w55 s2 r w39 s2 w5 r s3', # 23 Jun 2014 + 'en_US-vflws3c7_' => '16243 r s1 w52', # 24 Jun 2014 + 'en_US-vflPqsNqq' => '16243 r s1 w52', # 25 Jun 2014 + 'en_US-vflycBCEX' => '16247 w12 s1 r s3 w17 s1 w9 r', # 26 Jun 2014 + 'en_US-vflhZC-Jn' => '16252 w69 w70 s3', # 01 Jul 2014 + 'en_US-vfl9r3Wpv' => '16255 r s3 w57', # 07 Jul 2014 + 'en_US-vfl6UPpbU' => '16259 w37 r s1', # 08 Jul 2014 + 'en_US-vfl_oxbbV' => '16259 w37 r s1', # 09 Jul 2014 + 'en_US-vflXGBaUN' => '16259 w37 r s1', # 10 Jul 2014 + 'en_US-vflM1arS5' => '16262 s1 r w42 r s1 w27 r w54', # 11 Jul 2014 + 'en_US-vfl0Cbn9e' => '16265 w15 w44 r w24 s3 r w2 w50', # 14 Jul 2014 + 'en_US-vfl5aDZwb' => '16265 w15 w44 r w24 s3 r w2 w50', # 15 Jul 2014 + 'en_US-vflqZIm5b' => '16268 w1 w32 s1 r s3 r s3 r', # 17 Jul 2014 + 'en_US-vflBb0OQx' => '16272 w53 r w9 s2 r s1', # 22 Jul 2014 + 'en_US-vflCGk6yw/html5player' => '16275 s2 w28 w44 w26 w40 w64 r s1', # 24 Jul 2014 + 'en_US-vflNUsYw0/html5player' => '16280 r s3 w7', # 30 Jul 2014 + 'en_US-vflId8cpZ/html5player' => '16282 w30 w21 w26 s1 r s1 w30 w11 w20', # 31 Jul 2014 + 'en_US-vflEyBLiy/html5player' => '16283 w44 r w15 s2 w40 r s1', # 01 Aug 2014 + 'en_US-vflHkCS5P/html5player' => '16287 s2 r s3 r w41 s1 r s1 r', # 05 Aug 2014 + 'en_US-vflArxUZc/html5player' => '16289 r w12 r s3 w14 w61 r', # 07 Aug 2014 + 'en_US-vflCsMU2l/html5player' => '16292 r s2 r w64 s1 r s3', # 11 Aug 2014 + 'en_US-vflY5yrKt/html5player' => '16294 w8 r s2 w37 s1 w21 s3', # 12 Aug 2014 + 'en_US-vfl4b4S6W/html5player' => '16295 w40 s1 r w40 s3 r w47 r', # 13 Aug 2014 + 'en_US-vflLKRtyE/html5player' => '16298 w5 r s1 r s2 r', # 18 Aug 2014 + 'en_US-vflrSlC04/html5player' => '16300 w28 w58 w19 r s1 r s1 r', # 19 Aug 2014 + 'en_US-vflC7g_iA/html5player' => '16300 w28 w58 w19 r s1 r s1 r', # 20 Aug 2014 + 'en_US-vfll1XmaE/html5player' => '16303 r w9 w23 w29 w36 s2 r', # 21 Aug 2014 + 'en_US-vflWRK4zF/html5player' => '16307 r w63 r s3', # 26 Aug 2014 + 'en_US-vflQSzMIW/html5player' => '16309 r s1 w40 w70 s2 w28 s1', # 27 Aug 2014 + 'en_US-vfltYLx8B/html5player' => '16310 s3 w19 w24', # 29 Aug 2014 + 'en_US-vflWnljfv/html5player' => '16311 s2 w60 s3 w42 r w40 s2 w68 w20', # 02 Sep 2014 + 'en_US-vflDJ-wUY/html5player' => '16316 s2 w18 s2 w68 w15 s1 w45 s1 r', # 04 Sep 2014 + 'en_US-vfllxLx6Z/html5player' => '16309 r s1 w40 w70 s2 w28 s1', # 04 Sep 2014 + 'en_US-vflI3QYI2/html5player' => '16318 s3 w22 r s3 w19 s1 r', # 08 Sep 2014 + 'en_US-vfl-ZO7j_/html5player' => '16322 s3 w21 s1', # 09 Sep 2014 + 'en_US-vflWGRWFI/html5player' => '16324 r w27 r s1 r', # 12 Sep 2014 + 'en_US-vflJkTW89/html5player' => '16328 w12 s1 w67 r w39 w65 s3 r s1', # 15 Sep 2014 + 'en_US-vflB8RV2U/html5player' => '16329 r w26 r w28 w38 r s3', # 16 Sep 2014 + 'en_US-vflBFNwmh/html5player' => '16329 r w26 r w28 w38 r s3', # 17 Sep 2014 + 'en_US-vflE7vgXe/html5player' => '16331 w46 w22 r w33 r s3 w18 r s3', # 18 Sep 2014 + 'en_US-vflx8EenD/html5player' => '16334 w8 s3 w45 w46 s2 w29 w25 w56 w2', # 23 Sep 2014 + 'en_US-vflfgwjRj/html5player' => '16336 r s2 w56 r s3', # 24 Sep 2014 + 'en_US-vfl15y_l6/html5player' => '16334 w8 s3 w45 w46 s2 w29 w25 w56 w2', # 25 Sep 2014 + 'en_US-vflYqHPcx/html5player' => '16341 s3 r w1 r', # 30 Sep 2014 + 'en_US-vflcoeQIS/html5player' => '16344 s3 r w64 r s3 r w68', # 01 Oct 2014 + 'en_US-vflz7mN60/html5player' => '16345 s2 w16 w39', # 02 Oct 2014 + 'en_US-vfl4mDBLZ/html5player' => '16348 r w54 r s2 w49', # 06 Oct 2014 + 'en_US-vflKzH-7N/html5player' => '16348 r w54 r s2 w49', # 08 Oct 2014 + 'en_US-vflgoB_xN/html5player' => '16345 s2 w16 w39', # 09 Oct 2014 + 'en_US-vflPyRPNk/html5player' => '16353 r w34 w9 w56 r s3 r w30', # 12 Oct 2014 + 'en_US-vflG0qgr5/html5player' => '16345 s2 w16 w39', # 14 Oct 2014 + 'en_US-vflzDhHvc/html5player' => '16358 w26 s1 r w8 w24 w18 r s2 r', # 15 Oct 2014 + 'en_US-vflbeC7Ip/html5player' => '16359 r w21 r s2 r', # 16 Oct 2014 + 'en_US-vflBaDm_Z/html5player' => '16363 s3 w5 s1 w20 r', # 20 Oct 2014 + 'en_US-vflr38Js6/html5player' => '16364 w43 s1 r', # 21 Oct 2014 + 'en_US-vflg1j_O9/html5player' => '16365 s2 r s3 r s3 r w2', # 22 Oct 2014 + 'en_US-vflPOfApl/html5player' => '16371 s2 w38 r s3 r', # 28 Oct 2014 + 'en_US-vflMSJ2iW/html5player' => '16366 s2 r w4 w22 s2 r s2', # 29 Oct 2014 + 'en_US-vflckDNUK/html5player' => '16373 s3 r w66 r s3 w1 w12 r', # 30 Oct 2014 + 'en_US-vflKCJBPS/html5player' => '16374 w15 w2 s1 r s3 r', # 31 Oct 2014 + 'en_US-vflcF0gLP/html5player' => '16375 s3 w10 s1 r w28 s1 w40 w64 r', # 04 Nov 2014 + 'en_US-vflpRHqKc/html5player' => '16377 w39 r w48 r', # 05 Nov 2014 + 'en_US-vflbcuqSZ/html5player' => '16379 r s1 w27 s2 w5 w7 w51 r', # 06 Nov 2014 + 'en_US-vflHf2uUU/html5player' => '16379 r s1 w27 s2 w5 w7 w51 r', # 11 Nov 2014 + 'en_US-vfln6g5Eq/html5player' => '16385 w1 r s3 r s2 w10 s3 r', # 12 Nov 2014 + 'en_US-vflM7pYrM/html5player' => '16387 r s2 r w3 r w11 r', # 15 Nov 2014 + 'en_US-vflP2rJ1-/html5player' => '16387 r s2 r w3 r w11 r', # 18 Nov 2014 + 'en_US-vflXs0FWW/html5player' => '16392 w63 s1 r w46 s2 r s3', # 20 Nov 2014 + 'en_US-vflEhuJxd/html5player' => '16392 w63 s1 r w46 s2 r s3', # 21 Nov 2014 + 'en_US-vflp3wlqE/html5player' => '16396 w22 s3 r', # 24 Nov 2014 + 'en_US-vfl5_7-l5/html5player' => '16396 w22 s3 r', # 25 Nov 2014 + 'en_US-vfljnKokH/html5player' => '16400 s3 w15 s2 w30 w11', # 26 Nov 2014 + 'en_US-vflIlILAX/html5player' => '16407 r w7 w19 w38 s3 w41 s1 r w1', # 04 Dec 2014 + 'en_US-vflEegqdq/html5player' => '16407 r w7 w19 w38 s3 w41 s1 r w1', # 10 Dec 2014 + 'en_US-vflkOb-do/html5player' => '16407 r w7 w19 w38 s3 w41 s1 r w1', # 11 Dec 2014 + 'en_US-vfllt8pl6/html5player' => '16419 r w17 w33 w53', # 16 Dec 2014 + 'en_US-vflsXGZP2/html5player' => '16420 s3 w38 s1 w16 r w20 w69 s2 w15', # 18 Dec 2014 + 'en_US-vflw4H1P-/html5player' => '16427 w8 r s1', # 23 Dec 2014 + 'en_US-vflmgJnmS/html5player' => '16421 s3 w20 r w34 r s1 r', # 06 Jan 2015 + 'en_US-vfl86Quee/html5player' => '16450 s3 r w25 w29 r w17 s2 r', # 15 Jan 2015 + 'en_US-vfl19kCnd/html5player' => '16444 r w29 s1 r s1 r w4 w28', # 17 Jan 2015 + 'en_US-vflbHLA_P/html5player' => '16451 r w20 r w20 s2 r', # 20 Jan 2015 + 'en_US-vfl_ZlzZL/html5player' => '16455 w61 r s1 w31 w36 s1', # 22 Jan 2015 + 'en_US-vflbeV8LH/html5player' => '16455 w61 r s1 w31 w36 s1', # 26 Jan 2015 + 'en_US-vflhJatih/html5player' => '16462 s2 w44 r s3 w17 s1', # 28 Jan 2015 + 'en_US-vflvmwLwg/html5player' => '16462 s2 w44 r s3 w17 s1', # 29 Jan 2015 + 'en_US-vflljBsG4/html5player' => '16462 s2 w44 r s3 w17 s1', # 02 Feb 2015 + 'en_US-vflT5ziDW/html5player' => '16462 s2 w44 r s3 w17 s1', # 03 Feb 2015 + 'en_US-vflwImypH/html5player' => '16471 s3 r w23 s2 w29 r w44', # 05 Feb 2015 + 'en_US-vflQkSGin/html5player' => '16475 w70 r w66 s1 w70 w26 r w48', # 10 Feb 2015 + 'en_US-vflqnkATr/html5player' => '16475 w70 r w66 s1 w70 w26 r w48', # 11 Feb 2015 + 'en_US-vflZvrDTQ/html5player' => '16475 w70 r w66 s1 w70 w26 r w48', # 12 Feb 2015 + 'en_US-vflKjOTVq/html5player' => '16475 w70 r w66 s1 w70 w26 r w48', # 17 Feb 2015 + 'en_US-vfluEf7CP/html5player' => '16475 w70 r w66 s1 w70 w26 r w48', # 18 Feb 2015 + 'en_US-vflF2Mg88/html5player' => '16475 w70 r w66 s1 w70 w26 r w48', # 19 Feb 2015 + 'en_US-vflQTSOsS/html5player' => '16489 s3 r w23 s1 w19 w43 w36', # 24 Feb 2015 + 'en_US-vflbaqfRh/html5player' => '16489 s3 r w23 s1 w19 w43 w36', # 25 Feb 2015 + 'en_US-vflcL_htG/html5player' => '16491 w20 s3 w37 r', # 04 Mar 2015 + 'en_US-vflTbHYa9/html5player' => '16498 s3 w44 s1 r s1 r s3 r s3', # 04 Mar 2015 + 'en_US-vflT9SJ6t/html5player' => '16497 w66 r s3 w60', # 05 Mar 2015 + 'en_US-vfl6xsolJ/html5player' => '16503 s1 w4 s1 w39 s3 r', # 10 Mar 2015 + 'en_US-vflA6e-lH/html5player' => '16503 s1 w4 s1 w39 s3 r', # 13 Mar 2015 + 'en_US-vflu7AB7p/html5player' => '16503 s1 w4 s1 w39 s3 r', # 16 Mar 2015 + 'en_US-vflQb7e_A/html5player' => '16510 w19 w35 r s2 r s1 w64 s2 w53', # 18 Mar 2015 + 'en_US-vflicH9X6/html5player' => '16510 w19 w35 r s2 r s1 w64 s2 w53', # 20 Mar 2015 + 'en_US-vflvDDxpc/html5player' => '16510 w19 w35 r s2 r s1 w64 s2 w53', # 23 Mar 2015 + 'en_US-vflSp2y2y/html5player' => '16510 w19 w35 r s2 r s1 w64 s2 w53', # 24 Mar 2015 + 'en_US-vflFAPa9H/html5player' => '16510 w19 w35 r s2 r s1 w64 s2 w53', # 25 Mar 2015 + 'en_US-vflImsVHZ/html5player' => '16518 r w1 r w17 s2 r', # 30 Mar 2015 + 'en_US-vfllLRozy/html5player' => '16518 r w1 r w17 s2 r', # 31 Mar 2015 + 'en_US-vfldudhuW/html5player' => '16518 r w1 r w17 s2 r', # 02 Apr 2015 + 'en_US-vfl20EdcH/html5player' => '16511 w12 w18 s1 w60', # 06 Apr 2015 + 'en_US-vflCiLqoq/html5player' => '16511 w12 w18 s1 w60', # 07 Apr 2015 + 'en_US-vflOOhwh5/html5player' => '16518 r w1 r w17 s2 r', # 09 Apr 2015 + 'en_US-vflUPVjIh/html5player' => '16511 w12 w18 s1 w60', # 09 Apr 2015 + 'en_US-vfleI-biQ/html5player' => '16519 w39 s3 r s1 w36', # 13 Apr 2015 + 'en_US-vflWLYnud/html5player' => '16538 r w41 w65 w11 r', # 14 Apr 2015 + 'en_US-vflCbhV8k/html5player' => '16538 r w41 w65 w11 r', # 15 Apr 2015 + 'en_US-vflXIPlZ4/html5player' => '16538 r w41 w65 w11 r', # 16 Apr 2015 + 'en_US-vflJ97NhI/html5player' => '16538 r w41 w65 w11 r', # 20 Apr 2015 + 'en_US-vflV9R5dM/html5player' => '16538 r w41 w65 w11 r', # 21 Apr 2015 + 'en_US-vflkH_4LI/html5player' => '16546 w13 s1 w4 s2 r s2 w25', # 22 Apr 2015 + 'en_US-vflfy61br/html5player' => '16546 w13 s1 w4 s2 r s2 w25', # 23 Apr 2015 + 'en_US-vfl1r59NI/html5player' => '16548 r w42 s1 r w29 r w2 s2 r',# 28 Apr 2015 + 'en_US-vfl98hSpx/html5player' => '16548 r w42 s1 r w29 r w2 s2 r',# 29 Apr 2015 + 'en_US-vflheTb7D/html5player' => '16554 r s1 w40 s2 r w6 s3 w60',# 30 Apr 2015 + 'en_US-vflnbdC7j/html5player' => '16555 w52 w25 w62 w51 w2 s2 r s1',# 04 May 2015 + 'new-en_US-vfladkLoo/html5player-new' => '16555 w52 w25 w62 w51 w2 s2 r s1',# 05 May 2015 + 'en_US-vflTjpt_4/html5player' => '16560 w14 r s1 w37 w61 r', # 07 May 2015 + 'en_US-vflN74631/html5player' => '16560 w14 r s1 w37 w61 r', # 08 May 2015 + 'en_US-vflj7H3a2/html5player' => '16560 w14 r s1 w37 w61 r', # 12 May 2015 + 'en_US-vflQbG2p4/html5player' => '16560 w14 r s1 w37 w61 r', # 12 May 2015 + 'en_US-vflHV7Wup/html5player' => '16560 w14 r s1 w37 w61 r', # 13 May 2015 + 'en_US-vflCbZ69_/html5player' => '16574 w3 s3 w45 r w3 w2 r w13 r',# 20 May 2015 + 'en_US-vflugm_Hi/html5player' => '16574 w3 s3 w45 r w3 w2 r w13 r',# 21 May 2015 + 'en_US-vfl3tSKxJ/html5player' => '16577 w37 s3 w57 r w5 r w13 r',# 26 May 2015 + 'en_US-vflE8_7k0/html5player' => '16582 r w41 s3 w69 s1 w66 r w27 s2',# 28 May 2015 + 'en_US-vflmxRINy/html5player' => '16582 r w41 s3 w69 s1 w66 r w27 s2',# 01 Jun 2015 + 'en_US-vflQEtHy6/html5player' => '16582 r w41 s3 w69 s1 w66 r w27 s2',# 02 Jun 2015 + 'en_US-vflRqg76I/html5player' => '16582 r w41 s3 w69 s1 w66 r w27 s2',# 03 Jun 2015 + 'en_US-vfloIm75c/html5player' => '16582 r w41 s3 w69 s1 w66 r w27 s2',# 04 Jun 2015 + 'en_US-vfl0JH6Oo/html5player' => '16582 r w41 s3 w69 s1 w66 r w27 s2',# 08 Jun 2015 + 'en_US-vflHvL0kQ/html5player' => '16582 r w41 s3 w69 s1 w66 r w27 s2',# 09 Jun 2015 + 'new-en_US-vflGBorXT/html5player-new' => '16582 r w41 s3 w69 s1 w66 r w27 s2',# 10 Jun 2015 + 'en_US-vfl4Y6g4o/html5player' => '16582 r w41 s3 w69 s1 w66 r w27 s2',# 11 Jun 2015 + 'en_US-vflKAbZ28/html5player' => '16597 s3 r s2', # 15 Jun 2015 + 'en_US-vflM5YBLT/html5player' => '16602 s2 w25 w14 s1 r', # 17 Jun 2015 + 'en_US-vflnSSUZV/html5player' => '16603 w20 s2 w11 s3 r s1 w2 w15',# 18 Jun 2015 + 'en_US-vfla1HjWj/html5player' => '16603 w20 s2 w11 s3 r s1 w2 w15',# 22 Jun 2015 + 'en_US-vflPcWTEd/html5player' => '16603 w20 s2 w11 s3 r s1 w2 w15',# 23 Jun 2015 + 'en_US-vfljL8ofl/html5player' => '16609 w29 r s1 r w59 r w45', # 25 Jun 2015 + 'en_US-vflUXoyA8/html5player' => '16609 w29 r s1 r w59 r w45', # 29 Jun 2015 + 'en_US-vflzomeEU/html5player' => '16609 w29 r s1 r w59 r w45', # 30 Jun 2015 + 'en_US-vflihzZsw/html5player' => '16617 s3 r s3 w17', # 07 Jul 2015 + 'en_US-vfld2QbH7/html5player' => '16623 w58 w46 s1 w9 r w54 s2 r w55',# 08 Jul 2015 + 'en_US-vflVsMRd_/html5player' => '16623 w58 w46 s1 w9 r w54 s2 r w55',# 09 Jul 2015 + 'en_US-vflp6cSzi/html5player' => '16625 w52 w23 s1 r s2 r s2 r',# 16 Jul 2015 + 'en_US-vflr_ZqiK/html5player' => '16625 w52 w23 s1 r s2 r s2 r',# 20 Jul 2015 + 'en_US-vflDv401v/html5player' => '16636 r w68 w58 r w28 w44 r', # 21 Jul 2015 + 'en_US-vflP7pyW6/html5player' => '16636 r w68 w58 r w28 w44 r', # 22 Jul 2015 + 'en_US-vfly-Z1Od/html5player' => '16636 r w68 w58 r w28 w44 r', # 23 Jul 2015 + 'en_US-vflSxbpbe/html5player' => '16636 r w68 w58 r w28 w44 r', # 27 Jul 2015 + 'en_US-vflGx3XCd/html5player' => '16636 r w68 w58 r w28 w44 r', # 29 Jul 2015 + 'new-en_US-vflIgTSdc/html5player-new' => '16648 r s2 r w43 w41 w8 r w67 r',# 03 Aug 2015 + 'new-en_US-vflnk2PHx/html5player-new' => '16651 r w32 s3 r s1 r',# 06 Aug 2015 + 'new-en_US-vflo_te46/html5player-new' => '16652 r s2 w27 s1', # 06 Aug 2015 + 'new-en_US-vfllZzMNK/html5player-new' => '16657 w11 w29 w63 r w45 w34 s2',# 11 Aug 2015 + 'new-en_US-vflxgfwPf/html5player-new' => '16657 w11 w29 w63 r w45 w34 s2',# 13 Aug 2015 + 'new-en_US-vflTSd4UU/html5player-new' => '16657 w11 w29 w63 r w45 w34 s2',# 14 Aug 2015 + 'new-en_US-vfl2Ys-gC/html5player-new' => '16657 w11 w29 w63 r w45 w34 s2',# 15 Aug 2015 + 'new-en_US-vflRWS2p7/html5player-new' => '16657 w11 w29 w63 r w45 w34 s2',# 19 Aug 2015 + 'new-en_US-vflVBD1Nz/html5player-new' => '16657 w11 w29 w63 r w45 w34 s2',# 20 Aug 2015 + 'new-en_US-vflJVflpM/html5player-new' => '16667 r s1 r w8 r w5 s2 w30 w66',# 24 Aug 2015 + 'en_US-vfleu-UMC/html5player' => '16667 r s1 r w8 r w5 s2 w30 w66',# 26 Aug 2015 + 'new-en_US-vflOWWv0e/html5player-new' => '16667 r s1 r w8 r w5 s2 w30 w66',# 26 Aug 2015 + 'new-en_US-vflyGTTiE/html5player-new' => '16674 w68 s3 w66 s1 r',# 01 Sep 2015 + 'new-en_US-vflCeB3p5/html5player-new' => '16674 w68 s3 w66 s1 r',# 02 Sep 2015 + 'new-en_US-vflhlPTtB/html5player-new' => '16682 w40 s3 w53 w11 s3 r s3 w16 r',# 09 Sep 2015 + 'new-en_US-vflSnomqH/html5player-new' => '16689 w56 w12 r w26 r',# 16 Sep 2015 + 'new-en_US-vflkiOBi0/html5player-new' => '16696 w55 w69 w61 s2 r',# 22 Sep 2015 + 'new-en_US-vflpNjqAo/html5player-new' => '16696 w55 w69 w61 s2 r',# 22 Sep 2015 + 'new-en_US-vflOdTWmK/html5player-new' => '16696 w55 w69 w61 s2 r',# 23 Sep 2015 + 'new-en_US-vfl9jbnCC/html5player-new' => '16703 s1 r w18 w67 r s3 r',# 29 Sep 2015 + 'new-en_US-vflyM0pli/html5player-new' => '16696 w55 w69 w61 s2 r',# 29 Sep 2015 + 'new-en_US-vflJLt_ns/html5player-new' => '16708 w19 s2 r s2 w48 r s2 r',# 30 Sep 2015 + 'new-en_US-vflqLE6s6/html5player-new' => '16708 w19 s2 r s2 w48 r s2 r',# 02 Oct 2015 + 'new-en_US-vflzRMCkZ/html5player-new' => '16711 r s3 r s2 w62 w25 s1 r',# 04 Oct 2015 + 'new-en_US-vflIUNjzZ/html5player-new' => '16711 r s3 r s2 w62 w25 s1 r',# 08 Oct 2015 + 'new-en_US-vflOw5Ej1/html5player-new' => '16711 r s3 r s2 w62 w25 s1 r',# 08 Oct 2015 + 'new-en_US-vflq2mOFv/html5player-new' => '16714 r w37 r w19 r s3 r w5',# 12 Oct 2015 + 'new-en_US-vfl8AWn6F/html5player-new' => '16714 r w37 r w19 r s3 r w5',# 13 Oct 2015 + 'new-en_US-vflEA2BSM/html5player-new' => '16714 r w37 r w19 r s3 r w5',# 14 Oct 2015 + 'new-en_US-vflt2Xpp6/html5player-new' => '16717 r s1 w14', # 15 Oct 2015 + 'new-en_US-vflDpriqR/html5player-new' => '16714 r w37 r w19 r s3 r w5',# 15 Oct 2015 + 'new-en_US-vflptVjJB/html5player-new' => '16723 s2 r s3 w54 w60 w55 w65',# 21 Oct 2015 + 'new-en_US-vflmR8A04/html5player-new' => '16725 w28 s2 r', # 23 Oct 2015 + 'new-en_US-vflx6L8FI/html5player-new' => '16735 r s2 r w65 w1 s1',# 27 Oct 2015 + 'new-en_US-vflYZP7XE/html5player-new' => '16734 s1 r s1 w56 w46 s2 r',# 27 Oct 2015 + 'new-en_US-vflQZZsER/html5player-new' => '16734 s1 r s1 w56 w46 s2 r',# 29 Oct 2015 + 'new-en_US-vflsLAYSi/html5player-new' => '16734 s1 r s1 w56 w46 s2 r',# 29 Oct 2015 + 'new-en_US-vflZWDr6u/html5player-new' => '16734 s1 r s1 w56 w46 s2 r',# 02 Nov 2015 + 'new-en_US-vflJoRj2J/html5player-new' => '16742 w69 w47 r s1 r s1 r w43 s2',# 03 Nov 2015 + 'new-en_US-vflFSFCN-/html5player-new' => '16734 s1 r s1 w56 w46 s2 r',# 04 Nov 2015 + 'new-en_US-vfl6mEKMp/html5player-new' => '16734 s1 r s1 w56 w46 s2 r',# 05 Nov 2015 + 'player-en_US-vflJENbn4/base' => '16748 s1 w31 r', # 12 Nov 2015 + 'player-en_US-vfltBCT02/base' => '16756 r s2 r w18 w62 w45 s1', # 17 Nov 2015 + 'player-en_US-vfl0w9xAB/base' => '16756 r s2 r w18 w62 w45 s1', # 17 Nov 2015 + 'player-en_US-vflCIicNM/base' => '16759 w2 s3 r w38 w21 w58', # 20 Nov 2015 + 'player-en_US-vflUpjAy9/base' => '16758 w26 s3 r s3 r s3 w61 s3 r',# 23 Nov 2015 + 'player-en_US-vflFEzfy7/base' => '16758 w26 s3 r s3 r s3 w61 s3 r',# 24 Nov 2015 + 'player-en_US-vfl_RJZIW/base' => '16770 w3 w2 s3 w39 s2 r s2', # 01 Dec 2015 + 'player-en_US-vfln_PDe6/base' => '16770 w3 w2 s3 w39 s2 r s2', # 03 Dec 2015 + 'player-en_US-vflx9OkTA/base' => '16772 s2 w50 r w15 w66 s3', # 07 Dec 2015 + 'player-en_US-vflPRjCOu/base' => '16776 r s1 r w31 s1', # 08 Dec 2015 + 'player-en_US-vflOIF62G/base' => '16776 r s1 r w31 s1', # 10 Dec 2015 + 'player-en_US-vfl2sXoyn/base' => '16777 w13 r s3 w2 r s3 w36', # 10 Dec 2015 + 'player-en_US-vflF6iOW5/base' => '16777 w13 r s3 w2 r s3 w36', # 11 Dec 2015 + 'player-en_US-vfl_a6AWr/base' => '16777 w13 r s3 w2 r s3 w36', # 14 Dec 2015 + 'player-en_US-vflpPblA7/base' => '16777 w13 r s3 w2 r s3 w36', # 15 Dec 2015 + 'player-en_US-vflktcH0f/base' => '16777 w13 r s3 w2 r s3 w36', # 16 Dec 2015 + 'player-en_US-vflXJM_5_/base' => '16777 w13 r s3 w2 r s3 w36', # 17 Dec 2015 + 'player-en_US-vflrSqbyh/base' => '16777 w13 r s3 w2 r s3 w36', # 20 Dec 2015 + 'player-en_US-vflnrstgx/base' => '16777 w13 r s3 w2 r s3 w36', # 22 Dec 2015 + 'player-en_US-vflbZPqYk/base' => '16804 r w50 w8 s2 w40 w64 s1',# 05 Jan 2016 + 'player-en_US-vfl2TFPXm/base' => '16804 r w50 w8 s2 w40 w64 s1',# 06 Jan 2016 + 'player-en_US-vflra1XvP/base' => '16806 s1 r w65 s3 r', # 07 Jan 2016 + 'player-en_US-vfljksafM/base' => '16806 s1 r w65 s3 r', # 11 Jan 2016 + 'player-en_US-vfl844Wcq/base' => '16806 s1 r w65 s3 r', # 12 Jan 2016 + 'player-en_US-vflGR-A-c/base' => '16806 s1 r w65 s3 r', # 14 Jan 2016 + 'player-en_US-vflIfVKII/base' => '16816 s2 w66 r', # 19 Jan 2016 + 'player-en_US-vfl1SLb2X/base' => '16819 s3 r w29 s1 r s1 w54 r w48',# 20 Jan 2016 + 'player-en_US-vfl7CQfyl/base' => '16819 s3 r w29 s1 r s1 w54 r w48',# 22 Jan 2016 + 'player-en_US-vfl0zK-iw/base' => '16819 s3 r w29 s1 r s1 w54 r w48',# 22 Jan 2016 + 'player-en_US-vfl4ZhWmu/base' => '16825 w12 s1 w47 s2 r s1', # 26 Jan 2016 + 'player-en_US-vflYjf147/base' => '16826 s1 r s2 r w50 r', # 27 Jan 2016 + 'player-en_US-vfl66BZ3R/base' => '16826 s1 r s2 r w50 r', # 28 Jan 2016 + 'player-en_US-vflpwz3pO/base' => '16828 w60 w36 w43 r', # 01 Feb 2016 + 'player-en_US-vflwvK3-x/base' => '16832 r w67 w1 r s1 w17', # 03 Feb 2016 + 'player-en_US-vfl93P520/base' => '16832 r w67 w1 r s1 w17', # 04 Feb 2016 + 'player-en_US-vflj1re2B/base' => '16835 s1 r s3 w69 r s3 w53', # 08 Feb 2016 + 'player-en_US-vflpN2vEY/base' => '16836 w16 r s3 r', # 10 Feb 2016 + 'player-en_US-vflCdE8nM/base' => '16841 r w51 s3 r s3 w6 w24 r w21',# 11 Feb 2016 + 'player-en_US-vfl329t6E/base' => '16846 s3 w27 r s2 w29 s2 r s3',# 16 Feb 2016 + 'player-en_US-vflGk0Qy7/base' => '16846 s3 w27 r s2 w29 s2 r s3',# 17 Feb 2016 + 'player-en_US-vfligMRZC/base' => '16849 w4 w3 r w50 r s1 w20 s1',# 18 Feb 2016 + 'player-en_US-vfldIygzk/base' => '16850 w48 r s1 r', # 20 Feb 2016 + 'player-en_US-vflksMPCE/base' => '16853 s2 w61 s2', # 23 Feb 2016 + 'player-en_US-vflEGP5iK/base' => '16849 w4 w3 r w50 r s1 w20 s1',# 23 Feb 2016 + 'player-en_US-vflRVQlNU/base' => '16856 w44 w49 r', # 25 Feb 2016 + 'player-en_US-vflKlzoBL/base' => '16855 w54 r s1 w52 s3 r w16 r',# 28 Feb 2016 + 'player-en_US-vfl_cdzrt/base' => '16855 w54 r s1 w52 s3 r w16 r',# 01 Mar 2016 + 'player-en_US-vflteKQR7/base' => '16861 r w40 s2', # 04 Mar 2016 + 'player-en_US-vfltwl-FJ/base' => '16864 w42 r w14 s3 r s1 r s2',# 08 Mar 2016 + 'player-en_US-vfl6PWeOD/base' => '16864 w42 r w14 s3 r s1 r s2',# 10 Mar 2016 + 'player-en_US-vflcZVscy/base' => '16873 s1 w55 w32 w39 r s3 r w66 s3',# 14 Mar 2016 + 'player-en_US-vflXE5o5C/base' => '16873 s1 w55 w32 w39 r s3 r w66 s3',# 15 Mar 2016 + 'player-en_US-vfl1858es/base' => '16873 s1 w55 w32 w39 r s3 r w66 s3',# 16 Mar 2016 + 'player-en_US-vflKkAVgb/base' => '16873 s1 w55 w32 w39 r s3 r w66 s3',# 17 Mar 2016 + 'player-en_US-vflpmpoFG/base' => '16881 r w70 s2 w53 s1', # 22 Mar 2016 + 'player-en_US-vfl1uoDql/base' => '16881 r w70 s2 w53 s1', # 24 Mar 2016 + 'player-en_US-vfl9rzyi6/base' => '16884 w19 w32 w47 w41 w3 w56 r',# 29 Mar 2016 + 'player-en_US-vflEHWF5a/base' => '16884 w19 w32 w47 w41 w3 w56 r',# 31 Mar 2016 + 'player-en_US-vfl6tDF0R/base' => '16890 s3 r w31 w23 w29', # 31 Mar 2016 + 'player-en_US-vfljAl26P/base' => '16890 s3 r w31 w23 w29', # 01 Apr 2016 + 'player-en_US-vfl9xTY8I/base' => '16892 s1 r s3 w37 w43 w20', # 04 Apr 2016 + 'player-en_US-vfls3wurZ/base' => '16892 s1 r s3 w37 w43 w20', # 05 Apr 2016 + 'player-en_US-vfli5QvRo/base' => '16892 s1 r s3 w37 w43 w20', # 06 Apr 2016 + 'player-en_US-vfllNvdW4/base' => '16897 r w4 s2 w41 r w52 r', # 07 Apr 2016 + 'player-en_US-vfll2CKBY/base' => '16898 w19 r s3', # 12 Apr 2016 + 'player-en_US-vflELI9Sd/base' => '16903 s3 w53 s2 w2', # 13 Apr 2016 + 'player-en_US-vflg4mKgv/base' => '16903 s3 w53 s2 w2', # 14 Apr 2016 + 'player-en_US-vflHZ7KXs/base' => '16903 s3 w53 s2 w2', # 19 Apr 2016 + 'player-en_US-vflnFj56r/base' => '16903 s3 w53 s2 w2', # 20 Apr 2016 + 'player-en_US-vfljFzcWO/base' => '16913 w7 r w13 w69 s3 r w14', # 22 Apr 2016 + 'player-en_US-vflQ6YtHH/base' => '16913 w7 r w13 w69 s3 r w14', # 22 Apr 2016 + 'player-en_US-vflvBNQyW/base' => '16912 s3 w7 w24 s1', # 25 Apr 2016 + 'player-en_US-vflG0wokn/base' => '16916 w62 r w38 s1 r s2 r w13 w12',# 26 Apr 2016 + 'player-en_US-vfll6dEHf/base' => '16916 w62 r w38 s1 r s2 r w13 w12',# 27 Apr 2016 + 'player-en_US-vflA_6ZRP/base' => '16918 w14 s1 r w10', # 29 Apr 2016 + 'player-en_US-vflL5aRF-/base' => '16920 w42 r s1 r w30 r s2', # 02 May 2016 + 'player-en_US-vflKklr93/base' => '16920 w42 r s1 r w30 r s2', # 04 May 2016 + 'player-en_US-vflYi-PAF/base' => '16926 w58 r s3', # 09 May 2016 + 'player-en_US-vflPykJ0g/base' => '16926 w58 r s3', # 10 May 2016 + 'player-en_US-vflw9bxTw/base' => '16926 w58 r s3', # 11 May 2016 + 'player-en_US-vflGdEImZ/base' => '16932 w69 w26 r w8 w22 s1', # 12 May 2016 + 'player-en_US-vflTZ3kuV/base' => '16932 w69 w26 r w8 w22 s1', # 19 May 2016 + 'player-en_US-vfl5u7dIk/base' => '16932 w69 w26 r w8 w22 s1', # 19 May 2016 + 'player-en_US-vflGaNMBw/base' => '16932 w69 w26 r w8 w22 s1', # 21 May 2016 + 'player-en_US-vfl6uEgGV/base' => '16941 r w36 s1 r w26 s1 w60', # 23 May 2016 + 'player-en_US-vflKZdm1L/base' => '16944 w25 s2 r', # 24 May 2016 + 'player-en_US-vflNStq7e/base' => '16944 w25 s2 r', # 25 May 2016 + 'player-en_US-vflAwQJsE/base' => '16945 w53 r w19 s3 w37', # 31 May 2016 + 'player-en_US-vfl7FG-3v/base' => '16944 w25 s2 r', # 02 Jun 2016 + 'player-en_US-vfl7vBziO/base' => '16944 w25 s2 r', # 02 Jun 2016 + 'player-en_US-vflrmwhUy/base' => '16944 w25 s2 r', # 04 Jun 2016 + 'player-en_US-vfljqy_st/base' => '16958 s3 w46 w64 w67 s2 r', # 07 Jun 2016 + 'player-en_US-vflzxAejD/base' => '16959 s1 r w4 w67 s3 r w55 r s3',# 08 Jun 2016 + 'player-en_US-vflqpURrL/base' => '16960 r w65 r', # 09 Jun 2016 + 'player-en_US-vflcUEb1U/base' => '16962 w54 s1 r w9 s1', # 11 Jun 2016 + 'player-en_US-vflBUz8b9/base' => '16965 w1 r s2 w27', # 13 Jun 2016 + 'player-en_US-vfl9bYNJa/base' => '16961 s1 r s1 r w35 r', # 14 Jun 2016 + 'player-en_US-vflruV5iG/base' => '16966 w36 s2 w65 r s2 w11 w31',# 15 Jun 2016 + 'player-en_US-vfldefdPl/base' => '16961 s1 r s1 r w35 r', # 15 Jun 2016 + 'player-en_US-vfl-nPja1/base' => '16968 w21 s1 w60 s2', # 20 Jun 2016 + 'player-en_US-vflLyLvKU/base' => '16974 r w45 r', # 23 Jun 2016 + 'player-en_US-vfl0Cqdyd/base' => '16976 w57 r w57 w38 s3 w47 s2',# 27 Jun 2016 + 'player-en_US-vflOfyD_m/base' => '16976 w57 r w57 w38 s3 w47 s2',# 28 Jun 2016 + 'player-en_US-vflAbrXV8/base' => '16976 w57 r w57 w38 s3 w47 s2',# 30 Jun 2016 + 'player-en_US-vflYIVfbT/base' => '16976 w57 r w57 w38 s3 w47 s2',# 05 Jul 2016 + 'player-en_US-vflL1__zc/base' => '16989 s3 r w58 w34 r', # 07 Jul 2016 + 'player-en_US-vflH9xME5/base' => '16989 s3 r w58 w34 r', # 12 Jul 2016 + 'player-en_US-vflxUWFRm/base' => '16989 s3 r w58 w34 r', # 13 Jul 2016 + 'player-en_US-vflWoKF7f/base' => '16996 r w58 w62 s1 w62 r', # 14 Jul 2016 + 'player-en_US-vflbQww0A/base' => '16989 s3 r w58 w34 r', # 17 Jul 2016 + 'player-en_US-vflIl4-ZN/base' => '16989 s3 r w58 w34 r', # 19 Jul 2016 + 'player-en_US-vfl5RxDNb/base' => '17001 s1 w17 r s3', # 20 Jul 2016 + 'player-en_US-vflIB5TLK/base' => '16989 s3 r w58 w34 r', # 21 Jul 2016 + 'player-en_US-vflVo2R8O/base' => '17007 s1 r w35 r s1 r w36 s3',# 27 Jul 2016 + 'player-en_US-vfld7sVQ3/base' => '17007 s1 r w35 r s1 r w36 s3',# 28 Jul 2016 + 'player-en_US-vflua32tg/base' => '17011 w17 s3 r s3 w26 r w19 s2 w8',# 03 Aug 2016 + 'player-en_US-vflHuW2fm/base' => '17011 w17 s3 r s3 w26 r w19 s2 w8',# 04 Aug 2016 + 'player-en_US-vflI2is8G/base' => '17015 w22 r s2 w24 s2 r', # 08 Aug 2016 + 'player-en_US-vflxMAwM7/base' => '17015 w22 r s2 w24 s2 r', # 09 Aug 2016 + 'player-en_US-vflD53teA/base' => '17015 w22 r s2 w24 s2 r', # 12 Aug 2016 + 'player-en_US-vflduS31F/base' => '17015 w22 r s2 w24 s2 r', # 13 Aug 2016 + 'player-en_US-vflCWknvV/base' => '17015 w22 r s2 w24 s2 r', # 14 Aug 2016 + 'player-en_US-vflsfFMeN/base' => '17015 w22 r s2 w24 s2 r', # 16 Aug 2016 + 'player-en_US-vflYm48JC/base' => '17029 s3 w50 r w46 w5 s2', # 17 Aug 2016 + 'player-en_US-vfl9QlUdu/base' => '17030 r s2 w17 r w1 s1', # 18 Aug 2016 + 'player-en_US-vflIsoTq9/base' => '17031 r s3 w63 r', # 22 Aug 2016 + 'player-en_US-vflB4BK_2/base' => '17031 r s3 w63 r', # 23 Aug 2016 + 'player-en_US-vflrza-6I/base' => '17031 r s3 w63 r', # 25 Aug 2016 + 'player-en_US-vflCFz7Ac/base' => '17039 s3 w2 s2 w46 s1 w31 w27',# 30 Aug 2016 + 'player-en_US-vflYH10GU/base' => '17039 s3 w2 s2 w46 s1 w31 w27',# 31 Aug 2016 + 'player-en_US-vflqMMQzs/base' => '17039 s3 w2 s2 w46 s1 w31 w27',# 01 Sep 2016 + 'player-en_US-vfl3Us3jU/base' => '17046 s2 r s2 w31 w6 r s2', # 06 Sep 2016 + 'player-en_US-vfltdrc9Q/base' => '17050 w19 r s1 r s1 w7 r w38 s3',# 07 Sep 2016 + 'player-en_US-vflwEMtjy/base' => '17056 r s3 r w20 s3 r s2 r', # 13 Sep 2016 + 'player-en_US-vflIb3VDh/base' => '17056 r s3 r w20 s3 r s2 r', # 14 Sep 2016 + 'player-en_US-vflGe_KH9/base' => '17056 r s3 r w20 s3 r s2 r', # 15 Sep 2016 + 'player-en_US-vflOrSoUx/base' => '17060 w35 r s3 r w55 s3 w2', # 20 Sep 2016 + 'player-en_US-vflhmEPlj/base' => '17064 w70 s3 w7 s1 w68 s1 w64',# 21 Sep 2016 + 'player-en_US-vfl-naOSO/base' => '17066 r w30 w40 w48 r s1 w53 s3 r',# 22 Sep 2016 + 'player-en_US-vflHlG7su/base' => '17068 r w35 s2', # 26 Sep 2016 + 'player-en_US-vfl8j0dbL/base' => '17067 w63 s3 w38 s3 w16 w67 s3 r s1',# 26 Sep 2016 + 'player-en_US-vflw2cgEp/base' => '17067 w63 s3 w38 s3 w16 w67 s3 r s1',# 27 Sep 2016 + 'player-en_US-vflhPhaA1/base' => '17071 w15 s3 r s2 w4 s2 r', # 28 Sep 2016 + 'player-en_US-vflK2tmSr/base' => '17072 s3 r s3 r w20', # 29 Sep 2016 + 'player-en_US-vflKBaLr4/base' => '17072 s3 r s3 r w20', # 30 Sep 2016 + 'player-en_US-vflssZQ6P/base' => '17074 r w9 r s3 r s3 w51 r', # 03 Oct 2016 + 'player-en_US-vflXU8Lcz/base' => '17079 r w45 s1 r s2 r s2 r w3',# 05 Oct 2016 + 'player-en_US-vflOj6Vz8/base' => '17079 r w45 s1 r s2 r s2 r w3',# 07 Oct 2016 + 'player-en_US-vflQcYs5w/base' => '17079 r w45 s1 r s2 r s2 r w3',# 07 Oct 2016 + 'player-en_US-vfl-E2vny/base' => '17082 r w9 s1 r s1 w66 w30 r w48',# 11 Oct 2016 + 'player-en_US-vflabgyIE/base' => '17086 s2 w6 r s3 w53 r w46 w56',# 12 Oct 2016 +); + + +my $cipher_warning_printed_p = 0; +sub decipher_sig($$$) { + my ($id, $cipher, $signature) = @_; + + return $signature unless defined ($cipher); + + my $orig = $signature; + my @s = split (//, $signature); + + my $c = $ciphers{$cipher}; + if (! $c) { + print STDERR "$progname: WARNING: $id: unknown cipher $cipher!\n" + if ($verbose > 0 && !$cipher_warning_printed_p); + $c = guess_cipher ($cipher, 0, $cipher_warning_printed_p); + $cipher_warning_printed_p = 1; + } + + $c =~ s/([^\s])([a-z])/$1 $2/gs; + my ($sts) = $1 if ($c =~ s/^(\d+)\s*//si); + + foreach my $c (split(/\s+/, $c)) { + if ($c eq '') { } + elsif ($c eq 'r') { @s = reverse (@s); } + elsif ($c =~ m/^s(\d+)$/s) { @s = @s[$1 .. $#s]; } + elsif ($c =~ m/^w(\d+)$/s) { + my $a = 0; + my $b = $1 % @s; + ($s[$a], $s[$b]) = ($s[$b], $s[$a]); + } + else { errorI ("bogus cipher: $c"); } + } + + $signature = join ('', @s); + + my $L1 = length($orig); + my $L2 = length($signature); + if ($verbose > 4 && $signature ne $orig) { + print STDERR ("$progname: $id: translated sig, $sts $cipher:\n" . + "$progname: old: $L1: $orig\n" . + "$progname: new: $L2: $signature\n"); + } + + return $signature; +} + + +# Total kludge that downloads the current html5player, parses the JavaScript, +# and intuits what the current cipher is. Normally we go by the list of +# known ciphers above, but if that fails, we try and do it the hard way. +# +sub guess_cipher(;$$) { + my ($cipher_id, $selftest_p, $nowarn) = @_; + + # If we're in cipher-guessing mode, crank up the verbosity to also + # mention the list of formats and which format we ended up choosing. + $verbose = 2 if ($verbose == 1 && !$selftest_p); + + + my $url = "https://www.youtube.com/"; + my ($http, $head, $body); + my $id = '-'; + + if (! $cipher_id) { + ($http, $head, $body) = get_url ($url); # Get home page + check_http_status ('-', $url, $http, 2); + + my @vids = (); + $body =~ s%/watch\?v=([^\"\'<>]+)%{ + push @vids, $1; + ''; + }%gsex; + + errorI ("no videos found on home page $url") unless @vids; + + # Get random video -- pick one towards the middle, because sometimes + # the early ones are rental videos. + my $id = @vids[int(@vids / 2)]; + $url .= "/watch\?v=$id"; + + ($http, $head, $body) = get_url ($url); # Get random video's info + check_http_status ($id, $url, $http, 2); + + $body =~ s/\\//gs; + ($cipher_id) = ($body =~ m@/jsbin\\?/((?:html5)?player-.+?)\.js@s); + errorI ("$id: unparsable cipher url: $url\n\nBody:\n\n$body") + unless $cipher_id; + } + + $cipher_id =~ s@\\@@gs; + $url = "https://s.ytimg.com/yts/jsbin/$cipher_id.js"; + + ($http, $head, $body) = get_url ($url); + check_http_status ($id, $url, $http, 2); + + my ($date) = ($head =~ m/^Last-Modified:\s+(.*)$/mi); + $date =~ s/^[A-Z][a-z][a-z], (\d\d? [A-Z][a-z][a-z] \d{4}).*$/$1/s; + + my $v = '[\$a-zA-Z][a-zA-Z\d]*'; # JS variable + + $v = "$v(?:\.$v)?"; # Also allow "a.b" where "a" would be used as a var. + + + # First, find the sts parameter: + my ($sts) = ($body =~ m/\bsts:(\d+)\b/si); + errorI ("$cipher_id: no sts parameter: $url") unless $sts; + + + # Since the script is minimized and obfuscated, we can't search for + # specific function names, since those change. Instead we match the + # code structure. + # + # Note that the obfuscator sometimes does crap like y="split", + # so a[y]("") really means a.split("") + + + # Find "C" in this: var A = B.sig || C (B.s) + my (undef, $fn) = ($body =~ m/$v = ( $v ) \.sig \|\| ( $v ) \( \1 \.s \)/sx); + errorI ("$cipher_id: unparsable cipher js: $url") unless $fn; + + # Find body of function C(D) { ... } + # might be: var C = function(D) { ... } + # might be: , C = function(D) { ... } + my ($fn2) = ($body =~ m@\b function \s+ \Q$fn\E \s* \( $v \) + \s* { ( .*? ) } @sx); + ($fn2) = ($body =~ m@(?: \b var \s+ | [,;] \s* ) + \Q$fn\E \s* = \s* function \s* \( $v \) + \s* { ( .*? ) } @sx) + unless $fn2; + + errorI ("$cipher_id: unparsable fn \"$fn\"") unless $fn2; + + $fn = $fn2; + + # They inline the swapper if it's used only once. + # Convert "var b=a[0];a[0]=a[63%a.length];a[63]=b;" to "a=swap(a,63);". + $fn2 =~ s@ + var \s ( $v ) = ( $v ) \[ 0 \]; + \2 \[ 0 \] = \2 \[ ( \d+ ) % \2 \. length \]; + \2 \[ \3 \]= \1 ; + @$2=swap($2,$3);@sx; + + my @cipher = (); + foreach my $c (split (/\s*;\s*/, $fn2)) { + if ($c =~ m@^ ( $v ) = \1 . $v \(""\) $@sx) { # A=A.split(""); + } elsif ($c =~ m@^ ( $v ) = \1 . $v \(\) $@sx) { # A=A.reverse(); + push @cipher, "r"; + } elsif ($c =~ m@^ ( $v ) = \1 . $v \( (\d+) \) $@sx) { # A=A.slice(N); + push @cipher, "s$2"; + + } elsif ($c =~ m@^ ( $v ) = ( $v ) \( \1 , ( \d+ ) \) $@sx || # A=F(A,N); + $c =~ m@^ ( ) ( $v ) \( $v , ( \d+ ) \) $@sx) { # F(A,N); + my $f = $2; + my $n = $3; + $f =~ s/^.*\.//gs; # C.D => D + # Find function D, of the form: C={ ... D:function(a,b) { ... }, ... } + my ($fn3) = ($body =~ m@ \b \Q$f\E: \s* + function \s* \( .*? \) \s* + ( { [^{}]+ } ) + @sx); + # Look at body of D to decide what it is. + if ($fn3 =~ m@ var \s ( $v ) = ( $v ) \[ 0 \]; @sx) { # swap + push @cipher, "w$n"; + } elsif ($fn3 =~ m@ \b $v \. reverse\( @sx) { # reverse + push @cipher, "r"; + } elsif ($fn3 =~ m@ return \s* $v \. slice @sx || # slice + $fn3 =~ m@ \b $v \. splice @sx) { # splice + push @cipher, "s$n"; + } else { + $fn =~ s/;/;\n\t /gs; + errorI ("unrecognized cipher body $f($n) = $fn3\n\tin: $fn"); + } + } elsif ($c =~ m@^ return \s+ $v \. $v \(""\) $@sx) { # return A.join(""); + } else { + $fn =~ s/;/;\n\t /gs; + errorI ("$cipher_id: unparsable: $c\n\tin: $fn"); + } + } + my $cipher = "$sts " . join(' ', @cipher); + + if ($selftest_p) { + return $cipher if defined($ciphers{$cipher_id}); + $verbose = 2 if ($verbose < 2); + } + + if ($verbose > 1 && !$nowarn) { + my $c2 = " '$cipher_id' => '$cipher',"; + $c2 = sprintf ("%-66s# %s", $c2, $date); + auto_update($c2) if ($selftest_p && $selftest_p == 2); + print STDERR "$progname: current cipher is:\n$c2\n"; + } + + return $cipher; +} + + +# Tired of doing this by hand. Crontabbed self-modifying code! +# +sub auto_update($) { + my ($cipher_line) = @_; + + open (my $in, '<', $progname0) || error ("$progname0: $!"); + local $/ = undef; # read entire file + my ($body) = <$in>; + close $in; + + $body =~ s@(\nmy %ciphers = .*?)(\);)@$1$cipher_line\n$2@s || + error ("auto-update: unable to splice"); + + # Since I'm not using CVS any more, also update the version number. + $body =~ s@([\$]Revision:\s+\d+\.)(\d+)(\s+[\$])@ + { $1 . ($2 + 1) . $3 }@sexi || + error ("auto-update: unable to tick version"); + + open (my $out, '>', $progname0) || error ("$progname0: $!"); + print $out $body; + close $out; + print STDERR "$progname: auto-updated $progname0\n"; + + # This part isn't expected to work for you. + my ($dir) = $ENV{HOME} . '/www/hacks'; + system ("cd '$dir'" . + " && git commit -q -m 'cipher auto-update' '$progname'" . + " && git push -q") + if -d $dir; +} + + +# For verifying that decipher_sig() implements exactly the same transformation +# that the JavaScript implementations do. +# +sub decipher_selftest() { + my $tests = { +# 'UNKNOWN 88' . "\t" . +# ' !"#$%&\'()*+,-x/0123456789:;<=>?@ABCDEFGHIJ.' . # 88 +# 'LMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvw' => +# 'Pqponmlkjihgfedrba`_u]\\[ZYXWVUTSRQcONML.' . +# 'JIHGFEDCBA@?>=<;:9876543210/x-#+*)(\'&%$",', + + 'vflmOfVEX' . "\t" . + ' !"#$%&\'()*+,-x/0123456789:;<=>?@ABCDEFGHIJ.' . # 87 + 'LMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuv' => + '^rqponmlkjihgfedcba`_s]\\[ZYXWVU SRQPONML.' . + 'JIHGFEDCBA@?>=<;:9876543210/x-,+*)(\'&%$#', + + 'vfl_ymO4Z' . "\t" . + ' !"#$%&\'()*+,-x/0123456789:;<=>?@ABCDEFGHI.' . # 86 + 'KLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstu' => + '"#$%&\'()*+,-x/0123456789:;<=>?@ABCDEFGHI.' . + 'KLMNOPQRSTUVWXYZ[\]^r`abcdefghijklmnopq_', + + 'vfltM3odl' . "\t" . + ' !"#$%&\'()*+,-x/0123456789:;<=>?@ABCDEFGHI.' . # 85 + 'KLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrst' => + 'lrqponmskjihgfedcba`_^] [ZYXWVUTS!QPONMLK.' . + 'IHGFEDCBA@?>=<;:9876543210/x-,+*)(\'&%$#', + +# 'UNKNOWN 84' . "\t" . +# ' !"#$%&\'()*+,-x/0123456789:;<=>?@ABCDEFGH.' . # 84 +# 'JKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrs' => +# 'srqponmlkjihgfedcba`_^]\\[ZYXWVUTSRQPONMLKJ.' . +# 'HGFE"CBA@?>=<;#9876543210/x-,+*)(\'&%$:', + +# 'UNKNOWN 83' . "\t" . +# ' !"#$%&\'()*+,-x/0123456789:;<=>?@ABCDEFGH.' . # 83 +# 'JKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqr' => +# 'Tqponmlkjihgfedcba`_^]\\[ZYX"VUrSRQPONMLKJ.' . +# 'HGFEWCBA@?>=<;:9876543210/x-,+*)(\'&%$#D', + +# 'UNKNOWN 82' . "\t" . +# ' !"#$%&\'()*+,-x/0123456789:;<=>?@ABCDEFG.' . # 82 +# 'IJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopq' => +# 'Donmlkjihgfedqba`_^]\\[ZYXWVUTSRQPONMLKJIAGFE.' . +# 'C c@?>=<;:9876543210/x-,+*)(\'&%$#"!B', + + 'vflmOfVEX' . "\t" . + '5AEEAE0EC39677BC65FD9021CCD115F1F2DBD5A59E4.' . # Real examples + 'C0B243A3E2DED6769199AF3461781E75122AE135135' => # 87 + '931EA22157E1871643FA9519676DED253A342B0C.' . + '4E95A5DBD2F1F511DCC1209DF56CB77693CE0EAE', + + 'vflmOfVEX' . "\t" . + '7C03C0B9B947D9DCCB27CD2D1144BA8F91B7462B430.' . # 87 + '8CFE5FA73DDE66DCA33BF9F902E09B160BC42924924' => + '32924CB061B90E209F9FB43ACD66EDD77AF5EFC8.' . + '034B2647B19F8AB4411D2DC72BCCD9D749B9B0C3', + + 'vflmOfVEX' . "\t" . + '38A48AA6FAC88C2240DEBE5F74F4E62DC1F0828E990.' . # 87 + '53B824774161BD7CE735CA84963AA17B002D1901901' => + '3091D200B71AA36948AC517EC7DB161377428B35.' . + '099E8280F1CD26E4F47F5EBED0422C88CAF6AA84', + + 'vfl_ymO4Z' . "\t" . + '7272B1BA35548BA3939F9CE39C4E72A98BB78ABB28.' . # 86 + '560A7424D42FF070C115935232F8BDB8A1F3E05C05C' => + '72B1BA35548BA3939F9CE39C4E72A98BB78ABB28.' . + '560A7424D42FF070C115C35232F8BDB8A1F3E059', + + 'vflmOfVEX' . "\t" . + 'CFDEFDEBFC25C1BA6E940A10E4ED8326FD4EDDD0B1A.' . # 87 from "watch?v=" + '22F7E77BE9637FBE657ED4FDE0DEE96F06CB011D11D' => +# '61661661658E036DF1B58C21783028FE116E7DB7C62B.' . # corresponding sig +# 'D225BE11FBCBD59C62F163A57BF8EC1B47897485E85E' => # from "get_video_info" + '7110BC60F69EED0EDF4DED56EBF7369CB77E7F22.' . + 'A1B0DDDE4DF6238DE4E01A049E6AB1C52CFBEDFE', + + 'en_US-vfl0Cbn9e' . "\t" . + '9977B9CA5435687412E6E3436260447A98CA0268.' . + '83C3A50B214CE0D9279695F4B5A31FEFEC4CFAA9AA5' => + '1937B9CA5435687912E6E3436260447A98CA0268.' . + '83C4A50B274CE0D9275695F4B5A31FEFEC4CFAA9', + }; + + my %verified; + foreach my $key (sort { my ($aa, $bb) = ($a, $b); + foreach ($aa, $bb) { s/^.*?\t//s; } + length($aa) == length($bb) + ? $aa cmp $bb + : length($aa) <=> length($bb) } + keys (%$tests)) { + my $expect = $tests->{$key}; + my ($cipher, $sig) = split (/\t/, $key); + my $id = $cipher . " " . length ($sig); + my $got = decipher_sig ($id, $cipher, $sig); + my $L2 = length ($got); + if ($expect eq $got) { + my $v = ($key !~ m/ABCDEF/s); + print STDERR "$id: OK ($L2) $got\n"; + $verified{$id} = $verified{$id} || $v; + } + else { print STDERR "$id: FAIL: $got\n"; } + } + my @un = (); + foreach my $k (sort (keys %verified)) { + push @un, $k unless $verified{$k}; + } + print STDERR "Unverified: " . join(', ', @un) . "\n"; +} + +# decipher_selftest(); exit(); + + +# Replace the signature in the URL, deciphering it first if necessary. +# +sub apply_signature($$$$$) { + my ($id, $fmt, $url, $cipher, $sig) = @_; + if ($sig) { + if (defined ($cipher)) { + my $o = $sig; + $sig = decipher_sig ("$id/$fmt", $cipher, $sig); + if ($o ne $sig) { + my $n = $sig; + my ($a, $b) = split(/\./, $o); + my ($c, $d) = split(/\./, $sig); + ($a, $b) = ($o, '') unless defined($b); + ($c, $d) = ($sig, '') unless defined($d); + my $L1 = sprintf("%d %d.%d", length($o), length($a), length($b)); + my $L2 = sprintf("%d %d.%d", length($sig), length($c), length($d)); + foreach ($o, $n) { s/\./.\n /gs; } + my $s = "cipher: $cipher\n$L1: $o\n$L2: $n"; + $error_whiteboard .= "\n" if $error_whiteboard; + $error_whiteboard .= "$fmt: " . + "https://www.youtube.com/watch?v=$id\n$s"; + if ($verbose > 3) { + print STDERR "$progname: $id: deciphered and replaced signature\n"; + $s =~ s/^([^ ]+)( )/$2$1/s; + $s =~ s/^/$progname: /gm; + print STDERR "$s\n"; + } + } + } + $url =~ s@&signature=[^&]+@@gs; + $url .= '&signature=' . $sig; + } + return $url; +} + + + + +# Convert the text of a Youtube urlmap field into a structure. +# Apply signatures to enclosed URLs as necessary. +# Returns a hashref, or undef if the signatures could not be applied. +# +sub youtube_parse_urlmap($$$) { + my ($id, $urlmap, $cipher) = @_; + + my $cipher_printed_p = 0; + + my %fmts; + foreach (split (/,/, $urlmap)) { + # Format used to be: "N|url,N|url,N|url" + # Now it is: "url=...&quality=hd720&fallback_host=...&type=...&itag=N" + my ($k, $v, $e, $sig, $sig2); + if (m/^\d+\|/s) { + ($k, $v) = m/^(.*?)\|(.*)$/s; + } elsif (m/^[a-z][a-z\d_]*=/s) { + + ($sig) = m/\bsig=([^&]+)/s; # sig= when un-ciphered. + ($sig2) = m/\bs=([^&]+)/s; # s= when enciphered. + + ($k) = m/\bitag=(\d+)/s; + ($v) = m/\burl=([^&]+)/s; + $v = url_unquote($v) if ($v); + + my ($q) = m/\bquality=([^&]+)/s; + my ($t) = m/\btype=([^&]+)/s; + $t = url_unquote($t) if ($t); + if ($q && $t) { + $e = "\t$q, $t"; + } elsif ($t) { + $e = $t; + } + $e = url_unquote($e) if ($e); + } + + error ("$id: can't download RTMPE DRM videos") + # There was no indiciation in get_video_info that this is an RTMPE + # stream, so it took us several retries to fail here. + if (!$v && $urlmap =~ m/\bconn=rtmpe%3A/s); + + errorI ("$id: unparsable urlmap entry: no itag: $_") unless ($k); + errorI ("$id: unparsable urlmap entry: no url: $_") unless ($v); + + my ($ct) = ($e =~ m@\b((audio|video|text|application)/[-_a-z\d]+)\b@si); + + $v =~ s@^.*?\|@@s; # VEVO + + if ($verbose > 1 && !$cipher_printed_p) { + print STDERR "$progname: $id: " . + ($sig2 ? "enciphered" : "non-enciphered") . + ($sig2 && $cipher ? " ($cipher)" : "") . "\n"; + $cipher_printed_p = 1; + } + + # If we have an enciphered sig, but don't know the cipher, we have to + # go through the HTML path. + # + if ($sig2 && !$cipher) { + print STDERR "$progname: $id: enciphered sig. Scraping HTML...\n" + if ($verbose > 1); + return undef; + } + + # Apply the signature to the URL, deciphering it if necessary. + # + # The "use_cipher_signature" parameter is as lie: it is sometimes true + # even when the signatures are not enciphered. The only way to tell + # is if the URLs in the map contain "s=" instead of "sig=". + # + # If we loaded get_video_info with the "sts" parameter, meaning we told + # it what cipher to use, then the returned URLs have that cipher, and + # all is good. However, if we had omitted the "sts" parameter, then + # the URLs come back with some unknown cipher (it's not the last cipher + # in the list, for example) so we can't decode it. + # + # So in the bad old days, we didn't use "sts", and when we got an + # enciphered video, we had to scrape the HTML to find the real cipher. + # This had the shitty side effect that when a video was both enciphered + # and was "content warning", we couldn't download it at all. + # + # But now that we always pass "sts" to get_video_info, this isn't a + # problem any more. I think that in this modern world, we never actually + # need to scrape HTML any more, because we should always know a working + # cipher ahead of time. + # + $v = apply_signature ($id, $k, $v, + $sig2 ? $cipher : undef, + $sig || $sig2); + + # Finally! The "ratebypass" parameter turns off rate limiting! + # But we can't add it to a URL that signs the "ratebypass" parameter, + # which (currently, at least) is format 18, which is not rate-limited + # anyway. + # + $v .= '&ratebypass=yes' + unless ($v =~ m@sparams=[^?&]*ratebypass@); + + print STDERR "\t\t$k\t$v\t$e\n" if ($verbose > 3); + + my %v = ( fmt => $k, + url => $v, + content_type => $ct, + # w => undef, + # h => undef, + # size => undef, + # abr => undef, + ); + + $fmts{$k} = \%v; + } + + return \%fmts; +} + + +# This version parses the HTML instead of get_video_info, +# in the case where get_video_info didn't work. +# #### But does that case still exist, now that we use "sts"? +# +sub load_youtube_formats_html($$$) { + my ($id, $url, $oerror) = @_; + + my ($http, $head, $body) = get_url ($url); + + my ($title) = ($body =~ m@<title>\s*(.*?)\s*@si); + $title = munge_title (html_unquote ($title || '')); + + my $unquote_p = 1; + my ($args) = ($body =~ m@'SWF_ARGS' *: *{(.*?)}@s); + + if (! $args) { # Sigh, new way as of Apr 2010... + ($args) = ($body =~ m@var swfHTML = [^\"]*\"(.*?)\";@si); + $args =~ s@\\@@gs if $args; + ($args) = ($args =~ m@@si) if $args; + ($args) = ($args =~ m@fmt_url_map=([^&]+)@si) if $args; + $args = "\"fmt_url_map\": \"$args\"" if $args; + } + if (! $args) { # Sigh, new way as of Aug 2011... + ($args) = ($body =~ m@'PLAYER_CONFIG':\s*{(.*?)}@s); + $args =~ s@\\u0026@&@gs if $args; + $unquote_p = 0; + } + if (! $args) { # Sigh, new way as of Jun 2013... + ($args) = ($body =~ m@ytplayer\.config\s*=\s*{(.*?)};@s); + $args =~ s@\\u0026@&@gs if $args; + $unquote_p = 1; + } + + my $blocked_re = join ('|', + ('(available|blocked it) in your country', + 'copyright (claim|grounds)', + 'removed by the user', + 'is not available', + 'Content Warning')); + + if (! $args) { + # Try to find a better error message + my (undef, $err) = ($body =~ m@<( div | h1 ) \s+ + (?: id | class ) = + "(?: error-box | + yt-alert-content | + unavailable-message )" + [^<>]* > \s* + ( [^<>]+? ) \s* + @six); + $err = "Rate limited: CAPCHA required" + if (!$err && $body =~ m/large volume of requests/); + if ($err) { + my ($err2) = ($body =~ m@

(.*?)
@si); + if ($err2) { + $err2 =~ s@]*>//gs; + $err .= ": $err2"; + } + $err =~ s/^"[^\"\n]+"\n//s; + $err =~ s/^"[^\"\n]+?"\n//s; + $err =~ s/\s+/ /gs; + $err =~ s/^\s+|\s+$//s; + $err =~ s/\.(: )/$1/gs; + $err =~ s/\.$//gs; + + $err = "$err ($title)" if ($title); + + $oerror = $err; + $http = 'HTTP/1.0 404'; + } + } + + if ($args && $body =~ m/LIVESTREAMING_CARDIO_POLLING_INTERVAL/si) { + $oerror = "can't download livestream videos$oerror"; + # With --quiet, just silently ignore livestream failures, + # for "youtubefeed". + exit (0) if ($verbose <= 0); + } + + + if ($verbose <= 0 && $oerror =~ m/$blocked_re/sio) { + # With --quiet, just silently ignore country-locked video failures, + # for "youtubefeed". + exit (0); + } + + $oerror =~ s@<.*?>@@gs if $oerror; + + # Sometimes Youtube returns HTTP 404 pages that have real messages in them, + # so we have to check the HTTP status late. But sometimes it doesn't return + # 404 for pages that no longer exist. Hooray. + + $http = 'HTTP/1.0 404' + if ($oerror && $oerror =~ m/$blocked_re/sio); + error ("$id: $http: $oerror") + unless (check_http_status ($id, $url, $http, 0)); + errorI ("$id: no ytplayer.config$oerror") + unless $args; + + my ($kind, $kind2, $urlmap, $urlmap2); + + ($kind, $urlmap) = ($args =~ m@"(fmt_url_map)": *"(.*?)"@s) + unless $urlmap; + ($kind, $urlmap) = ($args =~ m@"(fmt_stream_map)": *"(.*?)"@s) # VEVO + unless $urlmap; + ($kind, $urlmap) = ($args =~ m@"(url_encoded_fmt_stream_map)": *"(.*?)"@s) + unless $urlmap; # New nonsense seen in Aug 2011 + + ($kind2, $urlmap2) = ($args =~ m@"(adaptive_fmts)": *"(.*?)"@s) + unless $urlmap2; + + if (! $urlmap) { + if ($body =~ m/This video has been age-restricted/s) { + error ("$id: enciphered but age-restricted$oerror"); + } + errorI ("$id: no fmt_url_map: $oerror"); + } + + $kind = $kind2 if $kind2; + print STDERR "$progname: $id: found $kind in HTML\n" + if ($kind && $verbose > 1); + + my ($cipher) = ($body =~ m@/jsbin\\?/((?:html5)?player-.+?)\.js@s); + $cipher =~ s@\\@@gs if $cipher; + + return ($title, $urlmap, $urlmap2, $cipher); +} + + + +# Returns a hash of: +# [ title: "T", +# N: [ ...video info... ], +# M: [ ...video info... ], ... ] +# +sub load_youtube_formats($$) { + my ($id, $url) = @_; + + my $cipher = undef; + my $sts = undef; + + # Let's just use an old cipher. Doing this allows us to download + # videos that are both enciphered and "content warning". + # + # But not all old ciphers work! Though all of them used to. + # + # Current theory is that as of 4-Mar-2015, only 'sts' values >= 16497 + # work. Which means the first three still work, and more recent ones. + # + # And as of 31-Mar-2015, 16497 stopped working, but the next one, 16503, + # still works. So they are expiring them now, after something less than + # a month. But the three really old ones (135957536242, etc.) still + # work -- possibly only because those are larger numbers? + # + # The large sts numbers are time_t in 1/100th sec. The smaller numbers are + # who-knows-what, and are sorted alphabetically rather than numerically, + # so "1588" == "15880" and "16" == "16000". Yeah, really. + # + $cipher = 'vflNzKG7n'; # This is our oldest cipher, 30-Jan-2013. + + if ($cipher) { + $sts = $1 if ($ciphers{$cipher} =~ m/^\s*(\d+)\s/si); + errorI ("$cipher: no sts") unless $sts; + } + + my $info_url = ("https://www.youtube.com/get_video_info?video_id=$id" . + # Avoid the "playback restricted" error. This is a referer. + '&eurl=' . url_quote ($url) . + ($sts ? '&sts=' . $sts : '') + ); + my ($title, $kind, $kind2, $urlmap, $urlmap2, $body, $rental, $realtime, + $rtmpe_p, $embed_p, $dashmpd); + + my $retries = 5; + my $err = undef; + + while (--$retries) { # Sometimes the $info_url fails; try a few times. + + my ($http, $head); + ($http, $head, $body) = get_url ($info_url); + $err = (check_http_status ($id, $url, $http, 0) ? undef : $http); + + ($kind, $urlmap) = ($body =~ m@&(fmt_url_map)=([^&]+)@si) + unless $urlmap; + ($kind, $urlmap) = ($body =~ m@&(fmt_stream_map)=([^&]+)@si) # VEVO + unless $urlmap; + ($kind, $urlmap) = ($body =~ m@&(url_encoded_fmt_stream_map)=([^&]+)@si) + unless $urlmap; # New nonsense seen in Aug 2011 + + ($kind2, $urlmap2) = ($body =~ m@&(adaptive_fmts)=([^&]+)@si) # 2014 + unless $urlmap2; + + if (!$err && + $body =~ m/\bstatus=fail\b/si && + $body =~ m/\breason=([^?&]+)/si) { + $err = url_unquote ($1); + } + + ($title) = ($body =~ m@&title=([^&]+)@si) unless $title; + ($rental) = ($body =~ m@&ypc_video_rental_bar_text=([^&]+)@si); + ($realtime) = ($body =~ m@&(?:livestream|live_playback|hlsvp)=([^&]+)@si); + ($embed_p) = ($body =~ m@&allow_embed=([^&]+)@si); + $rtmpe_p = ($urlmap && $urlmap =~ m/rtmpe(=|%3D|%253D)yes/s); + ($dashmpd) = ($body =~ m@&dashmpd=([^&]+)@s); + $dashmpd = url_unquote($dashmpd) if $dashmpd; + + $embed_p = 0 if (!defined($embed_p) && + $body =~ m/on[\s+]other[\s+]websites/s); + + $kind = $kind2 if $kind2; + print STDERR "$progname: $id: found $kind in JSON" . + (defined($embed_p) + ? ($embed_p ? " (embeddable)" : " (non-embeddable)") + : "") . + "\n" + if ($kind && $verbose > 1); + + last if ($rental || $realtime || $rtmpe_p || + ($urlmap && $urlmap2 && $title) || + (defined($embed_p) && !$embed_p)); + + if ($verbose > 0) { + if (!$urlmap2) { + print STDERR "$progname: $id: no adaptive_fmts, retrying...\n"; + } elsif (! $urlmap) { + print STDERR "$progname: $id: no fmt_url_map, retrying...\n"; + } else { + print STDERR "$progname: $id: no title, retrying...\n"; + } + } + + sleep (1); + } + + $err = "video is not embeddable" + if ($err && (defined($embed_p) && !$embed_p)); + + if ($err && (defined($embed_p) && !$embed_p)) { + # Ignore the embed error and go on to HTML scraping. + $err = undef; + } + + $err = "can't download rental videos" + if (!$err && !$urlmap && $rental); + + $err = "can't download livestream videos" + if (!$err && !$urlmap && $realtime); + + $err = "can't download RTMPE DRM videos" + if (!$err && $rtmpe_p); + + if ($err && $verbose <= 0) { + my $blocked_re = join ('|', + ('(available|blocked it) in your country', + 'copyright (claim|grounds)', + 'removed by the user', + 'invalid parameters', + 'is not available', + 'is not embeddable', + 'account.*has been terminated', + 'livestream videos', + 'RTMPE DRM', + )); + if ($err =~ m/$blocked_re/sio) { + # With --quiet, just silently ignore country-locked video failures, + # for "youtubefeed". + exit (0); + } + } + + error ("$progname: $id: $err") + if $err; + + ($title) = ($body =~ m@&title=([^&]+)@si) unless $title; + errorI ("$id: no title in $info_url") if (!$title && $urlmap); + $title = url_unquote($title) if $title; + + my $fmts = undef; + + if (! $urlmap) { + print STDERR "$progname: $id: no fmt_url_map" . + (defined($embed_p) + ? ($embed_p ? " (embeddable)" : " (non-embeddable)") + : "") . + ", scraping HTML.\n" + if ($verbose > 1); + } + + # Sometimes the DASH MPD lists formats the get_video_info file does + # not list, and vice versa! E.g., format 141. WTF. + # + if (0 && $dashmpd && $verbose) { + my ($http2, $head2, $body2) = get_url ($dashmpd); + if (check_http_status ($id, $dashmpd, $http2, 0)) { + my @reps = split(/]*>([^<>]+)@si); + print STDERR "\t$id\t$url2\n"; + } + } + } + + if ($urlmap) { + $urlmap = url_unquote ($urlmap); + $urlmap2 = url_unquote ($urlmap2) if ($urlmap2); + + # Use both url_encoded_fmt_stream_map and adaptive_fmts. + $urlmap .= ",$urlmap2" if $urlmap2; + $fmts = youtube_parse_urlmap ($id, $urlmap, $cipher); + } + + if (! defined($fmts)) { + + # We couldn't get a URL map out of the info URL. + # Scrape the HTML instead. + # + # This still happens for non-embeddable videos, where get_video_info + # says status=fail with no formats data. It also happens for RTMPE, + # but in that case we fail anyway. + + if ($body =~ m/private[+\s]video|video[+\s]is[+\s]private/si) { + error ("$id: private video"); # scraping won't work. + } + + my ($err) = ($body =~ m@reason=([^&]+)@s); + $err = '' unless $err; + if ($err) { + $err = url_unquote($err); + $err =~ s/^"[^\"\n]+"\n//s; + $err =~ s/\s+/ /gs; + $err =~ s/^\s+|\s+$//s; + $err = " (\"$err\")"; + } + + ($title, $urlmap, $urlmap2, $cipher) = + load_youtube_formats_html ($id, $url, $err); + + # Use both url_encoded_fmt_stream_map and adaptive_fmts. + $urlmap .= ",$urlmap2" if $urlmap2; + $fmts = youtube_parse_urlmap ($id, $urlmap, $cipher); + } + + errorI ("$id: no formats available") unless (defined($fmts)); + + $fmts->{title} = $title; + return $fmts; +} + + +# Returns a hash of: +# [ title: "T", +# N: [ ...video info... ], +# M: [ ...video info... ], ... ] +# +sub load_vimeo_formats($$) { + my ($id, $url) = @_; + + # Vimeo's new way, 3-Mar-2015. + # The "/NNNN?action=download" page no longer exists. There is JSON now. + + # This URL is *often* all that we need: + # + my $info_url = ("https://player.vimeo.com/video/$id/config" . + "?bypass_privacy=1"); # Not sure if this does anything + + # But if we scrape the HTML page for the version of the config URL + # that has "&s=XXXXX" on it (some kind of signature, I presume) then + # we *sometimes* get HD when we would not have gotten it with the + # other URL: + # + my ($http, $head, $body) = get_url ($url); + if (check_http_status ($id, $url, $http, 0)) { + if ($body =~ m@(\bhttps?://[^/]+/video/\d+/config\?[^\s\"\'<>]+)@si) { + $info_url = html_unquote($1); + } else { + print STDERR "$progname: $id: no info URL\n" if ($verbose > 1); + } + } + + my $referer = $url; + + # Test cases: + # + # https://vimeo.com/120401488 + # Has a Download link on the page that lists 270p, 360p, 720p, 1080p + # The config url only lists 270p, 360p, 1080p + # https://vimeo.com/70949607 + # No download link on the page + # The config URL gives us 270p, 360p, 1080p + # https://vimeo.com/104323624 + # No download link + # Simple info URL gives us only one size, 360p + # Signed info URL gives us 720p and 360p + # https://vimeo.com/117166426 + # A private video + # https://vimeo.com/88309465 + # "HTTP/1.1 451 Unavailable For Legal Reasons" + # "removed as a result of a third-party notification" + # https://vimeo.com/121870373 + # A private video that isn't 404 for some reason + # https://vimeo.com/83711059 + # The HTML page is 404, but the simple info URL works, + # and the video is downloadable anyway! + # https://vimeo.com/209 + # Yes, this is a real video. No "h264" in "files" metadata, + # only .flv as "vp6". + # https://www.vimeo.com/142574658 + # Only has "progressive" formats, not h264. Downloads fine though. + + ($http, $head, $body) = get_url ($info_url, $referer); + + my $err = undef; + if (!check_http_status ($id, $info_url, $http, 0)) { + ($err) = ($body =~ m@ \{ "message" : \s* " ( .+? ) " , @six); + $err = "Private video" if ($err && $err =~ m/privacy setting/si); + $err = $http . ($err ? ": $err" : ""); + } else { + $http = ''; # 200 + } + + my ($title) = ($body =~ m@ "title" : \s* " (.+?) ", @six); + my ($files0) = ($body =~ m@ \{ "h264" : \s* \{ ( .+? \} ) \} , @six); + my ($files1) = ($body =~ m@ \{ "vp6" : \s* \{ ( .+? \} ) \} , @six); + my ($files2) = ($body =~ m@ "progressive" : \s* \[ ( .+? \] ) \} @six); + my $files = ($files0 || '') . ($files1 || '') . ($files2 || ''); + + # Sometimes we get empty-ish data for "Private Video", but HTTP 200. + $err = "No video info (Private?)" + if (!$err && !$title && !$files); + + if ($err) { + if ($verbose <= 0 && $err =~ m/Private|\b404\b/s) { + # With --quiet, just silently ignore private videos and 404s, + # for "youtubefeed". + exit (0); + } + + error ("$id: $err") if ($http || $err =~ m/Private/s); + errorI ("$id: $err"); + } + + my %fmts; + + if ($files) { + errorI ("$id: no title") unless $title; + $fmts{title} = $title; + my $i = 0; + foreach my $f (split (/\},?\s*/, $files)) { + next unless (length($f) > 50); + my ($fmt) = ($f =~ m@^ \" (.+?) \": @six); + ($fmt) = ($f =~ m@^ \{ "profile": (\d+) @six) unless $fmt; + my ($url2) = ($f =~ m@ "url" : \s* " (.*?) " @six); + my ($w) = ($f =~ m@ "width" : \s* (\d+) @six); + my ($h) = ($f =~ m@ "height" : \s* (\d+) @six); + errorI ("$id: unparsable video formats") + unless ($fmt && $url2 && $w && $h); + print STDERR "$progname: $fmt: ${w}x$h: $url2\n" + if ($verbose > 2); + + my ($ext) = ($url2 =~ m@ ^ [^?&]+ \. ( [^./?&]+ ) ( [?&] | $ ) @sx); + $ext = 'mp4' unless $ext; + my $ct = ($ext =~ m/^(flv|webm|3gpp?)$/s ? "video/$ext" : + $ext =~ m/^(mov)$/s ? 'video/quicktime' : + 'video/mpeg'); + + my %v = ( fmt => $i, + url => $url2, + content_type => $ct, + w => $w, + h => $h, + # size => undef, + # abr => undef, + ); + $fmts{$i} = \%v; + $i++; + } + } + + return \%fmts; +} + + +# Returns a hash of: +# [ title: "T", +# year: "Y", +# N: [ ...video info... ], +# M: [ ...video info... ], ... ] +# +sub load_tumblr_formats($$) { + my ($id, $url) = @_; + + my ($host) = ($url =~ m@^https?://([^/]+)@si); + my $info_url = "https://api.tumblr.com/v2/blog/$host/posts/video?id=$id"; + + my ($http, $head, $body) = get_url ($info_url); + check_http_status ($id, $url, $http, 1); + + $body =~ s/^.* "posts" : \[ //six; + + my ($title) = ($body =~ m@ "slug" : \s* \" (.+?) \" @six); + my ($year) = ($body =~ m@ "date" : \s* \" (\d{4})- @six); + + $title = munge_title (html_unquote ($title || '')); + + my $fmts = {}; + + $body =~ s/^.* "player" : \[ //six; + + my $i = 0; + foreach my $chunk (split (/\},/, $body)) { + my ($e) = ($chunk =~ m@ "embed_code" : \s* " (.*?) " @six); + + $e =~ s/\\n/\n/gs; + $e =~ s/ \\[ux] { ([a-z0-9]+) } / unihex($1) /gsexi; # \u{XXXXXX} + $e =~ s/ \\[ux] ([a-z0-9]{4}) / unihex($1) /gsexi; # \uXXXX + $e =~ s/\\//gs; + + my ($w) = ($e =~ m@width=['"]?(\d+)@si); + my ($h) = ($e =~ m@height=['"]?(\d+)@si); + my ($src) = ($e =~ m@@si); + my ($v) = ($src =~ m@src=['"](.*?)['"]@si); + my ($ct) = ($src =~ m@type=['"](.*?)['"]@si); + + my %v = ( fmt => $i, + url => $v, + content_type => $ct, + w => $w, + h => $h, + # size => undef, + # abr => undef, + ); + $fmts->{$i} = \%v; + $i++; + } + + $fmts->{title} = $title; + $fmts->{year} = $year; + + return $fmts; +} + + +# Returns a hash of: +# [ title: "T", +# year: "Y", +# 0: [ ...video info... ], +# Since Vine only offers one resolution. +# +sub load_vine_formats($$) { + my ($id, $url) = @_; + + my ($http, $head, $body) = get_url ($url); + check_http_status ($id, $url, $http, 1); + + my ($user) = ($body =~ m@ $i, + url => $src, + content_type => $ct, + w => $w, + h => $h, + # size => undef, + # abr => undef, + ); + $fmts->{$i} = \%v; + + $fmts->{title} = $title; + $fmts->{year} = $year; + + return $fmts; +} + + +# Returns a hash of: +# [ title: "T", +# year: "Y", +# 0: [ ...video info... ], +# Since Instagram only offers one resolution. +# +sub load_instagram_formats($$) { + my ($id, $url) = @_; + + my ($http, $head, $body) = get_url ($url); + check_http_status ($id, $url, $http, 1); + + my ($title) = ($body =~ m@ $i, + url => $src, + content_type => $ct, + w => $w, + h => $h, + # size => undef, + # abr => undef, + ); + $fmts->{$i} = \%v; + + $fmts->{title} = $title; + $fmts->{year} = $year; + + return $fmts; +} + +# Returns a hash of: +# [ title: "T", +# year: "Y", +# 0: [ ...video info... ], +# Since Twitter only offers one resolution. +# +sub load_twitter_formats($$) { + my ($id, $url) = @_; + + my ($http, $head, $body) = get_url ($url); + check_http_status ($id, $url, $http, 1); + my ($title) = ($body =~ m@&?.]+)@s); + errorI ("$id: video ID not found") unless ($id2); + my $src = "https://pbs.twimg.com/tweet_video/$id2.mp4"; + + $title =~ s/ on Twitter$//s; + $title = munge_title (html_unquote ($title || '')); + + my $ct = 'image/mp4'; + my ($w, $h) = (0, 0); + my $year = undef; + + my $fmts = {}; + + my $i = 0; + my %v = ( fmt => $i, + url => $src, + content_type => $ct, + w => $w, + h => $h, + # size => undef, + # abr => undef, + ); + $fmts->{$i} = \%v; + + $fmts->{title} = $title; + $fmts->{year} = $year; + + return $fmts; +} + + +# Return the year at which this video was uploaded. +# +sub get_youtube_year($) { + my ($id) = @_; + + # 13-May-2015: https://www.youtube.com/watch?v=99lDR6jZ8yE (Lamb) + # HTML says this: + # Uploaded on Oct 28, 2011 + # But /feeds/api/videos/99lDR6jZ8yE?v=2 says: + # 2015-05-13T21:13:28.000Z + # 2015-04-17T15:23:22.000Z + # 2015-04-17T15:23:22.000Z + # + # And one of my own: https://www.youtube.com/watch?v=HbN4wBJMOuE + # Published on Sep 20, 2014 + # 2015-04-17T15:23:22.000Z + # 2015-05-16T18:48:26.000Z + # 2015-04-17T15:23:22.000Z + # + # In fact, I uploaded that on Sep 20, 2014, and when I did I set the + # Advanced Settings / Recording Date to Sep 14, 2014. Some time in + # 2015, I edited the description text. I have no theory for why the + # "published" and "updated" dates are different and are both 2015. + # + # So, let's scrape the HTML isntead of using the API. + # + # (Actually, we don't have a choice now anyway, since they turned off + # the v2 API in June 2015, and the v3 API requires authentication.) + + # my $data_url = ("https://gdata.youtube.com/feeds/api/videos/$id?v=2" . + # "&fields=published" . + # "&safeSearch=none" . + # "&strict=true"); + my $data_url = "https://www.youtube.com/watch?v=$id"; + + my ($http, $head, $body) = get_url ($data_url); + return undef unless check_http_status ($id, $data_url, $http, 0); + + # my ($year, $mon, $dotm, $hh, $mm, $ss) = + # ($body =~ m@(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)@si); + + my ($year) = ($body =~ m@\bclass="watch-time-text">[^<>]+\b(\d{4})(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)@si); + return $year; +} + + +# Given a list of available underlying videos, pick the ones we want. +# +sub pick_download_format($$$$$) { + my ($id, $site, $url, $force_fmt, $fmts) = @_; + + if (defined($force_fmt) && $force_fmt eq 'all') { + my @all = (); + foreach my $k (keys %$fmts) { + next if ($k eq 'title'); + next if ($k eq 'year'); + push @all, $k; + } + return sort { $a <=> $b } @all; + } + + if ($site eq 'vimeo' || + $site eq 'tumblr' || + $site eq 'vine' || + $site eq 'instagram' || + $site eq 'twitter') { + # On these sites, just pick the entry with the largest size + # and/or resolution. + + # No muxing needed on Vimeo + $force_fmt = undef if ($force_fmt && $force_fmt eq 'mux'); + + if (defined($force_fmt)) { + error ("$site --fmt must be digits: $force_fmt") + unless ($force_fmt =~ m/^\d+$/s); + foreach my $k (keys %$fmts) { + if ($k eq $force_fmt) { + print STDERR "$progname: $id: forced #$k (" . + $fmts->{$k}->{w} . " x " . + $fmts->{$k}->{h} . ")\n" + if ($verbose > 1); + return $k; + } + } + error ("$id: format $force_fmt does not exist"); + } + + my $best = undef; + foreach my $k (keys %$fmts) { + next if ($k eq 'title'); + next if ($k eq 'year'); + $best = $k + if (!defined($best) || + (($fmts->{$k}->{size} || 0) > ($fmts->{$best}->{size} || 0) || + ($fmts->{$k}->{w} * $fmts->{$k}->{h} > + $fmts->{$best}->{w} * $fmts->{$best}->{h}))); + } + print STDERR "$progname: $id: picked #$best (" . + $fmts->{$best}->{w} . " x " . + $fmts->{$best}->{h} . ")\n" + if ($verbose > 1); + return $best; + } elsif ($site ne 'youtube') { + errorI ("unknown site $site"); + } + + errorI ("$id: unrecognized site: $url") unless ($site eq 'youtube'); + + my %known_formats = ( + # + # v=undef means it's an audio-only format. + # a=undef means it's a video-only format. + # Codecs "mp4S" and "webmS" are 3d video (left/right stereo). + # + # ID video container video size audio codec bitrate + # + 0 => { v => 'flv', w => 320, h => 180, a => 'mp3', abr => 64 }, + 5 => { v => 'flv', w => 320, h => 180, a => 'mp3', abr => 64 }, + 6 => { v => 'flv', w => 480, h => 270, a => 'mp3', abr => 96 }, + 13 => { v => '3gp', w => 176, h => 144, a => 'amr', abr => 13 }, + 17 => { v => '3gp', w => 176, h => 144, a => 'aac', abr => 24 }, + 18 => { v => 'mp4', w => 480, h => 360, a => 'aac', abr => 125 }, + 22 => { v => 'mp4', w => 1280, h => 720, a => 'aac', abr => 198 }, + 34 => { v => 'flv', w => 640, h => 360, a => 'aac', abr => 52 }, + 35 => { v => 'flv', w => 854, h => 480, a => 'aac', abr => 107 }, + 36 => { v => '3gp', w => 320, h => 240, a => 'aac', abr => 37 }, + 37 => { v => 'mp4', w => 1920, h => 1080, a => 'aac', abr => 128 }, + 38 => { v => 'mp4', w => 4096, h => 2304, a => 'aac', abr => 128 }, + 43 => { v => 'webm', w => 640, h => 360, a => 'vor', abr => 128 }, + 44 => { v => 'webm', w => 854, h => 480, a => 'vor', abr => 128 }, + 45 => { v => 'webm', w => 1280, h => 720, a => 'vor', abr => 128 }, + 46 => { v => 'webmS',w => 1920, h => 1080, a => 'vor', abr => 128 }, + 59 => { v => 'mp4', w => 854, h => 480, a => 'aac', abr => 128 }, + 78 => { v => 'mp4', w => 720, h => 406, a => 'aac', abr => 128 }, + 82 => { v => 'mp4S', w => 640, h => 360, a => 'aac', abr => 128 }, + 83 => { v => 'mp4S', w => 854, h => 240, a => 'aac', abr => 128 }, + 84 => { v => 'mp4S', w => 1280, h => 720, a => 'aac', abr => 198 }, + 85 => { v => 'mp4S', w => 1920, h => 520, a => 'aac', abr => 198 }, + 92 => { v => 'mp4', w => 320, h => 240, a => undef }, + 93 => { v => 'mp4', w => 640, h => 360, a => undef }, + 94 => { v => 'mp4', w => 854, h => 480, a => undef }, + 95 => { v => 'mp4', w => 1280, h => 720, a => undef }, + 96 => { v => 'mp4', w => 1920, h => 1080, a => undef }, + 100 => { v => 'webmS',w => 640, h => 360, a => 'vor', abr => 128 }, + 101 => { v => 'webmS',w => 854, h => 480, a => 'vor', abr => 128 }, + 102 => { v => 'webmS',w => 1280, h => 720, a => 'vor', abr => 128 }, + 120 => { v => 'flv', w => 1280, h => 720, a => 'aac', abr => 128 }, + 132 => { v => 'mp4', w => 320, h => 240, a => undef }, + 133 => { v => 'mp4', w => 426, h => 240, a => undef }, + 134 => { v => 'mp4', w => 640, h => 360, a => undef }, + 135 => { v => 'mp4', w => 854, h => 480, a => undef }, + 136 => { v => 'mp4', w => 1280, h => 720, a => undef }, + 137 => { v => 'mp4', w => 1920, h => 1080, a => undef }, + 138 => { v => 'mp4', w => 3840, h => 2160, a => undef }, + 139 => { v => undef, a => 'm4a', abr => 48 }, + 140 => { v => undef, a => 'm4a', abr => 128 }, + 141 => { v => undef, a => 'm4a', abr => 256 }, + 151 => { v => 'mp4', w => 72, h => 32, a => undef }, + 160 => { v => 'mp4', w => 256, h => 144, a => undef }, + 167 => { v => 'webm', w => 640, h => 360, a => undef }, + 168 => { v => 'webm', w => 854, h => 480, a => undef }, + 169 => { v => 'webm', w => 1280, h => 720, a => undef }, + 170 => { v => 'webm', w => 1920, h => 1080, a => undef }, + 171 => { v => undef, a => 'vor', abr => 128 }, + 172 => { v => undef, a => 'vor', abr => 256 }, + 218 => { v => 'webm', w => 854, h => 480, a => undef }, + 219 => { v => 'webm', w => 854, h => 480, a => undef }, + 242 => { v => 'webm', w => 426, h => 240, a => undef }, + 243 => { v => 'webm', w => 640, h => 360, a => undef }, + 244 => { v => 'webm', w => 854, h => 480, a => undef }, + 245 => { v => 'webm', w => 854, h => 480, a => undef }, + 246 => { v => 'webm', w => 854, h => 480, a => undef }, + 247 => { v => 'webm', w => 1280, h => 720, a => undef }, + 248 => { v => 'webm', w => 1920, h => 1080, a => undef }, + 249 => { v => undef, a => 'vor', abr => 50 }, + 250 => { v => undef, a => 'vor', abr => 70 }, + 251 => { v => undef, a => 'vor', abr => 160 }, + 256 => { v => undef, a => 'm4a', abr => 97, c=>5.1}, + 258 => { v => undef, a => 'm4a', abr => 191, c=>5.1}, + 264 => { v => 'mp4', w => 2560, h => 1440, a => undef }, + 266 => { v => 'mp4', w => 3840, h => 2160, a => undef }, + 271 => { v => 'webm', w => 2560, h => 1440, a => undef }, + 272 => { v => 'webm', w => 3840, h => 2160, a => undef }, + 278 => { v => 'mp4', w => 256, h => 144, a => undef }, + 298 => { v => 'mp4', w => 1280, h => 720, a => undef }, + 299 => { v => 'mp4', w => 1920, h => 1080, a => undef }, + 302 => { v => 'webm', w => 1280, h => 720, a => undef }, + 303 => { v => 'webm', w => 1920, h => 1080, a => undef }, +# 308 => { v => 'mp4', w => 2560, h => 1440, a => undef }, + 308 => { v => 'webm', w => 2560, h => 1440, a => undef }, + 313 => { v => 'webm', w => 3840, h => 2160, a => undef }, + 315 => { v => 'webm', w => 3840, h => 2160, a => undef }, + 327 => { v => undef, a => 'm4a', abr => 128, c=>5.1 }, + 339 => { v => undef, a => 'vor', abr => 170, c=>5.1 }, + ); + # + # The table on https://en.wikipedia.org/wiki/YouTube#Quality_and_formats + # disagrees with the above to some extent. Which is more accurate? + # (Oh great, they deleted that table from Wikipedia. Lovely.) + # (Ah great, they added the table back to Wikipedia Mar 2016.) + # + # fmt=38/37/22 are only available if upload was that exact resolution. + # + # For things uploaded in 2009 and earlier, fmt=18 was higher resolution + # than fmt=34. But for things uploaded later, fmt=34 is higher resolution. + # This code assumes that 34 is the better of the two. + # + # The WebM formats 43, 44 and 45 began showing up around Jul 2011. + # The MP4 versions are higher resolution (e.g. 37=1080p but 45=720p). + # + # The stereo/3D formats 46, 82-84, 100-102 first spotted in Sep/Nov 2011. + # + # As of Jan 2015, Youtube seems to have stopped serving format 37 (1080p), + # but is instead serving 137 (1080p, video only). To download anything of + # 1080p or higher, you are expected to download a video-only and an + # audio-only stream and mux them on the client side. This is insane. + # It seems that "urlmap" contains the muxed videos and "adaptive_fmts" + # contains the unmuxed ones. + # + # For debugging this stuff, use "--fmt N" to force downloading of a + # particular format or "--fmt all" to grab them all. + # + # + # Test cases and examples: + # + # https://www.youtube.com/watch?v=wjzyv2Q_hdM + # 5-Aug-2011: 38=flv/1080p but 45=webm/720p. + # 6-Aug-2011: 38 no longer offered. + # + # https://www.youtube.com/watch?v=ms1C5WeSocY + # 6-Aug-2011: embedding disabled, but get_video_info works. + # + # https://www.youtube.com/watch?v=g40K0dFi9Bo + # 10-Sep-2011: 3D, fmts 82 and 84. + # + # https://www.youtube.com/watch?v=KZaVq1tFC9I + # 14-Nov-2011: 3D, fmts 100 and 102. This one has 2D images in most + # formats but left/right images in the 3D formats. + # + # https://www.youtube.com/watch?v=SlbpRviBVXA + # 15-Nov-2011: 3D, fmts 46, 83, 85, 101. This one has left/right images + # in all of the formats, even the 2D formats. + # + # https://www.youtube.com/watch?v=711bZ_pLusQ + # 30-May-2012: First sighting of fmt 36, 3gpp/240p. + # + # https://www.youtube.com/watch?v=0yyorhl6IjM + # 30-May-2013: Here's one that's more than an hour long. + # + # https://www.youtube.com/watch?v=pc4ANivCCgs + # 15-Nov-2013: First sighting of formats 59 and 78. + # + # https://www.youtube.com/watch?v=WQzVhOZnku8 + # 3-Sep-2014: First sighting of a 24/7 realtime stream. + # + # https://www.youtube.com/watch?v=gTIK2XawLDA + # 22-Jan-2015: DNA Lounge 24/7 live stream, 640x360. + # + # https://www.youtube.com/watch?v=hHKJ5eE7I1k + # 22-Jan-2015: 2K video. Formats 36, 136, 137, 138. + # + # https://www.youtube.com/watch?v=udAL48P5NJU + # 22-Jan-2015: 4K video. Formats 36, 136, 137, 138, 266, 313. + # + # https://www.youtube.com/watch?v=OEhRucEVzH8 + # 20-Feb-2015: best formats 18 (640 x 360) and 135 (854 x 480) + # First sighting of a video where we must mux to get the best + # non-HD version. + # + # https://www.youtube.com/watch?v=Ol61WOSzLF8 + # 10-Mar-2015: formerly RTMPE but 14-Apr-2015 no longer + # + # https://www.youtube.com/watch?v=1ltcDfZMA3U Maps + # 29-Mar-2015: formerly playable in US region, but no longer + # + # https://www.youtube.com/watch?v=ttqMGYHhFFA Metric + # 29-Mar-2015: Formerly enciphered, but no longer + # + # https://www.youtube.com/watch?v=7wL9NUZRZ4I Bowie + # 29-Mar-2015: Formerly enciphered and content warning; no longer CW. + # + # https://www.youtube.com/watch?v=07FYdnEawAQ Timberlake + # 29-Mar-2015: enciphered and "content warning" (HTML scraping fails) + # + # https://youtube.com/watch?v=HtVdAasjOgU + # 29-Mar-2015: content warning, but non-enciphered + # + # https://www.youtube.com/watch?v=__2ABJjxzNo + # 29-Mar-2015: has url_encoded_fmt_stream_map but not adaptive_fmts + # + # https://www.youtube.com/watch?v=lqQg6PlCWgI + # 29-Mar-2015: finite-length archive of a formerly livestreamed video. + # We currently can't download this, but it's doable. + # See dna/backstage/src/slideshow/slideshow-youtube-frame.pl + # Update, 7-Aug-2016: this one works now; it seems to have been + # converted to a normal video with a url map. + # + # Enciphered: + # https://www.youtube.com/watch?v=ktoaj1IpTbw Chvrches + # https://www.youtube.com/watch?v=28Vu8c9fDG4 Emika + # https://www.youtube.com/watch?v=_mDxcDjg9P4 Vampire Weekend + # https://www.youtube.com/watch?v=8UVNT4wvIGY Gotye + # https://www.youtube.com/watch?v=OhhOU5FUPBE Black Sabbath + # https://www.youtube.com/watch?v=UxxajLWwzqY Icona Pop + # + # https://www.youtube.com/watch?v=g_uoH6hJilc + # 28-Mar-2015: enciphered Vevo (Years & Years) on which CTF was failing + # + # https://www.youtube.com/watch?v=ccyE1Kz8AgM + # 28-Mar-2015: not viewable in US (US is not on the include list) + # + # https://www.youtube.com/watch?v=ccyE1Kz8AgM + # 28-Mar-2015: blocked in US (US is on the exclude list) + # + # https://www.youtube.com/watch?v=GjxOqc5hhqA + # 28-Mar-2015: says "please sign in", but when signed in, it's private + # + # https://www.youtube.com/watch?v=UlS_Rnb5WM4 + # 28-Mar-2015: non-embeddable (Pogo) + # + # https://www.youtube.com/watch?v=JYEfJhkPK7o + # 14-Apr-2015: RTMPE DRM + # get_video_info fails with "This video contains content from Mosfilm, + # who has blocked it from display on this website. Watch on Youtube." + # There's a generic rtmpe: URL in "conn" and a bunch of options in + # "stream", but I don't know how to put those together into an + # invocation of "rtmpdump" that does anything at all. + # + # https://www.youtube.com/watch?v=UXMG102kSvk + # 17-Aug-2015: WebM higher rez than MP4: + # 299 (1920 x 1080 mp4 v/o) + # 308 (2560 x 1440 webm v/o) <-- webm, not mp4 + # 315 (3840 x 2160 webm v/o) + # + # https://www.youtube.com/watch?v=dC_nFgJAcuQ + # 2-Dec-2015: First sighting of 5.1 stereo formats 256 and 258. + # + # https://www.youtube.com/watch?v=vBtlUl-Xh5w + # 30-Jun-2016: First sighting of 5.1 stereo formats 327 and 339. + # + # https://www.youtube.com/watch?v=uTnO1ITQWr0 + # 6-Aug-2016: finite-length archive of a formerly livestreamed video. + # This is Flash-player only because it has embedding disabled. + # We currently can't download this, but it's doable. + # See dna/backstage/src/slideshow/slideshow-youtube-frame.pl + + + # Divide %known_formats into muxed, video-only and audio-only lists. + # + my (@pref_muxed, @pref_vo, @pref_ao); + foreach my $id (keys (%known_formats)) { + my $fmt = $known_formats{$id}; + my $v = $fmt->{v}; + my $a = $fmt->{a}; + my $b = $fmt->{abr}; + my $c = $fmt->{c}; # channels (e.g. 5.1) + my $w = $fmt->{w}; + my $h = $fmt->{h}; + + $known_formats{$id}->{desc} = (($w && $h ? "$w x $h $v" : + $b ? "$b kbps $a" : + "???") . + ($c ? " $c" : '') . + ($w && $h && $b ? '' : + $w ? ' v/o' : ' a/o')); + + error ("W and H flipped: $id") if ($w && $h && $w < $h); + + # Ignore 3d video or other weirdo vcodecs. + next if ($v && !($v =~ m/^(mp4|flv|3gp|webm)$/)); + + # WebM must always go along with Vorbis audio. ffmpeg can't mux + # MP4 video and Vorbis audio together, or WebM video and MP3 audio. + # But sometimes the highest bandwidth streams are MP4 + Vorbis, + # or WebM + MP3. + # + # So you know what, fuck it, let's just always ignore both WebM + # and Vorbis. + + next if ($a && !$v && $a =~ m/^(vor)$/); + next if (!$a && $v && $v =~ m/^(webm)$/); + + if ($v && $a) { + push @pref_muxed, $id; + } elsif ($v) { + push @pref_vo, $id; + } else { + push @pref_ao, $id; + } + } + + # Sort each of those lists in order of download preference. + # + foreach my $S (\@pref_muxed, \@pref_vo, \@pref_ao) { + @$S = sort { + my $A = $known_formats{$a}; + my $B = $known_formats{$b}; + + my $aa = $A->{h} || 0; # Prefer taller video. + my $bb = $B->{h} || 0; + return ($bb - $aa) unless ($aa == $bb); + + $aa = (($A->{v} || '') eq 'mp4'); # Prefer MP4 over WebM. + $bb = (($B->{v} || '') eq 'mp4'); + return ($bb - $aa) unless ($aa == $bb); + + $aa = $A->{c} || 0; # Prefer 5.1 over stereo. + $bb = $B->{c} || 0; + return ($bb - $aa) unless ($aa == $bb); + + $aa = $A->{abr} || 0; # Prefer higher audio rate. + $bb = $B->{abr} || 0; + return ($bb - $aa) unless ($aa == $bb); + + $aa = (($A->{a} || '') eq 'aac'); # Prefer AAC over MP3. + $bb = (($B->{a} || '') eq 'aac'); + return ($bb - $aa) unless ($aa == $bb); + + $aa = (($A->{a} || '') eq 'mp3'); # Prefer MP3 over Vorbis. + $bb = (($B->{a} || '') eq 'mp3'); + return ($bb - $aa) unless ($aa == $bb); + + return 0; + } @$S; + } + + my $vfmt = undef; + my $afmt = undef; + my $mfmt = undef; + + # Find the best pre-muxed format. + # + foreach my $target (@pref_muxed) { + if ($fmts->{$target}) { + $mfmt = $target; + last; + } + } + + # If muxing is allowed, find the best un-muxed pair of formats, if + # such a pair exists that is higher resolution than the best + # pre-muxed format. + # + if (defined($force_fmt) && $force_fmt eq 'mux') { + foreach my $target (@pref_vo) { + if ($fmts->{$target}) { + $vfmt = $target; + last; + } + } + foreach my $target (@pref_ao) { + if ($fmts->{$target}) { + $afmt = $target; + last; + } + } + + # If we got one of the formats and not the other, this isn't going to + # work. Fall back on pre-muxed. + # + if (($vfmt || $afmt) && !($vfmt && $afmt)) { + print STDERR "$progname: $id: found " . + ($vfmt ? 'video-only' : 'audio-only') . ' but no ' . + ($afmt ? 'video-only' : 'audio-only') . " formats.\n" + if ($verbose > 1); + $vfmt = undef; + $afmt = undef; + } + + # If the best unmuxed format is not better resolution than the best + # pre-muxed format, just use the pre-muxed version. + # + if ($mfmt && + $vfmt && + $known_formats{$vfmt}->{h} <= $known_formats{$mfmt}->{h}) { + print STDERR "$progname: $id: rejecting $vfmt + $afmt (" . + $known_formats{$vfmt}->{w} . " x " . + $known_formats{$vfmt}->{h} . ") for $mfmt (" . + $known_formats{$mfmt}->{w} . " x " . + $known_formats{$mfmt}->{h} . ")\n" + if ($verbose > 1); + $vfmt = undef; + $afmt = undef; + } + + + # At this point, we're definitely intending to mux. + # But maybe we can't because there's no ffmpeg -- if so, print + # a warning, then fall back to a lower resolution stream. + # + if ($vfmt && $afmt && !which ("ffmpeg")) { + print STDERR "$progname: WARNING: $id: \"ffmpeg\" not installed.\n"; + print STDERR "$progname: $id: downloading lower resolution.\n"; + $vfmt = undef; + $afmt = undef; + } + } + + # If there is a format in the list that we don't know about, warn. + # This is the only way I have of knowing when new ones turn up... + # + { + my @unk = (); + foreach my $k (sort keys %$fmts) { + next if ($k eq 'title'); + push @unk, $k if (!$known_formats{$k}); + } + print STDERR "$progname: $id: unknown format " . join(', ', @unk) . + "$errorI\n" + if (@unk); + } + + if ($verbose > 1) { + print STDERR "$progname: $id: available formats:\n"; + foreach my $k (sort { ($a =~ m/^\d+$/s ? $a : 0) <=> + ($b =~ m/^\d+$/s ? $b : 0) } + keys(%$fmts)) { + next if ($k eq 'title'); + print STDERR sprintf("%s: %3d (%s)\n", + $progname, $k, + $known_formats{$k}->{desc} || '?'); + } + } + + if ($vfmt && $afmt) { + if ($verbose > 1) { + my $d1 = $known_formats{$vfmt}->{desc}; + my $d2 = $known_formats{$afmt}->{desc}; + foreach ($d1, $d2) { s@ [av]/?o$@@si; } + print STDERR "$progname: $id: picked $vfmt + $afmt ($d1 + $d2)\n"; + } + return ($vfmt, $afmt); + } else { + # Either not muxing, or muxing not available/necessary. + my $why = 'picked'; + if (defined($force_fmt) && $force_fmt ne 'mux') { + error ("$id: format $force_fmt does not exist") + unless ($fmts->{$force_fmt}); + $why = 'forced'; + $mfmt = $force_fmt; + } + print STDERR "$progname: $id: $why $mfmt (" . + ($known_formats{$mfmt}->{desc} || '???') . ")\n" + if ($verbose > 1); + + return ($mfmt); + } +} + + + +# This is all completely horrible: try to convert the random crap people +# throw into Youtube video titles into something more consistent. +# +# - Aims for "Artist -- Title" instead of various other ways of spelling that. +# - Omits noise phrases like "official music video" and "high quality". +# - Downcases things that appear to be gratuitously in all-caps. +# +# This likely does stupid things on Youtube things that aren't music videos. +# +sub munge_title($) { + my ($title) = @_; + + return 'Untitled' unless defined($title); + + sub unihex($;) { + my ($c) = @_; + $c = hex($c); + my $s = chr($c); + + # If this is a single-byte non-ASCII character, chr() created a + # single-byte non-Unicode string. Assume that byte is Latin1 and + # expand it to the corresponding unicode character. + # + # Test cases: + # https://www.vimeo.com/82503761 é as \u00e9\u00a0 + # https://www.vimeo.com/123397581 û– as \u00fb\u2013 + # https://www.youtube.com/watch?v=z9ScJBmEdQw ä as UTF8 (2 bytes) + # https://www.youtube.com/watch?v=eAXmgId3NTQ ø as UTF8 (2 bytes) + # https://www.youtube.com/watch?v=FszEaxrHGTs ∆ as UTF8 (3 bytes) + # https://www.youtube.com/watch?v=4ViwSeuWVfE JP as UTF8 (3 bytes) + # + + # If this is still a Latin1 string, upgrade it to wide chars. + if (! utf8::is_utf8($s)) { + utf8::encode ($s); # Unpack Latin1 into multi-byte UTF-8. + utf8::decode ($s); # Pack multi-byte UTF-8 into wide chars. + } + return $s; + } + + utf8::decode ($title); # Pack multi-byte UTF-8 back into wide chars. + + # Decode \u and \x syntax. + $title =~ s/ \\[ux] { ([a-z0-9]+) } / unihex($1) /gsexi; # \u{XXXXXX} + $title =~ s/ \\[ux] ([a-z0-9]{4}) / unihex($1) /gsexi; # \uXXXX + + $title =~ s/[\x{2012}-\x{2013}]+/-/gs; # various dashes + $title =~ s/[\x{2014}-\x{2015}]+/--/gs; # various long dashes + $title =~ s/\x{2018}+/\`/gs; # backquote + $title =~ s/\x{2019}+/\'/gs; # quote + $title =~ s/[\x{201c}\x{201d}]+/\"/gs; # ldquo, rdquo + $title =~ s/\`/\'/gs; + + $title =~ s/\\//gs; # I think we can just omit other backslashes entirely. + + # spacing, punctuation cleanups + $title =~ s/^\s+|\s+$//gs; + $title =~ s/\s+/ /gs; + $title =~ s/\s+,/,/gs; + $title =~ s@\s+w/\s+@ with @gs; # convert w/ to with + $title =~ s@(\d)/(?=\d)@$1.@gs; # convert / to . in dates + $title =~ s@/@ - @gs; # remaining / to delimiter + + $title =~ s/^Youtube -+ //si; + $title =~ s/ -+ Youtube$//si; + $title =~ s/ on Vimeo\s*$//si; + $title =~ s/Broadcast Yourself\.?$//si; + + $title =~ s/\b ( ( (in \s*)? + ( + HD | TV | HDTV | HQ | 720\s*p? | 1080\s*p? | 4K | + High [-\s]* Qual (ity)? + ) | + FM(\'s)? | + EP s? (?>[\s\.\#]*) (?!\d+) | # allow "episode" usage + MV | performance | + SXSW ( \s* Music )? ( \s* \d{4} )? | + Showcasing \s Artist | + Presents | + (DVD|CD)? \s+ (out \s+ now | on \s+ (iTunes|Amazon)) | + fan \s* made | + ( FULL|COMPLETE ) \s+ ( set|concert|album ) | + FREE \s+ ( download|D\s*[[:punct:]-]\s*L ) | + Live \s+ \@ \s .* + ) + \b \s* )+ //gsix; + + $title =~ s/\b (The\s*)? (Un)?Off?ici[ae]le? + ( [-\s]* + ( Video | Clip | Studio | Music | Audio | Stereo | Lyric )s? + )+ + \b//gsix; + $title =~ s/\b Music ( [-\s]* ( Video | Clip )s?)+ \b//gsix; + + $title =~ s/\.(mp[34]|m4[auv]|mov|mqv|flv|wmv)\b//si; + $title =~ s/\b(on\s*)? [A-Za-z-0-9.]+\.com $//gsix; # kill trailing urls + $title =~ s/\b(brought to you|made possible) by .*$//gsi; # herp derpidy derp + $title =~ s/\bour interview with\b/ interviews /gsi; # re-handled below + $title =~ s/\b(perform|performs|performing)\b/ - /gsi; # other delimiters + $title =~ s/\b(play |plays |playing )\b/ - /gsi; # other delimiters + $title =~ s/\s+ [\|+]+ \s+ / - /gsi; # other delimiters + $title =~ s/!+/!/gsi; # yes, I'm excited too + $title =~ s/\s+-+[\s-]*\s/ - /gsi; # condense multiple delimiters into one + + $title =~ s/\s+/ /gs; + + # Lose now-empty parens. +# 1 while ($title =~ s/\(\s*\)//gs); +# 1 while ($title =~ s/\[\s*\]//gs); +# 1 while ($title =~ s/\{\s*\}//gs); + + # Lose now-empty parens. + # + # Any combination of these words is an empty phrase. + my ($empty_phrase) + = q/ + \s*( + the | new | free | amazing | (un)?off?ici[ae]le? | + on | iTunes | Amazon | [\s[:punct:]]+ | version | + cc | song | video | audio | band | source | field | + extended | mix | remix | edit | stream | uncut | single | + track | to be | released? | out | now | + teaser | trailer + )?\s* + / + ; + # Jesus fuck, youtube, how much more diarrhea can there be?? + # None. None more diarrhea. + + 1 while ($title =~ s/\(($empty_phrase)*\)//gsix); # Check all + 1 while ($title =~ s/\[($empty_phrase)*\]//gsix); # three + 1 while ($title =~ s/\{($empty_phrase)*\}//gsix); # paren styles. + + $title =~ s/[-;:,\s]+$//gs; # trailing crap + $title =~ s/\bDirected by\b/Dir./gsi; # "Directed By" is not "A by B" + $title =~ s/\bProduced by\b/Prod./gsi; # "Produced By" is not "A by B" + + + # Guess the title and artist by applying a series of regexes, in order, + # Starting with the most sensitive attempts, + # slowly moving to the most stable attempts, + # and ending with the most desperate attempts. + + my $obrack = '[\(\[\{]'; # for readability; matches the 3 major brackets. + my $cbrack = '[\)\]\}]'; # /$obrack $cbrack/ matches "[ }". close enough. + + + my ($artist, $track, $junk) = (undef, undef, ''); + + ($title, $junk) = ($1, $2) # TITLE (JUNK) + if ($title =~ m/^(.*)\s+$obrack+ (.*) $cbrack+ $/six); + + ($title, $junk) = ($1, "$3 $junk") # TITLE (Dir. by D) .* + if ($title =~ m/^ ( .+? ) + ($obrack+|\s)\s* ((Dir|Prod)\. .*)$/six); + + + ($track, $artist) = ($1, $2) # TRACK performed by ARTIST + if (!$artist && # TRACK by ARTIST + $title =~ m/^ ( .+? ) \b + (?: performed \s+ )? by \b ( .+ )$/six); + + ($artist, $track) = ($1, $2) # ARTIST performing TRACK + if (!$artist && + $title =~ m/^ ( .+? ) \b (?: plays | playing | performs? | + performing ) + \b ( .+ )$/six); + + ($artist, $track) = ($1, "\L$2\E $3") # ARTIST talks about HIMSELF + if (!$artist && # ^^^^^^^^^^^^^^^^^^^ = TRACK + $title =~ m/^ ( .+? ) \b + \(? \s* (interview|talks about) \s* \)? + \b \s* ( .+ ) $/six); + + ($artist, $track) = ($2, "interview by $1") # IDIOT interviews ARTIST + if (!$artist && # TRACK = interview by IDIOT + $title =~ m/^ ( .+? ) \b + (?: interviews | interviewing ) + \b ( .+ )$/six); + + ($track, $artist) = ($1, $2) # "TRACK" ARTIST + if (!$artist && + $title =~ m/^ \" ( .+? ) \" [,\s]+ ( .+ )$/six); + + ($artist, $track, $junk) = ($1, $2, "$3 $junk") # ARTIST "TRACK" JUNK + if (!$artist && + $title =~ m/^ ( .+? ) [,\s]+ \" ( .+ ) \" ( .*? ) $/six); + + + ($track, $artist) = ($1, $2) # 'TRACK' ARTIST + if (!$artist && + $title =~ m/^ \' ( .+? ) \' [,\s]+ ( .+ )$/six); + + ($artist, $track, $junk) = ($1, $2, "$3 $junk") # ARTIST 'TRACK' JUNK + if (!$artist && + $title =~ m/^ ( .+? ) [,\s]+ \' ( .+ ) \' ( .*? ) $/six); + + + ($artist, $track) = ($1, $2) # ARTIST -- TRACK + if (!$artist && + $title =~ m/^ ( .+? ) \s* --+ \s* ( .+ )$/six); + + ($artist, $track) = ($1, $2) # ARTIST: TRACK + if (!$artist && + $title =~ m/^ ( .+? ) \s* :+ \s* ( .+ )$/six); + + + ($artist, $track) = ($1, $2) # ARTIST-- TRACK + if (!$artist && + $title =~ m/^ ( .+? ) --+ \s* ( .+ )$/six); + + ($artist, $track) = ($1, $2) # ARTIST - TRACK + if (!$artist && + $title =~ m/^ ( .+? ) \s+ - \s+ ( .+ )$/six); + + ($artist, $track) = ($1, $2) # ARTIST- TRACK + if (!$artist && + $title =~ m/^ ( .+? ) -+ \s* ( .+ )$/six); + + ($artist, $track) = ($1, $2) # ARTIST live at LOCATION + if (!$artist && # ^^^^^^^^^^^^^^^^ = TITLE + $title =~ m/^ ( .+? ) (live \s* (at|@) .+ )$/six); + + + ($artist, $junk) = ($1, "$2 $junk") # more JUNK in $artist? + if ($artist && + $artist =~ m/^ ( .+? ) \s+ -+ \s+ ( .+? ) $/six); + + ($track, $junk) = ($1, "$2 $junk") # live at LOCATION in $track? + if ($artist && $track && + $track =~ m/^ ( .+? ) \s+ $obrack? ( live \s* (at|@) .* )$/six); + # ^^^^^^^---closing paren to be chopped below + + + # You will find my junk requires extra scrubbing today. + if ($junk) { + $junk =~ s/^\s+|\s+$//gs; + + # disallow junk consisting of all punctuation, + # but allow junk consisting of all digits or foreign chars. + $junk = '' if $junk =~ m/^[[:punct:]\s]+$/i; + + # de-parenthesize + $junk =~ s/^ [\(\[\{\s]+ (.+?) [\)\]\}\s]+ $/$1/six; + + # Stahhhhhp... + $junk = '' if $junk =~ m/ ^ \s* ( (un)?off?ici[ae]le? | video ) \s* $/six; + } + + + # Thoroughly wash fruits and vegetables before eating. + foreach my $s ($artist, $track, $junk, $title) { + next unless $s; + + # Allow leading and trailing "." here. + # Otherwise, it messes up + # Seasons -- ...Of Our Discontent + # Jordin Sparks -- S.O.S. (Let The Music Play) + # R.E.M. -- Automatic for the People + $s =~ s/^ [-\s\"\'\`\|,;:]+ | + [-\s\"\'\`\|,;:]+ $ //gsx; + + # Remove easily-found unbalanced parens. + # + # "TRACK (by ARTIST)" becomes "ARTIST) - TRACK (". + # Cleaning unbalanced parens as below fixes that, + # but messes up the band name "Sunn O)))". Oh well. + next if $s =~ m/^Sunn [0O]\)\)\)?$/; + + # I use defined() and /e to avoid undef warning for $1 replacement. + 1 while ($s =~ s/^ ([^\(]*?) \) / defined($1)?$1:"" /gsex); # Leading + 1 while ($s =~ s/^ ([^\[]*?) \] / defined($1)?$1:"" /gsex); # close + 1 while ($s =~ s/^ ([^\{]*?) \} / defined($1)?$1:"" /gsex); # brackets. + + 1 while ($s =~ s/ \( ([^\)]*) $/ defined($1)?$1:"" /gsex); # Trailing + 1 while ($s =~ s/ \[ ([^\]]*) $/ defined($1)?$1:"" /gsex); # open + 1 while ($s =~ s/ \{ ([^\}]*) $/ defined($1)?$1:"" /gsex); # brackets. + # The above does NOT correct, for instance, "ARTIST - TRACK (2014) )". + # Maybe I'll fix that later; I do love the burden of inhuman toil. + + + # If there are no lower case letters, + # capitalize all fully-upper-case words (with some allowances). + my $okupper = + # There're fewer good all-caps artists than diarrhea words. + # Just list them. + # Ironically, the story of the band ALL CAPS + # is too stupid to warrant including them. + 'NIN|MS\s?MR|RJD2|HNN|' # ARTISTS + .'MF\|?\s?MB\|?|' + .'STRFKR|EMA|UDG|BDRG|HOTT MT|' + .'RAW|MNDR|HTRK|SPC ECO|RTX|2NE1|' + .'BT|INXS|THX|SNL|CTRL|' + + .'POB|JPL|LNX|' # are these DJs? abbreviations? + + .'YKWYR|MFN|TV|ICHRU|AAA|OK|MJ|' # TRACKS + .'I\s?L\s?U|TKO|SWAG|' + .'LAX|ADHD|BTR' + ; + + $s =~ s/\b([[:upper:]])([[:upper:]\d]+)\b/$1\L$2/gsi # Capitalize, + unless ($s =~ m/[a-z]/s || # unless lowercase or + $s =~ m/^($okupper)$/ # specifically okayed. + ) + ; + } + + # THIS IS IT! + $title = "$artist - $track" if $artist; + $title .= " ($junk)" if $junk; + + + # Final cleanups, to prevent bad filenames + $title =~ s@\s*[/:]+\s*@ - @gs; # no colons or slashes + $title =~ s/^ - | - $//gs; # leading, trailing delimeters + $title =~ s/^\s+|\s+$//gs; # leading, trailing space + $title =~ s/\s+/ /gs; # multiple spaces + + # Don't allow the title to begin with "." or it writes a hidden file. + # And dash causes a stdout dump. + $title =~ s/^[-.,\s]+//gs; + + return $title || "Untitled"; +} + + + +# Does any version of the file exist with the usual video suffixes? +# Returns the one that exists. +# +sub file_exists_with_suffix($;) { + my ($f) = @_; + foreach my $ext (@video_extensions) { + my $ff = "$f.$ext"; + # No, don't do this. + # utf8::encode($ff); # Unpack wide chars into multi-byte UTF-8. + return ($ff) if -f ($ff); + } + return undef; +} + + +# Generates HTML output that provides a link for direct downloading of +# the highest-resolution underlying video. The HTML also lists the +# video dimensions and file size, if possible. +# +sub cgi_output($$$$@) { + my ($id, $title, $orig_url, $bwlimit, @targets) = @_; + + my $video = $targets[0]; + my $audio = $targets[1]; + my $premux = $targets[2]; + + my ($w, $h, $size) = video_url_size ($id, + $video->{url}, + $video->{content_type}, + $bwlimit); + $size = -1 unless defined($size); + + my ($w3, $h3, $size3) = video_url_size ($id, + $premux->{url}, + $premux->{content_type}, + $bwlimit) + if ($premux); + $size3 = -1 unless defined($size3); + + my $ss = ($size <= 0 ? 'size unknown' : + fmt_size($size)); + my $wh = ($w && $h ? "$w × $h" : "resolution unknown"); + $wh = '' . $wh . '' + if (($w || 0) < 1024); + $ss = "$wh, $ss"; + + my $ss3 = ($size3 <= 0 ? 'size unknown' : + fmt_size($size3)) + if ($premux); + my $wh3 = ($w3 && $h3 ? "$w3 × $h3" : "resolution unknown"); + $wh3 = '' . $wh3 . '' + if (($w3 || 0) < 1024); + $ss3 = "$wh3, $ss3" + if ($wh3 && $ss3); + + my $file = $video->{file}; + my $url = $video->{url}; + my $ct = $video->{content_type}; + my $url2 = $audio->{url} if ($audio); + my $ct2 = $audio->{content_type} if ($audio); + my $url3 = $premux->{url} if ($premux); + my $ct3 = $premux->{content_type} if ($premux); + + + # I had hoped that transforming + # + # https://v5.lscache2.googlevideo.com/videoplayback?ip=.... + # + # into + # + # https://v5.lscache2.googlevideo.com/videoplayback/Video+Title.mp4?ip=.... + # + # would trick Safari into downloading the file with a sensible file name. + # Normally Safari picks the target file name for a download from the final + # component of the URL. Unfortunately that doesn't work in this case, + # because the "videoplayback" URL is sending + # + # Content-Disposition: attachment; filename="video.mp4" + # + # which overrides my trickery, and always downloads it as "video.mp4" + # regardless of what the final component in the path is. + # + # However, if you do "Save Link As..." on this link, the default file + # name is sensible! So it takes two clicks to download it instead of + # one. Oh well, I can live with that. + # + # UPDATE: If we do "proxy=" instead of "redir=", then all the data moves + # through this CGI, and it will insert a proper Content-Disposition header. + # However, if the CGI is not hosted on localhost, then this will first + # download the entire video to your web host, then download it again to + # your local machine. + # + # Sadly, Vimeo is now doing user-agent sniffing on the "moogaloop/play/" + # URLs, so this is now the *only* way to make it work: if you try to + # download one of those URLs with a Safari/Firefox user-agent, you get + # a "500 Server Error" back. + # + # Also, "proxy=" is the only way to make muxing work, and thus the only + # way to download HD videos from Youtube. + # + my $proxy_p = 1; + utf8::encode ($file); # Unpack wide chars into multi-byte UTF-8. + + $url = (url_quote($url) . # video URL + ($url2 + ? '|' . url_quote($url2) # audio URL + : '')); + $ct .= "|$ct2" if $ct2; + + $url3 = url_quote($url3) if $url3; # premuxed URL + + + my $muxed_file = $file; + $muxed_file =~ s@\.(audio-only|video-only)\.@.@gs; + + $url = ($ENV{SCRIPT_NAME} . + '/' . url_quote($muxed_file) . + '?src=' . url_quote($orig_url) . + '&' . ($proxy_p? 'proxy' : 'redir') . + '=' . $url . + '&ct=' . $ct + ); + $url3 = ($ENV{SCRIPT_NAME} . + '/' . url_quote($muxed_file) . + '?src=' . url_quote($orig_url) . + '&' . ($proxy_p? 'proxy' : 'redir') . + '=' . $url3 . + '&ct=' . $ct3 + ) + if ($url3); + + $url = html_quote ($url); + $url3 = html_quote ($url3) if ($url3); + $title = html_quote ($title); + + + # New HTML5 feature: seems to be a client-side way of + # doing the same thing that "Content-Disposition: attachment; filename=" + # does. Unfortunately, even with this, Safari still opens the .MP4 file + # after downloading instead of just saving it. + + my $body = $html_head . "\n"; + $body =~ s@()[^<>]*@$1Download "$title"@gsi; + $body .= " Save Link As: <B>$title</B><BR>"; + $body .= ("       • " . + "<A HREF=\"$url\"\n DOWNLOAD=\"$title\">$ss</A>"); + $body .= ("<BR>" . + "       • " . + "<A HREF=\"$url3\"\n DOWNLOAD=\"$title\">$ss3</A>") + if ($url3); + + $body .= "\n" . $html_tail; + + binmode (STDOUT, ':raw'); + print STDOUT ("Content-Type: text/html; charset=UTF-8\n" . + "\n" . + $body); +} + + +# There are so many ways to specify URLs of videos... Turn them all into +# something sane and parsable. +# +# Duplicated in youtubefeed. + +sub canonical_url($;) { + my ($url) = @_; + + # Forgive pinheaddery. + $url =~ s@&@&@gs; + $url =~ s@&@&@gs; + + # Add missing "https:" + $url = "https://$url" unless ($url =~ m@^https?://@si); + + # Rewrite youtu.be URL shortener. + $url =~ s@^https?://([a-z]+\.)?youtu\.be/@https://youtube.com/v/@si; + + # Rewrite Vimeo URLs so that we get a page with the proper video title: + # "/...#NNNNN" => "/NNNNN" + $url =~ s@^(https?://([a-z]+\.)?vimeo\.com/)[^\d].*\#(\d+)$@$1$3@s; + + $url =~ s@^http:@https:@s; # Always https. + + my ($id, $site, $playlist_p); + + # Youtube /view_play_list?p= or /p/ URLs. + if ($url =~ m@^https?://(?:[a-z]+\.)?(youtube) (?:-nocookie)? \.com/ + (?: view_play_list\?p= | + p/ | + embed/p/ | + playlist\?list=(?:PL)? | + watch\?list=(?:PL)? | + embed/videoseries\?list=(?:PL)? + ) + ([^<>?&,]+) ($|&) @sx) { + ($site, $id) = ($1, $2); + $url = "https://www.$site.com/view_play_list?p=$id"; + $playlist_p = 1; + + # Youtube "/verify_age" URLs. + } elsif ($url =~ + m@^https?://(?:[a-z]+\.)?(youtube) (?:-nocookie)? \.com/+ + .* next_url=([^&]+)@sx || + $url =~ m@^https?://(?:[a-z]+\.)?google\.com/ + .* service = (youtube) + .* continue = ( http%3A [^?&]+)@sx || + $url =~ m@^https?://(?:[a-z]+\.)?google\.com/ + .* service = (youtube) + .* next = ( [^?&]+)@sx + ) { + $site = $1; + $url = url_unquote($2); + if ($url =~ m@&next=([^&]+)@s) { + $url = url_unquote($1); + $url =~ s@&.*$@@s; + } + $url = "https://www.$site.com$url" if ($url =~ m@^/@s); + + # Youtube /watch/?v= or /watch#!v= or /v/ URLs. + } elsif ($url =~ m@^https?:// (?:[a-z]+\.)? + (youtube) (?:-nocookie)? (?:\.googleapis)? \.com/+ + (?: (?: watch/? )? (?: \? | \#! ) v= | + v/ | + embed/ | + .*? &v= | + [^/\#?&]+ \#p(?: /[a-zA-Z\d] )* / + ) + ([^<>?&,\'\"]+) ($|[?&]) @sx) { + ($site, $id) = ($1, $2); + $url = "https://www.$site.com/watch?v=$id"; + + # Youtube "/user" and "/profile" URLs. + } elsif ($url =~ m@^https?://(?:[a-z]+\.)?(youtube) (?:-nocookie)? \.com/ + (?:user|profile).*\#.*/([^&/]+)@sx) { + $site = $1; + $id = url_unquote($2); + $url = "https://www.$site.com/watch?v=$id"; + error ("unparsable user next_url: $url") unless $id; + + # Vimeo /NNNNNN URLs + # and player.vimeo.com/video/NNNNNN + # and vimeo.com/m/NNNNNN + } elsif ($url =~ + m@^https?://(?:[a-z]+\.)?(vimeo)\.com/(?:video/|m/)?(\d+)@s) { + ($site, $id) = ($1, $2); + $url = "https://$site.com/$id"; + + # Vimeo /videos/NNNNNN URLs. + } elsif ($url =~ m@^https?://(?:[a-z]+\.)?(vimeo)\.com/.*/videos/(\d+)@s) { + ($site, $id) = ($1, $2); + $url = "https://$site.com/$id"; + + # Vimeo /channels/name/NNNNNN URLs. + # Vimeo /ondemand/name/NNNNNN URLs. + } elsif ($url =~ + m@^https?://(?:[a-z]+\.)?(vimeo)\.com/[^/]+/[^/]+/(\d+)@s) { + ($site, $id) = ($1, $2); + $url = "https://$site.com/$id"; + + # Vimeo /album/NNNNNN/video/MMMMMM + } elsif ($url =~ + m@^https?://(?:[a-z]+\.)?(vimeo)\.com/album/\d+/video/(\d+)@s) { + ($site, $id) = ($1, $2); + $url = "https://$site.com/$id"; + + # Vimeo /moogaloop.swf?clip_id=NNNNN + } elsif ($url =~ m@^https?://(?:[a-z]+\.)?(vimeo)\.com/.*clip_id=(\d+)@s) { + ($site, $id) = ($1, $2); + $url = "https://$site.com/$id"; + + # Tumblr /video/UUU/NNNNN + } elsif ($url =~ + m@^https?://[-_a-z]+\.(tumblr)\.com/video/([^/]+)/(\d{8,})/@si) { + my $user; + ($site, $user, $id) = ($1, $2, $3); + $site = lc($site); + $url = "https://$user.$site.com/post/$id"; + + # Tumblr /post/NNNNN + } elsif ($url =~ m@^https?://([-_a-z]+)\.(tumblr)\.com/.*?/(\d{8,})/@si) { + my $user; + ($user, $site, $id) = ($1, $2, $3); + $site = lc($site); + $url = "https://$user.$site.com/post/$id"; + + # Vine /v/NNNNN + } elsif ($url =~ m@^https?://([-_a-z]+\.)?(vine)\.co/v/([^/?&]+)@si) { + (undef, $site, $id) = ($1, $2, $3); + $site = lc($site); + $url = "https://$site.co/v/$id"; + + # Instagram /p/NNNNN + } elsif ($url =~ m@^https?://([-_a-z]+\.)?(instagram)\.com/p/([^/?&]+)@si) { + (undef, $site, $id) = ($1, $2, $3); + $site = lc($site); + $url = "https://www.$site.com/p/$id"; + + # Twitter /USER/status/NNNNN + } elsif ($url =~ m@^https?://([-_a-z]+\.)?(twitter)\.com/([^/?&]+) + /status/([^/?&]+)@six) { + my $user; + (undef, $site, $user, $id) = ($1, $2, $3, $4); + $site = lc($site); + $url = "https://$site.com/$user/status/$id"; + + } else { + error ("unparsable URL: $url"); + } + + return ($url, $id, $site); +} + + +# Having downloaded a video file and an audio file, combine them and delete +# the two originals. +# +sub mux_downloaded_files($$$$$$) { + my ($id, $url, $title, $v1, $v2, $muxed_file) = @_; + + my $video_file = $v1->{file}; + my $audio_file = $v2->{file}; + + if (! defined($muxed_file)) { + $muxed_file = $video_file; + $muxed_file =~ s@\.(audio-only|video-only)\.@.@gs; + $muxed_file =~ s@ [^\s\[\]]+(\].)@$1@gs; + } + + error ("$id: mismunged filename $muxed_file") + if ($muxed_file eq $audio_file || $muxed_file eq $video_file); + error ("$id: exists: $muxed_file") if (-f $muxed_file); + + my @cmd = ('ffmpeg', + # "-hide_banner", # not present in 0.6.5 + # "-loglevel", "panic", + + '-i', $video_file, + '-i', $audio_file, + '-vcodec', 'copy', # no re-encoding + '-acodec', 'copy', + '-map', '0:v:0', # from file 0, video track 0 + '-map', '1:a:0', # from file 1, audio track 0 + '-shortest', # they should be the same length already + $muxed_file); + if ($verbose == 1) { + print STDERR "$progname: $id: combining audio and video...\n"; + } elsif ($verbose > 1) { + print STDERR "$progname: $id: exec: '" . join("' '", @cmd) . "'\n"; + } + + + { + my $result = ''; + my ($in, $out, $err); + $err = Symbol::gensym; + my $pid = eval { open3 ($in, $out, $err, @cmd) }; + if (!$pid) { + $err = "exec: $cmd[0]: $!"; + } else { + close ($in); + close ($out); + local $/ = undef; # read entire file + while (<$err>) { + $result .= $_; + } + + waitpid ($pid, 0); + my $exit_value = $? >> 8; + my $signal_num = $? & 127; + my $dumped_core = $? & 128; + + if ($verbose > 2) { + $_ = $result; + s/^/$cmd[0]: /gm; + print STDERR "$_\n"; + } + + $err = undef; + $err = "$id: $cmd[0]: core dumped!" if ($dumped_core); + $err = "$id: $cmd[0]: signal $signal_num!" if ($signal_num); + $err = "$id: $cmd[0]: exited with $exit_value!" if ($exit_value); + } + + if ($err) { + unlink ($muxed_file); # It's not a download, and it's broken. + if ($verbose < 2) { + my @L = split(/(?:\r?\n)+/, $result); + $result = join ("\n", @L[-5 .. -1]) # only last 5 lines + if (@L > 5); + } + if ($result) { + $result =~ s/^/$cmd[0]: /gm; + $err .= "\n\n$result\n"; + } + error ($err); + } + } + + my $s1 = (stat($audio_file))[7] || 0; + my $s2 = (stat($video_file))[7] || 0; + my $s3 = (stat($muxed_file))[7] || 0; + + $s1 = $s1 + $s2; + my $diff = $s1 * 0.05; # 5% of audio+video seems safe & sane + if (($s3 < ($s1 - $diff)) || # muxed is less than audio+video - 5% + ($s3 > ($s1 + $diff))) { # muxed is more than audio+video + 5% + my $s1b = fmt_size ($s1); + my $s3b = fmt_size ($s3); + unlink ($audio_file, $video_file, $muxed_file) + if ($verbose < 3); + error ("$id: $cmd[0] wrote a short file! Got $s3b, expected $s1b" . + " ($s1 - $s3 = $diff)"); + } + + unlink ($audio_file, $video_file) + if ($verbose < 3); + + write_file_metadata_url ($muxed_file, $id, $url); + + if ($verbose > 0) { + my ($w, $h, $size, $abr) = video_file_size ($muxed_file); + $size = -1 unless $size; + my $ss = fmt_size ($size); + $ss .= ", $w x $h" if ($w && $h); + print STDERR "$progname: wrote \"$muxed_file\"\n"; + print STDERR "$progname: $ss\n"; + } +} + + +sub content_type_ext($;) { + my ($ct) = @_; + if ($ct =~ m@/(x-)?flv$@si) { return 'flv'; } + elsif ($ct =~ m@/(x-)?webm$@si) { return 'webm'; } + elsif ($ct =~ m@/(x-)?3gpp$@si) { return '3gpp'; } + elsif ($ct =~ m@/quicktime$@si) { return 'mov'; } + elsif ($ct =~ m@^audio/mp4$@si) { return 'm4a'; } + else { return 'mp4'; } +} + + +sub download_video_url($$$$$$$$$;) { + my ($url, $title, $prefix, $size_p, $list_p, + $bwlimit, $progress_p, $cgi_p, $force_fmt) = @_; + + $error_whiteboard = ''; # reset per-URL diagnostics + $progress_ticks = 0; # reset progress-bar counters + $progress_time = 0; + + # Pack multi-byte UTF-8 back into wide chars. + utf8::decode ($title) if defined($title); + utf8::decode ($prefix) if defined($prefix); + + foreach ($title, $prefix) { + s@\s*[/:]+\s*@ - @gs if $_; # no colons or slashes + s/^\s+|\s+$//gs if $_; + } + + my ($id, $site); + ($url, $id, $site) = canonical_url ($url); + + # If downloading a playlist, recurse. + # + if ($url =~ m@view_play_list@s) { + return download_youtube_playlist ($id, $url, $title, $prefix, $size_p, + $list_p, $bwlimit, $progress_p, $cgi_p, + $force_fmt); + } + + # Handle --list for playlists. + # + if ($list_p) { + if ($list_p > 1) { + my $t2 = ($prefix ? "$prefix $title" : $title); + print STDOUT "$id\t$t2\n"; + } else { + print STDOUT "https://www.$site.com/watch?v=$id\n"; + } + return; + } + + + my $suf = (" [" . $id . + ($force_fmt && $force_fmt ne 'mux' ? " $force_fmt" : "") . + "]"); + + if (! ($size_p || $list_p)) { + + # If we're writing with --suffix, we can check for an existing file before + # knowing the title of the video. Check for a file with "[this-ID]" in it. + # (The quoting rules of perl's "glob" function are ridiculous and + # confusing, so let's do it the hard way instead.) + # + opendir (my $dir, '.') || error ("readdir: $!"); + foreach my $f (readdir ($dir)) { + if ($f =~ m/\Q$suf\E/s) { + exit (1) if ($verbose <= 0); # Skip silently if --quiet. + error ("$id: exists: $f"); + } + } + closedir $dir; + + # If we already have a --title, we can check for the existence of the file + # before hitting the network. Otherwise, we need to download the video + # info to find out the title and thus the file name. + # + if (defined($title)) { + my $t2 = ($prefix ? "$prefix $title" : $title); + my $o = (file_exists_with_suffix ("$t2") || + file_exists_with_suffix ("$t2$suf") || + file_exists_with_suffix ("$title") || + file_exists_with_suffix ("$title$suf")); + if ($o) { + exit (1) if ($verbose <= 0); # Skip silently if --quiet. + error ("$id: exists: $o"); + } + } + } + + + # Videos can come in multiple resolutions, and sometimes with audio and + # video in separate URLs. Get the list of all possible downloadable video + # formats. + # + my $fmts = ($site eq 'youtube' ? load_youtube_formats ($id, $url) : + $site eq 'vimeo' ? load_vimeo_formats ($id, $url) : + $site eq 'tumblr' ? load_tumblr_formats ($id, $url) : + $site eq 'vine' ? load_vine_formats ($id, $url) : + $site eq 'instagram' ? load_instagram_formats ($id, $url) : + $site eq 'twitter' ? load_twitter_formats ($id, $url) : + error ("$id: unknown site: $site")); + + # Set the title unless it was specified on the command line with --title. + # + if (! defined($title)) { + $title = munge_title ($fmts->{title}); + + # Add the year to the title unless there's a year there already. + # + if ($title !~ m@ \(\d{4}\)@si) { # skip if already contains " (NNNN)" + my $year = ($fmts->{year} ? $fmts->{year} : + $site eq 'youtube' ? get_youtube_year ($id) : + $site eq 'vimeo' ? get_vimeo_year ($id) : undef); + if ($year && + $year != (localtime())[5]+1900 && # Omit this year + $title !~ m@\b$year\b@s) { # Already in the title + $title .= " ($year)"; + } + } + + # Now that we've hit the network and determined the real title, we can + # check for existing files on disk. + # + if (! ($size_p || $list_p)) { + my $t2 = ($prefix ? "$prefix $title" : $title); + my $o = (file_exists_with_suffix ("$t2") || + file_exists_with_suffix ("$title") || + file_exists_with_suffix ("$title") || + file_exists_with_suffix ("$title$suf")); + if ($o) { + exit (1) if ($verbose <= 0); # Skip silently if --quiet. + error ("$id: exists: $o"); + } + } + } + + + # Now that we have the video info, decide what to download. + # If we're doing --fmt all, this is all of them. + # Otherwise, it's either one URL or two (audio + video mux). + # + my @targets = pick_download_format ($id, $site, $url, $force_fmt, $fmts); + my @pair = (@targets == 2 && $force_fmt ne 'all' ? @targets : ()); + + if ($cgi_p && @pair) { + # If we're producing CGI output, and we wanted and requested a muxed + # file, also add the non-muxed file onto the end of the list, to give + # the user an option of both formats. + my @t2 = pick_download_format ($id, $site, $url, undef, $fmts); + push @targets, @t2 if @t2; + } + + + if ($size_p && @pair) { + # With --size, we only need to examine the first pair of the mux. + @targets = ($pair[0]); + @pair = (); + } + + my @cgi_args; + + $append_suffix_p = 1 + if (!$size_p && defined($force_fmt) && $force_fmt eq 'all'); + + foreach my $target (@targets) { + my $fmt = $fmts->{$target}; + my $ct = $fmt->{content_type}; + my $w = $fmt->{width}; + my $h = $fmt->{height}; + my $abr = $fmt->{abr}; + my $size = $fmt->{size}; + my $url2 = $fmt->{url}; + + if ($size_p) { + if (! (($w && $h) || $abr)) { + ($w, $h, $size, $abr) = video_url_size ($id, $url2, $ct, $bwlimit); + } + + my $ii = $id . (@targets == 1 ? '' : ":$target"); + my $ss = fmt_size ($size); + my $wh = ($w && $h + ? "${w} x ${h}" + : "$abr "); + my $t2 = ($prefix ? "$prefix $title" : $title); + print STDOUT "$ii\t$wh\t$ss\t$t2\n"; + + } else { + + $suf = ($append_suffix_p + ? (" [" . $id . + (@targets == 1 ? '' : " $target") . + "]") + : (@pair + ? ($target == $pair[0] ? '.video-only' : '.audio-only') + : '')); + + my $file = ($prefix ? "$prefix $title" : $title) . $suf; + + $file .= '.' . content_type_ext($ct); + $fmt->{file} = $file; + + if ($cgi_p) { + push @cgi_args, $fmt; + next; + } + + if (-f $file) { + exit (1) if ($verbose <= 0); # Skip silently if --quiet. + error ("$id: exists: $file"); + } + + print STDERR "$progname: reading \"$file\"\n" if ($verbose > 0); + + my $start_time = time(); + my ($http, $head, $body) = get_url ($url2, undef, $file, + $bwlimit, undef, $progress_p); + my $download_time = time() - $start_time; + + check_http_status ($id, $url, $http, 2); # internal error if still 403 + + if (! -s $file) { + unlink ($file); + error ("$file: failed: $url"); + } + + write_file_metadata_url ($file, $id, $url) + # The metadata tags seem to confuse ffmpeg. + if (!@pair && !$ENV{HTTP_HOST}); + + if ($verbose > 0) { + + # Now that we've written the file, get the real numbers from it, + # in case the server metadata lied to us. + my $abr = 0; + ($w, $h, $size, $abr) = video_file_size ($file); + + $size = -1 unless $size; + my $ss = fmt_size ($size); + if ($w && $h) { + $ss .= ", $w x $h"; + } elsif ($abr) { + $ss .= ", $abr"; + } + + if ($download_time && $size > 0) { + # Let's see how badly youtube is rate-limiting our downloads. + my $t = sprintf("%d:%02d:%02d", + int($download_time/(60*60)), + int($download_time/(60))%60, + int($download_time)%60); + $ss .= " downloaded in $t"; + my $bps = fmt_bps ($size * 8 / $download_time); + $ss .= ", $bps"; + } + + print STDERR "$progname: wrote \"$file\"\n"; + print STDERR "$progname: $ss\n"; + } + } + } + + if ($cgi_p) { + cgi_output ($id, $title, $url, $bwlimit, @cgi_args); + + } elsif (@pair) { + mux_downloaded_files ($id, $url, $title, + $fmts->{$pair[0]}, + $fmts->{$pair[1]}, + undef); + } + +} + + +sub download_youtube_playlist($$$$$$$$$$) { + my ($id, $url, $title, $prefix, $size_p, $list_p, + $bwlimit, $progress_p, $cgi_p, + $force_fmt) = @_; + + my @playlist = (); + + my $start = 0; + + my ($http, $head, $body) = get_url ($url); + check_http_status ($id, $url, $http, 1); + + ($title) = ($body =~ m@<title>\s*([^<>]+?)\s*@si) + unless $title; + $title = munge_title($title); + $title = 'Untitled Playlist' unless $title; + + ($body =~ s/^.*?
\s* ([^<>]*?) \s* @{ + my ($href, $t2) = ($1, $2); + (undef, $href) = ($href =~ m% \b href \s* = \s* ([\"\'])(.*?)\1%six); + if ($href && $t2) { + $href = html_unquote($href); + if ($href =~ m%[?&]v=([^?&]+)%si) { + $href = $1; + $t2 = munge_title (html_unquote ($t2)); + $t2 = sprintf("%s: %02d: %s", $title, ++$i, $t2); + $href = 'https://www.youtube.com/watch?v=' . $href; + push \@playlist, [ $t2, $href ]; + } + } + ""; + }@gsexi; + + errorI ("$id: no playlist entries?") unless @playlist; + + # With "--size", only get the size of the first video. + # With "--size --size", get them all. + if ($size_p == 1) { + @playlist = ( $playlist[0] ); + } + + # Scraping the HTML only gives us the first hundred videos if the + # playlist has more than that. I don't yet know how to get the + # rest. The "Show More" button at the bottom does AJAX bullshit. + # + my $max = 100; + print STDERR "$progname: WARNING: $id: " . + "only able to download the first $max videos!\n" + if (@playlist == $max); + + print STDERR "$progname: playlist \"$title\" (" . scalar (@playlist) . + " entries)\n" + if ($verbose > 1); + + foreach my $P (@playlist) { + my ($t2, $u2) = @$P; + eval { + $noerror = 1; + download_video_url ($u2, $t2, $prefix, $size_p, $list_p, + $bwlimit, $progress_p, + $cgi_p, $force_fmt); + $noerror = 0; + }; + print STDERR "$progname: $@" if $@; + last if ($size_p == 1); + } +} + + +sub do_cgi($$) { + my ($muxp, $bwlimit) = @_; + + $|=1; + + my $args = ""; + if (!defined ($ENV{REQUEST_METHOD})) { + } elsif ($ENV{REQUEST_METHOD} eq "GET") { + $args = $ENV{QUERY_STRING} if (defined($ENV{QUERY_STRING})); + } elsif ($ENV{REQUEST_METHOD} eq "POST") { + local $/ = undef; # read entire file + $args .= ; + } + + if (!$args && + defined($ENV{REQUEST_URI}) && + $ENV{REQUEST_URI} =~ m/^(.*?)\?(.*)$/s) { + $args = $2; + # for cmd-line debugging + $ENV{SCRIPT_NAME} = $1 unless defined($ENV{SCRIPT_NAME}); +# $ENV{PATH_INFO} = $1 if (!$ENV{PATH_INFO} && +# $ENV{SCRIPT_NAME} =~ m@^.*/(.*)@s); + } + + my ($url, $orig_url, $redir, $proxy, $ct); + foreach (split (/&/, $args)) { + my ($key, $val) = m/^([^=]+)=(.*)$/; + $key = url_unquote ($key); + $val = url_unquote ($val); + if ($key eq 'url') { $url = $val; } + elsif ($key eq 'redir') { $redir = $val; } + elsif ($key eq 'proxy') { $proxy = $val; } + elsif ($key eq 'ct') { $ct = $val; } + elsif ($key eq 'src') { $orig_url = $val; } # Unused: only informative. + else { error ("unknown option: $key"); } + } + + if ($redir || $proxy) { + error ("can't specify both url and redir") if ($redir && $url); + error ("can't specify both url and proxy") if ($proxy && $url); + error ("can't specify both redir and proxy") if ($proxy && $redir); + my $title = $ENV{PATH_INFO} || ''; + $title =~ s@^/@@s; + $title = ($redir || $proxy) unless $title; + $title =~ s@^.*?/@@gs; + $title =~ s@[?&].*@@gs; + $title =~ s@\"@%22@gs; + + $ct = 'video/mpeg' unless $ct; + my $ct2 = $1 if ($ct =~ s/\|(.*)$//s); + + if ($redir) { + my ($audio) = ($redir =~ s@\|(.*)$@@s); + error ("can't redir URLs that require muxing") if ($audio); + + # Return a redirect to the underlying video URL. + binmode (STDOUT, ':raw'); + print STDOUT ("Content-Type: text/html\n" . + "Location: $redir\n" . + "Content-Disposition: attachment; filename=\"$title\"\n" . + "\n" . + "$title\n" . + "\n"); + } else { + # Proxy the data, so that we can feed it a non-browser user agent. + + my $audio = $1 if ($proxy =~ s@\|(.*)$@@s); + + if ($audio) { + # We need to download both files locally, then mux them, then + # stream that. Auuugh! + + my $tmp = $ENV{TMPDIR} || "/tmp"; + my $e1 = content_type_ext ($ct); + my $e2 = content_type_ext ($ct2 || $ct); + $progname =~ s/\..*?$//s; + my $video_file = sprintf("$tmp/$progname-V-%08x.$e1",rand(0xFFFFFFFF)); + my $audio_file = sprintf("$tmp/$progname-A-%08x.$e2",rand(0xFFFFFFFF)); + my $muxed_file = sprintf("$tmp/$progname-M-%08x.$e1",rand(0xFFFFFFFF)); + + unlink ($video_file, $audio_file, $muxed_file); + push @rm_f, ($video_file, $audio_file, $muxed_file); + + # So we're downloading two files and muxing them before we have any + # bytes we can send to the client. That means that several minutes + # could go by with 0 data being written, which might make Apache or + # the browser time out and drop the connection. So, before we have + # any real content to write, we write an "X-Heartbeat: ...." header, + # spitting out a new "." every few seconds. This means the client + # header block doesn't actually close until we have the body (which + # is necessary in order to have the true Content-Length) but we have + # technically not fallen fully idle. Let's hope Apache and the + # client fall for that trick. + + my $progress_p = 'cgi'; + my $hdr = "X-Heartbeat: "; + print STDOUT $hdr; + get_url ($proxy, undef, $video_file, $bwlimit, undef, 'cgi'); + print STDOUT "\n$hdr"; + get_url ($audio, undef, $audio_file, $bwlimit, undef, 'cgi'); + print STDOUT "\n"; # close $hdr + + my %v1 = ( file => $video_file ); + my %v2 = ( file => $audio_file ); + + $verbose = -1; + mux_downloaded_files ($orig_url, $orig_url, $title, + \%v1, \%v2, $muxed_file); + + unlink ($video_file, $audio_file); + open (my $in, '<:raw', $muxed_file) || + error ("$orig_url: $muxed_file: $!"); + + my @st = stat($in); + my $size = $st[7]; + unlink ($muxed_file); + + print STDOUT ("Content-Type: $ct\n" . + "Content-Length: $size\n" . + "Content-Disposition: attachment; filename=\"$title\"\n". + "\n"); + + binmode (STDOUT, ':raw'); + local $/ = undef; # read entire file + while (<$in>) { + print STDOUT $_; + } + close $in; + + } else { + # Otherwise we can just stream it without involving the disk. + print STDOUT "Content-Disposition: attachment; filename=\"$title\"\n"; + binmode (STDOUT, ':raw'); + get_url ($proxy, undef, '-', $bwlimit); + } + } + + } elsif ($url) { + error ("extraneous crap in URL: $ENV{PATH_INFO}") + if (defined($ENV{PATH_INFO}) && $ENV{PATH_INFO} ne ""); + + my $force_fmt = ($muxp ? 'mux' : undef); + download_video_url ($url, undef, undef, 0, 0, + $bwlimit, undef, 1, $force_fmt); + + } else { + error ("no URL specified for CGI"); + } +} + + +sub usage() { + print STDERR "usage: $progname" . + " [--verbose] [--quiet] [--progress] [--size]\n" . + "\t\t [--title txt] [--prefix txt] [--suffix]\n" . + "\t\t [--fmt N] [--no-mux] [--bwlimit N [kb | KB | mb | MB]]\n" . + "\t\t youtube-or-vimeo-urls ...\n"; + exit 1; +} + +sub main() { + + binmode (STDOUT, ':utf8'); # video titles in messages + binmode (STDERR, ':utf8'); + + $progname =~ s/\..*?$//s; # remove .cgi + srand(time ^ $$); # for tmp files + + # historical suckage: the environment variable name is lower case. + $http_proxy = ($ENV{http_proxy} || $ENV{HTTP_PROXY} || + $ENV{https_proxy} || $ENV{HTTPS_PROXY}); + delete $ENV{http_proxy}; + delete $ENV{HTTP_PROXY}; + delete $ENV{https_proxy}; + delete $ENV{HTTPS_PROXY}; + + if ($http_proxy && $http_proxy !~ m/^http/si) { + # historical suckage: allow "host:port" as well as "http://host:port". + $http_proxy = "http://$http_proxy"; + } + + my @urls = (); + my $title = undef; + my $prefix = undef; + my $size_p = 0; + my $list_p = 0; + my $progress_p = 0; + my $fmt = undef; + my $expect = undef; + my $guessp = 0; + my $muxp = 1; + my $bwlimit = undef; + + while ($#ARGV >= 0) { + $_ = shift @ARGV; + if (m/^--?verbose$/) { $verbose++; } + elsif (m/^-v+$/) { $verbose += length($_)-1; } + elsif (m/^--?q(uiet)?$/) { $verbose--; } + elsif (m/^--?progress$/) { $progress_p++; } + elsif (m/^--?suffix$/) { $append_suffix_p = 1; } + elsif (m/^--?prefix$/) { $expect = $_; $prefix = shift @ARGV; } + elsif (m/^--?title$/) { $expect = $_; $title = shift @ARGV; } + elsif (m/^--?size$/) { $expect = $_; $size_p++; } + elsif (m/^--?list$/) { $expect = $_; $list_p++; } + elsif (m/^--?fmt$/) { $expect = $_; $fmt = shift @ARGV; } + elsif (m/^--?mux$/) { $expect = $_; $muxp = 1; } + elsif (m/^--?no-?mux$/) { $expect = $_; $muxp = 0; } + elsif (m/^--?guess$/) { $guessp++; } + elsif (m/^--?bwlimit$/) { + # + # Many variant spellings are allowed: + # + # bits: k, kb, kbps, kps, kb/s, k/s; + # bytes: K, Kb, Kbps, Kps, Kb/s, K/s, + # KB, KBps, KBPS, KPS, KB/s, KB/S, K/S. + # + my $bit_suf = '(b|bps|ps|b/s|/s)?$'; + my $byte_suf = '(b|bps|ps|b/s|/s|B|Bps|BPS|PS|B/s|B/S|/S)?$'; + + $bwlimit = shift @ARGV; + if ($bwlimit =~ s@ \s* k $bit_suf @@sx) { # k bits + $bwlimit *= 1024; + } elsif ($bwlimit =~ s@ \s* K $byte_suf @@sx) { # K bytes + $bwlimit *= 1024 * 8; + } elsif ($bwlimit =~ s@ \s* m $bit_suf @@sx) { # m bits + $bwlimit *= 1024 * 1024; + } elsif ($bwlimit =~ s@ \s* M $byte_suf @@sx) { # M bytes + $bwlimit *= 1024 * 1024 * 8; + } elsif ($bwlimit =~ s@ \s* g $bit_suf @@sx) { # g bits + $bwlimit *= 1024 * 1024 * 1024; + } elsif ($bwlimit =~ s@ \s* G $byte_suf @@sx) { # G bytes + $bwlimit *= 1024 * 1024 * 1024 * 8; + } elsif ($bwlimit =~ s@ \s* $bit_suf @@sx) { # bits + $bwlimit += 0; + } elsif ($bwlimit =~ s@ \s* $byte_suf @@sx) { # Bytes + $bwlimit /= 8; + } elsif ($bwlimit =~ m@^ \d+ ( \.\d+ )? $ @sx) { # no units: k bits + $bwlimit *= 1024; + } else { + error ("unparsable units: $bwlimit"); + } + } elsif (m/^-./) { usage; } + else { + s@^//@https://@s; + error ("not a Youtube, Vimeo, Tumblr, Vine," . + " Instagram or Twitter URL: $_") + unless (m@^(https?://)? + ([a-z]+\.)? + ( youtube(-nocookie)?\.com/ | + youtu\.be/ | + vimeo\.com/ | + google\.com/ .* service=youtube | + youtube\.googleapis\.com + tumblr\.com/ | + vine\.co/ | + instagram\.com/ | + twitter\.com/ | + )@six); + $fmt = 'mux' if ($muxp && !defined($fmt)); + usage if (defined($fmt) && $fmt !~ m/^\d+|all|mux$/s); + my @P = ($title, $fmt, $_); + push @urls, \@P; + $title = undef; + $expect = undef; + } + } + + error ("$expect applies to the following URLs, so it must come first") + if ($expect); + + if ($guessp) { + guess_cipher (undef, $guessp - 1); + exit (0); + } + + return do_cgi($muxp, $bwlimit) if (defined ($ENV{REQUEST_URI})); + + usage unless ($#urls >= 0); + foreach (@urls) { + my ($title, $fmt, $url) = @$_; + download_video_url ($url, $title, $prefix, + $size_p, $list_p, $bwlimit, $progress_p, 0, $fmt); + } +} + +main(); +exit 0; \ No newline at end of file diff --git a/config/configstore/update-notifier-npm.json b/config/configstore/update-notifier-npm.json new file mode 100644 index 00000000..7c916712 --- /dev/null +++ b/config/configstore/update-notifier-npm.json @@ -0,0 +1,4 @@ +{ + "optOut": false, + "lastUpdateCheck": 1528106371129 +} \ No newline at end of file diff --git a/config/configstore/update-notifier-tsd.yml b/config/configstore/update-notifier-tsd.yml new file mode 100644 index 00000000..de0fcdac --- /dev/null +++ b/config/configstore/update-notifier-tsd.yml @@ -0,0 +1,2 @@ +optOut: false +lastUpdateCheck: 1499261412614 diff --git a/config/fish/config.fish b/config/fish/config.fish new file mode 100644 index 00000000..63b69e50 --- /dev/null +++ b/config/fish/config.fish @@ -0,0 +1,18 @@ +# AutoJump +[ -f /usr/local/share/autojump/autojump.fish ]; and source /usr/local/share/autojump/autojump.fish + +# Start GPG agent +if not begin + # Is the agent running already? Does the agent-info file exist, and if so, + # is there a process with the pid given in the file? + [ -f ~/.gpg-agent-info ] + and kill -0 (cut -d : -f 2 ~/.gpg-agent-info) ^/dev/null +end + # no, it is not running. Start it! + gpg-agent --daemon --no-grab --write-env-file ~/.gpg-agent-info >/dev/null ^&1 +end + +# get the agent info from the info file, and export it so GPG can see it. +set -gx GPG_AGENT_INFO (cut -c 16- ~/.gpg-agent-info) +set -gx GPG_TTY (tty) +rvm default diff --git a/config/fish/functions/fish_prompt.fish b/config/fish/functions/fish_prompt.fish new file mode 100644 index 00000000..63355a9c --- /dev/null +++ b/config/fish/functions/fish_prompt.fish @@ -0,0 +1,92 @@ +function fish_prompt --description 'Write out the prompt' + if not set -q __fish_git_prompt_show_informative_status + set -g __fish_git_prompt_show_informative_status 1 + end + if not set -q __fish_git_prompt_hide_untrackedfiles + set -g __fish_git_prompt_hide_untrackedfiles 1 + end + + if not set -q __fish_git_prompt_color_branch + set -g __fish_git_prompt_color_branch magenta --bold + end + if not set -q __fish_git_prompt_showupstream + set -g __fish_git_prompt_showupstream "informative" + end + if not set -q __fish_git_prompt_char_upstream_ahead + set -g __fish_git_prompt_char_upstream_ahead "↑" + end + if not set -q __fish_git_prompt_char_upstream_behind + set -g __fish_git_prompt_char_upstream_behind "↓" + end + if not set -q __fish_git_prompt_char_upstream_prefix + set -g __fish_git_prompt_char_upstream_prefix "" + end + + if not set -q __fish_git_prompt_char_stagedstate + set -g __fish_git_prompt_char_stagedstate "●" + end + if not set -q __fish_git_prompt_char_dirtystate + set -g __fish_git_prompt_char_dirtystate "✚" + end + if not set -q __fish_git_prompt_char_untrackedfiles + set -g __fish_git_prompt_char_untrackedfiles "…" + end + if not set -q __fish_git_prompt_char_conflictedstate + set -g __fish_git_prompt_char_conflictedstate "✖" + end + if not set -q __fish_git_prompt_char_cleanstate + set -g __fish_git_prompt_char_cleanstate "✔" + end + + if not set -q __fish_git_prompt_color_dirtystate + set -g __fish_git_prompt_color_dirtystate blue + end + if not set -q __fish_git_prompt_color_stagedstate + set -g __fish_git_prompt_color_stagedstate yellow + end + if not set -q __fish_git_prompt_color_invalidstate + set -g __fish_git_prompt_color_invalidstate red + end + if not set -q __fish_git_prompt_color_untrackedfiles + set -g __fish_git_prompt_color_untrackedfiles $fish_color_normal + end + if not set -q __fish_git_prompt_color_cleanstate + set -g __fish_git_prompt_color_cleanstate green --bold + end + + set -l last_status $status + + if not set -q __fish_prompt_normal + set -g __fish_prompt_normal (set_color normal) + end + + set -l color_cwd + set -l prefix + switch $USER + case root toor + if set -q fish_color_cwd_root + set color_cwd $fish_color_cwd_root + else + set color_cwd $fish_color_cwd + end + set suffix '#' + case '*' + set color_cwd $fish_color_cwd + set suffix '$' + end + + # PWD + set_color $color_cwd + echo -n (prompt_pwd) + set_color normal + + printf '%s ' (__fish_vcs_prompt) + + if not test $last_status -eq 0 + set_color $fish_color_error + end + + echo -n "$suffix " + + set_color normal +end diff --git a/config/fish/functions/rvm.fish b/config/fish/functions/rvm.fish new file mode 100644 index 00000000..7fb431d3 --- /dev/null +++ b/config/fish/functions/rvm.fish @@ -0,0 +1,39 @@ +function rvm --description='Ruby enVironment Manager' + # run RVM and capture the resulting environment + set --local env_file (mktemp -t rvm.fish.XXXXXXXXXX) + bash -c 'source ~/.rvm/scripts/rvm; rvm "$@"; status=$?; env > "$0"; exit $status' $env_file $argv + + # apply rvm_* and *PATH variables from the captured environment + and eval (grep '^rvm\|^[^=]*PATH\|^GEM_HOME' $env_file | grep -v '_clr=' | sed '/^[^=]*PATH/s/:/" "/g; s/^/set -xg /; s/=/ "/; s/$/" ;/; s/(//; s/)//') + # needed under fish >= 2.2.0 + and set -xg GEM_PATH (echo $GEM_PATH | sed 's/ /:/g') + + # clean up + rm -f $env_file +end + +function __handle_rvmrc_stuff --on-variable PWD + # Source a .rvmrc file in a directory after changing to it, if it exists. + # To disable this fature, set rvm_project_rvmrc=0 in $HOME/.rvmrc + if test "$rvm_project_rvmrc" != 0 + set -l cwd $PWD + while true + if contains $cwd "" $HOME "/" + if test "$rvm_project_rvmrc_default" = 1 + rvm default 1>/dev/null 2>&1 + end + break + else + if test -e .rvmrc -o -e .ruby-version -o -e .ruby-gemset + eval "rvm reload" > /dev/null + eval "rvm rvmrc load" >/dev/null + break + else + set cwd (dirname "$cwd") + end + end + end + + set -e cwd + end +end diff --git a/config/git/ignore b/config/git/ignore new file mode 100644 index 00000000..d2dfec9b --- /dev/null +++ b/config/git/ignore @@ -0,0 +1,29 @@ +# Automatically created by GitHub for Mac +# To make edits, delete these initial comments, or else your changes may be lost! + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk diff --git a/config/htop/htoprc b/config/htop/htoprc new file mode 100644 index 00000000..a1c6722c --- /dev/null +++ b/config/htop/htoprc @@ -0,0 +1,26 @@ +# Beware! This file is rewritten by htop when settings are changed in the interface. +# The parser is also very primitive, and not human-friendly. +fields=0 48 17 18 38 39 2 46 47 49 1 +sort_key=46 +sort_direction=1 +hide_threads=0 +hide_kernel_threads=1 +hide_userland_threads=0 +shadow_other_users=0 +show_thread_names=0 +show_program_path=1 +highlight_base_name=0 +highlight_megabytes=1 +highlight_threads=1 +tree_view=0 +header_margin=1 +detailed_cpu_time=0 +cpu_count_from_zero=0 +update_process_names=0 +account_guest_in_cpu_meter=0 +color_scheme=0 +delay=15 +left_meters=AllCPUs Memory Swap +left_meter_modes=1 1 1 +right_meters=Tasks LoadAverage Uptime +right_meter_modes=2 2 2 diff --git a/config/karabiner/assets/complex_modifications/1528116684.json b/config/karabiner/assets/complex_modifications/1528116684.json new file mode 100644 index 00000000..ed4dc3c2 --- /dev/null +++ b/config/karabiner/assets/complex_modifications/1528116684.json @@ -0,0 +1,2170 @@ +{ + "title": "Vi Mode (rev 5)", + "rules": [ + { + "description": "Vi Mode [S as Trigger Key]", + "manipulators": [ + { + "type": "basic", + "from": { + "key_code": "j", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "down_arrow", + "modifiers": [ + + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "s" + }, + { + "key_code": "j" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "down_arrow", + "modifiers": [ + + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "k", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "up_arrow", + "modifiers": [ + + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "s" + }, + { + "key_code": "k" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "up_arrow", + "modifiers": [ + + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "h", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "left_arrow", + "modifiers": [ + + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "s" + }, + { + "key_code": "h" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "left_arrow", + "modifiers": [ + + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "l", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "right_arrow", + "modifiers": [ + + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "s" + }, + { + "key_code": "l" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "right_arrow", + "modifiers": [ + + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "f", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "fn", + "modifiers": [ + + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "s" + }, + { + "key_code": "f" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "fn", + "modifiers": [ + + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "b", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "left_arrow", + "modifiers": [ + "left_option" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "s" + }, + { + "key_code": "b" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "left_arrow", + "modifiers": [ + "left_option" + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "w", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "right_arrow", + "modifiers": [ + "left_option" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "s" + }, + { + "key_code": "w" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "right_arrow", + "modifiers": [ + "left_option" + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "0", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "a", + "modifiers": [ + "left_control" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "s" + }, + { + "key_code": "0" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "a", + "modifiers": [ + "left_control" + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "4", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "e", + "modifiers": [ + "left_control" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "s" + }, + { + "key_code": "4" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "e", + "modifiers": [ + "left_control" + ] + } + ] + } + ] + }, + { + "description": "Vi Mode [D as Trigger Key]", + "manipulators": [ + { + "type": "basic", + "from": { + "key_code": "j", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "down_arrow", + "modifiers": [ + + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "d" + }, + { + "key_code": "j" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "down_arrow", + "modifiers": [ + + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "k", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "up_arrow", + "modifiers": [ + + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "d" + }, + { + "key_code": "k" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "up_arrow", + "modifiers": [ + + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "h", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "left_arrow", + "modifiers": [ + + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "d" + }, + { + "key_code": "h" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "left_arrow", + "modifiers": [ + + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "l", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "right_arrow", + "modifiers": [ + + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "d" + }, + { + "key_code": "l" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "right_arrow", + "modifiers": [ + + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "f", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "fn", + "modifiers": [ + + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "d" + }, + { + "key_code": "f" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "fn", + "modifiers": [ + + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "b", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "left_arrow", + "modifiers": [ + "left_option" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "d" + }, + { + "key_code": "b" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "left_arrow", + "modifiers": [ + "left_option" + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "w", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "right_arrow", + "modifiers": [ + "left_option" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "d" + }, + { + "key_code": "w" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "right_arrow", + "modifiers": [ + "left_option" + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "0", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "a", + "modifiers": [ + "left_control" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "d" + }, + { + "key_code": "0" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "a", + "modifiers": [ + "left_control" + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "4", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "e", + "modifiers": [ + "left_control" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "d" + }, + { + "key_code": "4" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_mode", + "value": 1 + } + }, + { + "key_code": "e", + "modifiers": [ + "left_control" + ] + } + ] + } + ] + }, + { + "description": "Vi Visual Mode", + "manipulators": [ + { + "type": "basic", + "from": { + "key_code": "j", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "down_arrow", + "modifiers": [ + "left_shift" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_visual_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "v" + }, + { + "key_code": "j" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 1 + } + }, + { + "key_code": "down_arrow", + "modifiers": [ + "left_shift" + ] + } + ], + "conditions": [ + { + "type": "frontmost_application_unless", + "bundle_identifiers": [ + "^com\\.apple\\.Terminal$", + "^com\\.googlecode\\.iterm2$", + "^co\\.zeit\\.hyperterm$", + "^co\\.zeit\\.hyper$", + "^io\\.alacritty$", + "^net\\.kovidgoyal\\.kitty$", + "^org\\.vim\\.", + "^com\\.qvacua\\.VimR$" + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "k", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "up_arrow", + "modifiers": [ + "left_shift" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_visual_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "v" + }, + { + "key_code": "k" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 1 + } + }, + { + "key_code": "up_arrow", + "modifiers": [ + "left_shift" + ] + } + ], + "conditions": [ + { + "type": "frontmost_application_unless", + "bundle_identifiers": [ + "^com\\.apple\\.Terminal$", + "^com\\.googlecode\\.iterm2$", + "^co\\.zeit\\.hyperterm$", + "^co\\.zeit\\.hyper$", + "^io\\.alacritty$", + "^net\\.kovidgoyal\\.kitty$", + "^org\\.vim\\.", + "^com\\.qvacua\\.VimR$" + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "h", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "left_arrow", + "modifiers": [ + "left_shift" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_visual_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "v" + }, + { + "key_code": "h" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 1 + } + }, + { + "key_code": "left_arrow", + "modifiers": [ + "left_shift" + ] + } + ], + "conditions": [ + { + "type": "frontmost_application_unless", + "bundle_identifiers": [ + "^com\\.apple\\.Terminal$", + "^com\\.googlecode\\.iterm2$", + "^co\\.zeit\\.hyperterm$", + "^co\\.zeit\\.hyper$", + "^io\\.alacritty$", + "^net\\.kovidgoyal\\.kitty$", + "^org\\.vim\\.", + "^com\\.qvacua\\.VimR$" + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "l", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "right_arrow", + "modifiers": [ + "left_shift" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_visual_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "v" + }, + { + "key_code": "l" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 1 + } + }, + { + "key_code": "right_arrow", + "modifiers": [ + "left_shift" + ] + } + ], + "conditions": [ + { + "type": "frontmost_application_unless", + "bundle_identifiers": [ + "^com\\.apple\\.Terminal$", + "^com\\.googlecode\\.iterm2$", + "^co\\.zeit\\.hyperterm$", + "^co\\.zeit\\.hyper$", + "^io\\.alacritty$", + "^net\\.kovidgoyal\\.kitty$", + "^org\\.vim\\.", + "^com\\.qvacua\\.VimR$" + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "b", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "left_arrow", + "modifiers": [ + "left_shift", + "left_option" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_visual_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "v" + }, + { + "key_code": "b" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 1 + } + }, + { + "key_code": "left_arrow", + "modifiers": [ + "left_shift", + "left_option" + ] + } + ], + "conditions": [ + { + "type": "frontmost_application_unless", + "bundle_identifiers": [ + "^com\\.apple\\.Terminal$", + "^com\\.googlecode\\.iterm2$", + "^co\\.zeit\\.hyperterm$", + "^co\\.zeit\\.hyper$", + "^io\\.alacritty$", + "^net\\.kovidgoyal\\.kitty$", + "^org\\.vim\\.", + "^com\\.qvacua\\.VimR$" + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "w", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "right_arrow", + "modifiers": [ + "left_shift", + "left_option" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_visual_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "v" + }, + { + "key_code": "w" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 1 + } + }, + { + "key_code": "right_arrow", + "modifiers": [ + "left_shift", + "left_option" + ] + } + ], + "conditions": [ + { + "type": "frontmost_application_unless", + "bundle_identifiers": [ + "^com\\.apple\\.Terminal$", + "^com\\.googlecode\\.iterm2$", + "^co\\.zeit\\.hyperterm$", + "^co\\.zeit\\.hyper$", + "^io\\.alacritty$", + "^net\\.kovidgoyal\\.kitty$", + "^org\\.vim\\.", + "^com\\.qvacua\\.VimR$" + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "0", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "left_arrow", + "modifiers": [ + "left_shift", + "left_command" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_visual_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "v" + }, + { + "key_code": "0" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 1 + } + }, + { + "key_code": "left_arrow", + "modifiers": [ + "left_shift", + "left_command" + ] + } + ], + "conditions": [ + { + "type": "frontmost_application_unless", + "bundle_identifiers": [ + "^com\\.apple\\.Terminal$", + "^com\\.googlecode\\.iterm2$", + "^co\\.zeit\\.hyperterm$", + "^co\\.zeit\\.hyper$", + "^io\\.alacritty$", + "^net\\.kovidgoyal\\.kitty$", + "^org\\.vim\\.", + "^com\\.qvacua\\.VimR$" + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "4", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "right_arrow", + "modifiers": [ + "left_shift", + "left_command" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_visual_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "v" + }, + { + "key_code": "4" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 1 + } + }, + { + "key_code": "right_arrow", + "modifiers": [ + "left_shift", + "left_command" + ] + } + ], + "conditions": [ + { + "type": "frontmost_application_unless", + "bundle_identifiers": [ + "^com\\.apple\\.Terminal$", + "^com\\.googlecode\\.iterm2$", + "^co\\.zeit\\.hyperterm$", + "^co\\.zeit\\.hyper$", + "^io\\.alacritty$", + "^net\\.kovidgoyal\\.kitty$", + "^org\\.vim\\.", + "^com\\.qvacua\\.VimR$" + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "open_bracket", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "up_arrow", + "modifiers": [ + "left_shift", + "left_option" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_visual_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "v" + }, + { + "key_code": "open_bracket" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 1 + } + }, + { + "key_code": "up_arrow", + "modifiers": [ + "left_shift", + "left_option" + ] + } + ], + "conditions": [ + { + "type": "frontmost_application_unless", + "bundle_identifiers": [ + "^com\\.apple\\.Terminal$", + "^com\\.googlecode\\.iterm2$", + "^co\\.zeit\\.hyperterm$", + "^co\\.zeit\\.hyper$", + "^io\\.alacritty$", + "^net\\.kovidgoyal\\.kitty$", + "^org\\.vim\\.", + "^com\\.qvacua\\.VimR$" + ] + } + ] + }, + { + "type": "basic", + "from": { + "key_code": "close_bracket", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "key_code": "down_arrow", + "modifiers": [ + "left_shift", + "left_option" + ] + } + ], + "conditions": [ + { + "type": "variable_if", + "name": "vi_visual_mode", + "value": 1 + } + ] + }, + { + "type": "basic", + "from": { + "simultaneous": [ + { + "key_code": "v" + }, + { + "key_code": "close_bracket" + } + ], + "simultaneous_options": { + "key_down_order": "strict", + "key_up_order": "strict_inverse", + "detect_key_down_uninterruptedly": true, + "to_after_key_up": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 0 + } + } + ] + }, + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [ + { + "set_variable": { + "name": "vi_visual_mode", + "value": 1 + } + }, + { + "key_code": "down_arrow", + "modifiers": [ + "left_shift", + "left_option" + ] + } + ], + "conditions": [ + { + "type": "frontmost_application_unless", + "bundle_identifiers": [ + "^com\\.apple\\.Terminal$", + "^com\\.googlecode\\.iterm2$", + "^co\\.zeit\\.hyperterm$", + "^co\\.zeit\\.hyper$", + "^io\\.alacritty$", + "^net\\.kovidgoyal\\.kitty$", + "^org\\.vim\\.", + "^com\\.qvacua\\.VimR$" + ] + } + ] + } + ] + } + ] +} diff --git a/config/karabiner/assets/complex_modifications/1528116704.json b/config/karabiner/assets/complex_modifications/1528116704.json new file mode 100644 index 00000000..7506643f --- /dev/null +++ b/config/karabiner/assets/complex_modifications/1528116704.json @@ -0,0 +1,210 @@ +{ + "title": "Use CAPS LOCK for vi navigation", + "rules": [{ + "description": "CAPS LOCK + hjkl to arrow keys", + "manipulators": [{ + "type": "basic", + "from": { + "key_code": "j", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [{ + "key_code": "down_arrow" + }], + "conditions": [{ + "type": "variable_if", + "name": "caps_lock pressed", + "value": 1 + }] + }, { + "type": "basic", + "from": { + "key_code": "k", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [{ + "key_code": "up_arrow" + }], + "conditions": [{ + "type": "variable_if", + "name": "caps_lock pressed", + "value": 1 + }] + }, { + "type": "basic", + "from": { + "key_code": "h", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [{ + "key_code": "left_arrow" + }], + "conditions": [{ + "type": "variable_if", + "name": "caps_lock pressed", + "value": 1 + }] + }, { + "type": "basic", + "from": { + "key_code": "l", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [{ + "key_code": "right_arrow" + }], + "conditions": [{ + "type": "variable_if", + "name": "caps_lock pressed", + "value": 1 + }] + }, + { + "type": "basic", + "from": { + "key_code": "caps_lock", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [{ + "set_variable": { + "name": "caps_lock pressed", + "value": 1 + } + }], + "to_after_key_up": [{ + "set_variable": { + "name": "caps_lock pressed", + "value": 0 + } + }] + } + ] + }, + { + "description": "CAPS LOCK + shift + hjkl to scroll", + "manipulators": [{ + "type": "basic", + "from": { + "key_code": "j", + "modifiers": { + "mandatory": [ + "shift" + ] + } + }, + "to": [{ + "mouse_key": { + "vertical_wheel": 40 + } + }], + "conditions": [{ + "type": "variable_if", + "name": "caps_lock pressed", + "value": 1 + }] + }, { + "type": "basic", + "from": { + "key_code": "k", + "modifiers": { + "mandatory": [ + "shift" + ] + } + }, + "to": [{ + "mouse_key": { + "vertical_wheel": -40 + } + }], + "conditions": [{ + "type": "variable_if", + "name": "caps_lock pressed", + "value": 1 + }] + }, { + "type": "basic", + "from": { + "key_code": "h", + "modifiers": { + "mandatory": [ + "shift" + ] + } + }, + "to": [{ + "mouse_key": { + "horizontal_wheel": -30 + } + }], + "conditions": [{ + "type": "variable_if", + "name": "caps_lock pressed", + "value": 1 + }] + }, { + "type": "basic", + "from": { + "key_code": "l", + "modifiers": { + "mandatory": [ + "shift" + ] + } + }, + "to": [{ + "mouse_key": { + "horizontal_wheel": -30 + } + }], + "conditions": [{ + "type": "variable_if", + "name": "caps_lock pressed", + "value": 1 + }] + }, { + "type": "basic", + "from": { + "key_code": "caps_lock", + "modifiers": { + "optional": [ + "any" + ] + } + }, + "to": [{ + "set_variable": { + "name": "caps_lock pressed", + "value": 1 + } + }], + "to_after_key_up": [{ + "set_variable": { + "name": "caps_lock pressed", + "value": 0 + } + }] + }] + } + ] +} diff --git a/config/karabiner/karabiner.json b/config/karabiner/karabiner.json new file mode 100644 index 00000000..17016923 --- /dev/null +++ b/config/karabiner/karabiner.json @@ -0,0 +1,163 @@ +{ + "global": { + "check_for_updates_on_startup": true, + "show_in_menu_bar": false, + "show_profile_name_in_menu_bar": false + }, + "profiles": [ + { + "complex_modifications": { + "parameters": { + "basic.simultaneous_threshold_milliseconds": 50, + "basic.to_delayed_action_delay_milliseconds": 500, + "basic.to_if_alone_timeout_milliseconds": 1000, + "basic.to_if_held_down_threshold_milliseconds": 500 + }, + "rules": [] + }, + "devices": [ + { + "disable_built_in_keyboard_if_exists": false, + "fn_function_keys": [], + "identifiers": { + "is_keyboard": true, + "is_pointing_device": false, + "product_id": 4, + "vendor_id": 4309 + }, + "ignore": false, + "manipulate_caps_lock_led": false, + "simple_modifications": [] + }, + { + "disable_built_in_keyboard_if_exists": false, + "fn_function_keys": [], + "identifiers": { + "is_keyboard": true, + "is_pointing_device": false, + "product_id": 627, + "vendor_id": 1452 + }, + "ignore": false, + "manipulate_caps_lock_led": true, + "simple_modifications": [] + } + ], + "fn_function_keys": [ + { + "from": { + "key_code": "f1" + }, + "to": { + "key_code": "vk_consumer_brightness_down" + } + }, + { + "from": { + "key_code": "f2" + }, + "to": { + "key_code": "vk_consumer_brightness_up" + } + }, + { + "from": { + "key_code": "f3" + }, + "to": { + "key_code": "vk_mission_control" + } + }, + { + "from": { + "key_code": "f4" + }, + "to": { + "key_code": "vk_launchpad" + } + }, + { + "from": { + "key_code": "f5" + }, + "to": { + "key_code": "vk_consumer_illumination_down" + } + }, + { + "from": { + "key_code": "f6" + }, + "to": { + "key_code": "vk_consumer_illumination_up" + } + }, + { + "from": { + "key_code": "f7" + }, + "to": { + "key_code": "vk_consumer_previous" + } + }, + { + "from": { + "key_code": "f8" + }, + "to": { + "key_code": "vk_consumer_play" + } + }, + { + "from": { + "key_code": "f9" + }, + "to": { + "key_code": "vk_consumer_next" + } + }, + { + "from": { + "key_code": "f10" + }, + "to": { + "key_code": "mute" + } + }, + { + "from": { + "key_code": "f11" + }, + "to": { + "key_code": "volume_down" + } + }, + { + "from": { + "key_code": "f12" + }, + "to": { + "key_code": "volume_up" + } + } + ], + "name": "Default profile", + "selected": true, + "simple_modifications": [ + { + "from": { + "key_code": "non_us_backslash" + }, + "to": { + "key_code": "grave_accent_and_tilde" + } + } + ], + "virtual_hid_keyboard": { + "caps_lock_delay_milliseconds": 0, + "country_code": 0, + "keyboard_type": "ansi" + } + } + ] +} \ No newline at end of file diff --git a/config/kitty/kitty.conf b/config/kitty/kitty.conf new file mode 100644 index 00000000..8eb1eaa3 --- /dev/null +++ b/config/kitty/kitty.conf @@ -0,0 +1,504 @@ +# vim:fileencoding=utf-8:ft=conf + +# You can include secondary config files via the "include" directive. +# If you use a relative path for include, it is resolved with respect to the +# location od the current config file. For example: +# include other.conf + +# Fonts {{{ +# Font family. You can also specify different fonts for the +# bold/italic/bold-italic variants. By default they are derived automatically, +# by the OSes font system. Setting them manually is useful for font families +# that have many weight variants like Book, Medium, Thick, etc. For example: +# font_family Operator Mono Book +# bold_font Operator Mono Medium +# italic_font Operator Mono Book Italic +# bold_italic_font Operator Mono Medium Italic +# +# You can get a list of full family names available on your computer by running +# kitty list-fonts +# The default values shown below rely on your OS to choose an appropriate monospace font family. +# font_family monospace +# italic_font auto +# bold_font auto +# bold_italic_font auto + +# Font size (in pts) +# font_size 11.0 + +# The amount the font size is changed by (in pts) when increasing/decreasing +# the font size in a running terminal. +# font_size_delta 2 + + +# Adjust the cell dimensions. +# You can use either numbers, which are interpreted as pixels or percentages +# (number followed by %), which are interpreted as percentages of the +# unmodified values. You can use negative pixels or percentages less than +# 100% to reduce sizes (but this might cause rendering artifacts). +# adjust_line_height 0 +# adjust_column_width 0 + +# Symbol mapping (special font for specified unicode code points). Map the +# specified unicode codepoints to a particular font. Useful if you need special +# rendering for some symbols, such as for Powerline. Avoids the need for +# patched fonts. Each unicode code point is specified in the form U+. You can specify multiple code points, separated by commas +# and ranges separated by hyphens. symbol_map itself can be specified multiple times. +# Syntax is: +# +# symbol_map codepoints Font Family Name +# +# For example: +# +# symbol_map U+E0A0-U+E0A2,U+E0B0-U+E0B3 PowerlineSymbols + +# Change the sizes of the lines used for the box drawing unicode characters +# These values are in pts. They will be scaled by the monitor DPI to arrive at +# a pixel value. There must be four values corresponding to thin, normal, thick, +# and very thick lines; +# box_drawing_scale 0.001, 1, 1.5, 2 +# }}} + + +# Cursor customization {{{ +# The cursor color +# cursor #cccccc + +# The cursor shape can be one of (block, beam, underline) +# cursor_shape block + +# The interval (in seconds) at which to blink the cursor. Set to zero to +# disable blinking. Note that numbers smaller than repaint_delay will be +# limited to repaint_delay. +# cursor_blink_interval 0.5 + +# Stop blinking cursor after the specified number of seconds of keyboard inactivity. Set to +# zero to never stop blinking. +# cursor_stop_blinking_after 15.0 +# }}} + + +# Scrollback {{{ +# Number of lines of history to keep in memory for scrolling back +# scrollback_lines 2000 + +# Program with which to view scrollback in a new window. The scrollback buffer is passed as +# STDIN to this program. If you change it, make sure the program you use can +# handle ANSI escape sequences for colors and text formatting. +# scrollback_pager less +G -R + +# Wheel scroll multiplier (modify the amount scrolled by the mouse wheel). Use negative +# numbers to change scroll direction. +# wheel_scroll_multiplier 5.0 +# }}} + + +# Mouse {{{ +# The color and style for highlighting URLs on mouse-over. url_style can be one of: +# none, single, double, curly +# url_color #0087BD +# url_style curly + +# The modifier keys to press when clicking with the mouse on URLs to open the URL +# open_url_modifiers ctrl+shift + +# The program with which to open URLs that are clicked on. The special value "default" means to +# use the operating system's default URL handler. +# open_url_with default + +# Copy to clipboard on select. With this enabled, simply selecting text with +# the mouse will cause the text to be copied to clipboard. Useful on platforms +# such as macOS/Wayland that do not have the concept of primary selections. Note +# that this is a security risk, as all programs, including websites open in your +# browser can read the contents of the clipboard. +# copy_on_select no + +# The modifiers to use rectangular selection (i.e. to select text in a +# rectangular block with the mouse) +# rectangle_select_modifiers ctrl+alt + +# Characters considered part of a word when double clicking. In addition to these characters +# any character that is marked as an alpha-numeric character in the unicode +# database will be matched. +# select_by_word_characters :@-./_~?&=%+# + +# The interval between successive clicks to detect double/triple clicks (in seconds) +# click_interval 0.5 + +# Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to +# zero to disable mouse cursor hiding. +# mouse_hide_wait 3.0 + +# Set the active window to the window under the mouse when moving the mouse around +# focus_follows_mouse no +# }}} + + +# Performance tuning {{{ +# Delay (in milliseconds) between screen updates. Decreasing it, increases +# frames-per-second (FPS) at the cost of more CPU usage. The default value +# yields ~100 FPS which is more than sufficient for most uses. Note that to +# actually achieve 100FPS you have to either set sync_to_monitor to no or use a +# monitor with a high refresh rate. +# repaint_delay 10 + +# Delay (in milliseconds) before input from the program running in the terminal +# is processed. Note that decreasing it will increase responsiveness, but also +# increase CPU usage and might cause flicker in full screen programs that +# redraw the entire screen on each loop, because kitty is so fast that partial +# screen updates will be drawn. +# input_delay 3 + +# Sync screen updates to the refresh rate of the monitor. This prevents +# tearing (https://en.wikipedia.org/wiki/Screen_tearing) when scrolling. However, +# it limits the rendering speed to the refresh rate of your monitor. With a +# very high speed mouse/high keyboard repeat rate, you may notice some slight input latency. +# If so, set this to no. +# sync_to_monitor yes +# }}} + + +# Audio/visual bell {{{ +# Visual bell duration. Flash the screen when a bell occurs for the specified number of +# seconds. Set to zero to disable. +# visual_bell_duration 0.0 + +# Enable/disable the audio bell. Useful in environments that require silence. +# enable_audio_bell yes +# }}} + + +# Window layout {{{ +# If enabled, the window size will be remembered so that new instances of kitty will have the same +# size as the previous instance. If disabled, the window will initially have size configured +# by initial_window_width/height, in pixels. +# remember_window_size yes +# initial_window_width 640 +# initial_window_height 400 + +# The enabled window layouts. A comma separated list of layout names. The special value * means +# all layouts. The first listed layout will be used as the startup layout. +# For a list of available layouts, see the README. +# enabled_layouts * + +# The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution. +# Note that borders are displayed only when more than one window is visible. They are meant to separate multiple windows. +# window_border_width 1 + +# The window margin (in pts) (blank area outside the border) +# window_margin_width 0 + +# The window padding (in pts) (blank area between the text and the window border) +# window_padding_width 0 + +# The color for the border of the active window +# active_border_color #00ff00 + +# The color for the border of inactive windows +# inactive_border_color #cccccc + +# Fade the text in inactive windows by the specified amount (a number between +# zero and one, with 0 being fully faded). +# inactive_text_alpha 1.0 +# }}} + + +# Tab bar {{{ +# Which edge to show the tab bar on, top or bottom +# tab_bar_edge bottom + +# The separator between tabs in the tab bar +# tab_separator " ┇" + +# Tab bar colors and styles +# active_tab_foreground #000 +# active_tab_background #eee +# active_tab_font_style bold-italic +# inactive_tab_foreground #444 +# inactive_tab_background #999 +# inactive_tab_font_style normal +# }}} + + +# Color scheme {{{ +# The foreground color +# foreground #dddddd + +# The background color +# background #000000 + +# The opacity of the background. A number between 0 and 1, where 1 is opaque and 0 is fully transparent. +# This will only work if supported by the OS (for instance, when using a compositor under X11). Note +# that it only sets the default background color's opacity. This is so that +# things like the status bar in vim, powerline prompts, etc. still look good. +# But it means that if you use a color theme with a background color in your +# editor, it will not be rendered as transparent. Instead you should change the +# default background color in your kitty config and not use a background color +# in the editor color scheme. Or use the escape codes to set the terminals +# default colors in a shell script to launch your editor. +# Be aware that using a value less than 1.0 is a (possibly significant) performance hit. +# background_opacity 1.0 + +# The foreground for selections +# selection_foreground #000000 + +# The background for selections +# selection_background #FFFACD + +# The 16 terminal colors. There are 8 basic colors, each color has a dull and +# bright version. You can also set the remaining colors from the 256 color table +# as color16 to color256. + +# black +# color0 #000000 +# color8 #767676 + +# red +# color1 #cc0403 +# color9 #f2201f + +# green +# color2 #19cb00 +# color10 #23fd00 + +# yellow +# color3 #cecb00 +# color11 #fffd00 + +# blue +# color4 #0d73cc +# color12 #1a8fff + +# magenta +# color5 #cb1ed1 +# color13 #fd28ff + +# cyan +# color6 #0dcdcd +# color14 #14ffff + +# white +# color7 #dddddd +# color15 #ffffff +# }}} + + +# Advanced {{{ + +# The shell program to execute. The default value of . means +# to use whatever shell is set as the default shell for the current user. +# Note that on macOS if you change this, you might need to add --login to +# ensure that the shell starts in interactive mode and reads its startup rc files. +# shell . + +# Close the window when the child process (shell) exits. If no (the default), +# the terminal will remain open when the child exits as long as there are still +# processes outputting to the terminal (for example disowned or backgrounded +# processes). If yes, the window will close as soon as the child process exits. +# Note that setting it to yes means that any background processes still using +# the terminal can fail silently because their stdout/stderr/stdin no longer +# work. +# close_on_child_death no + +# Allow other programs to control kitty. If you turn this on other programs can +# control all aspects of kitty, including sending text to kitty windows, +# opening new windows, closing windows, reading the content of windows, etc. +# Note that this even works over ssh connections. +# allow_remote_control no + +# The value of the TERM environment variable to set. Changing this can break +# many terminal programs, only change it if you know what you are doing, not +# because you read some advice on Stack Overflow to change it. +# term xterm-kitty + + +# }}} + + +# Keyboard shortcuts {{{ +# For a list of key names, see: http://www.glfw.org/docs/latest/group__keys.html +# For a list of modifier names, see: http://www.glfw.org/docs/latest/group__mods.html +# +# You can use the special action no_op to unmap a keyboard shortcut that is +# assigned in the default configuration. +# +# You can combine multiple actions to be triggered by a single shortcut, using the +# syntax below: +# map key combine action1 action2 action3 ... +# For example: +# map ctrl+shift+e combine : new_window : next_layout +# this will create a new window and switch to the next available layout +# +# You can use multi-key shortcuts using the syntax shown below: +# map key1>key2>key3 action +# For example: +# map ctrl+f>2 set_font_size 20 +# this will change the font size to 20 points when you press ctrl+f and then 2 + +# Clipboard {{{ +# map ctrl+shift+v paste_from_clipboard +# map ctrl+shift+s paste_from_selection +# map ctrl+shift+c copy_to_clipboard +# map shift+insert paste_from_selection +# You can also pass the contents of the current selection to any program using +# pass_selection_to_program. By default, the system's open program is used, but +# you can specify your own, for example: +# map ctrl+shift+o pass_selection_to_program firefox +# map ctrl+shift+o pass_selection_to_program +map super+c copy_to_clipboard +map super+v paste_from_clipboard +# }}} + +# Scrolling {{{ +# map ctrl+shift+up scroll_line_up +# map ctrl+shift+down scroll_line_down +# map ctrl+shift+k scroll_line_up +# map ctrl+shift+j scroll_line_down +# map ctrl+shift+page_up scroll_page_up +# map ctrl+shift+page_down scroll_page_down +# map ctrl+shift+home scroll_home +# map ctrl+shift+end scroll_end +# map ctrl+shift+h show_scrollback +# }}} + +# Window management {{{ +# map ctrl+shift+enter new_window +# map ctrl+shift+n new_os_window +# map ctrl+shift+w close_window +# map ctrl+shift+] next_window +# map ctrl+shift+[ previous_window +# map ctrl+shift+f move_window_forward +# map ctrl+shift+b move_window_backward +# map ctrl+shift+` move_window_to_top +# map ctrl+shift+1 first_window +# map ctrl+shift+2 second_window +# map ctrl+shift+3 third_window +# map ctrl+shift+4 fourth_window +# map ctrl+shift+5 fifth_window +# map ctrl+shift+6 sixth_window +# map ctrl+shift+7 seventh_window +# map ctrl+shift+8 eighth_window +# map ctrl+shift+9 ninth_window +# map ctrl+shift+0 tenth_window +# You can open a new window running an arbitrary program, for example: +# map ctrl+shift+y new_window mutt +# +# You can pass the current selection to the new program by using the @selection placeholder +# map ctrl+shift+y new_window less @selection +# +# You can even send the contents of the current screen + history buffer as stdin using +# the placeholders @text (which is the plain text) and @ansi (which includes text styling escape codes). +# For only the current screen, use @screen or @ansi_screen. +# For example, the following command opens the scrollback buffer in less in a new window. +# map ctrl+shift+y new_window @ansi less +G -R +# +# You can open a new window with the current working directory set to the +# working directory of the current window using +# map ctrl+alt+enter new_window_with_cwd +# }}} + +# Tab management {{{ +# map ctrl+shift+right next_tab +# map ctrl+shift+left previous_tab +# map ctrl+shift+t new_tab +# map ctrl+shift+q close_tab +# map ctrl+shift+l next_layout +# map ctrl+shift+. move_tab_forward +# map ctrl+shift+, move_tab_backward +# map ctrl+shift+alt+t set_tab_title +# You can also create shortcuts to go to specific tabs, with 1 being the first tab +# map ctrl+alt+1 goto_tab 1 +# map ctrl+alt+2 goto_tab 2 + +# Just as with new_window above, you can also pass the name of arbitrary +# commands to run when using new_tab and use new_tab_with_cwd. +# }}} + +# Layout management {{{ +# You can create shortcuts to switch to specific layouts +# map ctrl+alt+1 goto_layout tall +# map ctrl+alt+2 goto_layout stack +# }}} + +# Font sizes {{{ +# map ctrl+shift+equal increase_font_size +# map ctrl+shift+minus decrease_font_size +# map ctrl+shift+backspace restore_font_size +# To setup shortcuts for specific font sizes, follow the example below: +# map ctrl+shift+f6 set_font_size 10.0 +# map ctrl+shift+f7 set_font_size 20.5 +# }}} + +# Select and act on visible text {{{ +# Use the hints kitten to select text and either pass it to an external program or +# insert it into the terminal or copy it to the clipboard. +# +# Open a currently visible URL using the keyboard. The program used to open the +# URL is specified in open_url_with. +# map ctrl+shift+e run_kitten text hints + +# Select a path/filename and insert it into the terminal. Useful, for instance to +# run git commands on a filename output from a previous git command. +# map ctrl+shift+p>f run_kitten text hints --type path --program - + +# Select a path/filename and open it with the default open program. +# map ctrl+shift+p>shift+f run_kitten text hints --type path + +# Select a line of text and insert it into the terminal. Use for the +# output of things like: ls -1 +# map ctrl+shift+p>l run_kitten text hints --type line --program - + +# Select words and insert into terminal. +# map ctrl+shift+p>w run_kitten text hints --type word --program - + +# The hints kitten has many more modes of operation that you can map to different +# shortcuts. For a full description run: kitty +kitten hints --help +# }}} + +# Miscellaneous {{{ +# map ctrl+shift+f11 toggle_fullscreen +# map ctrl+shift+u input_unicode_character +# map ctrl+shift+f2 edit_config_file +# You can customize how the URLs are +# Open the kitty shell in a new window/tab/overlay/os_window to control kitty using commands. +# map ctrl+shift+escape kitty_shell window + +# Sending arbitrary text on shortcut key presses +# You can tell kitty to send arbitrary (UTF-8) encoded text to +# the client program when pressing specified shortcut keys. For example: +# map ctrl+alt+a send_text all Special text +# This will send "Special text" when you press the Ctrl+Alt+a key combination. +# The text to be sent is a python string literal so you can use escapes like +# \x1b to send control codes or \u21fb to send unicode characters (or you can +# just input the unicode characters directly as UTF-8 text). The first argument +# to send_text is the keyboard modes in which to activate the shortcut. The possible +# values are normal or application or kitty or a comma separated combination of them. +# The special keyword all means all modes. The modes normal and application refer to +# the DECCKM cursor key mode for terminals, and kitty refers to the special kitty +# extended keyboard protocol. Another example, that outputs a word and then moves the cursor +# to the start of the line (same as pressing the Home key): +# map ctrl+alt+a send_text normal Word\x1b[H +# map ctrl+alt+a send_text application Word\x1bOH +# }}} + +# }}} + + +# OS specific tweaks {{{ + +# Change the color of the kitty window's titlebar on macOS. A value of "system" +# means to use the default system color, a value of "background" means to use +# the default background color and finally you can use an arbitrary color, such +# as #12af59 or "red". +# macos_titlebar_color system + +# Hide the kitty window's title bar on macOS. +# macos_hide_titlebar no + +# Use the option key as an alt key. With this set to no, kitty will use +# the macOS native Option+Key = unicode character behavior. This will +# break any Alt+key keyboard shortcuts in your terminal programs, but you +# can use the macOS unicode input technique. +# macos_option_as_alt yes +# }}} \ No newline at end of file diff --git a/config/ranger/bookmarks b/config/ranger/bookmarks new file mode 100644 index 00000000..aafe1546 --- /dev/null +++ b/config/ranger/bookmarks @@ -0,0 +1 @@ +':/Users/marten/zoo/Serengeti/public/images diff --git a/config/ranger/history b/config/ranger/history new file mode 100644 index 00000000..7a85378b --- /dev/null +++ b/config/ranger/history @@ -0,0 +1,3 @@ +delete + +open_with diff --git a/config/ranger/tagged b/config/ranger/tagged new file mode 100644 index 00000000..e69de29b diff --git a/doom.d b/doom.d new file mode 160000 index 00000000..00eca23f --- /dev/null +++ b/doom.d @@ -0,0 +1 @@ +Subproject commit 00eca23f6a4bf6de59220c2da957f60ffe3309f8 diff --git a/emacs-old/.gitignore b/emacs-old/.gitignore new file mode 100644 index 00000000..f2775c56 --- /dev/null +++ b/emacs-old/.gitignore @@ -0,0 +1,4 @@ +*~ +el-get +auto-save-list +swank \ No newline at end of file diff --git a/emacs-old/README.markdown b/emacs-old/README.markdown new file mode 100644 index 00000000..e4f9e573 --- /dev/null +++ b/emacs-old/README.markdown @@ -0,0 +1,7 @@ +This is an Emacs configuration collected by someone who really likes the idea behind +Emacs, but digs the modal editing of Vim much more for plain day-to-day stuff. It +uses Evil to alleviate that pain. + +I am ignoring package.el in favor of el-get, because the latter is able to actually +access anything and everything, and I think that simple git repositories are the way +to go over ELPA, MELPA and Marmalade. diff --git a/emacs-old/emacs.d-old/.gitignore b/emacs-old/emacs.d-old/.gitignore new file mode 100644 index 00000000..7827733d --- /dev/null +++ b/emacs-old/emacs.d-old/.gitignore @@ -0,0 +1,9 @@ +ac-comphist.dat +.cask +.emacs.desktop +.emacs.desktop.lock +projectile.cache +projectile-bookmarks.eld +tramp +.#* +elpa/ diff --git a/emacs-old/emacs.d-old/.mc-lists.el b/emacs-old/emacs.d-old/.mc-lists.el new file mode 100644 index 00000000..be3d11f0 --- /dev/null +++ b/emacs-old/emacs.d-old/.mc-lists.el @@ -0,0 +1,20 @@ +;; This file is automatically generated by the multiple-cursors extension. +;; It keeps track of your preferences for running commands with multiple cursors. + +(setq mc/cmds-to-run-for-all + '( + evil-backward-char + evil-delete + evil-forward-char + evil-insert + evil-next-line + evil-normal-state + evil-previous-line + keyboard-quit + sp--self-insert-command + )) + +(setq mc/cmds-to-run-once + '( + evil-copy-from-below + )) diff --git a/emacs-old/emacs.d-old/.projectile b/emacs-old/emacs.d-old/.projectile new file mode 100644 index 00000000..953ce84b --- /dev/null +++ b/emacs-old/emacs.d-old/.projectile @@ -0,0 +1,3 @@ +-/vendor +-/tramp +-/.cask \ No newline at end of file diff --git a/emacs-old/emacs.d-old/Cask b/emacs-old/emacs.d-old/Cask new file mode 100644 index 00000000..45af775a --- /dev/null +++ b/emacs-old/emacs.d-old/Cask @@ -0,0 +1,26 @@ +(source "gnu" "http://elpa.gnu.org/packages/") +(source "melpa" "http://melpa.milkbox.net/packages/") + +(depends-on "auto-complete") +(depends-on "cask") +(depends-on "coffee-mode") +(depends-on "dash") +(depends-on "dash-at-point") +(depends-on "drag-stuff") +(depends-on "enh-ruby-mode") +(depends-on "evil") +(depends-on "evil-leader") +(depends-on "evil-nerd-commenter") +(depends-on "expand-region") +(depends-on "grizzl") +(depends-on "highlight-indentation") +(depends-on "magit") +(depends-on "monokai-theme") +(depends-on "multiple-cursors") +(depends-on "pallet") +(depends-on "popwin") +(depends-on "projectile") +(depends-on "s") +(depends-on "smex") +(depends-on "wrap-region") +(depends-on "yasnippet") \ No newline at end of file diff --git a/emacs-old/emacs.d-old/eshell/history b/emacs-old/emacs.d-old/eshell/history new file mode 100644 index 00000000..7ec0a103 --- /dev/null +++ b/emacs-old/emacs.d-old/eshell/history @@ -0,0 +1,5 @@ +guard +exit +lein +brew search lein +exit diff --git a/emacs-old/emacs.d-old/ido.last b/emacs-old/emacs.d-old/ido.last new file mode 100644 index 00000000..e69de29b diff --git a/emacs-old/emacs.d-old/init.el b/emacs-old/emacs.d-old/init.el new file mode 100644 index 00000000..f05f12df --- /dev/null +++ b/emacs-old/emacs.d-old/init.el @@ -0,0 +1,46 @@ +;; Package management + +(add-to-list 'load-path (expand-file-name "~/.emacs.d/init")) +(require 'init-packages) + +(add-to-list 'load-path (expand-file-name "~/.emacs.d/vendor")) +(add-to-list 'load-path (expand-file-name "~/.emacs.d/vendor/emacs-powerline")) +(add-to-list 'load-path (expand-file-name "~/.emacs.d/vendor/smartparens")) + +(cd "~/") + +; Necessities +(require 'init-core) ; Core setup +(require 'init-util) ; Provides utility functions +(require 'init-theme) ; Sets up the theme +(require 'init-visuals) ; Hides mode-line clutter, sets up visual aspects of Emacs +(require 'init-evil) ; VI-emulation +(require 'init-keys) + +; Editor improvements +(require 'init-powerline) +(require 'init-ido) +(require 'init-autocomplete) +(require 'init-projectile) +(require 'init-yasnippet) +(require 'init-magit) +(require 'init-project-explorer) + +; Languages +(require 'init-ruby) +(require 'init-coffee) + +;init-highlight-indentation +;init-dash-at-point +;; init-find-file-in-project +;init-smartparens + +(custom-set-variables + ;; custom-set-variables was added by Custom. + ;; If you edit it by hand, you could mess it up, so be careful. + ;; Your init file should contain only one such instance. + ;; If there is more than one, they won't work right. + '(custom-safe-themes + (quote + ("99cbc2aaa2b77374c2c06091494bd9d2ebfe6dc5f64c7ccdb36c083aff892f7d" default))) + '(fill-column 85)) diff --git a/emacs-old/emacs.d-old/init/init-ag.el b/emacs-old/emacs.d-old/init/init-ag.el new file mode 100644 index 00000000..73d82599 --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-ag.el @@ -0,0 +1,3 @@ +(require-package 'ag) + +(provide 'init-ag) diff --git a/emacs-old/emacs.d-old/init/init-autocomplete.el b/emacs-old/emacs.d-old/init/init-autocomplete.el new file mode 100644 index 00000000..da4b0ed4 --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-autocomplete.el @@ -0,0 +1,11 @@ +(require-package 'auto-complete) +(require 'auto-complete-config) + +(add-to-list 'ac-dictionary-directories + "~/.emacs.d/.cask/24.3.50.1/elpa/auto-complete-20130724.1750/dict") +(ac-config-default) +(setq ac-ignore-case nil) +(add-to-list 'ac-modes 'enh-ruby-mode) +(add-to-list 'ac-modes 'web-mode) + +(provide 'init-autocomplete) diff --git a/emacs-old/emacs.d-old/init/init-coffee.el b/emacs-old/emacs.d-old/init/init-coffee.el new file mode 100644 index 00000000..49e78f7f --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-coffee.el @@ -0,0 +1,4 @@ +(require-package 'coffee-mode) + + +(provide 'init-coffee) diff --git a/emacs-old/emacs.d-old/init/init-core.el b/emacs-old/emacs.d-old/init/init-core.el new file mode 100644 index 00000000..5f03f501 --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-core.el @@ -0,0 +1,34 @@ +(setq make-backup-files nil) +(setq auto-save-default nil) +(setq inhibit-startup-message t) +(setq initial-scratch-message nil) + +(tool-bar-mode -1) +(scroll-bar-mode -1) +(global-hl-line-mode 1) + +;; only turn off menus if not osx +(if (not (eq system-type 'darwin)) + (menu-bar-mode -1)) + +(column-number-mode t) +(setq fill-column 85) +(setq-default tab-width 2) +(setq-default indent-tabs-mode nil) + +; Highlight and remove trailing whitespace +(setq show-trailing-whitespace t) +(add-hook 'before-save-hook 'delete-trailing-whitespace) + +; Write save backups to a global directory instead of polluting my disk +(setq backup-directory-alist + (list (cons "." (expand-file-name "backup" user-emacs-directory)))) + +; Manage the $PATH +(push (expand-file-name "/usr/local/bin") exec-path) +(push (expand-file-name "~/bin") exec-path) + +; Save and restore open files across quits and restarts +(desktop-save-mode 1) + +(provide 'init-core) diff --git a/emacs-old/emacs.d-old/init/init-dash-at-point.el b/emacs-old/emacs.d-old/init/init-dash-at-point.el new file mode 100644 index 00000000..ccdfa872 --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-dash-at-point.el @@ -0,0 +1,6 @@ +(autoload 'dash-at-point "dash-at-point" "Search the word at point with Dash." t nil) + +(add-to-list 'dash-at-point-mode-alist '(enh-ruby-mode . "ruby")) +(global-set-key (kbd "C-c d") 'dash-at-point) + +(provide 'init-dash-at-point) diff --git a/emacs-old/emacs.d-old/init/init-evil.el b/emacs-old/emacs.d-old/init/init-evil.el new file mode 100644 index 00000000..0fbcadb5 --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-evil.el @@ -0,0 +1,62 @@ +(setq evil-search-module 'evil-search) +(setq evil-magic 'very-magic) + +(setq evil-emacs-state-cursor '("red" box)) +(setq evil-normal-state-cursor '("green" box)) +(setq evil-visual-state-cursor '("orange" box)) +(setq evil-insert-state-cursor '("red" bar)) +(setq evil-replace-state-cursor '("red" bar)) +(setq evil-operator-state-cursor '("red" hollow)) + +(setq evilnc-hotkey-comment-operator "gc") + +(require-package 'evil) +(require-package 'evil-leader) +(require-package 'evil-numbers) +(require-package 'evil-visualstar) +(require-package 'evil-nerd-commenter) +(require-package 'evil-indent-textobject) +(require-package 'evil-matchit) +(require-package 'evil-exchange) + +(require 'evil) +(require 'evil-nerd-commenter) +(require 'evil-indent-textobject) +(require 'evil-visualstar) + +(evil-mode 1) + +(require 'evil-leader) +(setq evil-leader/in-all-states t) +(evil-leader/set-leader ",") +(evil-leader/set-key "f" 'find-file-in-project) + +(define-key evil-normal-state-map "\C-d" 'evil-delete-char) +(define-key evil-insert-state-map "\C-d" 'evil-delete-char) +(define-key evil-visual-state-map "\C-d" 'evil-delete-char) + +; esc should always always quit what i'm doing +(defun minibuffer-keyboard-quit () + "Abort recursive edit. +In Delete Selection mode, if the mark is active, just deactivate it; +then it takes a second \\[keyboard-quit] to abort the minibuffer." + (interactive) + (if (and delete-selection-mode transient-mark-mode mark-active) + (setq deactivate-mark t) + (when (get-buffer "*Completions*") (delete-windows-on "*Completions*")) + (abort-recursive-edit))) + +(define-key evil-normal-state-map [escape] 'keyboard-quit) +(define-key evil-visual-state-map [escape] 'keyboard-quit) +(define-key minibuffer-local-map [escape] 'minibuffer-keyboard-quit) +(define-key minibuffer-local-ns-map [escape] 'minibuffer-keyboard-quit) +(define-key minibuffer-local-completion-map [escape] 'minibuffer-keyboard-quit) +(define-key minibuffer-local-must-match-map [escape] 'minibuffer-keyboard-quit) +(define-key minibuffer-local-isearch-map [escape] 'minibuffer-keyboard-quit) + + +(evil-set-initial-state 'dired 'emacs) +(evil-set-initial-state 'magit-log-edit 'emacs) +(evil-set-initial-state 'package-menu-mode 'normal) + +(provide 'init-evil) diff --git a/emacs-old/emacs.d-old/init/init-framebufs.el b/emacs-old/emacs.d-old/init/init-framebufs.el new file mode 100644 index 00000000..f0ca4a52 --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-framebufs.el @@ -0,0 +1,3 @@ +(require 'frame-bufs) + +(provide 'init-framebufs) diff --git a/emacs-old/emacs.d-old/init/init-highlight-indentation.el b/emacs-old/emacs.d-old/init/init-highlight-indentation.el new file mode 100644 index 00000000..b55903eb --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-highlight-indentation.el @@ -0,0 +1,8 @@ +(require 'highlight-indentation) + +(add-hook 'enh-ruby-mode-hook + (lambda () (highlight-indentation-current-column-mode))) +(add-hook 'coffee-mode-hook + (lambda () (highlight-indentation-current-column-mode))) + +(provide 'init-highlight-indentation) diff --git a/emacs-old/emacs.d-old/init/init-ido.el b/emacs-old/emacs.d-old/init/init-ido.el new file mode 100644 index 00000000..68be3add --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-ido.el @@ -0,0 +1,12 @@ +;; Use C-f during file selection to switch to regular find-file +(ido-mode t) ; use 'buffer rather than t to use only buffer switching +(ido-everywhere t) +(setq ido-enable-flex-matching t) +(setq ido-use-filename-at-point nil) +(setq ido-auto-merge-work-directories-length 0) +(setq ido-use-virtual-buffers t) + +;; Allow the same buffer to be open in different frames +(setq ido-default-buffer-method 'selected-window) + +(provide 'init-ido) diff --git a/emacs-old/emacs.d-old/init/init-keys.el b/emacs-old/emacs.d-old/init/init-keys.el new file mode 100644 index 00000000..ae8d8ed8 --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-keys.el @@ -0,0 +1,14 @@ +;; Font size +(define-key global-map (kbd "s-=") 'text-scale-increase) +(define-key global-map (kbd "s--") 'text-scale-decrease) + +;; Navigation +(define-key global-map (kbd "") 'move-beginning-of-line) +(define-key global-map (kbd "") 'end-of-line) + +(define-key global-map (kbd "s-w") 'kill-this-buffer) +(define-key global-map (kbd "s-F") 'ag-project) + +(evil-leader/set-key "/" 'evil-ex-nohighlight) + +(provide 'init-keys) diff --git a/emacs-old/emacs.d-old/init/init-magit.el b/emacs-old/emacs.d-old/init/init-magit.el new file mode 100644 index 00000000..8032ecfe --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-magit.el @@ -0,0 +1,3 @@ +(require-package 'magit) + +(provide 'init-magit) diff --git a/emacs-old/emacs.d-old/init/init-packages.el b/emacs-old/emacs.d-old/init/init-packages.el new file mode 100644 index 00000000..f21d483d --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-packages.el @@ -0,0 +1,16 @@ +(setq package-archives '(("melpa" . "http://melpa.milkbox.net/packages/") + ("org" . "http://orgmode.org/elpa/") + ("marmalade" . "http://marmalade-repo.org/packages/") + ("gnu" . "http://elpa.gnu.org/packages/"))) +(setq package-enable-at-startup nil) +(package-initialize) + +(defun require-package (package) + "Install given PACKAGE." + (unless (package-installed-p package) + (unless (assoc package package-archive-contents) + (package-refresh-contents)) + (package-install package))) + + +(provide 'init-packages) diff --git a/emacs-old/emacs.d-old/init/init-powerline.el b/emacs-old/emacs.d-old/init/init-powerline.el new file mode 100644 index 00000000..6345271e --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-powerline.el @@ -0,0 +1,3 @@ +(require 'powerline) + +(provide 'init-powerline) diff --git a/emacs-old/emacs.d-old/init/init-project-explorer.el b/emacs-old/emacs.d-old/init/init-project-explorer.el new file mode 100644 index 00000000..1b95daef --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-project-explorer.el @@ -0,0 +1,12 @@ +(require-package 'project-explorer) + +(evil-define-key 'normal project-explorer-mode-map + (kbd "") 'pe/return + "o" (lambda () + (interactive) + (if (string/ends-with (pe/user-get-filename) "/") + (pe/tab) + (pe/return))) + "r" 'revert-buffer) + +(provide 'init-project-explorer) diff --git a/emacs-old/emacs.d-old/init/init-projectile.el b/emacs-old/emacs.d-old/init/init-projectile.el new file mode 100644 index 00000000..a0534b68 --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-projectile.el @@ -0,0 +1,19 @@ +(require-package 'projectile) +(require-package 'grizzl) + +(require 'projectile) +(require 'grizzl) + +(projectile-global-mode t) +(setq projectile-enable-caching t) +(setq projectile-completion-system 'grizzl) + +(global-set-key (kbd "s-p") 'projectile-find-file) +(global-set-key (kbd "s-P") 'projectile-invalidate-cache) +(global-set-key (kbd "s-b") 'projectile-switch-to-buffer) + +(add-to-list 'projectile-globally-ignored-directories "elpa") +(add-to-list 'projectile-globally-ignored-directories ".cache") +(add-to-list 'projectile-globally-ignored-directories "node_modules") + +(provide 'init-projectile) diff --git a/emacs-old/emacs.d-old/init/init-ruby.el b/emacs-old/emacs.d-old/init/init-ruby.el new file mode 100644 index 00000000..d78e6e9a --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-ruby.el @@ -0,0 +1,31 @@ +(require-package 'enh-ruby-mode) + +(autoload 'enh-ruby-mode "enh-ruby-mode" "Major mode for Ruby files" t) +(add-to-list 'auto-mode-alist '("\\.rb$" . enh-ruby-mode)) +(add-to-list 'auto-mode-alist '("\\.rake$" . enh-ruby-mode)) +(add-to-list 'auto-mode-alist '("Rakefile$" . enh-ruby-mode)) +(add-to-list 'auto-mode-alist '("\\.gemspec$" . enh-ruby-mode)) +(add-to-list 'auto-mode-alist '("\\.ru$" . enh-ruby-mode)) +(add-to-list 'auto-mode-alist '("Gemfile$" . enh-ruby-mode)) + +(add-hook 'enh-ruby-mode-hook '(lambda () + (local-set-key (kbd "RET") 'newline-and-indent))) + +;(sp-pair "{" nil :actions :rem) +;(sp-local-pair '(ruby-mode enh-ruby-mode) "{" nil :actions :rem) +;(sp-local-pair '(ruby-mode enh-ruby-mode) "{" "}" +; :actions '(insert autoskip) +; :unless '(sp-ruby-in-string-or-word-p) +; :pre-handlers '(sp-ruby-pre-handler) +; :post-handlers '(sp-ruby-post-handler) +; ) +; +;(sp-local-pair '(ruby-mode enh-ruby-mode) "|" nil :actions :rem) +;(sp-local-pair '(ruby-mode enh-ruby-mode) "|" "|" +; :actions '(insert autoskip) +; :unless '(sp-ruby-in-string-or-word-p) +; :pre-handlers '(sp-ruby-pre-handler) +; :post-handlers '(sp-ruby-post-handler) +; :suffix "") + +(provide 'init-ruby) diff --git a/emacs-old/emacs.d-old/init/init-smartparens.el b/emacs-old/emacs.d-old/init/init-smartparens.el new file mode 100644 index 00000000..7f946927 --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-smartparens.el @@ -0,0 +1,11 @@ +(require 'smartparens-config) +(require 'smartparens-ruby) + +(smartparens-global-mode) +(show-smartparens-global-mode t) + +(sp-with-modes '(rhtml-mode) + (sp-local-pair "<" ">") + (sp-local-pair "<%" "%>")) + +(provide 'init-smartparens) diff --git a/emacs-old/emacs.d-old/init/init-theme.el b/emacs-old/emacs.d-old/init/init-theme.el new file mode 100644 index 00000000..3d965762 --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-theme.el @@ -0,0 +1,4 @@ +(require-package 'monokai-theme) +(load-theme 'monokai t) + +(provide 'init-theme) diff --git a/emacs-old/emacs.d-old/init/init-util.el b/emacs-old/emacs.d-old/init/init-util.el new file mode 100644 index 00000000..bc01f1ff --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-util.el @@ -0,0 +1,17 @@ +(if (fboundp 'with-eval-after-load) + (defmacro after (feature &rest body) + "After FEATURE is loaded, evaluate BODY." + (declare (indent defun)) + `(with-eval-after-load ,feature ,@body)) + (defmacro after (feature &rest body) + "After FEATURE is loaded, evaluate BODY." + (declare (indent defun)) + `(eval-after-load ,feature + '(progn ,@body)))) + +(defun string/ends-with (s ending) + "return non-nil if string S ends with ENDING." + (let ((elength (length ending))) + (string= (substring s (- 0 elength)) ending))) + +(provide 'init-util) diff --git a/emacs-old/emacs.d-old/init/init-visuals.el b/emacs-old/emacs.d-old/init/init-visuals.el new file mode 100644 index 00000000..8b135a0f --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-visuals.el @@ -0,0 +1,18 @@ +(require-package 'diminish) + +(diminish 'visual-line-mode) +(after 'autopair (diminish 'autopair-mode)) +(after 'undo-tree (diminish 'undo-tree-mode)) +(after 'auto-complete (diminish 'auto-complete-mode)) +(after 'projectile (diminish 'projectile-mode)) +(after 'yasnippet (diminish 'yas-minor-mode)) +(after 'guide-key (diminish 'guide-key-mode)) +(after 'eldoc (diminish 'eldoc-mode)) +(after 'smartparens (diminish 'smartparens-mode)) +(after 'company (diminish 'company-mode)) +(after 'elisp-slime-nav (diminish 'elisp-slime-nav-mode)) +(after 'git-gutter+ (diminish 'git-gutter+-mode)) +(after 'magit (diminish 'magit-auto-revert-mode)) + + +(provide 'init-visuals) diff --git a/emacs-old/emacs.d-old/init/init-whitespace.el b/emacs-old/emacs.d-old/init/init-whitespace.el new file mode 100644 index 00000000..1e14c469 --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-whitespace.el @@ -0,0 +1,6 @@ +(require-package 'ws-butler) + +(setq require-final-newline t) +(setq show-trailing-whitespace t) + +(provide 'init-whitespace) diff --git a/emacs-old/emacs.d-old/init/init-yasnippet.el b/emacs-old/emacs.d-old/init/init-yasnippet.el new file mode 100644 index 00000000..1ce9fc31 --- /dev/null +++ b/emacs-old/emacs.d-old/init/init-yasnippet.el @@ -0,0 +1,9 @@ +(require-package 'yasnippet) + +(require 'yasnippet) + +(add-to-list 'yas-snippet-dirs (concat user-emacs-directory "snippets")) +(yas-reload-all) + + +(provide 'init-yasnippet) diff --git a/emacs-old/emacs.d-old/recentf b/emacs-old/emacs.d-old/recentf new file mode 100644 index 00000000..e69de29b diff --git a/emacs-old/emacs.d-old/snippets/enh-ruby-mode/let.snippet b/emacs-old/emacs.d-old/snippets/enh-ruby-mode/let.snippet new file mode 100644 index 00000000..e5cd6152 --- /dev/null +++ b/emacs-old/emacs.d-old/snippets/enh-ruby-mode/let.snippet @@ -0,0 +1,6 @@ +# -*- mode: snippet; require-final-newline: nil -*- +# name: +# key: let +# binding: direct-keybinding +# -- +let(:${1:variable}) { $2 } \ No newline at end of file diff --git a/emacs-old/emacs.d-old/vendor/emacs-powerline/.gitignore b/emacs-old/emacs.d-old/vendor/emacs-powerline/.gitignore new file mode 100644 index 00000000..4d253789 --- /dev/null +++ b/emacs-old/emacs.d-old/vendor/emacs-powerline/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +*.elc diff --git a/emacs-old/emacs.d-old/vendor/emacs-powerline/AUTHORS b/emacs-old/emacs.d-old/vendor/emacs-powerline/AUTHORS new file mode 100644 index 00000000..1a733b9e --- /dev/null +++ b/emacs-old/emacs.d-old/vendor/emacs-powerline/AUTHORS @@ -0,0 +1,9 @@ +Jonathan Chu +Justine Tunney +Prakash Kailasa +Okasu +Hans Engel +Yesudeep Mangalapilly +Jason Filsinger +@katspaugh +Anatoly Smolyaninov diff --git a/emacs-old/emacs.d-old/vendor/emacs-powerline/README.rst b/emacs-old/emacs.d-old/vendor/emacs-powerline/README.rst new file mode 100644 index 00000000..d069edec --- /dev/null +++ b/emacs-old/emacs.d-old/vendor/emacs-powerline/README.rst @@ -0,0 +1,69 @@ +=============== +Emacs Powerline +=============== + +This is a fork of powerline.el from `http://www.emacswiki.org/emacs/powerline.el `_, which is ultimately inspired by `vim-powerline `_. + +Installation +------------ + +There are a number of ways to do this correctly. If you're comfortable with doing it your own way, please skip this section. If not, this is the recommended and tested way that I got it working. + +:: + + $ cd ~/.emacs.d/vendor + $ git clone git://github.com/jonathanchu/emacs-powerline.git + +In your Emacs config: + +:: + + (add-to-list 'load-path "~/.emacs.d/vendor/emacs-powerline") + (require 'powerline) + +*Note: Depending on what distribution of Emacs you're using, you might have to do:* + +:: + + (require 'cl) + +Customization +------------- + +You can choose between different arrow shapes: + +:: + + (setq powerline-arrow-shape 'arrow) ;; the default + (setq powerline-arrow-shape 'curve) ;; give your mode-line curves + (setq powerline-arrow-shape 'arrow14) ;; best for small fonts + +You can change the mode-line color using the standard method: + +:: + + (custom-set-faces + '(mode-line ((t (:foreground "#030303" :background "#bdbdbd" :box nil)))) + '(mode-line-inactive ((t (:foreground "#f9f9f9" :background "#666666" :box nil))))) + +Additionally, you can modify directly in `powerline.el`: + +:: + + (setq powerline-color1 "grey22") + (setq powerline-color2 "grey40") + +Change the :foreground, :background, powerline-color1, powerline-color2 to whatever you wish. + + +Screenshots +----------- + +.. image:: http://i.imgur.com/CECRc.png + +Comments/Feedback +----------------- + +Suggestions for any modifications, please feel free to fork and contribute! + +Please file bugs at `https://github.com/jonathanchu/emacs-powerline/issues `_. diff --git a/emacs-old/emacs.d-old/vendor/emacs-powerline/powerline.el b/emacs-old/emacs.d-old/vendor/emacs-powerline/powerline.el new file mode 100644 index 00000000..ab8aa263 --- /dev/null +++ b/emacs-old/emacs.d-old/vendor/emacs-powerline/powerline.el @@ -0,0 +1,485 @@ +;;; powerline.el --- fancy statusline + +;; Name: Emacs Powerline +;; Author: Unknown +;; Version: 1.0 +;; Keywords: statusline + +;;; Commentary: + +;; This package simply provides a minor mode for fancifying the status line. + +;; Modified by: Jonathan Chu + +;;; Code: + +(require 'cl) + +(defvar powerline-color1) +(defvar powerline-color2) + +(setq powerline-color1 "grey22") +(setq powerline-color2 "grey40") + +(set-face-attribute 'mode-line nil + :background "OliveDrab3" + :box nil) +(set-face-attribute 'mode-line-inactive nil + :box nil) + +(if (functionp 'scroll-bar-mode) + (scroll-bar-mode -1)) + +(defun get-arrow-dots + (leftp width height) + (mapconcat + (apply-partially 'format "\"%s\"") + (mapcar + (lambda (n) + (let* ((nx (if (< n (/ height 2)) n (- height n))) + (dots (make-string nx ?.)) + (spaces (make-string (- width nx) ? ))) + (if leftp (concat dots spaces) (concat spaces dots)))) + (number-sequence 1 height)) + ",\n")) + +(defun get-arrow-xpm + (direction width height &optional color1 color2) + "Create an XPM left arrow." + (let* ((leftp (eq 'left direction)) + (fg (if leftp color1 color2)) + (bg (if leftp color2 color1))) + (create-image + (format "/* XPM */ +static char * arrow_left[] = { +\"%d %d 2 1\", +\". c %s\", +\" c %s\", +%s};" + width height + (if fg fg "None") + (if bg bg "None") + (get-arrow-dots leftp width height)) + 'xpm t :ascent 'center))) + +(defun mode-line-height () + "The mode line height with its current font face." + (- (elt (window-pixel-edges) 3) + (elt (window-inside-pixel-edges) 3))) + +(defun proportional-arrow-xpm + (direction color1 color2) + (let* ((r 1.5) + (m-height (mode-line-height)) + (height (if (evenp m-height) m-height (+ 1 m-height))) + (width (floor (/ height r)))) + (get-arrow-xpm direction width height color1 color2))) + +(defun arrow-left-xpm + (color1 color2) + "Return an XPM left arrow string representing." + (proportional-arrow-xpm 'left color1 color2)) + +(defun arrow-right-xpm + (color1 color2) + "Return an XPM right arrow string representing." + (proportional-arrow-xpm 'right color1 color2)) + +(defun curve-right-xpm + (color1 color2) + "Return an XPM right curve string representing." + (create-image + (format "/* XPM */ +static char * curve_right[] = { +\"12 18 2 1\", +\". c %s\", +\" c %s\", +\" .\", +\" ...\", +\" ...\", +\" .....\", +\" .....\", +\" .....\", +\" ......\", +\" ......\", +\" ......\", +\" ......\", +\" ......\", +\" ......\", +\" .....\", +\" .....\", +\" .....\", +\" ...\", +\" ...\", +\" .\"};" + (if color2 color2 "None") + (if color1 color1 "None")) + 'xpm t :ascent 'center)) + +(defun curve-left-xpm + (color1 color2) + "Return an XPM left curve string representing." + (create-image + (format "/* XPM */ +static char * curve_left[] = { +\"12 18 2 1\", +\". c %s\", +\" c %s\", +\". \", +\"... \", +\"... \", +\"..... \", +\"..... \", +\"..... \", +\"...... \", +\"...... \", +\"...... \", +\"...... \", +\"...... \", +\"...... \", +\"..... \", +\"..... \", +\"..... \", +\"... \", +\"... \", +\". \"};" + (if color1 color1 "None") + (if color2 color2 "None")) + 'xpm t :ascent 'center)) + +(defun make-xpm + (name color1 color2 data) + "Return an XPM image for lol data" + (create-image + (concat + (format "/* XPM */ +static char * %s[] = { +\"%i %i 2 1\", +\". c %s\", +\" c %s\", +" + (downcase (replace-regexp-in-string " " "_" name)) + (length (car data)) + (length data) + (if color1 color1 "None") + (if color2 color2 "None")) + (let ((len (length data)) + (idx 0)) + (apply 'concat + (mapcar #'(lambda (dl) + (setq idx (+ idx 1)) + (concat + "\"" + (concat + (mapcar #'(lambda (d) + (if (eq d 0) + (string-to-char " ") + (string-to-char "."))) + dl)) + (if (eq idx len) + "\"};" + "\",\n"))) + data)))) + 'xpm t :ascent 'center)) + +(defun half-xpm + (color1 color2) + (make-xpm "half" color1 color2 + (make-list 18 + (append (make-list 6 0) + (make-list 6 1))))) + +(defun percent-xpm + (pmax pmin we ws width color1 color2) + (let* ((fs (if (eq pmin ws) + 0 + (round (* 17 (/ (float ws) (float pmax)))))) + (fe (if (eq pmax we) + 17 + (round (* 17 (/ (float we) (float pmax)))))) + (o nil) + (i 0)) + (while (< i 18) + (setq o (cons + (if (and (<= fs i) + (<= i fe)) + (append (list 0) (make-list width 1) (list 0)) + (append (list 0) (make-list width 0) (list 0))) + o)) + (setq i (+ i 1))) + (make-xpm "percent" color1 color2 (reverse o)))) + + +;; from memoize.el @ http://nullprogram.com/blog/2010/07/26/ +(defun memoize (func) + "Memoize the given function. If argument is a symbol then +install the memoized function over the original function." + (typecase func + (symbol (fset func (memoize-wrap (symbol-function func))) func) + (function (memoize-wrap func)))) + +(defun memoize-wrap (func) + "Return the memoized version of the given function." + (let ((table-sym (gensym)) + (val-sym (gensym)) + (args-sym (gensym))) + (set table-sym (make-hash-table :test 'equal)) + `(lambda (&rest ,args-sym) + ,(concat (documentation func) "\n(memoized function)") + (let ((,val-sym (gethash ,args-sym ,table-sym))) + (if ,val-sym + ,val-sym + (puthash ,args-sym (apply ,func ,args-sym) ,table-sym)))))) + +(memoize 'arrow-left-xpm) +(memoize 'arrow-right-xpm) +(memoize 'curve-left-xpm) +(memoize 'curve-right-xpm) +(memoize 'half-xpm) +(memoize 'percent-xpm) + +(defvar powerline-minor-modes nil) +(defvar powerline-arrow-shape 'arrow) +(defun powerline-make-face + (bg &optional fg) + (if bg + (let ((cface (intern (concat "powerline-" + bg + "-" + (if fg + (format "%s" fg) + "white"))))) + (make-face cface)2 + (if fg + (if (eq fg 0) + (set-face-attribute cface nil + :background bg + :box nil) + (set-face-attribute cface nil + :foreground fg + :background bg + :box nil)) + (set-face-attribute cface nil + :foreground "white" + :background bg + :box nil)) + cface) + nil)) + +(defun powerline-make-left + (string color1 &optional color2 localmap) + (let ((plface (powerline-make-face color1)) + (arrow (and color2 (not (string= color1 color2))))) + (concat + (if (or (not string) (string= string "")) + "" + (propertize " " 'face plface)) + (if string + (if localmap + (propertize string 'face plface 'mouse-face plface 'local-map localmap) + (propertize string 'face plface)) + "") + (if arrow + (propertize " " 'face plface) + "") + (if arrow + (propertize " " 'display + (cond ((eq powerline-arrow-shape 'arrow) + (arrow-left-xpm color1 color2)) + ((eq powerline-arrow-shape 'curve) + (curve-left-xpm color1 color2)) + ((eq powerline-arrow-shape 'half) + (half-xpm color2 color1)) + (t + (arrow-left-xpm color1 color2))) + 'local-map (make-mode-line-mouse-map + 'mouse-1 (lambda () (interactive) + (setq powerline-arrow-shape + (cond ((eq powerline-arrow-shape 'arrow) 'curve) + ((eq powerline-arrow-shape 'curve) 'half) + ((eq powerline-arrow-shape 'half) 'arrow) + (t 'arrow))) + (redraw-modeline)))) + "")))) + +(defun powerline-make-right + (string color2 &optional color1 localmap) + (let ((plface (powerline-make-face color2)) + (arrow (and color1 (not (string= color1 color2))))) + (concat + (if arrow + (propertize " " 'display + (cond ((eq powerline-arrow-shape 'arrow) + (arrow-right-xpm color1 color2)) + ((eq powerline-arrow-shape 'curve) + (curve-right-xpm color1 color2)) + ((eq powerline-arrow-shape 'half) + (half-xpm color2 color1)) + (t + (arrow-right-xpm color1 color2))) + 'local-map (make-mode-line-mouse-map + 'mouse-1 (lambda () (interactive) + (setq powerline-arrow-shape + (cond ((eq powerline-arrow-shape 'arrow) 'curve) + ((eq powerline-arrow-shape 'curve) 'half) + ((eq powerline-arrow-shape 'half) 'arrow) + (t 'arrow))) + (redraw-modeline)))) + "") + (if arrow + (propertize " " 'face plface) + "") + (if string + (if localmap + (propertize string 'face plface 'mouse-face plface 'local-map localmap) + (propertize string 'face plface)) + "") + (if (or (not string) (string= string "")) + "" + (propertize " " 'face plface))))) + +;; get-scroll-bar-mode is not available in emacs 23.2 +(if (not (functionp 'get-scroll-bar-mode)) + (defun get-scroll-bar-mode () scroll-bar-mode)) + +(defun powerline-make-fill + (color) + ;; justify right by filling with spaces to right fringe, 20 should be calculated + (let ((plface (powerline-make-face color))) + (if (eq 'right (get-scroll-bar-mode)) + (propertize " " 'display '((space :align-to (- right-fringe 21))) + 'face plface) + (propertize " " 'display '((space :align-to (- right-fringe 24))) + 'face plface)))) + +(defun powerline-make-text + (string color &optional fg localmap) + (let ((plface (powerline-make-face color))) + (if string + (if localmap + (propertize string 'face plface 'mouse-face plface 'local-map localmap) + (propertize string 'face plface)) + ""))) + +(defun powerline-make (side string color1 &optional color2 localmap) + (cond ((and (eq side 'right) color2) (powerline-make-right string color1 color2 localmap)) + ((and (eq side 'left) color2) (powerline-make-left string color1 color2 localmap)) + ((eq side 'left) (powerline-make-left string color1 color1 localmap)) + ((eq side 'right) (powerline-make-right string color1 color1 localmap)) + (t (powerline-make-text string color1 localmap)))) + +(defmacro defpowerline (name string) + `(defun ,(intern (concat "powerline-" (symbol-name name))) + (side color1 &optional color2) + (powerline-make side + ,string + color1 color2))) + +(defun powerline-mouse (click-group click-type string) + (cond ((eq click-group 'minor) + (cond ((eq click-type 'menu) + `(lambda (event) + (interactive "@e") + (minor-mode-menu-from-indicator ,string))) + ((eq click-type 'help) + `(lambda (event) + (interactive "@e") + (describe-minor-mode-from-indicator ,string))) + (t + `(lambda (event) + (interactive "@e") + nil)))) + (t + `(lambda (event) + (interactive "@e") + nil)))) + +(defpowerline arrow "") +(defpowerline buffer-id (propertize (car (propertized-buffer-identification "%12b")) + 'face (powerline-make-face color1))) +(defvar powerline-buffer-size-suffix t) +(defpowerline buffer-size (propertize + (if powerline-buffer-size-suffix + "%I" + "%i") + 'local-map (make-mode-line-mouse-map + 'mouse-1 (lambda () (interactive) + (setq powerline-buffer-size-suffix + (not powerline-buffer-size-suffix)) + (redraw-modeline))))) +(defpowerline lcl current-input-method-title) +(defpowerline rmw "%*") +(defpowerline major-mode (propertize (format-mode-line mode-name) + 'help-echo "Major mode\n\ mouse-1: Display major mode menu\n\ mouse-2: Show help for major mode\n\ mouse-3: Toggle minor modes" + 'local-map (let ((map (make-sparse-keymap))) + (define-key map [mode-line down-mouse-1] + `(menu-item ,(purecopy "Menu Bar") ignore + :filter (lambda (_) (mouse-menu-major-mode-map)))) + (define-key map [mode-line mouse-2] 'describe-mode) + (define-key map [mode-line down-mouse-3] mode-line-mode-menu) + map))) +(defpowerline minor-modes (let ((mms (split-string (format-mode-line minor-mode-alist)))) + (apply 'concat + (mapcar #'(lambda (mm) + (propertize (if (string= (car mms) + mm) + mm + (concat " " mm)) + 'help-echo "Minor mode\n mouse-1: Display minor mode menu\n mouse-2: Show help for minor mode\n mouse-3: Toggle minor modes" + 'local-map (let ((map (make-sparse-keymap))) + (define-key map [mode-line down-mouse-1] (powerline-mouse 'minor 'menu mm)) + (define-key map [mode-line mouse-2] (powerline-mouse 'minor 'help mm)) + (define-key map [mode-line down-mouse-3] (powerline-mouse 'minor 'menu mm)) + (define-key map [header-line down-mouse-3] (powerline-mouse 'minor 'menu mm)) + map))) + mms)))) +(defpowerline row "%4l") +(defpowerline column "%3c") +(defpowerline percent "%6p") +(defpowerline narrow (let (real-point-min real-point-max) + (save-excursion + (save-restriction + (widen) + (setq real-point-min (point-min) real-point-max (point-max)))) + (when (or (/= real-point-min (point-min)) + (/= real-point-max (point-max))) + (propertize "Narrow" + 'help-echo "mouse-1: Remove narrowing from the current buffer" + 'local-map (make-mode-line-mouse-map + 'mouse-1 'mode-line-widen))))) +(defpowerline status "%s") +(defpowerline emacsclient mode-line-client) +(defpowerline vc vc-mode) + +(defpowerline percent-xpm (propertize " " + 'display + (let (pmax + pmin + (ws (window-start)) + (we (window-end))) + (save-restriction + (widen) + (setq pmax (point-max)) + (setq pmin (point-min))) + (percent-xpm pmax pmin we ws 15 color1 color2)))) + +(setq-default mode-line-format + (list "%e" + '(:eval (concat + (powerline-lcl 'left nil ) + (powerline-rmw 'left nil ) + (powerline-buffer-id 'left nil powerline-color1 ) + (powerline-major-mode 'left powerline-color1 ) + (powerline-minor-modes 'left powerline-color1 ) + (powerline-narrow 'left powerline-color1 powerline-color2 ) + (powerline-vc 'center powerline-color2 ) + (powerline-make-fill powerline-color2 ) + (powerline-row 'right powerline-color1 powerline-color2 ) + (powerline-make-text ":" powerline-color1 ) + (powerline-column 'right powerline-color1 ) + (powerline-percent 'right nil powerline-color1 ) + (powerline-make-text " " nil ))))) + +(provide 'powerline) + +;;; powerline.el ends here diff --git a/emacs-old/emacs.d-old/vendor/frame-bufs.el b/emacs-old/emacs.d-old/vendor/frame-bufs.el new file mode 100644 index 00000000..10a89659 --- /dev/null +++ b/emacs-old/emacs.d-old/vendor/frame-bufs.el @@ -0,0 +1,906 @@ +;;; frame-bufs.el --- a minor mode for frame-relative buffer lists + +;; Copyright (c) 2011-2013 Alp Aker + +;; Author: Alp Aker +;; Version: 2.04 +;; Keywords: convenience, buffers + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation; either version 2 of the +;; License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. + +;; A copy of the GNU General Public License can be obtained from the +;; Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, +;; MA 02111-1307 USA + +;;; Commentary: + +;; Frame-bufs extends Emacs's buffer menu so that it understands a +;; distinction between those buffers that "belong" to a frame and those that +;; do not. The buffer menu can be toggled between listing all buffers and +;; listing only those buffers associated with the selected frame. Buffers can be +;; added to and removed from the list of buffers. The criteria governing +;; which buffers are automatically associated with a frame can be customized. + +;; The package interacts properly with `other-buffer' and respects changes in +;; buffer ordering made by `bury-buffer'. It does not alter the +;; `buffer-list' or `buried-buffer-list' frame parameters. It is not +;; compatible with non-nil values of `pop-up-frames'. + +;; Installation +;; ============ + +;; Put this file in your load path and put: +;; +;; (require 'frame-bufs) +;; +;; in your .emacs. To toggle frame-bufs mode on and off, use the command +;;`frame-bufs-mode'. To turn it on automatically when starting Emacs, put: +;; +;; (frame-bufs-mode t) +;; +;; in your .emacs. + +;; Usage +;; ===== + +;; When frame-bufs-mode is enabled, the buffer menu has two modes: In global +;; mode, it lists all buffers; in local mode it lists only those buffers that +;; are associated with the selected frame. One can toggle between the modes +;; by typing "F". + +;; In global mode, there is a new fourth column after the initial CRM +;; columns--the `F' column. Buffers associated with the selected frame are +;; indicated with an `o' in this column. In local mode, the fourth `F' +;; column is suppressed. (Global/local status is also indicated in the mode +;; line.) + +;; The typical way a buffer becomes associated with a frame is by being +;; selected in a window on the frame. One can manually associate buffers +;; with a frame, and disassociate them as well, using two other commands in +;; the buffer menu. By typing `A' a buffer can be marked as to be added to +;; the buffers associated with the selected frame. By typing `N' a buffer +;; can be marked as to have its association with the selected frame +;; severed. As with other actions in the buffer menu, these changes take +;; effect when `Buffer-menu-execute' is called. + +;; When first called, the buffer menu opens in global mode. In subsequent +;; calls it opens in whatever mode it was last in. + +;; Criteria That Control Buffer-Frame Association +;; ============================================== + +;; The association between buffers and frames is dynamic: If a buffer is +;; selected on a frame, then it becomes associated with that frame. Note, +;; then, that a buffer can be associated with more than one frame. + +;; In addition, several other variables control which buffers automatically +;; become associated with a frame: + +;; o If `frame-bufs-include-displayed-buffers' is non-nil, then buffers that +;; are merely displayed on a frame become associated with the frame, even +;; if they have not been selected. + +;; o If a buffer's name is a member of `frame-bufs-always-include-names' then +;; that buffer is automatically associated with every frame. The default +;; value is ("*scratch*" "*notes*"). + +;; o Three variables control which buffers are associated with a newly created +;; frame: +;; +;; - `frame-bufs-new-frames-inherit': If non-nil, then the buffers +;; associated with a new frame include (at least) the buffers that were +;; associated with the new frame's "parent," i.e., the frame that was +;; selected when the new frame was created. +;; - `frame-bufs-include-new-buffers': If non-nil, and the command that +;; creates a new frame also creates new buffers, the new buffers are +;; associated with the new frame. (This applies only to buffers that +;; are created *after* the new frame is created.) +;; - `frame-bufs-include-init-buffer': If non-nil, then the buffer that is +;; current when a new frame is created will be associated with the new +;; frame. If nil, it will not. (Note that +;; frame-bufs-new-frames-inherit takes precedence over this +;; variable. Also note: If the buffer in question is displayed on the +;; new frame when the frame-creating command terminates, it will still +;; be associated with the new frame.) + +;; Other Commands and Features +;; =========================== + +;; o If `frame-bufs-use-buffer-predicate' is non-nil, each frame's buffer +;; predicate is set so that `other-buffer' will prefer buffers associated +;; with the selected frame. Thus, when a buffer is removed from a window +;; and automatically replaced with another (as happens, say, when one kills +;; a buffer), the newly displayed buffer will, if possible, be another +;; frame-associated buffer. The default value is t. + +;; Frame-bufs provides three other commands that are available everywhere, +;; not just in the buffer menu: + +;; o `frame-bufs-dismiss-buffer' is somewhat analogous to `bury-buffer'. It +;; severs the association of a buffer with a frame, and if that buffer is +;; displayed in any windows on the selected frame, it is replaced by +;; another buffer. When called with no arguments, it acts on the current +;; buffer. + +;; o `frame-bufs-reset-frame' resets a frame's associated-buffer list; +;; specifically, it sets the list of associated buffers to the list of +;; buffers that have been selected on the frame. When called with no +;; argument, it acts on the current frame. + +;; o `frame-bufs-reset-all-frames' resets the associated buffers of all +;; frames. + +;; None of these commands is given a defualt key binding. + +;; Other Customization Options +;; =========================== + +;; o To rebind the new buffer menu commands, alter their bindings in the +;; keymap `frame-bufs-mode-map'. + +;; o The indicator bit used for frame-associated buffers (default `o') can be +;; set via the variable `frame-bufs-associated-buffer-bit'. + +;; o The strings used to indicate local/global state in the buffer menu's +;; mode line can be changed by means of the variables +;; `frame-bufs-mode-line-local-string' and +;; `frame-bufs-mode-line-global-string'. The mode-line indication can be +;; turned off by setting `frame-bufs-mode-line-indication' to nil. (This +;; latter variable can be set to any valid mode-line construct; users +;; setting this variable to a custom mode-line construct will probably want +;; to make use of the variable `frame-bufs--global-list'.) + +;; Using Frame-Bufs in Programs +;; ============================ + +;; o To use a frame's associated-buffer list from within a Lisp progam, it is +;; recommended that you work with the list returned by the function +;; `frame-bufs-buffer-list'; don't use the value of the +;; frame-bufs-buffer-list frame parameter. The latter can contain internal +;; buffers (buffers whose names starts with a space) and dead buffers; it +;; is not guaranteed to respect `frame-bufs-always-include-names'; and its +;; order is meaningless. The list returned by `frame-bufs-buffer-list' +;; will contain only live, non-internal buffers; be updated to reflect the +;; current value of frame-bufs-always-include-names; and be sorted +;; stably by selection order on the current frame. + +;; Acknowledgements +;; ============================ + +;; Thanks to Greg Bognar for alpha testing and to Drew Adams for suggesting +;; many improvements. + +;;; Code: + +(when (< emacs-major-version 24) + (error "Frame-Bufs requires version 22 or later")) + +;;; --------------------------------------------------------------------- +;;; User Options +;;; --------------------------------------------------------------------- + +(defgroup frame-bufs nil + "Extend buffer-menu to allow listing of buffers associated with particular frame." + :group 'convenience) + +(defcustom frame-bufs-mode-hook nil + "Hook run when frame-bufs mode is enabled or disabled." + :group 'frame-bufs + :type 'hook) + +(defcustom frame-bufs-mode-on-hook nil + "Hook run when frame-bufs mode is enabled." + :group 'frame-bufs + :type 'hook) + +(defcustom frame-bufs-mode-off-hook nil + "Hook run when frame-bufs mode is disabled." + :group 'frame-bufs + :type 'hook) + +(defcustom frame-bufs-use-buffer-predicate t + "Make `other-buffer' prefer associated buffers. +If non-nil, frame-bufs sets the buffer predicate of each frame +so that `other-buffer' will prefer buffers associated with that +frame. If nil, `other-buffer' does not prefer frame-associated +buffers. + +Changes to this variable do not take effect until the +mode-function `frame-bufs-mode' is run." + :group 'frame-bufs + :type 'boolean) + +(defcustom frame-bufs-always-include-names '("*scratch*" "*notes*") + "Buffers whose names are in this list are associated with every frame." + :group 'frame-bufs + :type '(repeat string)) + +(defcustom frame-bufs-include-displayed-buffers nil + "If non-nil, buffers displayed on a frame becomes associated with it. +If nil, buffers becomes associated with a frame only if they are +selected on that frame, not merely displayed." + :group 'frame-bufs + :type 'boolean) + +(defcustom frame-bufs-include-new-buffers nil + "Include new buffers in a new frame's associated-buffer list. +If non-nil, and the command that creates a new frame also creates +new buffers, those buffers will be associated with the new frame, +even if they have not been selected. (Buffers created before the +new frame is created are not thus captured.)" + :group 'frame-bufs + :type 'boolean) + +(defcustom frame-bufs-new-frames-inherit nil + "Whether a new frame inherits the associations of its \"parent\". +If non-nil, the associated buffers of a newly created frame +include (at least) those buffers that were associated with the +frame that was selected when the frame-creating command was +called." + :group 'frame-bufs + :type 'boolean) + +(defcustom frame-bufs-include-init-buffer nil + "Whether a new frame's associated buffers include the last buffer before creation. +If non-nil, then the buffer that is current when a frame-creating +command is called--the \"init buffer\"--is associated with the +new frame. If nil, it is not. + +Note: If the init buffer is displayed on the new frame after the +frame-creating command terminates, then it will be associated +with the new frame, even if this variable is nil. Also note: +`frame-bufs-new-frames-inherit' takes precedence over this +variable." + :group 'frame-bufs + :type 'boolean) + +; Experimental. +(defcustom frame-bufs-assoc-rules nil + "" + :group 'frame-bufs + :type 'list) + +(defcustom frame-bufs-mode-line-local-string "[Local]" + "Mode-line indication that the buffer menu is in local mode." + :group 'frame-bufs + :type 'string) + +(defcustom frame-bufs-mode-line-global-string "[Global]" + "Mode-line indication that the buffer menu is in global mode." + :group 'frame-bufs + :type 'string) + +(defcustom frame-bufs-mode-line-identification + '(frame-bufs--global-list + (:eval (propertize frame-bufs-mode-line-global-string + 'local-map frame-bufs-mode-line-keymap + 'help-echo (concat "List of all buffers\n" + "mouse-1 for local list"))) + (:eval (propertize frame-bufs-mode-line-local-string + 'local-map frame-bufs-mode-line-keymap + 'help-echo (concat "Buffer list for frame \"" + (frame-parameter nil 'name) + "\"\n" + "mouse-1 for global list")))) + "Mode-line indication of the buffer menu's state. +When frame-bufs is enabled, this variable is inserted into the +value of `mode-line-format' in the buffer menu, after +`mode-line-buffer-identification'. If this variable is set to +nil, no special information appears in the mode-line. The value +should be a valid mode-line construct. + +When customizing this variable, users will probably want to make +use of the variable `frame-bufs--global-list'." + :group 'frame-bufs + :type 'sexp) + +(defcustom frame-bufs-associated-buffer-bit ?o + "Character used to indicate frame-associated buffers in the buffer menu." + :group 'frame-bufs + :type 'character) + +;;; --------------------------------------------------------------------- +;;; Internal Variables +;;; --------------------------------------------------------------------- + +(defvar frame-bufs--global-list t + "Records whether the buffer menu is in global or local mode.") + +;; The following are used in initializing the associated-buffer list of a +;; newly created frame. + +;; Records which buffer is current when a new frame is created. Used when +;; `frame-bufs-include-new-buffers' is non-nil. +(defvar frame-bufs--init-buffer nil) + +;; Records the associated buffers of the selected frame before a new frame is +;; created. Used when `per-frame-new-frames-inherit' is non-nil. +(defvar frame-bufs--parent-buffer-list nil) + +;; Records which buffers are already in existence when a new frame is +;; created. Used when `frame-bufs-include-new-buffers' is non-nil. +(defvar frame-bufs--prev-buffers nil) + +;; When a new frame is created, records the identity of that frame. Used by +;; `frame-bufs--initialize-new-frame' in conjunction with the previous +;; variables to initialized the associated-buffer list. +(defvar frame-bufs--new-frame nil) + +(defconst frame-bufs--size-column 4) + +(defconst frame-bufs--advised-fns + '(electric-buffer-list select-window)) + +(defconst frame-bufs--hook-assignments + '((Buffer-menu-mode-hook . frame-bufs--set-up-buff-menu) + (window-configuration-change-hook . frame-bufs--window-change) + (before-make-frame-hook . frame-bufs--before-make-frame) + (after-make-frame-functions . frame-bufs--after-make-frame))) + +;;; --------------------------------------------------------------------- +;;; Mode Definition and Keymaps +;;; --------------------------------------------------------------------- + +(defvar frame-bufs-mode-map + (let ((map (make-sparse-keymap))) + (define-key map "F" 'frame-bufs-toggle-global-list) + (define-key map "A" 'frame-bufs-make-associated) + (define-key map "N" 'frame-bufs-make-non-associated) + map) + "Keymap for `frame-bufs-mode'. +See the documentation of that command for details.") + +(set-keymap-parent frame-bufs-mode-map Buffer-menu-mode-map) + +(defvar frame-bufs-mode-line-keymap + (let ((map (make-sparse-keymap))) + (define-key map [mode-line mouse-1] 'frame-bufs-mode-line-toggle-global-list) + map) +"Keymap for `frame-bufs-mode-line-identification'.") + +(defvar frame-bufs-mode nil + "Non-nil if frame-bufs mode is enabled. + +Do not set this variable directly. Use the command +`frame-bufs-mode' instead.") + +(add-to-list 'minor-mode-list 'frame-bufs-mode) + +(defun frame-bufs-mode (&optional arg) + "Toggle frame-bufs-mode. + +Frame-bufs-mode tracks which buffers are associated with a given +frame and extends the buffer menu to take advantage of this +information. The buffer menu can be toggled between listing all +buffers and listing only frame-associated buffers. + +When listing all buffers, there is a fourth column in the buffer +menu after the CRM columns: the F column. Buffers associated +with the current frame are indicated with an `o' in this column +. When listing only frame-associated buffers, this fourth column +is suppressed. + +The list of buffers associated with a frame can be manually +edited from within the buffer menu. + +The following new commands are available in the buffer +menu: + +\\\\[frame-bufs-toggle-global-list] -- Toggle between listing frame-associated buffers and all buffers. +\\[frame-bufs-make-associated] -- Mark a buffer to be added to the associated buffer list. +\\[frame-bufs-make-non-associated] -- Mark a buffer to be removed from the associated buffer list. + +Requested changes in frame-buffer associations are effected by +calling `Buffer-menu-execute'. + +Buffers automatically become associated with a frame if they are +selected in one of the frame's windows. Further control over +which buffers are automatically associated with a frame is +provided by the variables `frame-bufs-include-displayed-buffers', +`frame-bufs-always-include-names', +`frame-bufs-include-new-buffers', +`frame-bufs-new-frames-inherit', and +`frame-bufs-include-init-buffer'. + +For further customization options, see the documentation of the +variables `frame-bufs-associated-buffer-bit', `frame-bufs-use-buffer-predicate', +`frame-bufs-mode-line-local-string', +`frame-bufs-mode-line-global-string', and +`frame-bufs-mode-line-identification'." + (interactive "P") + (setq frame-bufs-mode (if (not arg) + (not frame-bufs-mode) + (> (prefix-numeric-value arg) 0))) + (if frame-bufs-mode + ;; Enabling. + (progn + (dolist (frame (frame-list)) + (frame-bufs--set-buffer-predicate frame frame-bufs-use-buffer-predicate) + (frame-bufs--initialize-existing-frame frame)) + (setq Buffer-menu-buffer-column 5 + frame-bufs--size-column 5) + (ad-enable-regexp "frame-bufs") + (dolist (fn frame-bufs--advised-fns) + (ad-activate fn)) + (dolist (hook frame-bufs--hook-assignments) + (add-hook (car hook) (cdr hook))) + ;; In case we toggle the mode while the buffer menu exists. + (let ((buf (get-buffer "*Buffer List*"))) + (when buf + (with-current-buffer buf + (revert-buffer) + (frame-bufs--set-up-buff-menu)))) + (run-hooks 'frame-bufs-mode-on-hook) + (message "Per-frame buffer menus are enabled")) + ;; Disabling. + (dolist (frame (frame-list)) + (frame-bufs--set-buffer-predicate frame nil)) + (setq Buffer-menu-buffer-column 4 + frame-bufs--size-column 4) + (ad-disable-regexp "frame-bufs") + (dolist (fn frame-bufs--advised-fns) + (ad-activate fn)) + (dolist (hook frame-bufs--hook-assignments) + (remove-hook (car hook) (cdr hook))) + ;; Again, in case we toggle the mode while the buffer menu exists. + (let ((buf (get-buffer "*Buffer List*"))) + (when buf + (with-current-buffer buf + (revert-buffer) + (frame-bufs--unload-from-buff-menu)))) + (run-hooks 'frame-bufs-mode-off-hook) + (message "Per-frame buffer menus are disabled")) + (run-mode-hooks 'frame-bufs-mode-hook)) + +;;; --------------------------------------------------------------------- +;;; Frame Initialization and Clean Up +;;; --------------------------------------------------------------------- + +;; Set the associated-buffer list for frames already in existence when frame-bufs +;; is enabled. +(defun frame-bufs--initialize-existing-frame (frame) + (frame-bufs--add-buffers (append (frame-parameter frame 'buffer-list) + (frame-parameter frame 'buried-buffer-list) + (if frame-bufs-include-displayed-buffers + (mapcar #'(lambda (x) (window-buffer x)) + (window-list frame 'no-minibuf)))) + frame)) + +;; The next four functions handle initialization of the associated-buffer +;; list for newly created frames. We defer some of the initialization until +;; after the command creating a new frame terminates, for the sake of the +;; option `frame-bufs-include-new-buffers'. +(defun frame-bufs--before-make-frame () + (setq frame-bufs--init-buffer (current-buffer) + frame-bufs--prev-buffers (buffer-list) + frame-bufs--parent-buffer-list (copy-sequence + (frame-parameter (selected-frame) + 'frame-bufs-buffer-list)))) + +(defun frame-bufs--after-make-frame (frame) + (frame-bufs--set-buffer-predicate frame frame-bufs-use-buffer-predicate) + (add-hook 'post-command-hook 'frame-bufs--initialize-new-frame) + (setq frame-bufs--new-frame frame)) + +(defun frame-bufs--initialize-new-frame () + (remove-hook 'post-command-hook 'frame-bufs-initialize-new-frame) + (unwind-protect + (when (frame-live-p frame-bufs--new-frame) + (when frame-bufs-include-new-buffers + (frame-bufs--add-buffers (frame-bufs--set-diff (buffer-list) + frame-bufs--prev-buffers) + frame-bufs--new-frame)) + (unless (or frame-bufs-include-init-buffer + (memq frame-bufs--init-buffer + (mapcar #'(lambda (x) (window-buffer x)) + (window-list frame-bufs--new-frame 'no-minibuf)))) + (frame-bufs--remove-buffer frame-bufs--init-buffer frame-bufs--new-frame)) + (when frame-bufs-new-frames-inherit + (frame-bufs--add-buffers frame-bufs--parent-buffer-list frame-bufs--new-frame)) + ;; Enforce custom buffer-frame associations. + (frame-bufs--enforce-rules frame-bufs--new-frame)) + (setq frame-bufs--new-frame nil + frame-bufs--parent-buffer-list nil + frame-bufs--init-buffer nil + frame-bufs--prev-buffers nil))) + +(defun frame-bufs--set-buffer-predicate (frame on) + (let ((buffer-pred (frame-parameter frame 'buffer-predicate))) + (if on + (unless (eq buffer-pred 'frame-bufs--ok-to-display-p) + (set-frame-parameter frame + 'frame-bufs-saved-buffer-pred + buffer-pred) + (set-frame-parameter frame + 'buffer-predicate + 'frame-bufs--ok-to-display-p)) + (when (eq buffer-pred 'frame-bufs--ok-to-display-p) + (set-frame-parameter frame + 'buffer-predicate + (frame-parameter frame 'frame-bufs-saved-buffer-predicate)) + (set-frame-parameter frame + 'frame-bufs-saved-buffer-predicate + nil))))) + +;;; --------------------------------------------------------------------- +;;; Per-Frame Buffer List Maintenance and Manipulation +;;; --------------------------------------------------------------------- + +(defadvice select-window (after frame-bufs) + (frame-bufs--add-buffer (window-buffer) (window-frame))) + +;; Called by window-configuration-change-hook to update the associated-buffer +;; list. +(defun frame-bufs--window-change () + (let ((frame (selected-frame))) + (dolist (win (window-list frame 'no-minibuf)) + (let ((buf (window-buffer win))) + ;; If merely displayed buffers are ok add buf. If not, add buf if + ;; it's been selected on the frame. + (when (or frame-bufs-include-displayed-buffers + (memq buf (frame-parameter frame 'buffer-list)) + (memq buf (frame-parameter frame 'buried-buffer-list))) + (frame-bufs--add-buffer buf frame)))))) + +(defun frame-bufs--remove-buffer (buf frame) + "Remove BUF from FRAME's associated-buffer list." + (set-frame-parameter frame + 'frame-bufs-buffer-list + (delq buf (frame-parameter frame 'frame-bufs-buffer-list)))) + +(defun frame-bufs--add-buffer (buf frame) + "Add BUF to FRAME's associated-buffer list if not already present." + (unless (bufferp buf) + (signal 'wrong-type-argument (list 'bufferp buf))) + (let ((associated-bufs (frame-parameter frame 'frame-bufs-buffer-list))) + (unless (memq buf associated-bufs) + (set-frame-parameter frame 'frame-bufs-buffer-list (cons buf associated-bufs))))) + +(defun frame-bufs--add-buffers (bufs frame) + "Add each member of BUFS to FRAME's associated-buffer list if +not already present." + (dolist (buf bufs) + (frame-bufs--add-buffer buf frame))) + +(defun frame-bufs-buffer-list (frame &optional global) + "When called with argument GLOBAL non-nil, return the same +result as (buffer-list FRAME). With GLOBAL nil, update FRAME's +associated-buffer list and return it, sorted by selection order +on FRAME. The return value is a copy of the list, not the list +itself." + (frame-bufs--filter-buffers + (if global + (buffer-list frame) + ;; First, remove dead buffers. (Should be able to do this as the + ;; buffers are killed, via kill-buffer-hook, but there are a few corner + ;; cases that let dead buffers slip through that way.) + (set-frame-parameter frame + 'frame-bufs-buffer-list + (delq nil + (mapcar #'(lambda (x) (if (buffer-live-p x) x)) + (frame-parameter frame + 'frame-bufs-buffer-list)))) + ;; Include members of frame-bufs-always-include-names + (dolist (bufname frame-bufs-always-include-names) + (when (get-buffer bufname) + (frame-bufs--add-buffer (get-buffer bufname) frame))) + ;; Enforce custom buffer-frame associations. + (frame-bufs--enforce-rules frame) + (frame-bufs--sort-buffers frame (frame-parameter frame 'frame-bufs-buffer-list))))) + +(defun frame-bufs--enforce-rules (&optional frame) + (or frame (setq frame (selected-frame))) + (dolist (rule frame-bufs-assoc-rules) + (dolist (buffer (buffer-list)) + (if (funcall rule frame buffer) + (frame-bufs--add-buffer buffer frame) + (frame-bufs--remove-buffer buffer frame))))) + +;;; --------------------------------------------------------------------- +;;; Utilities and Predicates +;;; --------------------------------------------------------------------- + +;; Return a list in which BUFS are sorted according to selection order on +;; FRAME. +(defun frame-bufs--sort-buffers (frame bufs) + (let ((l (buffer-list frame))) + (sort (copy-sequence bufs) #'(lambda (x y) (> (length (memq x l)) + (length (memq y l))))))) + +;; Remove internal buffers from BUFS. +(defun frame-bufs--filter-buffers (bufs) + (delq nil + (mapcar #'(lambda (x) (if (not (string-match "^ " (buffer-name x))) x)) + bufs))) + +(defun frame-bufs--set-diff (minuend subtrahend) + (let ((res '())) + (dolist (e minuend) + (unless (memq e subtrahend) + (push e res))) + (nreverse res))) + +(defun frame-bufs--ok-to-display-p (buf) + (let ((other-pred (frame-parameter nil 'frame-bufs-saved-buffer-pred))) + (and (frame-bufs--associated-p buf) + (or (not (functionp other-pred) + (funcall other-pred buf)))))) + +(defun frame-bufs--associated-p (buf &optional frame) + (memq buf (frame-parameter frame 'frame-bufs-buffer-list))) + +;; Return bit info for BUF appropriate for the 4th column in the buffer-menu. +(defun frame-bufs--bit-info (buf) + (if (and frame-bufs--global-list + (frame-bufs--associated-p buf)) + (char-to-string frame-bufs-associated-buffer-bit) + " ")) + +(defun frame-bufs--set-up-buff-menu () + (use-local-map frame-bufs-mode-map) + (let ((before (reverse (memq 'mode-line-buffer-identification + (reverse mode-line-format)))) + (after (cdr (memq 'mode-line-buffer-identification mode-line-format)))) + (setq mode-line-format (append before + (list frame-bufs-mode-line-identification) + after)))) + +(defun frame-bufs--unload-from-buff-menu () + (use-local-map Buffer-menu-mode-map) + (setq mode-line-format (delq frame-bufs-mode-line-identification mode-line-format))) + +;;; --------------------------------------------------------------------- +;;; Global Commands +;;; --------------------------------------------------------------------- + +(defun frame-bufs-dismiss-buffer (&optional buf frame) + "Remove assocation between BUF and FRAME. +If any windows on FRAME are currently displaying BUF, replace BUF +in those windows with some other buffer. Arguments default to +the current buffer and the selected frame." + (interactive) + (unless buf + (setq buf (current-buffer))) + (unless frame + (setq frame (selected-frame))) + ;; We loop over the windows ourselves because replace-buffer-in-windows + ;; acts on all frames; we only want to act on the selected frame. + (dolist (win (get-buffer-window-list buf 'no-minibuf frame)) + (set-window-buffer win (other-buffer buf))) + (frame-bufs--remove-buffer buf frame)) + +(defun frame-bufs-reset-frame (&optional frame) + "Reset FRAME's associated-buffer list. +Set list of buffers associated with FRAME to the list of all +buffers that have been selected on FRAME, and no others. FRAME +defualts to the selected frame." + (interactive) + (unless frame + (setq frame (selected-frame))) + (set-frame-parameter frame + 'frame-bufs-buffer-list + ;; Make sure we get copies, not the lists themselves. + (append + (frame-parameter frame 'buffer-list) + (frame-parameter frame 'buried-buffer-list) + '()))) + +(defun frame-bufs-reset-all-frames () + "Call `frame-bufs-reset-frame' on all live frames." + (interactive) + (dolist (frame (frame-list)) + (frame-bufs-reset-frame frame))) + +;;; --------------------------------------------------------------------- +;;; Buffer Menu Commands (New) +;;; --------------------------------------------------------------------- + +(defun frame-bufs-toggle-global-list (&optional arg) + "Toggle whether the buffer-menu displays only buffers associated with this frame. +With a positive or true ARG display only frame-associated buffers. With +zero, negative, or nil ARG, display all buffers." + (interactive "P") + (setq frame-bufs--global-list + (cond ((not arg) (not frame-bufs--global-list)) + ((<= (prefix-numeric-value arg) 0) t))) + (revert-buffer)) + +(defun frame-bufs-mode-line-toggle-global-list (e) + "Toggle whether the buffer-menu displays only buffers associated with this frame." + (interactive "e") + (save-selected-window + (select-window (posn-window (event-start e))) + (frame-bufs-toggle-global-list))) + +(defun frame-bufs-make-associated (&optional arg) + "Mark buffer on this line to be associated with this frame by \\\\[Buffer-menu-execute]. +Prefix arg is how many buffers to associate. Negative arg means +work backwards." + (interactive "p") + (when (or (null arg) (= arg 0)) + (setq arg 1)) + (if (not frame-bufs--global-list) + (forward-line arg) + (while (< 0 arg) + (when (Buffer-menu-buffer) + (tabulated-list-set-col 3 "A" t) + (forward-line 1) + (setq arg (1- arg)))) + (while (< arg 0) + (when (Buffer-menu-buffer) + (tabulated-list-set-col 3 "A" t) + (forward-line -1) + (setq arg (1+ arg)))))) + +(defun frame-bufs-make-non-associated (&optional arg) + "Mark buffer on this line to be associated with this frame by \\\\[Buffer-menu-execute]. +Prefix arg is how many buffers to associate. Negative arg means +work backwards." + (interactive "p") + (when (or (null arg) (= arg 0)) + (setq arg 1)) + (while (< 0 arg) + (when (Buffer-menu-buffer) + (tabulated-list-set-col 3 "N" t) + (forward-line 1) + (setq arg (1- arg)))) + (while (< arg 0) + (when (Buffer-menu-buffer) + (tabulated-list-set-col 3 "N" t) + (forward-line -1) + (setq arg (1+ arg))))) + +;;; --------------------------------------------------------------------- +;;; Buffer Menu Commands (Redefined) +;;; --------------------------------------------------------------------- + +(defun Buffer-menu-unmark (&optional backup) + "Cancel all requested operations on buffer on this line and move down. +Optional ARG means move up." + (interactive "P") + (Buffer-menu--unmark) + (forward-line (if backup -1 1))) + +(defun Buffer-menu--unmark () + (tabulated-list-set-col 0 " " t) + (let ((buf (Buffer-menu-buffer))) + (when buf + (if (buffer-modified-p buf) + (tabulated-list-set-col 2 "*" t) + (tabulated-list-set-col 2 " " t)) + (when frame-bufs-mode + (tabulated-list-set-col 3 (frame-bufs--bit-info buf) t))))) + +(defun Buffer-menu-backup-unmark () + "Move up and cancel all requested operations on buffer on line above." + (interactive) + (forward-line -1) + (Buffer-menu--unmark)) + +(defun Buffer-menu-execute () + "Save and/or delete buffers marked with \\\\[Buffer-menu-save] or \\\\[Buffer-menu-delete]." + (interactive) + (save-excursion + (Buffer-menu-beginning) + (while (not (eobp)) + (let ((buffer (tabulated-list-get-id)) + (entry (tabulated-list-get-entry))) + (cond ((null entry) + (forward-line 1)) + ((not (buffer-live-p buffer)) + (tabulated-list-delete-entry)) + (t + (let ((delete (eq (char-after) ?D))) + (when (and frame-bufs-mode (not delete)) + (cond + ((equal (aref entry 3) "A") + (frame-bufs--add-buffer buffer (selected-frame)) + (tabulated-list-set-col 3 (char-to-string frame-bufs-associated-buffer-bit) t)) + ((equal (aref entry 3) "N") + (frame-bufs-dismiss-buffer buffer (selected-frame)) + (if frame-bufs--global-list + (tabulated-list-set-col 3 " " t) + (tabulated-list-delete-entry))))) + (when (equal (aref entry 2) "S") + (condition-case nil + (progn + (with-current-buffer buffer + (save-buffer)) + (tabulated-list-set-col 2 " " t)) + (error (warn "Error saving %s" buffer)))) + (if delete + (unless (eq buffer (current-buffer)) + (kill-buffer buffer) + (tabulated-list-delete-entry)) + (forward-line 1))))))))) + +;;; --------------------------------------------------------------------- +;;; Buffer Menu Functions +;;; --------------------------------------------------------------------- + +(defun list-buffers--refresh (&optional buffer-list old-buffer) + ;; Set up `tabulated-list-format'. + (let ((name-width Buffer-menu-name-width) + (size-width Buffer-menu-size-width)) + ;; Handle obsolete variable: + (if Buffer-menu-buffer+size-width + (setq name-width (- Buffer-menu-buffer+size-width size-width))) + (setq tabulated-list-format + (let* ((bits `(("C" 1 t :pad-right 0) + ("R" 1 t :pad-right 0) + ,(if frame-bufs-mode + '("M" 1 t :pad-right 0) + '("M" 1 t)))) + (fb-bit (if frame-bufs-mode + (if frame-bufs--global-list + '(("F" 1 t)) + '(("-" 1 t))))) + (tail `(("Buffer" ,name-width t) + ("Size" ,size-width tabulated-list-entry-size-> + :right-align t) + ("Mode" ,Buffer-menu-mode-width t) + ("File" 1 t)))) + (apply 'vector (append bits fb-bit tail))))) + (setq tabulated-list-use-header-line Buffer-menu-use-header-line) + ;; Collect info for each buffer we're interested in. + (let ((buffer-menu-buffer (current-buffer)) + (show-non-file (not Buffer-menu-files-only)) + entries) + (dolist (buffer (or buffer-list + (and frame-bufs-mode + (frame-bufs-buffer-list (selected-frame) frame-bufs--global-list)) + (buffer-list (if Buffer-menu-use-frame-buffer-list + (selected-frame))))) + (with-current-buffer buffer + (let* ((name (buffer-name)) + (file buffer-file-name)) + (when (and (buffer-live-p buffer) + (or buffer-list + (and (not (string= (substring name 0 1) " ")) + (not (eq buffer buffer-menu-buffer)) + (or file show-non-file)))) + (push (list buffer + (let* ((bits (list (if (eq buffer old-buffer) "." " ") + (if buffer-read-only "%" " ") + (if (buffer-modified-p) "*" " "))) + (fb-bit (if frame-bufs-mode + (list (frame-bufs--bit-info buffer)))) + (tail (list (Buffer-menu--pretty-name name) + (number-to-string (buffer-size)) + (concat (format-mode-line mode-name nil nil buffer) + (if mode-line-process + (format-mode-line mode-line-process + nil nil buffer))) + (Buffer-menu--pretty-file-name file)))) + (apply 'vector (append bits fb-bit tail)))) + entries))))) + (setq tabulated-list-entries (nreverse entries))) + (tabulated-list-init-header)) + +(defun tabulated-list-entry-size-> (entry1 entry2) + (> (string-to-number (aref (cadr entry1) frame-bufs--size-column)) + (string-to-number (aref (cadr entry2) frame-bufs--size-column)))) + +;;; --------------------------------------------------------------------- +;;; Electric Buffer List Accomodation +;;; --------------------------------------------------------------------- + +;; Make sure we don't interfere with electric-buffer-list. Dynamic scoping +;; to the rescue. +(defadvice electric-buffer-list (around frame-bufs) + (let ((frame-bufs-mode nil) + (Buffer-menu-buffer-column 4) + (frame-bufs--size-column 4)) + ad-do-it)) + +(provide 'frame-bufs) + +;;; frame-bufs.el ends here diff --git a/emacs-old/emacs.d-old/vendor/smartparens/.gitignore b/emacs-old/emacs.d-old/vendor/smartparens/.gitignore new file mode 100644 index 00000000..5cb52bd0 --- /dev/null +++ b/emacs-old/emacs.d-old/vendor/smartparens/.gitignore @@ -0,0 +1,6 @@ +elpa +*.elc +hybridline.el +loading.el +presentation2.org +talk.org \ No newline at end of file diff --git a/.travis.yml b/emacs-old/emacs.d-old/vendor/smartparens/.travis.yml similarity index 100% rename from .travis.yml rename to emacs-old/emacs.d-old/vendor/smartparens/.travis.yml diff --git a/Cask b/emacs-old/emacs.d-old/vendor/smartparens/Cask similarity index 100% rename from Cask rename to emacs-old/emacs.d-old/vendor/smartparens/Cask diff --git a/Makefile b/emacs-old/emacs.d-old/vendor/smartparens/Makefile similarity index 100% rename from Makefile rename to emacs-old/emacs.d-old/vendor/smartparens/Makefile diff --git a/emacs-old/emacs.d-old/vendor/smartparens/README.md b/emacs-old/emacs.d-old/vendor/smartparens/README.md new file mode 100644 index 00000000..b576d322 --- /dev/null +++ b/emacs-old/emacs.d-old/vendor/smartparens/README.md @@ -0,0 +1,27 @@ +# Smartparens [![Build Status](https://travis-ci.org/Fuco1/smartparens.png?branch=master)](https://travis-ci.org/Fuco1/smartparens) [![Paypal logo](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CEYP5YVHDRX8C) + +Smartparens is minor mode for Emacs that *deals with parens pairs and tries to be smart about it*. It started as a unification effort to combine functionality of several existing packages in a single, compatible and extensible way to deal with parentheses, delimiters, tags and the like. Some of these packages include [autopair](https://github.com/capitaomorte/autopair), [textmate](http://code.google.com/p/emacs-textmate/), [wrap-region](https://github.com/rejeep/wrap-region), [electric-pair-mode](http://www.emacswiki.org/emacs/ElectricPair), [paredit](http://emacswiki.org/emacs/ParEdit) and others. With the basic features found in other packages it also brings many improvements as well as completely new features. [Here's][wiki-what] a highlight of some features, for a complete list and detailed documentation look in the [manual][wiki-new]. + +For the complete documentation visit the [documentation wiki][wiki]. + +[wiki]: https://github.com/Fuco1/smartparens/wiki +[wiki-what]: https://github.com/Fuco1/smartparens/wiki#wiki-what-is-this-package-about? +[wiki-new]: https://github.com/Fuco1/smartparens/wiki#wiki-information-for-new-users + +# Default configuration + +The default configuration was moved into a separate file. If you wish to use the default configuration as a basis for your own additional customization, add: + +```scheme +(require 'smartparens-config) +``` + +in your configuration files (e.g. init.el) to load it. There are also files with additional configuration for specific modes, such as `smartparens-latex.el`. The usage is similar as with the default config. Note however that the `smartparens-config.el` file will auto-load all the mode-speceific customizations. + +# Support the project + +If you want to support this project, you can do it in the following ways: + +* Contribute code. If you have an idea that is not yet implemented and will benefit smartparens, feel free to implement it and submit a pull request. If you have any concerns whether your contribution will be accepted, ask beforehand. You can email the author or [start an issue](https://github.com/Fuco1/smartparens/issues/new) on the tracker. +* Contribute ideas. Even if you can't code Emacs LISP, you can still contribute valuable ideas for other programmers to implement. Simply [start new issue](https://github.com/Fuco1/smartparens/issues/new) on the tracker and submit your suggestion. +* You can make a [financial donation](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CEYP5YVHDRX8C) through PayPal. I currently major in mathematics, having no full-time job as the school (and emacs :) eats most my day time. If you like smartparens or want a specific feature to be implemented and can spare a modest amount on a donation, feel free to do so. Regardless of the donations, smartparens will always be free both as in beer and as in speech. diff --git a/features/insert-pair.feature b/emacs-old/emacs.d-old/vendor/smartparens/features/insert-pair.feature similarity index 100% rename from features/insert-pair.feature rename to emacs-old/emacs.d-old/vendor/smartparens/features/insert-pair.feature diff --git a/features/sexp.feature b/emacs-old/emacs.d-old/vendor/smartparens/features/sexp.feature similarity index 100% rename from features/sexp.feature rename to emacs-old/emacs.d-old/vendor/smartparens/features/sexp.feature diff --git a/features/smartparens.feature b/emacs-old/emacs.d-old/vendor/smartparens/features/smartparens.feature similarity index 100% rename from features/smartparens.feature rename to emacs-old/emacs.d-old/vendor/smartparens/features/smartparens.feature diff --git a/features/step-definitions/smartparens-steps.el b/emacs-old/emacs.d-old/vendor/smartparens/features/step-definitions/smartparens-steps.el similarity index 100% rename from features/step-definitions/smartparens-steps.el rename to emacs-old/emacs.d-old/vendor/smartparens/features/step-definitions/smartparens-steps.el diff --git a/features/support/env.el b/emacs-old/emacs.d-old/vendor/smartparens/features/support/env.el similarity index 100% rename from features/support/env.el rename to emacs-old/emacs.d-old/vendor/smartparens/features/support/env.el diff --git a/features/wrap-region.feature b/emacs-old/emacs.d-old/vendor/smartparens/features/wrap-region.feature similarity index 100% rename from features/wrap-region.feature rename to emacs-old/emacs.d-old/vendor/smartparens/features/wrap-region.feature diff --git a/smartparens-config.el b/emacs-old/emacs.d-old/vendor/smartparens/smartparens-config.el similarity index 100% rename from smartparens-config.el rename to emacs-old/emacs.d-old/vendor/smartparens/smartparens-config.el diff --git a/smartparens-html.el b/emacs-old/emacs.d-old/vendor/smartparens/smartparens-html.el similarity index 100% rename from smartparens-html.el rename to emacs-old/emacs.d-old/vendor/smartparens/smartparens-html.el diff --git a/smartparens-latex.el b/emacs-old/emacs.d-old/vendor/smartparens/smartparens-latex.el similarity index 100% rename from smartparens-latex.el rename to emacs-old/emacs.d-old/vendor/smartparens/smartparens-latex.el diff --git a/smartparens-lua.el b/emacs-old/emacs.d-old/vendor/smartparens/smartparens-lua.el similarity index 100% rename from smartparens-lua.el rename to emacs-old/emacs.d-old/vendor/smartparens/smartparens-lua.el diff --git a/smartparens-pkg.el b/emacs-old/emacs.d-old/vendor/smartparens/smartparens-pkg.el similarity index 100% rename from smartparens-pkg.el rename to emacs-old/emacs.d-old/vendor/smartparens/smartparens-pkg.el diff --git a/smartparens-ruby.el b/emacs-old/emacs.d-old/vendor/smartparens/smartparens-ruby.el similarity index 100% rename from smartparens-ruby.el rename to emacs-old/emacs.d-old/vendor/smartparens/smartparens-ruby.el diff --git a/smartparens.el b/emacs-old/emacs.d-old/vendor/smartparens/smartparens.el similarity index 100% rename from smartparens.el rename to emacs-old/emacs.d-old/vendor/smartparens/smartparens.el diff --git a/smartparens.org b/emacs-old/emacs.d-old/vendor/smartparens/smartparens.org similarity index 100% rename from smartparens.org rename to emacs-old/emacs.d-old/vendor/smartparens/smartparens.org diff --git a/tests/smartparens-test-env.el b/emacs-old/emacs.d-old/vendor/smartparens/tests/smartparens-test-env.el similarity index 100% rename from tests/smartparens-test-env.el rename to emacs-old/emacs.d-old/vendor/smartparens/tests/smartparens-test-env.el diff --git a/tests/smartparens-test-get-paired-expression.el b/emacs-old/emacs.d-old/vendor/smartparens/tests/smartparens-test-get-paired-expression.el similarity index 100% rename from tests/smartparens-test-get-paired-expression.el rename to emacs-old/emacs.d-old/vendor/smartparens/tests/smartparens-test-get-paired-expression.el diff --git a/tests/smartparens-test-get-stringlike-expression.el b/emacs-old/emacs.d-old/vendor/smartparens/tests/smartparens-test-get-stringlike-expression.el similarity index 100% rename from tests/smartparens-test-get-stringlike-expression.el rename to emacs-old/emacs.d-old/vendor/smartparens/tests/smartparens-test-get-stringlike-expression.el diff --git a/tests/smartparens-test-ruby-mode.el b/emacs-old/emacs.d-old/vendor/smartparens/tests/smartparens-test-ruby-mode.el similarity index 100% rename from tests/smartparens-test-ruby-mode.el rename to emacs-old/emacs.d-old/vendor/smartparens/tests/smartparens-test-ruby-mode.el diff --git a/tests/smartparens-test.el b/emacs-old/emacs.d-old/vendor/smartparens/tests/smartparens-test.el similarity index 100% rename from tests/smartparens-test.el rename to emacs-old/emacs.d-old/vendor/smartparens/tests/smartparens-test.el diff --git a/emacs-old/spacemacs-init.el-old b/emacs-old/spacemacs-init.el-old new file mode 100644 index 00000000..2ff01ec9 --- /dev/null +++ b/emacs-old/spacemacs-init.el-old @@ -0,0 +1,337 @@ +;; -*- mode: emacs-lisp -*- +;; This file is loaded by Spacemacs at startup. +;; It must be stored in your home directory. + +(defun dotspacemacs/layers () + "Configuration Layers declaration. +You should not put any user code in this function besides modifying the variable +values." + (setq-default + ;; Base distribution to use. This is a layer contained in the directory + ;; `+distribution'. For now available distributions are `spacemacs-base' + ;; or `spacemacs'. (default 'spacemacs) + dotspacemacs-distribution 'spacemacs + ;; Lazy installation of layers (i.e. layers are installed only when a file + ;; with a supported type is opened). Possible values are `all', `unused' + ;; and `nil'. `unused' will lazy install only unused layers (i.e. layers + ;; not listed in variable `dotspacemacs-configuration-layers'), `all' will + ;; lazy install any layer that support lazy installation even the layers + ;; listed in `dotspacemacs-configuration-layers'. `nil' disable the lazy + ;; installation feature and you have to explicitly list a layer in the + ;; variable `dotspacemacs-configuration-layers' to install it. + ;; (default 'unused) + dotspacemacs-enable-lazy-installation 'unused + ;; If non-nil then Spacemacs will ask for confirmation before installing + ;; a layer lazily. (default t) + dotspacemacs-ask-for-lazy-installation t + ;; If non-nil layers with lazy install support are lazy installed. + ;; List of additional paths where to look for configuration layers. + ;; Paths must have a trailing slash (i.e. `~/.mycontribs/') + dotspacemacs-configuration-layer-path '() + ;; List of configuration layers to load. + dotspacemacs-configuration-layers + '( + vimscript + haskell + ;; language layers + csv + erlang + elixir + elm + emacs-lisp + markdown + python + react + (ruby :variables ruby-test-runner 'rspec) + sql + yaml + + ;; general layers + dash + emoji + git + helm + org + restclient + (shell :variables shell-default-shell 'eshell shell-default-height 30 shell-default-position 'bottom) + syntax-checking + version-control + ) + ;; List of additional packages that will be installed without being + ;; wrapped in a layer. If you need some configuration for these + ;; packages, then consider creating a layer. You can also put the + ;; configuration in `dotspacemacs/user-config'. + dotspacemacs-additional-packages '(ruby-hash-syntax) + ;; A list of packages that cannot be updated. + dotspacemacs-frozen-packages '() + ;; A list of packages that will not be installed and loaded. + dotspacemacs-excluded-packages '() + ;; Defines the behaviour of Spacemacs when installing packages. + ;; Possible values are `used-only', `used-but-keep-unused' and `all'. + ;; `used-only' installs only explicitly used packages and uninstall any + ;; unused packages as well as their unused dependencies. + ;; `used-but-keep-unused' installs only the used packages but won't uninstall + ;; them if they become unused. `all' installs *all* packages supported by + ;; Spacemacs and never uninstall them. (default is `used-only') + dotspacemacs-install-packages 'used-only)) + +(defun dotspacemacs/init () + "Initialization function. +This function is called at the very startup of Spacemacs initialization +before layers configuration. +You should not put any user code in there besides modifying the variable +values." + ;; This setq-default sexp is an exhaustive list of all the supported + ;; spacemacs settings. + (setq-default + ;; If non nil ELPA repositories are contacted via HTTPS whenever it's + ;; possible. Set it to nil if you have no way to use HTTPS in your + ;; environment, otherwise it is strongly recommended to let it set to t. + ;; This variable has no effect if Emacs is launched with the parameter + ;; `--insecure' which forces the value of this variable to nil. + ;; (default t) + dotspacemacs-elpa-https t + ;; Maximum allowed time in seconds to contact an ELPA repository. + dotspacemacs-elpa-timeout 5 + ;; If non nil then spacemacs will check for updates at startup + ;; when the current branch is not `develop'. Note that checking for + ;; new versions works via git commands, thus it calls GitHub services + ;; whenever you start Emacs. (default nil) + dotspacemacs-check-for-update nil + ;; If non-nil, a form that evaluates to a package directory. For example, to + ;; use different package directories for different Emacs versions, set this + ;; to `emacs-version'. + dotspacemacs-elpa-subdirectory nil + ;; One of `vim', `emacs' or `hybrid'. + ;; `hybrid' is like `vim' except that `insert state' is replaced by the + ;; `hybrid state' with `emacs' key bindings. The value can also be a list + ;; with `:variables' keyword (similar to layers). Check the editing styles + ;; section of the documentation for details on available variables. + ;; (default 'vim) + dotspacemacs-editing-style 'vim + ;; If non nil output loading progress in `*Messages*' buffer. (default nil) + dotspacemacs-verbose-loading nil + ;; Specify the startup banner. Default value is `official', it displays + ;; the official spacemacs logo. An integer value is the index of text + ;; banner, `random' chooses a random text banner in `core/banners' + ;; directory. A string value must be a path to an image format supported + ;; by your Emacs build. + ;; If the value is nil then no banner is displayed. (default 'official) + dotspacemacs-startup-banner 'official + ;; List of items to show in startup buffer or an association list of + ;; the form `(list-type . list-size)`. If nil then it is disabled. + ;; Possible values for list-type are: + ;; `recents' `bookmarks' `projects' `agenda' `todos'." + ;; List sizes may be nil, in which case + ;; `spacemacs-buffer-startup-lists-length' takes effect. + dotspacemacs-startup-lists '((recents . 5) + (projects . 7)) + ;; True if the home buffer should respond to resize events. + dotspacemacs-startup-buffer-responsive t + ;; Default major mode of the scratch buffer (default `text-mode') + dotspacemacs-scratch-mode 'text-mode + ;; List of themes, the first of the list is loaded when spacemacs starts. + ;; Press T n to cycle to the next theme in the list (works great + ;; with 2 themes variants, one dark and one light) + dotspacemacs-themes '(sanityinc-tomorrow-bright + spacemacs-dark + spacemacs-light) + ;; If non nil the cursor color matches the state color in GUI Emacs. + dotspacemacs-colorize-cursor-according-to-state t + ;; Default font, or prioritized list of fonts. `powerline-scale' allows to + ;; quickly tweak the mode-line size to make separators look not too crappy. + dotspacemacs-default-font '("Source Code Pro" + :size 13 + :weight normal + :width normal + :powerline-scale 1.1) + ;; The leader key + dotspacemacs-leader-key "SPC" + ;; The key used for Emacs commands (M-x) (after pressing on the leader key). + ;; (default "SPC") + dotspacemacs-emacs-command-key "SPC" + ;; The key used for Vim Ex commands (default ":") + dotspacemacs-ex-command-key ":" + ;; The leader key accessible in `emacs state' and `insert state' + ;; (default "M-m") + dotspacemacs-emacs-leader-key "M-m" + ;; Major mode leader key is a shortcut key which is the equivalent of + ;; pressing ` m`. Set it to `nil` to disable it. (default ",") + dotspacemacs-major-mode-leader-key "," + ;; Major mode leader key accessible in `emacs state' and `insert state'. + ;; (default "C-M-m") + dotspacemacs-major-mode-emacs-leader-key "C-M-m" + ;; These variables control whether separate commands are bound in the GUI to + ;; the key pairs C-i, TAB and C-m, RET. + ;; Setting it to a non-nil value, allows for separate commands under + ;; and TAB or and RET. + ;; In the terminal, these pairs are generally indistinguishable, so this only + ;; works in the GUI. (default nil) + dotspacemacs-distinguish-gui-tab nil + ;; If non nil `Y' is remapped to `y$' in Evil states. (default nil) + dotspacemacs-remap-Y-to-y$ nil + ;; If non-nil, the shift mappings `<' and `>' retain visual state if used + ;; there. (default t) + dotspacemacs-retain-visual-state-on-shift t + ;; If non-nil, J and K move lines up and down when in visual mode. + ;; (default nil) + dotspacemacs-visual-line-move-text nil + ;; If non nil, inverse the meaning of `g' in `:substitute' Evil ex-command. + ;; (default nil) + dotspacemacs-ex-substitute-global nil + ;; Name of the default layout (default "Default") + dotspacemacs-default-layout-name "Default" + ;; If non nil the default layout name is displayed in the mode-line. + ;; (default nil) + dotspacemacs-display-default-layout nil + ;; If non nil then the last auto saved layouts are resume automatically upon + ;; start. (default nil) + dotspacemacs-auto-resume-layouts nil + ;; Size (in MB) above which spacemacs will prompt to open the large file + ;; literally to avoid performance issues. Opening a file literally means that + ;; no major mode or minor modes are active. (default is 1) + dotspacemacs-large-file-size 1 + ;; Location where to auto-save files. Possible values are `original' to + ;; auto-save the file in-place, `cache' to auto-save the file to another + ;; file stored in the cache directory and `nil' to disable auto-saving. + ;; (default 'cache) + dotspacemacs-auto-save-file-location 'cache + ;; Maximum number of rollback slots to keep in the cache. (default 5) + dotspacemacs-max-rollback-slots 5 + ;; If non nil, `helm' will try to minimize the space it uses. (default nil) + dotspacemacs-helm-resize nil + ;; if non nil, the helm header is hidden when there is only one source. + ;; (default nil) + dotspacemacs-helm-no-header nil + ;; define the position to display `helm', options are `bottom', `top', + ;; `left', or `right'. (default 'bottom) + dotspacemacs-helm-position 'bottom + ;; Controls fuzzy matching in helm. If set to `always', force fuzzy matching + ;; in all non-asynchronous sources. If set to `source', preserve individual + ;; source settings. Else, disable fuzzy matching in all sources. + ;; (default 'always) + dotspacemacs-helm-use-fuzzy 'always + ;; If non nil the paste micro-state is enabled. When enabled pressing `p` + ;; several times cycle between the kill ring content. (default nil) + dotspacemacs-enable-paste-transient-state nil + ;; Which-key delay in seconds. The which-key buffer is the popup listing + ;; the commands bound to the current keystroke sequence. (default 0.4) + dotspacemacs-which-key-delay 0.4 + ;; Which-key frame position. Possible values are `right', `bottom' and + ;; `right-then-bottom'. right-then-bottom tries to display the frame to the + ;; right; if there is insufficient space it displays it at the bottom. + ;; (default 'bottom) + dotspacemacs-which-key-position 'bottom + ;; If non nil a progress bar is displayed when spacemacs is loading. This + ;; may increase the boot time on some systems and emacs builds, set it to + ;; nil to boost the loading time. (default t) + dotspacemacs-loading-progress-bar t + ;; If non nil the frame is fullscreen when Emacs starts up. (default nil) + ;; (Emacs 24.4+ only) + dotspacemacs-fullscreen-at-startup nil + ;; If non nil `spacemacs/toggle-fullscreen' will not use native fullscreen. + ;; Use to disable fullscreen animations in OSX. (default nil) + dotspacemacs-fullscreen-use-non-native nil + ;; If non nil the frame is maximized when Emacs starts up. + ;; Takes effect only if `dotspacemacs-fullscreen-at-startup' is nil. + ;; (default nil) (Emacs 24.4+ only) + dotspacemacs-maximized-at-startup nil + ;; A value from the range (0..100), in increasing opacity, which describes + ;; the transparency level of a frame when it's active or selected. + ;; Transparency can be toggled through `toggle-transparency'. (default 90) + dotspacemacs-active-transparency 90 + ;; A value from the range (0..100), in increasing opacity, which describes + ;; the transparency level of a frame when it's inactive or deselected. + ;; Transparency can be toggled through `toggle-transparency'. (default 90) + dotspacemacs-inactive-transparency 90 + ;; If non nil show the titles of transient states. (default t) + dotspacemacs-show-transient-state-title t + ;; If non nil show the color guide hint for transient state keys. (default t) + dotspacemacs-show-transient-state-color-guide t + ;; If non nil unicode symbols are displayed in the mode line. (default t) + dotspacemacs-mode-line-unicode-symbols t + ;; If non nil smooth scrolling (native-scrolling) is enabled. Smooth + ;; scrolling overrides the default behavior of Emacs which recenters point + ;; when it reaches the top or bottom of the screen. (default t) + dotspacemacs-smooth-scrolling t + ;; If non nil line numbers are turned on in all `prog-mode' and `text-mode' + ;; derivatives. If set to `relative', also turns on relative line numbers. + ;; (default nil) + dotspacemacs-line-numbers nil + ;; Code folding method. Possible values are `evil' and `origami'. + ;; (default 'evil) + dotspacemacs-folding-method 'evil + ;; If non-nil smartparens-strict-mode will be enabled in programming modes. + ;; (default nil) + dotspacemacs-smartparens-strict-mode nil + ;; If non-nil pressing the closing parenthesis `)' key in insert mode passes + ;; over any automatically added closing parenthesis, bracket, quote, etc… + ;; This can be temporary disabled by pressing `C-q' before `)'. (default nil) + dotspacemacs-smart-closing-parenthesis nil + ;; Select a scope to highlight delimiters. Possible values are `any', + ;; `current', `all' or `nil'. Default is `all' (highlight any scope and + ;; emphasis the current one). (default 'all) + dotspacemacs-highlight-delimiters 'all + ;; If non nil, advise quit functions to keep server open when quitting. + ;; (default nil) + dotspacemacs-persistent-server nil + ;; List of search tool executable names. Spacemacs uses the first installed + ;; tool of the list. Supported tools are `ag', `pt', `ack' and `grep'. + ;; (default '("ag" "pt" "ack" "grep")) + dotspacemacs-search-tools '("ag" "pt" "ack" "grep") + ;; The default package repository used if no explicit repository has been + ;; specified with an installed package. + ;; Not used for now. (default nil) + dotspacemacs-default-package-repository nil + ;; Delete whitespace while saving buffer. Possible values are `all' + ;; to aggressively delete empty line and long sequences of whitespace, + ;; `trailing' to delete only the whitespace at end of lines, `changed'to + ;; delete only whitespace for changed lines or `nil' to disable cleanup. + ;; (default nil) + dotspacemacs-whitespace-cleanup nil + )) + +(defun dotspacemacs/user-init () + "Initialization function for user code. +It is called immediately after `dotspacemacs/init', before layer configuration +executes. + This function is mostly useful for variables that need to be set +before packages are loaded. If you are unsure, you should try in setting them in +`dotspacemacs/user-config' first." + ) + +(defun dotspacemacs/user-config () + "Configuration function for user code. +This function is called at the very end of Spacemacs initialization after +layers configuration. +This is the place where most of your configurations should be done. Unless it is +explicitly specified that a variable should be set before a package is loaded, +you should place your code here." + ;; (spacemacs/set-leader-keys "o," 'spacemacs/alternate-buffer) + ;; (spacemacs/set-leader-keys "o/" 'evil-search-clear-highlight) + (global-evil-mc-mode) + (global-set-key (kbd "s-d") 'evil-mc-make-and-goto-next-match) + ;; (global-set-key (kbd "Q") 'evil-fill-and-move) + (make-directory "~/.emacs.d/backups" t) + ) + +;; Do not write anything past this comment. This is where Emacs will +;; auto-generate custom variable definitions. +(custom-set-variables + ;; custom-set-variables was added by Custom. + ;; If you edit it by hand, you could mess it up, so be careful. + ;; Your init file should contain only one such instance. + ;; If there is more than one, they won't work right. + '(ansi-color-names-vector + ["#d2ceda" "#f2241f" "#67b11d" "#b1951d" "#3a81c3" "#a31db1" "#21b8c7" "#655370"]) + '(backup-directory-alist (quote ((".*" . "~/.emacs.d/backups/")))) + '(evil-want-Y-yank-to-eol nil) + '(package-selected-packages + (quote + (flycheck-elm elm-mode vimrc-mode dactyl-mode winum restclient-helm ob-restclient multiple-cursors flycheck-credo diminish avy evil f s ruby-hash-syntax intero hlint-refactor hindent helm-hoogle haskell-snippets flycheck-haskell company-ghci company-ghc ghc haskell-mode cmm-mode powerline spinner hydra bind-key packed company elixir-mode bind-map request yasnippet skewer-mode js2-mode simple-httpd magit-popup git-commit with-editor inf-ruby pcre2el haml-mode erlang restclient ob-http sql-indent yapfify pyvenv pytest pyenv-mode py-isort pip-requirements live-py-mode hy-mode helm-pydoc cython-mode anaconda-mode pythonic xterm-color shell-pop org-projectile pcache org-present org-pomodoro alert log4e gntp org-download multi-term htmlize gnuplot eshell-z eshell-prompt-extras esh-help mmm-mode markdown-toc markdown-mode gh-md color-theme-sanityinc-tomorrow yaml-mode org iedit smartparens highlight flycheck git-gutter projectile helm helm-core magit async dash tern ws-butler window-numbering which-key web-mode web-beautify volatile-highlights vi-tilde-fringe uuidgen use-package toc-org tagedit spacemacs-theme spaceline smeargle slim-mode scss-mode sass-mode rvm ruby-tools ruby-test-mode rubocop rspec-mode robe restart-emacs rbenv rake rainbow-delimiters quelpa pug-mode popwin persp-mode paradox orgit org-plus-contrib org-bullets open-junk-file ob-elixir neotree move-text minitest magit-gitflow macrostep lorem-ipsum livid-mode linum-relative link-hint less-css-mode json-mode js2-refactor js-doc info+ indent-guide ido-vertical-mode hungry-delete hl-todo highlight-parentheses highlight-numbers highlight-indentation hide-comnt help-fns+ helm-themes helm-swoop helm-projectile helm-mode-manager helm-make helm-gitignore helm-flx helm-descbinds helm-dash helm-css-scss helm-ag google-translate golden-ratio gitconfig-mode gitattributes-mode git-timemachine git-messenger git-link git-gutter-fringe git-gutter-fringe+ flycheck-pos-tip flycheck-mix flx-ido fill-column-indicator fancy-battery eyebrowse expand-region exec-path-from-shell evil-visualstar evil-visual-mark-mode evil-unimpaired evil-tutor evil-surround evil-search-highlight-persist evil-numbers evil-nerd-commenter evil-mc evil-matchit evil-magit evil-lisp-state evil-indent-plus evil-iedit-state evil-exchange evil-escape evil-ediff evil-args evil-anzu eval-sexp-fu emoji-cheat-sheet-plus emmet-mode elisp-slime-nav dumb-jump diff-hl define-word dash-at-point csv-mode column-enforce-mode coffee-mode clean-aindent-mode chruby bundler auto-highlight-symbol auto-compile alchemist aggressive-indent adaptive-wrap ace-window ace-link ace-jump-helm-line)))) +(custom-set-faces + ;; custom-set-faces was added by Custom. + ;; If you edit it by hand, you could mess it up, so be careful. + ;; Your init file should contain only one such instance. + ;; If there is more than one, they won't work right. + ) diff --git a/emacs-old/spacemacs.symlink b/emacs-old/spacemacs.symlink new file mode 100644 index 00000000..9ad974a0 --- /dev/null +++ b/emacs-old/spacemacs.symlink @@ -0,0 +1,317 @@ +;; -*- mode: emacs-lisp -*- +;; This file is loaded by Spacemacs at startup. +;; It must be stored in your home directory. + +(defun dotspacemacs/layers () + "Configuration Layers declaration. +You should not put any user code in this function besides modifying the variable +values." + (setq-default + ;; Base distribution to use. This is a layer contained in the directory + ;; `+distribution'. For now available distributions are `spacemacs-base' + ;; or `spacemacs'. (default 'spacemacs) + dotspacemacs-distribution 'spacemacs + ;; Lazy installation of layers (i.e. layers are installed only when a file + ;; with a supported type is opened). Possible values are `all', `unused' + ;; and `nil'. `unused' will lazy install only unused layers (i.e. layers + ;; not listed in variable `dotspacemacs-configuration-layers'), `all' will + ;; lazy install any layer that support lazy installation even the layers + ;; listed in `dotspacemacs-configuration-layers'. `nil' disable the lazy + ;; installation feature and you have to explicitly list a layer in the + ;; variable `dotspacemacs-configuration-layers' to install it. + ;; (default 'unused) + dotspacemacs-enable-lazy-installation 'unused + ;; If non-nil then Spacemacs will ask for confirmation before installing + ;; a layer lazily. (default t) + dotspacemacs-ask-for-lazy-installation t + ;; If non-nil layers with lazy install support are lazy installed. + ;; List of additional paths where to look for configuration layers. + ;; Paths must have a trailing slash (ie. `~/.mycontribs/') + dotspacemacs-configuration-layer-path '("~/dotfiles/emacs/spacemacs-layers/") + ;; List of configuration layers to load. + dotspacemacs-configuration-layers + '( + rust + auto-completion + clojure + crystal + csv + dash + elixir + elm + emacs-lisp + emoji + erlang + git + haskell + helm + html + javascript + lua + markdown + mu4e + org + osx + python + react + restclient + (ruby :variables ruby-test-runner 'rspec) + (shell :variables + shell-default-shell 'eshell + shell-default-height 30 + shell-default-position 'bottom + shell-default-term-shell "/bin/zsh") + sql + syntax-checking + twitter + version-control + vimscript + yaml + ) + + ;; List of packages without layers that need to be installed + dotspacemacs-additional-packages '(ruby-hash-syntax tldr) + ;; A list of packages that cannot be updated. + dotspacemacs-frozen-packages '() + ;; A list of packages that will not be installed and loaded. + dotspacemacs-excluded-packages '() + ;; Defines the behaviour of Spacemacs when installing packages. + ;; Possible values are `used-only', `used-but-keep-unused' and `all'. + ;; `used-only' installs only explicitly used packages and uninstall any + ;; unused packages as well as their unused dependencies. + ;; `used-but-keep-unused' installs only the used packages but won't uninstall + ;; them if they become unused. `all' installs *all* packages supported by + ;; Spacemacs and never uninstall them. (default is `used-only') + dotspacemacs-install-packages 'used-only)) + +(defun dotspacemacs/init () + "Initialization function. +This function is called at the very startup of Spacemacs initialization +before layers configuration. +You should not put any user code in there besides modifying the variable +values." + ;; This setq-default sexp is an exhaustive list of all the supported + ;; spacemacs settings. + (setq-default + ;; If non nil ELPA repositories are contacted via HTTPS whenever it's + ;; possible. Set it to nil if you have no way to use HTTPS in your + ;; environment, otherwise it is strongly recommended to let it set to t. + ;; This variable has no effect if Emacs is launched with the parameter + ;; `--insecure' which forces the value of this variable to nil. + ;; (default t) + dotspacemacs-elpa-https t + ;; Maximum allowed time in seconds to contact an ELPA repository. + dotspacemacs-elpa-timeout 5 + ;; If non nil then spacemacs will check for updates at startup + ;; when the current branch is not `develop'. Note that checking for + ;; new versions works via git commands, thus it calls GitHub services + ;; whenever you start Emacs. (default nil) + dotspacemacs-check-for-update nil + ;; If non-nil, a form that evaluates to a package directory. For example, to + ;; use different package directories for different Emacs versions, set this + ;; to `emacs-version'. + dotspacemacs-elpa-subdirectory nil + ;; One of `vim', `emacs' or `hybrid'. + ;; `hybrid' is like `vim' except that `insert state' is replaced by the + ;; `hybrid state' with `emacs' key bindings. The value can also be a list + ;; with `:variables' keyword (similar to layers). Check the editing styles + ;; section of the documentation for details on available variables. + ;; (default 'vim) + dotspacemacs-editing-style 'vim + ;; If non nil output loading progress in `*Messages*' buffer. (default nil) + dotspacemacs-verbose-loading nil + ;; Specify the startup banner. Default value is `official', it displays + ;; the official spacemacs logo. An integer value is the index of text + ;; banner, `random' chooses a random text banner in `core/banners' + ;; directory. A string value must be a path to an image format supported + ;; by your Emacs build. + ;; If the value is nil then no banner is displayed. (default 'official) + dotspacemacs-startup-banner 'official + ;; List of items to show in startup buffer or an association list of + ;; the form `(list-type . list-size)`. If nil then it is disabled. + ;; Possible values for list-type are: + ;; `recents' `bookmarks' `projects' `agenda' `todos'." + ;; List sizes may be nil, in which case + ;; `spacemacs-buffer-startup-lists-length' takes effect. + dotspacemacs-startup-lists '((recents . 5) + (projects . 7)) + ;; True if the home buffer should respond to resize events. + dotspacemacs-startup-buffer-responsive t + ;; Default major mode of the scratch buffer (default `text-mode') + dotspacemacs-scratch-mode 'text-mode + ;; List of themes, the first of the list is loaded when spacemacs starts. + ;; Press T n to cycle to the next theme in the list (works great + ;; with 2 themes variants, one dark and one light) + dotspacemacs-themes '(sanityinc-tomorrow-bright + monokai + spolsky + zenburn + leuven) + ;; If non nil the cursor color matches the state color. + dotspacemacs-colorize-cursor-according-to-state t + ;; Default font, or prioritized list of fonts. `powerline-scale' allows to + ;; quickly tweak the mode-line size to make separators look not too crappy. + dotspacemacs-default-font '("Source Code Pro" + :size 13 + :weight normal + :width normal + :powerline-scale 1.1) + ;; The leader key + dotspacemacs-leader-key "SPC" + ;; The key used for Emacs commands (M-x) (after pressing on the leader key). + ;; (default "SPC") + dotspacemacs-emacs-command-key "SPC" + ;; The key used for Vim Ex commands (default ":") + dotspacemacs-ex-command-key ":" + ;; The leader key accessible in `emacs state' and `insert state' + ;; (default "M-m") + dotspacemacs-emacs-leader-key "M-m" + ;; Major mode leader key is a shortcut key which is the equivalent of + ;; pressing ` m`. Set it to `nil` to disable it. (default ",") + dotspacemacs-major-mode-leader-key "," + ;; The command key used for Evil commands (ex-commands) and + ;; Emacs commands (M-x). + ;; By default the command key is `:' so ex-commands are executed like in Vim + ;; with `:' and Emacs commands are executed with ` :'. + dotspacemacs-command-key ":" + ;; If non nil the paste micro-state is enabled. While enabled pressing `p` + ;; several times cycle between the kill ring content. + ; dotspacemacs-enable-paste-micro-state t + ;; Guide-key delay in seconds. The Guide-key is the popup buffer listing + ;; the commands bound to the current keystrokes. + dotspacemacs-guide-key-delay 0.1 + ;; If non nil a progress bar is displayed when spacemacs is loading. This + ;; may increase the boot time on some systems and emacs builds, set it to + ;; nil to boost the loading time. (default t) + dotspacemacs-loading-progress-bar t + ;; If non nil the frame is fullscreen when Emacs starts up. (default nil) + ;; (Emacs 24.4+ only) + dotspacemacs-fullscreen-at-startup nil + ;; If non nil `spacemacs/toggle-fullscreen' will not use native fullscreen. + ;; Use to disable fullscreen animations in OSX. (default nil) + dotspacemacs-fullscreen-use-non-native nil + ;; If non nil the frame is maximized when Emacs starts up. + ;; Takes effect only if `dotspacemacs-fullscreen-at-startup' is nil. + ;; (default nil) (Emacs 24.4+ only) + dotspacemacs-maximized-at-startup nil + ;; A value from the range (0..100), in increasing opacity, which describes + ;; the transparency level of a frame when it's active or selected. + ;; Transparency can be toggled through `toggle-transparency'. (default 90) + dotspacemacs-active-transparency 90 + ;; A value from the range (0..100), in increasing opacity, which describes + ;; the transparency level of a frame when it's inactive or deselected. + ;; Transparency can be toggled through `toggle-transparency'. (default 90) + dotspacemacs-inactive-transparency 90 + ;; If non nil show the titles of transient states. (default t) + dotspacemacs-show-transient-state-title t + ;; If non nil show the color guide hint for transient state keys. (default t) + dotspacemacs-show-transient-state-color-guide t + ;; If non nil unicode symbols are displayed in the mode line. (default t) + dotspacemacs-mode-line-unicode-symbols t + ;; If non nil smooth scrolling (native-scrolling) is enabled. Smooth + ;; scrolling overrides the default behavior of Emacs which recenters point + ;; when it reaches the top or bottom of the screen. (default t) + dotspacemacs-smooth-scrolling t + ;; Control line numbers activation. + ;; If set to `t' or `relative' line numbers are turned on in all `prog-mode' and + ;; `text-mode' derivatives. If set to `relative', line numbers are relative. + ;; This variable can also be set to a property list for finer control: + ;; '(:relative nil + ;; :disabled-for-modes dired-mode + ;; doc-view-mode + ;; markdown-mode + ;; org-mode + ;; pdf-view-mode + ;; text-mode + ;; :size-limit-kb 1000) + ;; (default nil) + dotspacemacs-line-numbers t + ;; Code folding method. Possible values are `evil' and `origami'. + ;; (default 'evil) + dotspacemacs-folding-method 'evil + ;; If non-nil smartparens-strict-mode will be enabled in programming modes. + ;; (default nil) + dotspacemacs-smartparens-strict-mode nil + ;; If non-nil pressing the closing parenthesis `)' key in insert mode passes + ;; over any automatically added closing parenthesis, bracket, quote, etc… + ;; This can be temporary disabled by pressing `C-q' before `)'. (default nil) + dotspacemacs-smart-closing-parenthesis t + ;; Select a scope to highlight delimiters. Possible values are `any', + ;; `current', `all' or `nil'. Default is `all' (highlight any scope and + ;; emphasis the current one). (default 'all) + dotspacemacs-highlight-delimiters 'all + ;; If non nil, advise quit functions to keep server open when quitting. + ;; (default nil) + dotspacemacs-persistent-server nil + ;; List of search tool executable names. Spacemacs uses the first installed + ;; tool of the list. Supported tools are `ag', `pt', `ack' and `grep'. + ;; (default '("ag" "pt" "ack" "grep")) + dotspacemacs-search-tools '("ag" "pt" "ack" "grep") + ;; The default package repository used if no explicit repository has been + ;; specified with an installed package. + ;; Not used for now. (default nil) + dotspacemacs-default-package-repository nil + dotspacemacs-whitespace-cleanup 'changed + ;; Enable RVM support + ruby-version-manager 'rvm + ;; Enable Rails and HAML support + ruby-enable-ruby-on-rails-support t + ;; NeoTree + neo-vc-integration nil + ) + + (setq mu4e-maildir "~/mail" + mu4e-sent-folder "/Sent" + mu4e-trash-folder "/Trash" + mu4e-refile-folder "/Archive" + mu4e-get-mail-command "mbsync -a" + mu4e-update-interval nil + mu4e-compose-signature-auto-include nil + mu4e-view-show-images t + mu4e-view-show-addresses t) + ;; User initialization goes here + ;;sp-highlight-pair-overlay nil + ;;sp-highlight-wrap-overlay nil + ;;sp-highlight-wrap-tag-overlay nil + (setq + ;; Indentation in web files + js2-basic-offset 2 + css-indent-offset 2 + web-mode-markup-indent-offset 2 + web-mode-css-indent-offset 2 + web-mode-code-indent-offset 2 + web-mode-attr-indent-offset 2 + ) + + (add-to-list 'org-babel-load-languages '(js . t)) + + (setq backup-directory-alist '((".*" . "~/.emacs.d/backups/"))) + ) + +(defun dotspacemacs/user-config () + "Configuration function. + This function is called at the very end of Spacemacs initialization after +layers configuration." + (spacemacs/set-leader-keys-for-major-mode 'ruby-mode "TAB" 'rspec-toggle-spec-and-target) + + (make-directory "~/.emacs.d/backups" t) + ) + +;; Do not write anything past this comment. This is where Emacs will +;; auto-generate custom variable definitions. +(custom-set-variables + ;; custom-set-variables was added by Custom. + ;; If you edit it by hand, you could mess it up, so be careful. + ;; Your init file should contain only one such instance. + ;; If there is more than one, they won't work right. + '(browse-url-browser-function (quote browse-url-chromium)) + '(evil-search-module (quote evil-search)) + '(package-selected-packages + (quote + (play-crystal inf-crystal flycheck-crystal crystal-mode ghub let-alist toml-mode racer flycheck-rust cargo rust-mode tldr twittering-mode helm-company helm-c-yasnippet fuzzy company-web web-completion-data company-tern company-statistics company-restclient know-your-http-well company-emoji company-cabal company-anaconda clojure-snippets auto-yasnippet ac-ispell auto-complete ruby-hash-syntax yapfify vimrc-mode sql-indent restclient-helm pyvenv pytest pyenv-mode py-isort pip-requirements org-projectile org-category-capture org-present org-pomodoro org-download ob-restclient restclient ob-http live-py-mode hy-mode dash-functional htmlize helm-pydoc gnuplot git-gutter-fringe+ git-gutter-fringe fringe-helper git-gutter+ git-gutter flycheck-pos-tip pos-tip flycheck-haskell flycheck-elm erlang emoji-cheat-sheet-plus elm-mode diff-hl dactyl-mode cython-mode csv-mode anaconda-mode pythonic intero hlint-refactor hindent helm-hoogle haskell-snippets company-ghci company-ghc ghc haskell-mode cmm-mode tern mu4e-maildirs-extension mu4e-alert ht alert log4e gntp livid-mode skewer-mode js2-refactor web-beautify simple-httpd json-mode json-snatcher json-reformat js2-mode js-doc coffee-mode ob-elixir flycheck-mix flycheck-credo flycheck alchemist company elixir-mode web-mode tagedit slim-mode scss-mode sass-mode pug-mode less-css-mode helm-css-scss haml-mode emmet-mode mmm-mode markdown-toc markdown-mode gh-md lua-mode yaml-mode dockerfile-mode zeal-at-point xterm-color ws-butler winum which-key volatile-highlights vi-tilde-fringe uuidgen use-package toc-org spaceline powerline smeargle shell-pop rvm ruby-tools ruby-test-mode rubocop rspec-mode robe reveal-in-osx-finder restart-emacs rbenv rake rainbow-delimiters popwin persp-mode pcre2el pbcopy paradox osx-trash osx-dictionary orgit org-plus-contrib org-bullets open-junk-file neotree multi-term move-text minitest magit-gitflow macrostep lorem-ipsum linum-relative link-hint launchctl info+ indent-guide hungry-delete hl-todo highlight-parentheses highlight-numbers parent-mode highlight-indentation hide-comnt help-fns+ helm-themes helm-swoop helm-projectile helm-mode-manager helm-make projectile helm-gitignore request helm-flx helm-descbinds helm-dash helm-ag google-translate golden-ratio gitignore-mode gitconfig-mode gitattributes-mode git-timemachine git-messenger git-link flx-ido flx fill-column-indicator fancy-battery eyebrowse expand-region exec-path-from-shell evil-visualstar evil-visual-mark-mode evil-unimpaired evil-tutor evil-surround evil-search-highlight-persist evil-numbers evil-nerd-commenter evil-mc evil-matchit evil-magit magit magit-popup git-commit with-editor evil-lisp-state smartparens evil-indent-plus evil-iedit-state iedit evil-exchange evil-escape evil-ediff evil-args evil-anzu anzu evil goto-chg undo-tree eshell-z eshell-prompt-extras esh-help elisp-slime-nav dumb-jump f diminish column-enforce-mode clj-refactor hydra inflections edn multiple-cursors paredit yasnippet s peg clean-aindent-mode cider-eval-sexp-fu eval-sexp-fu highlight cider seq spinner queue pkg-info clojure-mode epl chruby bundler inf-ruby bind-map bind-key auto-highlight-symbol auto-compile packed dash aggressive-indent adaptive-wrap ace-window ace-link ace-jump-helm-line helm avy helm-core popup async monokai-theme)))) +(custom-set-faces + ;; custom-set-faces was added by Custom. + ;; If you edit it by hand, you could mess it up, so be careful. + ;; Your init file should contain only one such instance. + ;; If there is more than one, they won't work right. + ) diff --git a/emacs.d b/emacs.d new file mode 160000 index 00000000..af41e173 --- /dev/null +++ b/emacs.d @@ -0,0 +1 @@ +Subproject commit af41e1735590597545f83c290bd706523cef8884 diff --git a/gemrc b/gemrc new file mode 100644 index 00000000..f251674c --- /dev/null +++ b/gemrc @@ -0,0 +1,7 @@ +--- +:update_sources: true +:verbose: true +:bulk_threshold: 1000 +:backtrace: false +:benchmark: false +gem: --no-ri --no-rdoc \ No newline at end of file diff --git a/git/aliases.zsh b/git/aliases.zsh new file mode 100644 index 00000000..ef704999 --- /dev/null +++ b/git/aliases.zsh @@ -0,0 +1 @@ +alias git='nocorrect git' \ No newline at end of file diff --git a/git/completion.sh b/git/completion.sh new file mode 100644 index 00000000..5eb92792 --- /dev/null +++ b/git/completion.sh @@ -0,0 +1,9 @@ +# Uses git's autocompletion for inner commands. Assumes an install of git's +# bash `git-completion` script at $completion below (this is where Homebrew +# tosses it, at least). +completion=/usr/local/share/zsh/site-functions/_git + +if test -f $completion +then + zstyle ':completion:*:*:git:*' source $completion +fi diff --git a/gitconfig b/gitconfig new file mode 100644 index 00000000..5a85200a --- /dev/null +++ b/gitconfig @@ -0,0 +1,127 @@ +[user] + name = Marten Veldthuis + email = marten@veldthuis.com + signingkey = 03C0DA07 + +[branch] + autosetuprebase = always + +[color] + ui = auto + +[cola] + savewindowsettings = true + fontui = Bitstream Vera Sans Mono,8,-1,5,50,0,0,0,0,0 + fontdiff = Bitstream Vera Sans Mono,8,-1,5,50,0,0,0,0,0 + geometry = 1680x973+1280,49 + +[giggle] + compact-mode = true + main-window-maximized = true + main-window-geometry = 1072x947+0+25 + main-window-view = HistoryView + file-view-hpane-position = 274 + history-view-vpane-position = 409 + file-view-vpane-position = 533 + +[merge] + tool = Kaleidoscope + +[mergetool "threesome"] + cmd = "vim -f $BASE $LOCAL $REMOTE $MERGED -c 'ThreesomeInit'" + trustExitCode = true + +[mergetool "unityyamlmerge"] + trustExitCode = false + cmd = /Applications/Unity/Unity.app/Contents/Tools/UnityYAMLMerge merge -p "$BASE" "$REMOTE" "$LOCAL" "$MERGED" + +[push] + default = simple + recurseSubmodules = check + +[alias] + ci = commit ; this stuff is just shorter + st = status + co = checkout + di = diff + dc = diff --cached + aa = add --all + amend = commit --amend + div = divergence ; git-divergence is a ~/bin script + ff = merge --ff-only ; if you want to make sure it won't create a merge commit + noff = merge --no-ff ; if you want to make sure it does create a merge commit + sha = log -1 --pretty=format:%H ; the sha1 of the latest commit + standup = log --since '1 day ago' --oneline --author=Marten + + ; typos + puhs = push + + ; stolen from https://github.com/garybernhardt/dotfiles/blob/master/.gitconfig + l = log --graph --abbrev-commit --date=relative + la = !git l --all + ;head = !git l -1 + h = !git head + r = !git --no-pager l -20 + ra = !git r --all + +; stolen from https://github.com/garybernhardt/dotfiles/blob/master/.gitconfig +[format] + pretty=format:%C(yellow)%h %Cgreen(%ad) %C(red)%d%Creset %s %C(bold blue)<%an>%Creset + +[github] + user = marten + site = https://github.com + endpoint = https://api.github.com + oauth-token-file = /Users/marten/.github-oauth-token + +[core] + excludesfile = ~/.gitignore_global + quotepath = false + pager = less -S + +[mergetool "opendiff"] + cmd = opendiff +[mergetool] + keepBackup = true + prompt = false +[credential] + helper = cache --timeout=3600 +[difftool "Kaleidoscope"] + cmd = ksdiff --partial-changeset --relative-path \"$MERGED\" -- \"$LOCAL\" \"$REMOTE\" +[diff] + tool = Kaleidoscope + submodule = log +[difftool] + prompt = false +[mergetool "Kaleidoscope"] + cmd = ksdiff --merge --output \"$MERGED\" --base \"$BASE\" -- \"$LOCAL\" --snapshot \"$REMOTE\" --snapshot + trustExitCode = true +[difftool "sourcetree"] + cmd = opendiff \"$LOCAL\" \"$REMOTE\" + path = +[mergetool "sourcetree"] + cmd = /Applications/SourceTree.app/Contents/Resources/opendiff-w.sh \"$LOCAL\" \"$REMOTE\" -ancestor \"$BASE\" -merge \"$MERGED\" + trustExitCode = true +[git-up "rebase"] + log-hook = "echo \"CHANGES FOR $1\"; git log $1..$2" +[filter "media"] + required = true + clean = git media clean %f + smudge = git media smudge %f +[filter "hawser"] + clean = git hawser clean %f + smudge = git hawser smudge %f + required = true +[filter "lfs"] + required = true + clean = git-lfs clean -- %f + smudge = git-lfs smudge -- %f + process = git-lfs filter-process +[pager] + diff = diff-highlight | less -S + show = diff-highlight | less -S + log = diff-highlight | less -S +[vain] + default = c0de +[status] + submodulesummary = 1 diff --git a/gvimrc b/gvimrc new file mode 100644 index 00000000..2fa0836a --- /dev/null +++ b/gvimrc @@ -0,0 +1 @@ +set guifont=Meslo\ LG\ M\ DZ\ 14 diff --git a/inputrc b/inputrc new file mode 100644 index 00000000..788c8636 --- /dev/null +++ b/inputrc @@ -0,0 +1,2 @@ +"\e[1;5D": backward-word +"\e[1;5C": forward-word \ No newline at end of file diff --git a/irbrc b/irbrc new file mode 100644 index 00000000..0efcce7d --- /dev/null +++ b/irbrc @@ -0,0 +1,7 @@ +# Use Pry everywhere +unless ENV['NOPRY'] + require "rubygems" + require 'pry' + Pry.start + exit +end diff --git a/irssi/.gitignore b/irssi/.gitignore new file mode 100644 index 00000000..bf0824e5 --- /dev/null +++ b/irssi/.gitignore @@ -0,0 +1 @@ +*.log \ No newline at end of file diff --git a/irssi/config b/irssi/config new file mode 100644 index 00000000..de36ee65 --- /dev/null +++ b/irssi/config @@ -0,0 +1,321 @@ +servers = ( + { address = "irc.stealth.net"; chatnet = "IRCnet"; port = "6668"; }, + { address = "irc.efnet.org"; chatnet = "EFNet"; port = "6667"; }, + { + address = "irc.undernet.org"; + chatnet = "Undernet"; + port = "6667"; + }, + { address = "irc.dal.net"; chatnet = "DALnet"; port = "6667"; }, + { + address = "irc.quakenet.org"; + chatnet = "QuakeNet"; + port = "6667"; + }, + { address = "silc.silcnet.org"; chatnet = "SILC"; port = "706"; }, + { + address = "dimension.mhil.net"; + chatnet = "michiel"; + port = "6667"; + use_ssl = "no"; + ssl_verify = "no"; + }, + { + address = "andromache.mhil.net"; + chatnet = "michiel"; + port = "6667"; + use_ssl = "no"; + ssl_verify = "no"; + }, + { + address = "irc.freenode.org"; + chatnet = "freenode"; + port = "6667"; + use_ssl = "no"; + ssl_verify = "no"; + }, + { + address = "irc.rgoc.rug.nl"; + chatnet = "rgoc"; + port = "6667"; + use_ssl = "no"; + ssl_verify = "no"; + } +); + +chatnets = { + IRCnet = { + type = "IRC"; + max_kicks = "4"; + max_msgs = "5"; + max_whois = "4"; + max_query_chans = "5"; + }; + EFNet = { + type = "IRC"; + max_kicks = "4"; + max_msgs = "3"; + max_whois = "1"; + }; + Undernet = { + type = "IRC"; + max_kicks = "1"; + max_msgs = "3"; + max_whois = "30"; + }; + DALnet = { + type = "IRC"; + max_kicks = "4"; + max_msgs = "3"; + max_whois = "30"; + }; + QuakeNet = { + type = "IRC"; + max_kicks = "1"; + max_msgs = "3"; + max_whois = "30"; + }; + SILC = { type = "SILC"; }; + michiel = { + type = "IRC"; + nick = "marten"; + autosendcmd = "/join #brak #perio"; + }; + freenode = { + type = "IRC"; + autosendcmd = "/msg NickServ identify 236ffca38153f7d4a3cc2357add5a552"; + }; + rgoc = { type = "IRC"; autosendcmd = "/join #roqua"; }; +}; + +channels = ( + { name = "#irssi"; chatnet = "ircnet"; autojoin = "No"; }, + { name = "silc"; chatnet = "silc"; autojoin = "No"; } +); + +aliases = { + J = "join"; + WJOIN = "join -window"; + WQUERY = "query -window"; + LEAVE = "part"; + BYE = "quit"; + EXIT = "quit"; + SIGNOFF = "quit"; + DESCRIBE = "action"; + DATE = "time"; + HOST = "userhost"; + LAST = "lastlog"; + SAY = "msg *"; + WI = "whois"; + WII = "whois $0 $0"; + WW = "whowas"; + W = "who"; + N = "names"; + M = "msg"; + T = "topic"; + C = "clear"; + CL = "clear"; + K = "kick"; + KB = "kickban"; + KN = "knockout"; + BANS = "ban"; + B = "ban"; + MUB = "unban *"; + UB = "unban"; + IG = "ignore"; + UNIG = "unignore"; + SB = "scrollback"; + UMODE = "mode $N"; + WC = "window close"; + WN = "window new hide"; + SV = "say Irssi $J ($V) - http://irssi.org/"; + GOTO = "sb goto"; + CHAT = "dcc chat"; + RUN = "SCRIPT LOAD"; + CALC = "exec - if which bc &>/dev/null\\; then echo '$*' | bc | awk '{print \"$*=\"$$1}'\\; else echo bc was not found\\; fi"; + SBAR = "STATUSBAR"; + INVITELIST = "mode $C +I"; + Q = "QUERY"; + "MANUAL-WINDOWS" = "set use_status_window off;set autocreate_windows off;set autocreate_query_level none;set autoclose_windows off;set reuse_unused_windows on;save"; + EXEMPTLIST = "mode $C +e"; + ATAG = "WINDOW SERVER"; +}; + +statusbar = { + # formats: + # when using {templates}, the template is shown only if it's argument isn't + # empty unless no argument is given. for example {sb} is printed always, + # but {sb $T} is printed only if $T isn't empty. + + items = { + # start/end text in statusbars + barstart = "{sbstart}"; + barend = "{sbend}"; + + topicbarstart = "{topicsbstart}"; + topicbarend = "{topicsbend}"; + + # treated "normally", you could change the time/user name to whatever + time = "{sb $Z}"; + user = "{sb {sbnickmode $cumode}$N{sbmode $usermode}{sbaway $A}}"; + + # treated specially .. window is printed with non-empty windows, + # window_empty is printed with empty windows + window = "{sb $winref:$tag/$itemname{sbmode $M}}"; + window_empty = "{sb $winref{sbservertag $tag}}"; + prompt = "{prompt $[.15]itemname}"; + prompt_empty = "{prompt $winname}"; + topic = " $topic"; + topic_empty = " Irssi v$J - http://irssi.org/help/"; + + # all of these treated specially, they're only displayed when needed + lag = "{sb Lag: $0-}"; + act = "{sb Act: $0-}"; + more = "-- more --"; + }; + + # there's two type of statusbars. root statusbars are either at the top + # of the screen or at the bottom of the screen. window statusbars are at + # the top/bottom of each split window in screen. + default = { + # the "default statusbar" to be displayed at the bottom of the window. + # contains all the normal items. + window = { + disabled = "no"; + + # window, root + type = "window"; + # top, bottom + placement = "bottom"; + # number + position = "1"; + # active, inactive, always + visible = "active"; + + # list of items in statusbar in the display order + items = { + barstart = { priority = "100"; }; + time = { }; + user = { }; + window = { }; + window_empty = { }; + lag = { priority = "-1"; }; + act = { priority = "10"; }; + more = { priority = "-1"; alignment = "right"; }; + barend = { priority = "100"; alignment = "right"; }; + }; + }; + + # statusbar to use in inactive split windows + window_inact = { + type = "window"; + placement = "bottom"; + position = "1"; + visible = "inactive"; + items = { + barstart = { priority = "100"; }; + window = { }; + window_empty = { }; + more = { priority = "-1"; alignment = "right"; }; + barend = { priority = "100"; alignment = "right"; }; + }; + }; + + # we treat input line as yet another statusbar :) It's possible to + # add other items before or after the input line item. + prompt = { + type = "root"; + placement = "bottom"; + # we want to be at the bottom always + position = "100"; + visible = "always"; + items = { + prompt = { priority = "-1"; }; + prompt_empty = { priority = "-1"; }; + # treated specially, this is the real input line. + input = { priority = "10"; }; + }; + }; + + # topicbar + topic = { + type = "root"; + placement = "top"; + position = "1"; + visible = "always"; + items = { + topicbarstart = { priority = "100"; }; + topic = { }; + topic_empty = { }; + topicbarend = { priority = "100"; alignment = "right"; }; + }; + }; + }; +}; +settings = { + core = { + real_name = "Marten Veldthuis"; + user_name = "marten@irc.mhil.net"; + nick = "marten"; + hostname = "marten.rgoc.rug.nl"; + }; + "fe-text" = { + actlist_sort = "refnum"; + scrollback_lines = "9999999"; + scrollback_time = "30"; + }; + "fe-common/core" = { + autolog_path = "~/logs/irc/$tag/$0.log"; + autolog = "yes"; + }; + "perl/core/scripts" = { twitter_usernames = "\"mveldth\""; }; +}; +hilights = ( { text = "marten"; nick = "yes"; word = "yes"; } ); +logs = { }; +ignores = ( + { + mask = "github@*"; + level = "JOINS PARTS"; + channels = ( "#commits" ); + }, + { level = "JOINS PARTS"; channels = ( "#commits" ); } +); +windows = { + 1 = { immortal = "yes"; name = "(status)"; level = "ALL"; }; + 2 = { name = "hilight"; sticky = "yes"; }; + 3 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#brak"; + tag = "michiel"; + } + ); + }; + 4 = { + items = ( + { + type = "QUERY"; + chat_type = "IRC"; + name = "ellen"; + tag = "michiel"; + } + ); + }; + 5 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#roqua"; + tag = "rgoc"; + } + ); + }; + 6 = { name = "twitter"; }; +}; +mainwindows = { + 1 = { first_line = "4"; lines = "49"; }; + 2 = { first_line = "1"; lines = "3"; }; +}; diff --git a/irssi/default.theme b/irssi/default.theme new file mode 100644 index 00000000..98af18b3 --- /dev/null +++ b/irssi/default.theme @@ -0,0 +1,294 @@ +# When testing changes, the easiest way to reload the theme is with /RELOAD. +# This reloads the configuration file too, so if you did any changes remember +# to /SAVE it first. Remember also that /SAVE overwrites the theme file with +# old data so keep backups :) + +# TEMPLATES: + +# The real text formats that irssi uses are the ones you can find with +# /FORMAT command. Back in the old days all the colors and texts were mixed +# up in those formats, and it was really hard to change the colors since you +# might have had to change them in tens of different places. So, then came +# this templating system. + +# Now the /FORMATs don't have any colors in them, and they also have very +# little other styling. Most of the stuff you need to change is in this +# theme file. If you can't change something here, you can always go back +# to change the /FORMATs directly, they're also saved in these .theme files. + +# So .. the templates. They're those {blahblah} parts you see all over the +# /FORMATs and here. Their usage is simply {name parameter1 parameter2}. +# When irssi sees this kind of text, it goes to find "name" from abstracts +# block below and sets "parameter1" into $0 and "parameter2" into $1 (you +# can have more parameters of course). Templates can have subtemplates. +# Here's a small example: +# /FORMAT format hello {colorify {underline world}} +# abstracts = { colorify = "%G$0-%n"; underline = "%U$0-%U"; } +# When irssi expands the templates in "format", the final string would be: +# hello %G%Uworld%U%n +# ie. underlined bright green "world" text. +# and why "$0-", why not "$0"? $0 would only mean the first parameter, +# $0- means all the parameters. With {underline hello world} you'd really +# want to underline both of the words, not just the hello (and world would +# actually be removed entirely). + +# COLORS: + +# You can find definitions for the color format codes in docs/formats.txt. + +# There's one difference here though. %n format. Normally it means the +# default color of the terminal (white mostly), but here it means the +# "reset color back to the one it was in higher template". For example +# if there was /FORMAT test %g{foo}bar, and foo = "%Y$0%n", irssi would +# print yellow "foo" (as set with %Y) but "bar" would be green, which was +# set at the beginning before the {foo} template. If there wasn't the %g +# at start, the normal behaviour of %n would occur. If you _really_ want +# to use the terminal's default color, use %N. + +############################################################################# + +# default foreground color (%N) - -1 is the "default terminal color" +default_color = "-1"; + +# print timestamp/servertag at the end of line, not at beginning +info_eol = "false"; + +# these characters are automatically replaced with specified color +# (dark grey by default) +replaces = { "[]=" = "%K$*%n"; }; + +abstracts = { + ## + ## generic + ## + + # text to insert at the beginning of each non-message line + line_start = "%B-%W!%B-%n "; + + # timestamp styling, nothing by default + timestamp = "$*"; + + # any kind of text that needs hilighting, default is to bold + hilight = "%_$*%_"; + + # any kind of error message, default is bright red + error = "%R$*%n"; + + # channel name is printed + channel = "%_$*%_"; + + # nick is printed + nick = "%_$*%_"; + + # nick host is printed + nickhost = "[$*]"; + + # server name is printed + server = "%_$*%_"; + + # some kind of comment is printed + comment = "[$*]"; + + # reason for something is printed (part, quit, kick, ..) + reason = "{comment $*}"; + + # mode change is printed ([+o nick]) + mode = "{comment $*}"; + + ## + ## channel specific messages + ## + + # highlighted nick/host is printed (joins) + channick_hilight = "%C$*%n"; + chanhost_hilight = "{nickhost %c$*%n}"; + + # nick/host is printed (parts, quits, etc.) + channick = "%c$*%n"; + chanhost = "{nickhost $*}"; + + # highlighted channel name is printed + channelhilight = "%c$*%n"; + + # ban/ban exception/invite list mask is printed + ban = "%c$*%n"; + + ## + ## messages + ## + + # the basic styling of how to print message, $0 = nick mode, $1 = nick + msgnick = "%K<%n$0$1-%K>%n %|"; + + # message from you is printed. "msgownnick" specifies the styling of the + # nick ($0 part in msgnick) and "ownmsgnick" specifies the styling of the + # whole line. + + # Example1: You want the message text to be green: + # ownmsgnick = "{msgnick $0 $1-}%g"; + # Example2.1: You want < and > chars to be yellow: + # ownmsgnick = "%Y{msgnick $0 $1-%Y}%n"; + # (you'll also have to remove <> from replaces list above) + # Example2.2: But you still want to keep <> grey for other messages: + # pubmsgnick = "%K{msgnick $0 $1-%K}%n"; + # pubmsgmenick = "%K{msgnick $0 $1-%K}%n"; + # pubmsghinick = "%K{msgnick $1 $0$2-%n%K}%n"; + # ownprivmsgnick = "%K{msgnick $*%K}%n"; + # privmsgnick = "%K{msgnick %R$*%K}%n"; + + # $0 = nick mode, $1 = nick + ownmsgnick = "{msgnick $0 $1-}"; + ownnick = "%W$*%n"; + + # public message in channel, $0 = nick mode, $1 = nick + pubmsgnick = "{msgnick $0 $1-}"; + pubnick = "%N$*%n"; + + # public message in channel meant for me, $0 = nick mode, $1 = nick + pubmsgmenick = "{msgnick $0 $1-}"; + menick = "%Y$*%n"; + + # public highlighted message in channel + # $0 = highlight color, $1 = nick mode, $2 = nick + pubmsghinick = "{msgnick $1 $0$2-%n}"; + + # channel name is printed with message + msgchannel = "%K:%c$*%n"; + + # private message, $0 = nick, $1 = host + privmsg = "[%R$0%K(%r$1-%K)%n] "; + + # private message from you, $0 = "msg", $1 = target nick + ownprivmsg = "[%r$0%K(%R$1-%K)%n] "; + + # own private message in query + ownprivmsgnick = "{msgnick $*}"; + ownprivnick = "%W$*%n"; + + # private message in query + privmsgnick = "{msgnick %R$*%n}"; + + ## + ## Actions (/ME stuff) + ## + + # used internally by this theme + action_core = "%W * $*%n"; + + # generic one that's used by most actions + action = "{action_core $*} "; + + # own action, both private/public + ownaction = "{action $*}"; + + # own action with target, both private/public + ownaction_target = "{action_core $0}%K:%c$1%n "; + + # private action sent by others + pvtaction = "%W (*) $*%n "; + pvtaction_query = "{action $*}"; + + # public action sent by others + pubaction = "{action $*}"; + + + ## + ## other IRC events + ## + + # whois + whois = "%# $[8]0 : $1-"; + + # notices + ownnotice = "[%r$0%K(%R$1-%K)]%n "; + notice = "%K-%M$*%K-%n "; + pubnotice_channel = "%K:%m$*"; + pvtnotice_host = "%K(%m$*%K)"; + servernotice = "%g!$*%n "; + + # CTCPs + ownctcp = "[%r$0%K(%R$1-%K)] "; + ctcp = "%g$*%n"; + + # wallops + wallop = "%W$*%n: "; + wallop_nick = "%n$*"; + wallop_action = "%W * $*%n "; + + # netsplits + netsplit = "%R$*%n"; + netjoin = "%C$*%n"; + + # /names list + names_prefix = ""; + names_nick = "[%_$0%_$1-] "; + names_nick_op = "{names_nick $*}"; + names_nick_halfop = "{names_nick $*}"; + names_nick_voice = "{names_nick $*}"; + names_users = "[%g$*%n]"; + names_channel = "%G$*%n"; + + # DCC + dcc = "%g$*%n"; + dccfile = "%_$*%_"; + + # DCC chat, own msg/action + dccownmsg = "[%r$0%K($1-%K)%n] "; + dccownnick = "%R$*%n"; + dccownquerynick = "%W$*%n"; + dccownaction = "{action $*}"; + dccownaction_target = "{action_core $0}%K:%c$1%n "; + + # DCC chat, others + dccmsg = "[%G$1-%K(%g$0%K)%n] "; + dccquerynick = "%G$*%n"; + dccaction = "%W (*dcc*) $*%n %|"; + + ## + ## statusbar + ## + + # default background for all statusbars. You can also give + # the default foreground color for statusbar items. + sb_background = "%4%w"; + + # default backround for "default" statusbar group + #sb_default_bg = "%4"; + # background for prompt / input line + sb_prompt_bg = "%n"; + # background for info statusbar + sb_info_bg = "%8"; + # background for topicbar (same default) + #sb_topic_bg = "%4"; + + # text at the beginning of statusbars. sb-item already puts + # space there,so we don't use anything by default. + sbstart = ""; + # text at the end of statusbars. Use space so that it's never + # used for anything. + sbend = " "; + + topicsbstart = "{sbstart $*}"; + topicsbend = "{sbend $*}"; + + prompt = "[$*] "; + + sb = " %c[%n$*%c]%n"; + sbmode = "(%c+%n$*)"; + sbaway = " (%GzZzZ%n)"; + sbservertag = ":$0 (change with ^X)"; + sbnickmode = "$0"; + + # activity in statusbar + + # ',' separator + sb_act_sep = "%c$*"; + # normal text + sb_act_text = "%c$*"; + # public message + sb_act_msg = "%W$*"; + # hilight + sb_act_hilight = "%M$*"; + # hilight with specified color, $0 = color, $1 = text + sb_act_hilight_color = "$0$1-%n"; +}; diff --git a/irssi/scripts/autorun/hilightwin.pl b/irssi/scripts/autorun/hilightwin.pl new file mode 100644 index 00000000..2591816d --- /dev/null +++ b/irssi/scripts/autorun/hilightwin.pl @@ -0,0 +1,33 @@ +# Print hilighted messages & private messages to window named "hilight" +# for irssi 0.7.99 by Timo Sirainen +use Irssi; +use vars qw($VERSION %IRSSI); +$VERSION = "0.01"; +%IRSSI = ( + authors => "Timo \'cras\' Sirainen", + contact => "tss\@iki.fi", + name => "hilightwin", + description => "Print hilighted messages & private messages to window named \"hilight\"", + license => "Public Domain", + url => "http://irssi.org/", + changed => "2002-03-04T22:47+0100" +); + +sub sig_printtext { + my ($dest, $text, $stripped) = @_; + + if (($dest->{level} & (MSGLEVEL_HILIGHT|MSGLEVEL_MSGS)) && + ($dest->{level} & MSGLEVEL_NOHILIGHT) == 0) { + $window = Irssi::window_find_name('hilight'); + + if ($dest->{level} & MSGLEVEL_PUBLIC) { + $text = $dest->{target}.": ".$text; + } + $window->print($text, MSGLEVEL_CLIENTCRAP) if ($window); + } +} + +$window = Irssi::window_find_name('hilight'); +Irssi::print("Create a window named 'hilight'") if (!$window); + +Irssi::signal_add('print text', 'sig_printtext'); diff --git a/irssi/scripts/autorun/web.pl b/irssi/scripts/autorun/web.pl new file mode 100644 index 00000000..0e413aef --- /dev/null +++ b/irssi/scripts/autorun/web.pl @@ -0,0 +1,243 @@ +# +# $Id: web.pl 47 2004-10-05 18:53:17Z max $ +# 2007-02-25 00:44:00Z don $ +# +# web-irssi v0.2.1 +# A web interface for irssi +# http://max.kellermann.name/projects/web-irssi/ +# http://www.donneker.de/projects/web-irssi/ +# +# (c) 2004 Max Kellermann (max@duempel.org) +# Enhancements (c) 2007 Stefan Hummert (web.irssi@my.donster.de) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +use strict; +use Irssi; + +use Fcntl ':flock'; +use CGI::Util qw(escape unescape); + +use vars qw($VERSION %IRSSI); + +$VERSION = '0.2.1'; +%IRSSI = ( + authors => 'Max Kellermann, Stefan Hummert', + contact => 'max@duempel.org, web.irssi@my.donster.de', + name => 'web-irssi', + description => 'A web interface for irssi', + url => 'http://readme.gzipped.org/projects/web-irssi/', + license => 'GNU GPL v2', + ); + +use vars qw($next_userlist_update); +BEGIN { + $next_userlist_update = 0; +} + +sub poll { + my $state_dir = "$ENV{HOME}/.web-irssi"; + mkdir $state_dir; + + local *FILE; + + my @servers = Irssi::servers; + my %servers = map { + $_->{chatnet} => $_ + } @servers; + + # handle input dir + + my $input_dir = "$state_dir/input"; + + local *DIR; + if (opendir DIR, $input_dir) { + my @names = sort { $a cmp $b } readdir DIR; + closedir DIR; + foreach my $name (@names) { + next unless -f "$input_dir/$name"; + + if ($name =~ /^(\w+):\d+$/) { + # parse file name + + my $server_name = $1; + + my $server = $servers{$server_name}; + next unless defined $server; + + # handle file contents + + if (open FILE, "$input_dir/$name") { + while () { + s/^\s+//; + s/\s+$//; + if (m,^/,) { + # forward server command + $server->command($_); + } elsif (m,^:close +(.*)$,) { + # close an irssi window + my $window = Irssi::window_find_item($1); + $window->destroy + if defined $window; + } + } + + close FILE; + } + } elsif ($name =~ /^:\d+$/) { + if (open FILE, "$input_dir/$name") { + # a global command without chatnet + + while () { + s/^\s+//; + s/\s+$//; + if (m,^:connect +(.*)$,) { + # connect to a chatnet + + Irssi::command_runsub('server', "connect $1", undef, undef); + } + } + + close FILE; + } + } + + unlink "$input_dir/$name"; + } + + closedir DIR; + } + + # create chatnets file, a list of all connected chatnets + + if (open FILE, ">$state_dir/chatnets") { + flock FILE, LOCK_EX; + + foreach my $chatnet (Irssi::chatnets) { + my $server = Irssi::server_find_chatnet($chatnet->{name}); + my $address = defined $server ? $server->{address} : ''; + print FILE "$chatnet->{name}\t$address\n"; + } + + close FILE; + } + + # create windows files, a file for each open window + + my $windows_dir = "$ENV{HOME}/.web-irssi/windows"; + mkdir $windows_dir; + my $userlist_dir = "$ENV{HOME}/.web-irssi/userlist"; + + opendir DIR, $windows_dir; + my %files = map { -f "$windows_dir/$_" ? ( $_ => 1 ) : () } readdir DIR; + closedir DIR; + #if (exists $files{"."}) delete $files{"."}; + delete $files{"."} if exists $files{"."}; + delete $files{".."} if exists $files{".."}; + + my $userlist_update = time >= $next_userlist_update; + if ($userlist_update) { + $next_userlist_update = time + 60; + # Irssi::print("next_userlst_upd: $next_userlist_update " . time . " time userlst_upd: $userlist_update", MSGLEVEL_CRAP); + mkdir $userlist_dir + } + + foreach my $server (@servers) { + my $server_ename = escape($server->{chatnet}); + + my $min_time = time - 60; + + # write channel files + + my @channels = $server->channels; + foreach my $channel (@channels) { + # write channel properties to file + + my $ename = escape($channel->{name}); + my $fname = "$server_ename:$ename"; + my $mtime = 0; + if (exists $files{$fname}) { + delete $files{$fname}; + $mtime = (stat "$windows_dir/$fname")[9]; + } + # Irssi::print("$windows_dir/$fname mtime: $mtime, min_time: $min_time", MSGLEVEL_CRAP); + + local *FILE; + if ($userlist_update) { + # update userlist file + # Irssi::print("in userlist_update to $userlist_dir/$fname", MSGLEVEL_CRAP); + + open FILE, ">$userlist_dir/$fname"; + flock FILE, LOCK_EX; + foreach my $nick ($channel->nicks) { + print FILE "$nick->{nick}\t$nick->{host}\t$nick->{realname}\n"; + } + } + + next if $mtime > $min_time; + open FILE, ">$windows_dir/$fname"; + flock FILE, LOCK_EX; + foreach my $key (qw(topic topic_by)) { + print FILE "$key=$channel->{$key}\n" + if defined $channel->{$key}; + } + print FILE "ownnick=$channel->{ownnick}{nick}\n"; + } + + # write query files + + my @queries = $server->queries; + foreach my $query (@queries) { + # write query properties to file + + my $ename = escape($query->{name}); + my $fname = "$server_ename:$ename"; + if (exists $files{$fname}) { + delete $files{$fname}; + } else { + local *FILE; + open FILE, ">$windows_dir/$fname"; + close FILE; + } + } + } + + # remove all stale window files + + foreach my $ename (keys %files) { + # Irssi::print("unlinked $windows_dir/$ename", MSGLEVEL_CRAP); + unlink("$windows_dir/$ename"); + } + + # remove all stale userlist files + + if ($userlist_update) { + if (opendir DIR, $userlist_dir) { + my $min_time = time - 7201; + while (my $name = readdir DIR) { + my $filename = "$userlist_dir/$name"; + my $mtime = (stat $filename)[9]; + if ( $mtime < $min_time and $filename != "." and $filename != ".." ) + { + unlink $filename; + # Irssi::print("unlinked $filename mtime: $mtime min_time: $min_time ", MSGLEVEL_CRAP); + } + } + closedir DIR; + } + } +} + +Irssi::timeout_add(1000, "poll", undef); diff --git a/irssi/scripts/twirssi.oauth b/irssi/scripts/twirssi.oauth new file mode 100644 index 00000000..96e7e2aa --- /dev/null +++ b/irssi/scripts/twirssi.oauth @@ -0,0 +1,2 @@ +mveldth@Twitter 14447826-sokCKpXFMm17wv0lOxwxq1X89qQ4MGOe49GMe9ioN Iu0uz7Lx8fcqEV5IIp4iq4wvyvpwhW1oeIaovFTOM +"mveldth"@Twitter 14447826-sokCKpXFMm17wv0lOxwxq1X89qQ4MGOe49GMe9ioN Iu0uz7Lx8fcqEV5IIp4iq4wvyvpwhW1oeIaovFTOM diff --git a/mrconfig b/mrconfig new file mode 100644 index 00000000..a82c3993 --- /dev/null +++ b/mrconfig @@ -0,0 +1,39 @@ +[oversight/agent] +checkout = git clone 'git@github.com:oversight/agent.git' 'agent' + +[oversight/docs] +checkout = git clone 'git@github.com:oversight/docs.git' 'docs' + + +[rgoc/deployer] +checkout = git clone 'git@github.com:roqua/deployer.git' 'deployer' +[rgoc/roqua] +checkout = git clone 'git@github.com:roqua/roqua.git' 'roqua' +[rgoc/quby] +checkout = git clone 'git@github.com:roqua/quby_engine.git' 'quby' +[rgoc/core] +checkout = git clone 'git@github.com:roqua/core.git' 'core' +[rgoc/client_portal] +checkout = git clone 'git@github.com:roqua/client_portal.git' 'client_portal' +[rgoc/questionnaires] +checkout = git clone 'git@github.com:roqua/questionnaires.git' 'questionnaires' +[rgoc/patty] +checkout = git clone 'git@github.com:roqua/patty.git' 'patty' +[rgoc/quby_admin] +checkout = git clone 'git@github.com:roqua/quby_admin.git' 'quby_admin' +[rgoc/sysop] +checkout = git clone 'git@github.com:roqua/sysop.git' 'sysop' +[rgoc/devop] +checkout = git clone 'git@github.com:roqua/devop.git' 'devop' +[rgoc/chef] +checkout = git clone 'git@github.com:roqua/chef.git' 'chef' +[rgoc/roqua-support] +checkout = git clone 'git@github.com:roqua/roqua-support.git' 'roqua-support' +[rgoc/authmac] +checkout = git clone 'git@github.com:roqua/authmac.git' 'authmac' +[rgoc/medo] +checkout = git clone 'git@github.com:roqua/medo.git' 'medo' +[rgoc/healthy] +checkout = git clone 'git@github.com:roqua/healthy.git' 'healthy' +[rgoc/status] +checkout = git clone 'git@github.com:roqua/status.git' 'status' diff --git a/rcrc b/rcrc new file mode 100644 index 00000000..f670ad13 --- /dev/null +++ b/rcrc @@ -0,0 +1,4 @@ +DOTFILES_DIRS="/Users/marten/dotfiles /home/marten/dotfiles" +UNDOTTED="bin" +SYMLINK_DIRS="bin config/* doom.d emacs.d git irssi vim zsh" +EXCLUDES="Brewfile README.md SHARPEN.md Rakefile emacs-old emacs-spacemacs sublime-text-2 sublime-text-3" diff --git a/ssh/.gitignore b/ssh/.gitignore new file mode 100644 index 00000000..f811513f --- /dev/null +++ b/ssh/.gitignore @@ -0,0 +1,3 @@ +id_* +known_hosts +github_rsa* \ No newline at end of file diff --git a/ssh/authorized_keys b/ssh/authorized_keys new file mode 100644 index 00000000..5685a83f --- /dev/null +++ b/ssh/authorized_keys @@ -0,0 +1,3 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAujyN4aDueYwlm10hd1WrROqnPApJf+0epFrUCRHLUprLWMq/DkimGvEhKSqTLQhwR19Iy7INCv71qzkF+GASF7PTzSQfFmRC9KFUk2BX4C9GNto2K/EyZG3jvIENtIdI6nb9gHY55WB/R/5tGRoRNbIpVZBWlxLXTHG4NGKG7KE= marten@Tigger.local +ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA4HY1Kxm0uLvO8LzdgWCw9wjkztjTIVFSHQ6tpX5Yjhkl+TimPsoKeDRHymqceSOmsd/qTYeIaptKBFTP9+xohwFSFxinwo+hUGw7G2dR56aXKW7AwTSOCntmWDs3YRjHXhb9aS0zgf69Nzej2Mz7XP6IcGxbswD+csC5k7DwenrygDT5vfl5G1E7DP5F9hdJOX+B+CBr5tI4k3fLE/Pe6sW0I/fTJ5KGYR1fjGTXMegzJhPlKH9v4rLVlBRCAil/9XL0SOSQyVNmLnzHJNndFXGnVCL8t4cinb3xzuWLU//QJkb1g2t99oYTn0X6FaavdZy6qlu3AMUDExiHb/6QTQ== marten@marten +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDP9uBkDmYNTqe81ZkP7cJAYXBwgBnqTW/rWaAMwC0kXav7OtrI7XNIzur2b+bADNUs6crGxseQeuzc7Fj/FANBHNKjJfAtmBi5F0aB9NFvXPqPXFOpf4OlE1+IZyLE9x+uBr9TF/xED/7c7DckcSFS70uoDC04XFWg/GCA7I4DVGurHv9Hv2c9x7fiTSs6jzUjm7h1fSoWszQI4MFJziU+7JIWEpKI0lbG3TF1gmoaJOYoPbBQLQzahmpO3kp70hVzVe9fkdDkAp1Z2Jcx1eM0p3o6dmElOnqJqzN+dFUBuiE1Vvx4yrnIvdJZimbD4zaywIH427KRyONjf/I9YAJ6jWClPl06Msu6KTlRPNa9tF7TUp4xTN6rl5eTtsO5XtXujwCy9PaltsytnlRh20Lt4xEAi7isWB7pLnXxNvDDRj/tbHNE/Sl2NCzcvB3XAKDCG8ohkikAeBigUB1dDsvu5kz+ox+jXCGR26z38v0BadQuPoAgs0l+xFMpL2XVmi7qY6gjqUt2bLkk1+omrLgnMDILUSrVKdQKt/5YEiLLF1mypqE1x7JZHDSJ6zDPAsNuWGe76F40WvCBxcx721p7v4V2kdncT4xM/O8MuOPi85CHFUWgWnGaVTKKzzhtRTvyYsdwy55j0ieQSM5XJpaZQjkA0ztpDNmeN11lcapkOQ== /Users/marten/.ssh/id_rsa diff --git a/ssh/config b/ssh/config new file mode 100644 index 00000000..3a4dac97 --- /dev/null +++ b/ssh/config @@ -0,0 +1,732 @@ +Host * + AddKeysToAgent yes + +Host oxford_ssh_gateway +Hostname ssh.physics.ox.ac.uk +User veldthuis +ForwardX11 yes +GSSAPIDelegateCredentials true +GSSAPIAuthentication true + +Host w +Hostname marten-desktop.nat.physics.ox.ac.uk +User marten +ProxyCommand ssh oxford_ssh_gateway nc -q 0 marten-desktop.nat.physics.ox.ac.uk 22 +ForwardX11 yes +GSSAPIDelegateCredentials true +GSSAPIAuthentication true + +# Zooniverse +Host *.compute-1.amazonaws.com + User ubuntu + IdentityFile /Users/marten/.ssh/zooniverse_1.pem + UserKnownHostsFile /dev/null + StrictHostKeyChecking=no + +Host heroku.com + HostName heroku.com + IdentityFile ~/.ssh/id_rsa_heroku + User git + +# My workstations +Host h + Hostname home.veldthuis.com + User marten + UserKnownHostsFile /dev/null + StrictHostKeyChecking no + +Host s + Hostname home.veldthuis.com + User marten + +Host erik + Hostname saffier.no-ip.org + User marten + +# RGOc + +Host x0 + Hostname 10.254.0.200 + User root + +Host x1 + Hostname 10.254.0.201 + User root + +Host x2 + Hostname 10.254.0.202 + User root + +Host x3 + Hostname 10.254.0.203 + User root + +Host x4 + Hostname 10.254.0.204 + User root + +Host x5 + Hostname 10.254.0.205 + User root + +Host x6 + Hostname 10.254.0.206 + User root + + +Host x0gateway + Hostname r0.rgoc.rug.nl + Port 995 + User mveldthuis + +Host x1gateway + Hostname 129.125.147.99 + Port 995 + User vyatta + +Host x2gateway + Hostname 129.125.147.100 + Port 995 + User vyatta + +Host backup + ProxyCommand ssh veldthuis@rsync.rgoc.nl nc -w 1 127.0.0.1 873 + +Host x0repo + ProxyCommand ssh x2gateway nc -w 1 10.10.42.100 22 + User root + + +Host vyatta-beheerlan + Hostname 129.125.147.11 + +Host x0git + Hostname git.roqua.nl + User git + +Host x0irc + ProxyCommand ssh x0gateway nc -w 1 10.10.10.201 22 + User root + +Host x0ci + Hostname 10.254.0.98 + User marten + +Host dell-chef + Hostname 10.254.0.101 + #ProxyCommand ssh r3 nc -w 1 10.20.250.251 22 + User marten + +Host git.roqua + Hostname localhost + Port 2205 + User git + +Host metrics + Hostname 10.20.250.1 + User marten + +######################################################################### +# Applicatie machines +######################################################################### +Host x3backup + #ProxyCommand ssh x3gateway nc -w 1 10.20.200.11 + Hostname 10.220.200.11 + User roquabackup + +Host x4logger + Hostname 10.20.200.21 + User marten + +Host x4backup + Hostname 10.20.200.12 + User root + +Host x3gateway + Hostname r3.rgoc.rug.nl + Port 995 + User mveldthuis + +Host r5 + Hostname r5.rgoc.rug.nl + Port 995 + User mveldthuis + +Host x3mirth + Hostname 10.220.0.101 + User root + +Host x3apache + Hostname 10.220.1.1 + User root + +Host x4apache + Hostname 10.220.1.2 + User root + +Host prod_mongo1 + Hostname 10.20.0.21 + User marten + +Host prod_mongo2 + Hostname 10.20.0.22 + User marten + +Host prod_mongo3 + Hostname 10.20.0.23 + User marten + +Host stag_mongo1 + Hostname 10.10.0.21 + User marten + +Host stag_mongo2 + Hostname 10.10.0.22 + User marten + +Host stag_mongo3 + Hostname 10.10.0.23 + User marten + +Host app_nieuw + Hostname 10.20.0.251 + User deploy + +Host app_research_1 + Hostname 10.20.20.1 + User deploy +Host app_research_2 + Hostname 10.20.20.2 + User deploy + +Host stag_research_1 + Hostname 10.10.20.1 + User deploy +Host stag_research_2 + Hostname 10.10.20.2 + User deploy + +Host app_demo_1 + Hostname 10.20.19.1 + User deploy +Host app_demo_2 + Hostname 10.20.19.2 + User deploy + +Host app_mckesson_1 + Hostname 10.20.8.1 + User deploy +Host app_mckesson_2 + Hostname 10.20.8.2 + User deploy + +Host app_ucp_1 + Hostname 10.20.11.1 + User deploy +Host app_ucp_2 + Hostname 10.20.11.2 + User deploy + +Host app_meerkanten_1 + Hostname 10.20.12.1 + User deploy +Host app_meerkanten_2 + Hostname 10.20.12.2 + User deploy + +Host app_symfora_1 + Hostname 10.20.13.1 + User deploy +Host app_symfora_2 + Hostname 10.20.13.2 + User deploy + +Host app_ggzdrenthe_1 + Hostname 10.20.14.1 + User deploy +Host app_ggzdrenthe_2 + Hostname 10.20.14.2 + User deploy + +Host app_ggzfriesland_1 + Hostname 10.20.15.1 + User deploy +Host app_ggzfriesland_2 + Hostname 10.20.15.2 + User deploy + +Host app_lentis_1 + Hostname 10.20.16.1 + User deploy +Host app_lentis_2 + Hostname 10.20.16.2 + User deploy + +Host app_ggzoostbrabant_1 + Hostname 10.20.17.1 + User deploy +Host app_ggzoostbrabant_2 + Hostname 10.20.17.2 + User deploy + +Host app_accare_1 + Hostname 10.20.18.1 + User deploy +Host app_accare_2 + Hostname 10.20.18.2 + User deploy + +################################ STAGING +# +Host stag_demo_1 + Hostname 10.10.19.1 + User deploy +Host stag_demo_2 + Hostname 10.10.19.2 + User deploy + +Host stag_rgoc_1 + Hostname 10.10.10.1 + User deploy +Host stag_rgoc_2 + Hostname 10.10.10.2 + User deploy + +Host stag_mckesson_1 + Hostname 10.10.8.1 + User deploy +Host stag_mckesson_2 + Hostname 10.10.8.2 + User deploy + +Host stag_ucp_1 + Hostname 10.10.11.1 + User deploy +Host stag_ucp_2 + Hostname 10.10.11.2 + User deploy + +Host stag_meerkanten_1 + Hostname 10.10.12.1 + User deploy +Host stag_meerkanten_2 + Hostname 10.10.12.2 + User deploy + +Host stag_symfora_1 + Hostname 10.10.13.1 + User deploy +Host stag_symfora_2 + Hostname 10.10.13.2 + User deploy + +Host stag_ggzdrenthe_1 + Hostname 10.10.14.1 + User deploy +Host stag_ggzdrenthe_2 + Hostname 10.10.14.2 + User deploy + +Host stag_ggzfriesland_1 + Hostname 10.10.15.1 + User deploy +Host stag_ggzfriesland_2 + Hostname 10.10.15.2 + User deploy + +Host stag_lentis_1 + Hostname 10.10.16.1 + User deploy +Host stag_lentis_2 + Hostname 10.10.16.2 + User deploy + +Host stag_ggzoostbrabant_1 + Hostname 10.10.17.1 + User deploy +Host stag_ggzoostbrabant_2 + Hostname 10.10.17.2 + User deploy + +Host stag_accare_1 + Hostname 10.10.18.1 + User deploy +Host stag_accare_2 + Hostname 10.10.18.2 + User deploy + +########################## DELL +# +Host dell_demo_1 + ProxyCommand ssh r5 nc -w 1 10.20.19.1 22 + User root + +Host dell_demo_2 + ProxyCommand ssh r5 nc -w 1 10.20.19.2 22 + User root + + +# University + +Host uni +HostName ssh.wing.rug.nl +User csg2053 + +Host fmf +HostName fmf.nl +User marten + +# Hosted services + +Host comox +HostName comox.textdrive.com +User mveldthuis + +Host ss +HostName strongspace.com +User txd-mv + + +# BEGIN ROQUA AUTO-GENERATED SSH CONFIG +Host backup25 +Hostname 129.125.147.25 +Host beheer26 +Hostname 129.125.147.26 +Host aws_monitor +Hostname 54.77.139.69 +Host testserver3 +Hostname 10.240.199.3 +Host rplay1 +Hostname 129.125.147.91 +Host rplay2 +Hostname 129.125.147.92 +Host playground-ansible +Hostname 10.240.253.4 +Host playground-lb1 +Hostname 10.240.1.3 +Host playground-lb2 +Hostname 10.240.1.4 +Host playground-mysql1 +Hostname 10.240.2.11 +Host playground-mysql2 +Hostname 10.240.2.12 +Host playground-percona3 +Hostname 10.240.2.13 +Host playground-percona4 +Hostname 10.240.2.14 +Host playground-psql1 +Hostname 10.240.4.11 +Host playground-psql2 +Hostname 10.240.4.12 +Host playground-psqltest +Hostname 10.240.4.200 +Host playground-nfs1 +Hostname 10.240.5.11 +Host playground-rom-web1 +Hostname 10.240.10.11 +Host playground-rom-web2 +Hostname 10.240.10.12 +Host playground-rom-web3 +Hostname 10.240.10.13 +Host playground-rom-util1 +Hostname 10.240.11.11 +Host playground-grip-web1 +Hostname 10.240.20.11 +Host playground-core-web1 +Hostname 10.240.30.11 +Host playground-core-web2 +Hostname 10.240.30.12 +Host playground-core-web3 +Hostname 10.240.30.13 +Host playground-medo-web1 +Hostname 10.240.40.11 +Host playground-moodmapp-web1 +Hostname 10.240.60.11 +Host playground-mirth +Hostname 10.240.0.100 +Host playground-screensmart1 +Hostname 10.240.90.11 +Host playground-opencpu1 +Hostname 10.240.50.11 +Host ns1 +Hostname 129.125.147.1 +Host ns2 +Hostname 129.125.147.2 +Host ns3 +Hostname 129.125.147.3 +Host rpvpn1 +Hostname 10.20.0.1 +Host rpvpn2 +Hostname 10.20.0.2 +Host rpweb1 +Hostname 10.20.0.11 +Host rpweb2 +Hostname 10.20.0.12 +Host rpgn +Hostname 10.20.0.3 +Host prod-lb1 +Hostname 10.220.1.1 +Host prod-lb2 +Hostname 10.220.1.2 +Host prod-lbbackup +Hostname 10.220.1.9 +Host prod-percona1 +Hostname 10.220.2.11 +Host prod-percona2 +Hostname 10.220.2.12 +Host prod-postgres3 +Hostname 10.220.4.13 +Host prod-postgres4 +Hostname 10.220.4.14 +Host prod-nfs1 +Hostname 10.220.5.11 +Host prod-rom-web1 +Hostname 10.220.10.11 +Host prod-rom-web2 +Hostname 10.220.10.12 +Host prod-rom-web3 +Hostname 10.220.10.13 +Host prod-rom-util1 +Hostname 10.220.11.11 +Host prod-rom-util2 +Hostname 10.220.11.12 +Host prod-grip-web1 +Hostname 10.220.20.11 +Host prod-grip-web2 +Hostname 10.220.20.12 +Host prod-grip-web3 +Hostname 10.220.20.13 +Host prod-grip-util1 +Hostname 10.220.21.11 +Host prod-core-web1 +Hostname 10.220.30.11 +Host prod-core-web2 +Hostname 10.220.30.12 +Host prod-core-web3 +Hostname 10.220.30.13 +Host prod-core-util1 +Hostname 10.220.31.11 +Host prod-medo-web1 +Hostname 10.220.40.11 +Host prod-medo-web2 +Hostname 10.220.40.12 +Host prod-medo-web3 +Hostname 10.220.40.13 +Host prod-screensmart-web1 +Hostname 10.220.90.11 +Host prod-screensmart-web2 +Hostname 10.220.90.12 +Host mirth +Hostname 10.220.0.101 +Host prod-mirth +Hostname 10.220.12.1 +Host prod-moodmapp-web1 +Hostname 10.220.60.11 +Host prod-moodmapp-web2 +Hostname 10.220.60.12 +Host prod-moodmapp-web3 +Hostname 10.220.60.13 +Host prod-leefplezier-web1 +Hostname 10.220.120.11 +Host prod-leefplezier-web2 +Hostname 10.220.120.12 +Host prod-leefplezier-web3 +Hostname 10.220.120.13 +Host prod-nemesis-web1 +Hostname 10.220.100.11 +Host prod-nemesis-web2 +Hostname 10.220.100.12 +Host prod-nemesis-web3 +Hostname 10.220.100.13 +Host prod-nemesis-util1 +Hostname 10.220.101.11 +Host prod-nemesis-util2 +Hostname 10.220.101.12 +Host prod-transid-web1 +Hostname 10.220.110.11 +Host prod-transid-web2 +Hostname 10.220.110.12 +Host prod-transid-web3 +Hostname 10.220.110.13 +Host prod-opencpu1 +Hostname 10.220.50.11 +Host prod-opencpu2 +Hostname 10.220.50.12 +Host prod-fizzy-web1 +Hostname 10.220.80.11 +Host prod-fizzy-util +Hostname 10.220.81.11 +Host dellbackup1 +Hostname 10.220.200.11 +Host prod-metrics +Hostname 10.20.250.1 +Host lograrian +Hostname 10.254.0.105 +Host backup1 +Hostname 10.254.200.11 +Host switch-to +Hostname 10.254.0.100 +Host migratietest1 +Hostname 10.254.0.100 +Host ra1 +Hostname 10.10.0.1 +Host ra2 +Hostname 10.10.0.2 +Host stag-lb1 +Hostname 10.210.1.1 +Host stag-lb2 +Hostname 10.210.1.2 +Host stag-percona1 +Hostname 10.210.2.11 +Host stag-percona2 +Hostname 10.210.2.12 +Host stag-percona3 +Hostname 10.210.2.13 +Host stag-percona4 +Hostname 10.210.2.14 +Host stag-postgres1 +Hostname 10.210.4.11 +Host stag-postgres2 +Hostname 10.210.4.12 +Host stag-postgres3 +Hostname 10.210.4.13 +Host stag-postgres4 +Hostname 10.210.4.14 +Host stag-nfs1 +Hostname 10.210.5.11 +Host stag-rom-web1 +Hostname 10.210.10.11 +Host stag-rom-web2 +Hostname 10.210.10.12 +Host stag-rom-web3 +Hostname 10.210.10.13 +Host stag-rom-util1 +Hostname 10.210.11.11 +Host stag-rom-util2 +Hostname 10.210.11.12 +Host stag-grip-web1 +Hostname 10.210.20.11 +Host stag-grip-web2 +Hostname 10.210.20.12 +Host stag-grip-web3 +Hostname 10.210.20.13 +Host stag-grip-util1 +Hostname 10.210.21.11 +Host stag-core-web1 +Hostname 10.210.30.11 +Host stag-core-web2 +Hostname 10.210.30.12 +Host stag-core-web3 +Hostname 10.210.30.13 +Host stag-core-util1 +Hostname 10.210.31.11 +Host stag-medo-web1 +Hostname 10.210.40.11 +Host stag-medo-web2 +Hostname 10.210.40.12 +Host stag-medo-web3 +Hostname 10.210.40.13 +Host stag-moodmapp-web1 +Hostname 10.210.60.11 +Host stag-moodmapp-web2 +Hostname 10.210.60.12 +Host stag-moodmapp-web3 +Hostname 10.210.60.13 +Host stag-leefplezier-web1 +Hostname 10.210.120.11 +Host stag-leefplezier-web2 +Hostname 10.210.120.12 +Host stag-leefplezier-web3 +Hostname 10.210.120.13 +Host stag-nemesis-web1 +Hostname 10.210.100.11 +Host stag-nemesis-web2 +Hostname 10.210.100.12 +Host stag-nemesis-web3 +Hostname 10.210.100.13 +Host stag-nemesis-util1 +Hostname 10.210.101.11 +Host stag-nemesis-util2 +Hostname 10.210.101.12 +Host stag-transid-web1 +Hostname 10.210.110.11 +Host stag-transid-web2 +Hostname 10.210.110.12 +Host stag-transid-web3 +Hostname 10.210.110.13 +Host stag-opencpu1 +Hostname 10.210.50.11 +Host stag-opencpu2 +Hostname 10.210.50.12 +Host stag-fizzy-web1 +Hostname 10.210.80.11 +Host stag-fizzy-util +Hostname 10.210.81.11 +Host stag-screensmart-web1 +Hostname 10.210.90.11 +Host stag-screensmart-web2 +Hostname 10.210.90.12 +Host logstash +Hostname 10.254.0.102 +Host stag-backup +Hostname 10.220.200.100 +Host testserver +Hostname 10.129.0.1 +Host win-r1-old +Hostname 129.125.147.79 +Host windows_x1mssql +Hostname 10.29.0.11 +Host windows_x2mssql +Hostname 10.29.0.12 +Host windows_x2IIS +Hostname 10.29.0.30 +Host windows_backup +Hostname 10.29.0.100 +Host win-rp1 +Hostname 129.125.147.90 +Host win-rsync +Hostname 10.229.1.11 +Host r0 +Hostname 129.125.147.90 +Host monitoring +Hostname 10.254.0.103 +Host qubyadmin +Hostname 129.125.147.80 +Host docker +Hostname 10.254.0.106 +Host docker2 +Hostname 10.254.0.107 +Host docker3 +Hostname 10.254.0.108 +Host redirect_test +Hostname 129.125.147.3 +Host chefserver1 +Hostname 10.250.0.11 +Host chefserver2 +Hostname 10.250.0.12 +Host piwik +Hostname 10.250.0.1 +Host vpngate +Hostname 10.254.254.199 +Host redirect +Hostname 129.125.147.32 +Host consul1 +Hostname 10.250.10.11 +Host consul2 +Hostname 10.250.10.12 +Host consul3 +Hostname 10.250.10.13 +Host consulclient +Hostname 10.250.11.11 +# END ROQUA AUTO-GENERATED SSH CONFIG}}}}} +# +Host beheer11 +Hostname 129.125.147.11 +Port 22 +IdentityFile ~/.ssh/id_rsa_sshuttle + +Host beheer12 +Hostname 129.125.147.12 +Port 22 +IdentityFile ~/.ssh/id_rsa_sshuttle + diff --git a/sublime-text-2/.DS_Store b/sublime-text-2/.DS_Store new file mode 100644 index 00000000..8eecf06d Binary files /dev/null and b/sublime-text-2/.DS_Store differ diff --git a/sublime-text-2/.gitignore b/sublime-text-2/.gitignore new file mode 100644 index 00000000..00004d08 --- /dev/null +++ b/sublime-text-2/.gitignore @@ -0,0 +1,8 @@ +Settings/License.sublime_license +Packages/User/SublimeLinter.sublime-settings +Packages/User/RubyTest.last-run +Installed\ Packages +Pristine\ Packages +Packages/* +Backup +Settings diff --git a/sublime-text-2/Packages/User/.DS_Store b/sublime-text-2/Packages/User/.DS_Store new file mode 100644 index 00000000..5008ddfc Binary files /dev/null and b/sublime-text-2/Packages/User/.DS_Store differ diff --git a/sublime-text-2/Packages/User/Base File.sublime-settings b/sublime-text-2/Packages/User/Base File.sublime-settings new file mode 100644 index 00000000..9720f40a --- /dev/null +++ b/sublime-text-2/Packages/User/Base File.sublime-settings @@ -0,0 +1,5 @@ +// Settings in here override those in "Default/Base File.sublime-settings", and +// are overridden in turn by file type specific settings. Place your settings +// here, to ensure they're preserved when upgrading. +{ +} diff --git a/sublime-text-2/Packages/User/Default (Linux).sublime-keymap b/sublime-text-2/Packages/User/Default (Linux).sublime-keymap new file mode 100644 index 00000000..0d4f101c --- /dev/null +++ b/sublime-text-2/Packages/User/Default (Linux).sublime-keymap @@ -0,0 +1,2 @@ +[ +] diff --git a/sublime-text-2/Packages/User/Default (OSX).sublime-keymap b/sublime-text-2/Packages/User/Default (OSX).sublime-keymap new file mode 100644 index 00000000..726c0e4f --- /dev/null +++ b/sublime-text-2/Packages/User/Default (OSX).sublime-keymap @@ -0,0 +1,8 @@ +[ + // run tests + { + "keys": ["super+r"], + "command": "run_tests", + "args": {"single": false} + } +] \ No newline at end of file diff --git a/sublime-text-2/Packages/User/Default (Windows).sublime-keymap b/sublime-text-2/Packages/User/Default (Windows).sublime-keymap new file mode 100644 index 00000000..0d4f101c --- /dev/null +++ b/sublime-text-2/Packages/User/Default (Windows).sublime-keymap @@ -0,0 +1,2 @@ +[ +] diff --git a/sublime-text-2/Packages/User/Distraction Free.sublime-settings b/sublime-text-2/Packages/User/Distraction Free.sublime-settings new file mode 100644 index 00000000..1ef894ff --- /dev/null +++ b/sublime-text-2/Packages/User/Distraction Free.sublime-settings @@ -0,0 +1,3 @@ +{ + "wrap_width": 84 +} \ No newline at end of file diff --git a/sublime-text-2/Packages/User/Global.sublime-settings b/sublime-text-2/Packages/User/Global.sublime-settings new file mode 100644 index 00000000..c3dc991d --- /dev/null +++ b/sublime-text-2/Packages/User/Global.sublime-settings @@ -0,0 +1,4 @@ +// Place user-specific overrides in this file, to ensure they're preserved +// when upgrading +{ +} diff --git a/sublime-text-2/Packages/User/Latex-Figure.sublime-snippet b/sublime-text-2/Packages/User/Latex-Figure.sublime-snippet new file mode 100644 index 00000000..cddb83c2 --- /dev/null +++ b/sublime-text-2/Packages/User/Latex-Figure.sublime-snippet @@ -0,0 +1,14 @@ + + + + incl + + + diff --git a/sublime-text-2/Packages/User/Latex-LstInline.sublime-snippet b/sublime-text-2/Packages/User/Latex-LstInline.sublime-snippet new file mode 100644 index 00000000..26bdb900 --- /dev/null +++ b/sublime-text-2/Packages/User/Latex-LstInline.sublime-snippet @@ -0,0 +1,9 @@ + + + + \| + + + diff --git a/sublime-text-2/Packages/User/Package Control.sublime-settings b/sublime-text-2/Packages/User/Package Control.sublime-settings new file mode 100644 index 00000000..31b07903 --- /dev/null +++ b/sublime-text-2/Packages/User/Package Control.sublime-settings @@ -0,0 +1,37 @@ +{ + "auto_upgrade_last_run": null, + "installed_packages": + [ + "Alignment", + "All Autocomplete", + "CoffeeScript", + "Color Highlighter", + "DetectSyntax", + "Emacs-like Modelines", + "Gist", + "Git", + "GitGutter", + "Gitignore", + "Handlebars", + "LaTeXTools", + "LESS", + "Markdown Preview", + "Package Control", + "Print to HTML", + "RSpec", + "RuboCop", + "Ruby Hash Converter", + "RubyTest", + "SCSS", + "SideBarEnhancements", + "SideBarGit", + "SublimeLinter", + "SmartMarkdown", + "Theme - Nexus", + "Theme - Soda", + "Tomorrow Color Schemes", + "Vintage Numbers", + "VintageEx", + "WordCount" + ] +} diff --git a/sublime-text-2/Packages/User/Preferences.sublime-settings b/sublime-text-2/Packages/User/Preferences.sublime-settings new file mode 100644 index 00000000..d35ce557 --- /dev/null +++ b/sublime-text-2/Packages/User/Preferences.sublime-settings @@ -0,0 +1,70 @@ +{ + "bold_folder_labels": true, + "color_scheme": "Packages/Color Scheme - Default/Monokai.tmTheme", + "file_exclude_patterns": + [ + ".DS_Store", + ".tags*", + "*.pyc", + "*.pyo", + "*.exe", + "*.dll", + "*.obj", + "*.o", + "*.a", + "*.lib", + "*.so", + "*.dylib", + "*.ncb", + "*.sdf", + "*.suo", + "*.pdb", + "*.idb", + "*.class", + "*.psd", + "*.db", + "*.bbl", + "*.blg", + "*.glo", + "*.out", + "*.toc", + "*.aux" + ], + "folder_exclude_patterns": + [ + "data", + ".git", + ".svn", + ".hg", + "CVS", + ".sass-cache", + ".bundle", + "bundle", + ".rbx", + "tmp" + ], + "font_size": 13.0, + "highlight_line": true, + "highlight_modified_tabs": true, + "hot_exit": false, + "ignored_packages": + [ + "Vintage" + ], + "ignored_words": + [ + "Quby", + "constantize" + ], + "remember_open_files": false, + "rulers": + [ + 80, + 120 + ], + "spell_check": false, + "tab_size": 2, + "translate_tabs_to_spaces": true, + "trim_trailing_white_space_on_save": true, + "use_simple_full_screen": true +} diff --git a/sublime-text-2/Packages/User/RubyTest.sublime-settings b/sublime-text-2/Packages/User/RubyTest.sublime-settings new file mode 100644 index 00000000..959757c3 --- /dev/null +++ b/sublime-text-2/Packages/User/RubyTest.sublime-settings @@ -0,0 +1,10 @@ +{ + "erb_exec": "erb", + "ruby_unit_exec": "ruby", + "ruby_cucumber_exec": "bundle exec cucumber -f pretty", + "ruby_rspec_exec": "bundle exec rspec", + + "ruby_unit_folder": "test", + "ruby_cucumber_folder": "features", + "ruby_rspec_folder": "spec" +} diff --git a/sublime-text-3/.gitignore b/sublime-text-3/.gitignore new file mode 100644 index 00000000..57976fe9 --- /dev/null +++ b/sublime-text-3/.gitignore @@ -0,0 +1,8 @@ +Local/* +Index/* +Cache/* +Installed\ Packages +Pristine\ Packages +Packages/* +Backup +Settings diff --git a/sublime-text-3/Packages/User/Preferences.sublime-settings b/sublime-text-3/Packages/User/Preferences.sublime-settings new file mode 100644 index 00000000..b6a73de2 --- /dev/null +++ b/sublime-text-3/Packages/User/Preferences.sublime-settings @@ -0,0 +1,74 @@ +{ + "bold_folder_labels": true, + "color_scheme": "Packages/User/SublimeLinter/Monokai Bright (SL).tmTheme", + "detect_indentation": false, + "ensure_newline_at_eof_on_save": true, + "file_exclude_patterns": + [ + ".DS_Store", + ".tags*", + "*.pyc", + "*.pyo", + "*.exe", + "*.dll", + "*.obj", + "*.o", + "*.a", + "*.lib", + "*.so", + "*.dylib", + "*.ncb", + "*.sdf", + "*.suo", + "*.pdb", + "*.idb", + "*.class", + "*.psd", + "*.db", + "*.bbl", + "*.blg", + "*.glo", + "*.out", + "*.toc", + "*.aux", + "*.log" + ], + "folder_exclude_patterns": + [ + ".git", + ".svn", + ".hg", + "CVS", + ".sass-cache", + ".bundle", + "bundle", + ".rbx", + "tmp", + ".idea" + ], + "font_size": 14, + "highlight_line": true, + "highlight_modified_tabs": true, + "hot_exit": false, + "ignored_packages": + [ + "Vintage" + ], + "ignored_words": + [ + "Quby", + "constantize" + ], + "index_files": true, + "remember_open_files": false, + "rulers": + [ + 80, + 120 + ], + "spell_check": false, + "tab_size": 2, + "translate_tabs_to_spaces": true, + "trim_trailing_white_space_on_save": true, + "use_simple_full_screen": true +} diff --git a/vim/.gitignore b/vim/.gitignore new file mode 100644 index 00000000..cc0be59d --- /dev/null +++ b/vim/.gitignore @@ -0,0 +1,2 @@ +bundle +plugged diff --git a/vim/NERDTreeBookmarks b/vim/NERDTreeBookmarks new file mode 100644 index 00000000..e69de29b diff --git a/vim/autoload/plug.vim b/vim/autoload/plug.vim new file mode 100644 index 00000000..9dd02c06 --- /dev/null +++ b/vim/autoload/plug.vim @@ -0,0 +1,2504 @@ +" vim-plug: Vim plugin manager +" ============================ +" +" Download plug.vim and put it in ~/.vim/autoload +" +" curl -fLo ~/.vim/autoload/plug.vim --create-dirs \ +" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim +" +" Edit your .vimrc +" +" call plug#begin('~/.vim/plugged') +" +" " Make sure you use single quotes +" +" " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align +" Plug 'junegunn/vim-easy-align' +" +" " Any valid git URL is allowed +" Plug 'https://github.com/junegunn/vim-github-dashboard.git' +" +" " Multiple Plug commands can be written in a single line using | separators +" Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets' +" +" " On-demand loading +" Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' } +" Plug 'tpope/vim-fireplace', { 'for': 'clojure' } +" +" " Using a non-master branch +" Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' } +" +" " Using a tagged release; wildcard allowed (requires git 1.9.2 or above) +" Plug 'fatih/vim-go', { 'tag': '*' } +" +" " Plugin options +" Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' } +" +" " Plugin outside ~/.vim/plugged with post-update hook +" Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' } +" +" " Unmanaged plugin (manually installed and updated) +" Plug '~/my-prototype-plugin' +" +" " Initialize plugin system +" call plug#end() +" +" Then reload .vimrc and :PlugInstall to install plugins. +" +" Plug options: +" +"| Option | Description | +"| ----------------------- | ------------------------------------------------ | +"| `branch`/`tag`/`commit` | Branch/tag/commit of the repository to use | +"| `rtp` | Subdirectory that contains Vim plugin | +"| `dir` | Custom directory for the plugin | +"| `as` | Use different name for the plugin | +"| `do` | Post-update hook (string or funcref) | +"| `on` | On-demand loading: Commands or ``-mappings | +"| `for` | On-demand loading: File types | +"| `frozen` | Do not update unless explicitly specified | +" +" More information: https://github.com/junegunn/vim-plug +" +" +" Copyright (c) 2017 Junegunn Choi +" +" MIT License +" +" Permission is hereby granted, free of charge, to any person obtaining +" a copy of this software and associated documentation files (the +" "Software"), to deal in the Software without restriction, including +" without limitation the rights to use, copy, modify, merge, publish, +" distribute, sublicense, and/or sell copies of the Software, and to +" permit persons to whom the Software is furnished to do so, subject to +" the following conditions: +" +" The above copyright notice and this permission notice shall be +" included in all copies or substantial portions of the Software. +" +" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +if exists('g:loaded_plug') + finish +endif +let g:loaded_plug = 1 + +let s:cpo_save = &cpo +set cpo&vim + +let s:plug_src = 'https://github.com/junegunn/vim-plug.git' +let s:plug_tab = get(s:, 'plug_tab', -1) +let s:plug_buf = get(s:, 'plug_buf', -1) +let s:mac_gui = has('gui_macvim') && has('gui_running') +let s:is_win = has('win32') || has('win64') +let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win) +let s:vim8 = has('patch-8.0.0039') && exists('*job_start') +let s:me = resolve(expand(':p')) +let s:base_spec = { 'branch': 'master', 'frozen': 0 } +let s:TYPE = { +\ 'string': type(''), +\ 'list': type([]), +\ 'dict': type({}), +\ 'funcref': type(function('call')) +\ } +let s:loaded = get(s:, 'loaded', {}) +let s:triggers = get(s:, 'triggers', {}) + +function! plug#begin(...) + if a:0 > 0 + let s:plug_home_org = a:1 + let home = s:path(fnamemodify(expand(a:1), ':p')) + elseif exists('g:plug_home') + let home = s:path(g:plug_home) + elseif !empty(&rtp) + let home = s:path(split(&rtp, ',')[0]) . '/plugged' + else + return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.') + endif + if fnamemodify(home, ':t') ==# 'plugin' && fnamemodify(home, ':h') ==# s:first_rtp + return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.') + endif + + let g:plug_home = home + let g:plugs = {} + let g:plugs_order = [] + let s:triggers = {} + + call s:define_commands() + return 1 +endfunction + +function! s:define_commands() + command! -nargs=+ -bar Plug call plug#() + if !executable('git') + return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.') + endif + command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(0, []) + command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(0, []) + command! -nargs=0 -bar -bang PlugClean call s:clean(0) + command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif + command! -nargs=0 -bar PlugStatus call s:status() + command! -nargs=0 -bar PlugDiff call s:diff() + command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(0, ) +endfunction + +function! s:to_a(v) + return type(a:v) == s:TYPE.list ? a:v : [a:v] +endfunction + +function! s:to_s(v) + return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n" +endfunction + +function! s:glob(from, pattern) + return s:lines(globpath(a:from, a:pattern)) +endfunction + +function! s:source(from, ...) + let found = 0 + for pattern in a:000 + for vim in s:glob(a:from, pattern) + execute 'source' s:esc(vim) + let found = 1 + endfor + endfor + return found +endfunction + +function! s:assoc(dict, key, val) + let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) +endfunction + +function! s:ask(message, ...) + call inputsave() + echohl WarningMsg + let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) ')) + echohl None + call inputrestore() + echo "\r" + return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0 +endfunction + +function! s:ask_no_interrupt(...) + try + return call('s:ask', a:000) + catch + return 0 + endtry +endfunction + +function! plug#end() + if !exists('g:plugs') + return s:err('Call plug#begin() first') + endif + + if exists('#PlugLOD') + augroup PlugLOD + autocmd! + augroup END + augroup! PlugLOD + endif + let lod = { 'ft': {}, 'map': {}, 'cmd': {} } + + if exists('g:did_load_filetypes') + filetype off + endif + for name in g:plugs_order + if !has_key(g:plugs, name) + continue + endif + let plug = g:plugs[name] + if get(s:loaded, name, 0) || !has_key(plug, 'on') && !has_key(plug, 'for') + let s:loaded[name] = 1 + continue + endif + + if has_key(plug, 'on') + let s:triggers[name] = { 'map': [], 'cmd': [] } + for cmd in s:to_a(plug.on) + if cmd =~? '^.\+' + if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) + call s:assoc(lod.map, cmd, name) + endif + call add(s:triggers[name].map, cmd) + elseif cmd =~# '^[A-Z]' + let cmd = substitute(cmd, '!*$', '', '') + if exists(':'.cmd) != 2 + call s:assoc(lod.cmd, cmd, name) + endif + call add(s:triggers[name].cmd, cmd) + else + call s:err('Invalid `on` option: '.cmd. + \ '. Should start with an uppercase letter or ``.') + endif + endfor + endif + + if has_key(plug, 'for') + let types = s:to_a(plug.for) + if !empty(types) + augroup filetypedetect + call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') + augroup END + endif + for type in types + call s:assoc(lod.ft, type, name) + endfor + endif + endfor + + for [cmd, names] in items(lod.cmd) + execute printf( + \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "", , , , %s)', + \ cmd, string(cmd), string(names)) + endfor + + for [map, names] in items(lod.map) + for [mode, map_prefix, key_prefix] in + \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] + execute printf( + \ '%snoremap %s %s:call lod_map(%s, %s, %s, "%s")', + \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix) + endfor + endfor + + for [ft, names] in items(lod.ft) + augroup PlugLOD + execute printf('autocmd FileType %s call lod_ft(%s, %s)', + \ ft, string(ft), string(names)) + augroup END + endfor + + call s:reorg_rtp() + filetype plugin indent on + if has('vim_starting') + if has('syntax') && !exists('g:syntax_on') + syntax enable + end + else + call s:reload_plugins() + endif +endfunction + +function! s:loaded_names() + return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') +endfunction + +function! s:load_plugin(spec) + call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim') +endfunction + +function! s:reload_plugins() + for name in s:loaded_names() + call s:load_plugin(g:plugs[name]) + endfor +endfunction + +function! s:trim(str) + return substitute(a:str, '[\/]\+$', '', '') +endfunction + +function! s:version_requirement(val, min) + for idx in range(0, len(a:min) - 1) + let v = get(a:val, idx, 0) + if v < a:min[idx] | return 0 + elseif v > a:min[idx] | return 1 + endif + endfor + return 1 +endfunction + +function! s:git_version_requirement(...) + if !exists('s:git_version') + let s:git_version = map(split(split(s:system('git --version'))[2], '\.'), 'str2nr(v:val)') + endif + return s:version_requirement(s:git_version, a:000) +endfunction + +function! s:progress_opt(base) + return a:base && !s:is_win && + \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' +endfunction + +if s:is_win + function! s:rtp(spec) + return s:path(a:spec.dir . get(a:spec, 'rtp', '')) + endfunction + + function! s:path(path) + return s:trim(substitute(a:path, '/', '\', 'g')) + endfunction + + function! s:dirpath(path) + return s:path(a:path) . '\' + endfunction + + function! s:is_local_plug(repo) + return a:repo =~? '^[a-z]:\|^[%~]' + endfunction +else + function! s:rtp(spec) + return s:dirpath(a:spec.dir . get(a:spec, 'rtp', '')) + endfunction + + function! s:path(path) + return s:trim(a:path) + endfunction + + function! s:dirpath(path) + return substitute(a:path, '[/\\]*$', '/', '') + endfunction + + function! s:is_local_plug(repo) + return a:repo[0] =~ '[/$~]' + endfunction +endif + +function! s:err(msg) + echohl ErrorMsg + echom '[vim-plug] '.a:msg + echohl None +endfunction + +function! s:warn(cmd, msg) + echohl WarningMsg + execute a:cmd 'a:msg' + echohl None +endfunction + +function! s:esc(path) + return escape(a:path, ' ') +endfunction + +function! s:escrtp(path) + return escape(a:path, ' ,') +endfunction + +function! s:remove_rtp() + for name in s:loaded_names() + let rtp = s:rtp(g:plugs[name]) + execute 'set rtp-='.s:escrtp(rtp) + let after = globpath(rtp, 'after') + if isdirectory(after) + execute 'set rtp-='.s:escrtp(after) + endif + endfor +endfunction + +function! s:reorg_rtp() + if !empty(s:first_rtp) + execute 'set rtp-='.s:first_rtp + execute 'set rtp-='.s:last_rtp + endif + + " &rtp is modified from outside + if exists('s:prtp') && s:prtp !=# &rtp + call s:remove_rtp() + unlet! s:middle + endif + + let s:middle = get(s:, 'middle', &rtp) + let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') + let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)') + let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') + \ . ','.s:middle.',' + \ . join(map(afters, 'escape(v:val, ",")'), ',') + let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') + let s:prtp = &rtp + + if !empty(s:first_rtp) + execute 'set rtp^='.s:first_rtp + execute 'set rtp+='.s:last_rtp + endif +endfunction + +function! s:doautocmd(...) + if exists('#'.join(a:000, '#')) + execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '' : '') join(a:000) + endif +endfunction + +function! s:dobufread(names) + for name in a:names + let path = s:rtp(g:plugs[name]).'/**' + for dir in ['ftdetect', 'ftplugin'] + if len(finddir(dir, path)) + if exists('#BufRead') + doautocmd BufRead + endif + return + endif + endfor + endfor +endfunction + +function! plug#load(...) + if a:0 == 0 + return s:err('Argument missing: plugin name(s) required') + endif + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000 + let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)') + if !empty(unknowns) + let s = len(unknowns) > 1 ? 's' : '' + return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) + end + let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)') + if !empty(unloaded) + for name in unloaded + call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + endfor + call s:dobufread(unloaded) + return 1 + end + return 0 +endfunction + +function! s:remove_triggers(name) + if !has_key(s:triggers, a:name) + return + endif + for cmd in s:triggers[a:name].cmd + execute 'silent! delc' cmd + endfor + for map in s:triggers[a:name].map + execute 'silent! unmap' map + execute 'silent! iunmap' map + endfor + call remove(s:triggers, a:name) +endfunction + +function! s:lod(names, types, ...) + for name in a:names + call s:remove_triggers(name) + let s:loaded[name] = 1 + endfor + call s:reorg_rtp() + + for name in a:names + let rtp = s:rtp(g:plugs[name]) + for dir in a:types + call s:source(rtp, dir.'/**/*.vim') + endfor + if a:0 + if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2)) + execute 'runtime' a:1 + endif + call s:source(rtp, a:2) + endif + call s:doautocmd('User', name) + endfor +endfunction + +function! s:lod_ft(pat, names) + let syn = 'syntax/'.a:pat.'.vim' + call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) + execute 'autocmd! PlugLOD FileType' a:pat + call s:doautocmd('filetypeplugin', 'FileType') + call s:doautocmd('filetypeindent', 'FileType') +endfunction + +function! s:lod_cmd(cmd, bang, l1, l2, args, names) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) +endfunction + +function! s:lod_map(map, names, with_prefix, prefix) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + let extra = '' + while 1 + let c = getchar(0) + if c == 0 + break + endif + let extra .= nr2char(c) + endwhile + + if a:with_prefix + let prefix = v:count ? v:count : '' + let prefix .= '"'.v:register.a:prefix + if mode(1) == 'no' + if v:operator == 'c' + let prefix = "\" . prefix + endif + let prefix .= v:operator + endif + call feedkeys(prefix, 'n') + endif + call feedkeys(substitute(a:map, '^', "\", '') . extra) +endfunction + +function! plug#(repo, ...) + if a:0 > 1 + return s:err('Invalid number of arguments (1..2)') + endif + + try + let repo = s:trim(a:repo) + let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec + let name = get(opts, 'as', fnamemodify(repo, ':t:s?\.git$??')) + let spec = extend(s:infer_properties(name, repo), opts) + if !has_key(g:plugs, name) + call add(g:plugs_order, name) + endif + let g:plugs[name] = spec + let s:loaded[name] = get(s:loaded, name, 0) + catch + return s:err(v:exception) + endtry +endfunction + +function! s:parse_options(arg) + let opts = copy(s:base_spec) + let type = type(a:arg) + if type == s:TYPE.string + let opts.tag = a:arg + elseif type == s:TYPE.dict + call extend(opts, a:arg) + if has_key(opts, 'dir') + let opts.dir = s:dirpath(expand(opts.dir)) + endif + else + throw 'Invalid argument type (expected: string or dictionary)' + endif + return opts +endfunction + +function! s:infer_properties(name, repo) + let repo = a:repo + if s:is_local_plug(repo) + return { 'dir': s:dirpath(expand(repo)) } + else + if repo =~ ':' + let uri = repo + else + if repo !~ '/' + throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo) + endif + let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') + let uri = printf(fmt, repo) + endif + return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri } + endif +endfunction + +function! s:install(force, names) + call s:update_impl(0, a:force, a:names) +endfunction + +function! s:update(force, names) + call s:update_impl(1, a:force, a:names) +endfunction + +function! plug#helptags() + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + for spec in values(g:plugs) + let docd = join([s:rtp(spec), 'doc'], '/') + if isdirectory(docd) + silent! execute 'helptags' s:esc(docd) + endif + endfor + return 1 +endfunction + +function! s:syntax() + syntax clear + syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber + syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX + syn match plugNumber /[0-9]\+[0-9.]*/ contained + syn match plugBracket /[[\]]/ contained + syn match plugX /x/ contained + syn match plugDash /^-/ + syn match plugPlus /^+/ + syn match plugStar /^*/ + syn match plugMessage /\(^- \)\@<=.*/ + syn match plugName /\(^- \)\@<=[^ ]*:/ + syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/ + syn match plugTag /(tag: [^)]\+)/ + syn match plugInstall /\(^+ \)\@<=[^:]*/ + syn match plugUpdate /\(^* \)\@<=[^:]*/ + syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag + syn match plugEdge /^ \X\+$/ + syn match plugEdge /^ \X*/ contained nextgroup=plugSha + syn match plugSha /[0-9a-f]\{7,9}/ contained + syn match plugRelDate /([^)]*)$/ contained + syn match plugNotLoaded /(not loaded)$/ + syn match plugError /^x.*/ + syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/ + syn match plugH2 /^.*:\n-\+$/ + syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean + hi def link plug1 Title + hi def link plug2 Repeat + hi def link plugH2 Type + hi def link plugX Exception + hi def link plugBracket Structure + hi def link plugNumber Number + + hi def link plugDash Special + hi def link plugPlus Constant + hi def link plugStar Boolean + + hi def link plugMessage Function + hi def link plugName Label + hi def link plugInstall Function + hi def link plugUpdate Type + + hi def link plugError Error + hi def link plugDeleted Ignore + hi def link plugRelDate Comment + hi def link plugEdge PreProc + hi def link plugSha Identifier + hi def link plugTag Constant + + hi def link plugNotLoaded Comment +endfunction + +function! s:lpad(str, len) + return a:str . repeat(' ', a:len - len(a:str)) +endfunction + +function! s:lines(msg) + return split(a:msg, "[\r\n]") +endfunction + +function! s:lastline(msg) + return get(s:lines(a:msg), -1, '') +endfunction + +function! s:new_window() + execute get(g:, 'plug_window', 'vertical topleft new') +endfunction + +function! s:plug_window_exists() + let buflist = tabpagebuflist(s:plug_tab) + return !empty(buflist) && index(buflist, s:plug_buf) >= 0 +endfunction + +function! s:switch_in() + if !s:plug_window_exists() + return 0 + endif + + if winbufnr(0) != s:plug_buf + let s:pos = [tabpagenr(), winnr(), winsaveview()] + execute 'normal!' s:plug_tab.'gt' + let winnr = bufwinnr(s:plug_buf) + execute winnr.'wincmd w' + call add(s:pos, winsaveview()) + else + let s:pos = [winsaveview()] + endif + + setlocal modifiable + return 1 +endfunction + +function! s:switch_out(...) + call winrestview(s:pos[-1]) + setlocal nomodifiable + if a:0 > 0 + execute a:1 + endif + + if len(s:pos) > 1 + execute 'normal!' s:pos[0].'gt' + execute s:pos[1] 'wincmd w' + call winrestview(s:pos[2]) + endif +endfunction + +function! s:finish_bindings() + nnoremap R :call retry() + nnoremap D :PlugDiff + nnoremap S :PlugStatus + nnoremap U :call status_update() + xnoremap U :call status_update() + nnoremap ]] :silent! call section('') + nnoremap [[ :silent! call section('b') +endfunction + +function! s:prepare(...) + if empty(getcwd()) + throw 'Invalid current working directory. Cannot proceed.' + endif + + for evar in ['$GIT_DIR', '$GIT_WORK_TREE'] + if exists(evar) + throw evar.' detected. Cannot proceed.' + endif + endfor + + call s:job_abort() + if s:switch_in() + if b:plug_preview == 1 + pc + endif + enew + else + call s:new_window() + endif + + nnoremap q :if b:plug_preview==1pcendifbd + if a:0 == 0 + call s:finish_bindings() + endif + let b:plug_preview = -1 + let s:plug_tab = tabpagenr() + let s:plug_buf = winbufnr(0) + call s:assign_name() + + for k in ['', 'L', 'o', 'X', 'd', 'dd'] + execute 'silent! unmap ' k + endfor + setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell + setf vim-plug + if exists('g:syntax_on') + call s:syntax() + endif +endfunction + +function! s:assign_name() + " Assign buffer name + let prefix = '[Plugins]' + let name = prefix + let idx = 2 + while bufexists(name) + let name = printf('%s (%s)', prefix, idx) + let idx = idx + 1 + endwhile + silent! execute 'f' fnameescape(name) +endfunction + +function! s:chsh(swap) + let prev = [&shell, &shellcmdflag, &shellredir] + if s:is_win + set shell=cmd.exe shellcmdflag=/c shellredir=>%s\ 2>&1 + elseif a:swap + set shell=sh shellredir=>%s\ 2>&1 + endif + return prev +endfunction + +function! s:bang(cmd, ...) + try + let [sh, shellcmdflag, shrd] = s:chsh(a:0) + " FIXME: Escaping is incomplete. We could use shellescape with eval, + " but it won't work on Windows. + let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let batchfile = tempname().'.bat' + call writefile(["@echo off\r", cmd . "\r"], batchfile) + let cmd = batchfile + endif + let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%') + execute "normal! :execute g:_plug_bang\\" + finally + unlet g:_plug_bang + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry + return v:shell_error ? 'Exit status: ' . v:shell_error : '' +endfunction + +function! s:regress_bar() + let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '') + call s:progress_bar(2, bar, len(bar)) +endfunction + +function! s:is_updated(dir) + return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir)) +endfunction + +function! s:do(pull, force, todo) + for [name, spec] in items(a:todo) + if !isdirectory(spec.dir) + continue + endif + let installed = has_key(s:update.new, name) + let updated = installed ? 0 : + \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir)) + if a:force || installed || updated + execute 'cd' s:esc(spec.dir) + call append(3, '- Post-update hook for '. name .' ... ') + let error = '' + let type = type(spec.do) + if type == s:TYPE.string + if spec.do[0] == ':' + if !get(s:loaded, name, 0) + let s:loaded[name] = 1 + call s:reorg_rtp() + endif + call s:load_plugin(spec) + try + execute spec.do[1:] + catch + let error = v:exception + endtry + if !s:plug_window_exists() + cd - + throw 'Warning: vim-plug was terminated by the post-update hook of '.name + endif + else + let error = s:bang(spec.do) + endif + elseif type == s:TYPE.funcref + try + let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') + call spec.do({ 'name': name, 'status': status, 'force': a:force }) + catch + let error = v:exception + endtry + else + let error = 'Invalid hook type' + endif + call s:switch_in() + call setline(4, empty(error) ? (getline(4) . 'OK') + \ : ('x' . getline(4)[1:] . error)) + if !empty(error) + call add(s:update.errors, name) + call s:regress_bar() + endif + cd - + endif + endfor +endfunction + +function! s:hash_match(a, b) + return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 +endfunction + +function! s:checkout(spec) + let sha = a:spec.commit + let output = s:system('git rev-parse HEAD', a:spec.dir) + if !v:shell_error && !s:hash_match(sha, s:lines(output)[0]) + let output = s:system( + \ 'git fetch --depth 999999 && git checkout '.s:esc(sha).' --', a:spec.dir) + endif + return output +endfunction + +function! s:finish(pull) + let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) + if new_frozen + let s = new_frozen > 1 ? 's' : '' + call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) + endif + call append(3, '- Finishing ... ') | 4 + redraw + call plug#helptags() + call plug#end() + call setline(4, getline(4) . 'Done!') + redraw + let msgs = [] + if !empty(s:update.errors) + call add(msgs, "Press 'R' to retry.") + endif + if a:pull && len(s:update.new) < len(filter(getline(5, '$'), + \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'")) + call add(msgs, "Press 'D' to see the updated changes.") + endif + echo join(msgs, ' ') + call s:finish_bindings() +endfunction + +function! s:retry() + if empty(s:update.errors) + return + endif + echo + call s:update_impl(s:update.pull, s:update.force, + \ extend(copy(s:update.errors), [s:update.threads])) +endfunction + +function! s:is_managed(name) + return has_key(g:plugs[a:name], 'uri') +endfunction + +function! s:names(...) + return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) +endfunction + +function! s:check_ruby() + silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'") + if !exists('g:plug_ruby') + redraw! + return s:warn('echom', 'Warning: Ruby interface is broken') + endif + let ruby_version = split(g:plug_ruby, '\.') + unlet g:plug_ruby + return s:version_requirement(ruby_version, [1, 8, 7]) +endfunction + +function! s:update_impl(pull, force, args) abort + let sync = index(a:args, '--sync') >= 0 || has('vim_starting') + let args = filter(copy(a:args), 'v:val != "--sync"') + let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? + \ remove(args, -1) : get(g:, 'plug_threads', 16) + + let managed = filter(copy(g:plugs), 's:is_managed(v:key)') + let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : + \ filter(managed, 'index(args, v:key) >= 0') + + if empty(todo) + return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install')) + endif + + if !s:is_win && s:git_version_requirement(2, 3) + let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' + let $GIT_TERMINAL_PROMPT = 0 + for plug in values(todo) + let plug.uri = substitute(plug.uri, + \ '^https://git::@github\.com', 'https://github.com', '') + endfor + endif + + if !isdirectory(g:plug_home) + try + call mkdir(g:plug_home, 'p') + catch + return s:err(printf('Invalid plug directory: %s. '. + \ 'Try to call plug#begin with a valid directory', g:plug_home)) + endtry + endif + + if has('nvim') && !exists('*jobwait') && threads > 1 + call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') + endif + + let use_job = s:nvim || s:vim8 + let python = (has('python') || has('python3')) && !use_job + let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby() + + let s:update = { + \ 'start': reltime(), + \ 'all': todo, + \ 'todo': copy(todo), + \ 'errors': [], + \ 'pull': a:pull, + \ 'force': a:force, + \ 'new': {}, + \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1, + \ 'bar': '', + \ 'fin': 0 + \ } + + call s:prepare(1) + call append(0, ['', '']) + normal! 2G + silent! redraw + + let s:clone_opt = get(g:, 'plug_shallow', 1) ? + \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : '' + + if has('win32unix') + let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input' + endif + + " Python version requirement (>= 2.7) + if python && !has('python3') && !ruby && !use_job && s:update.threads > 1 + redir => pyv + silent python import platform; print platform.python_version() + redir END + let python = s:version_requirement( + \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) + endif + + if (python || ruby) && s:update.threads > 1 + try + let imd = &imd + if s:mac_gui + set noimd + endif + if ruby + call s:update_ruby() + else + call s:update_python() + endif + catch + let lines = getline(4, '$') + let printed = {} + silent! 4,$d _ + for line in lines + let name = s:extract_name(line, '.', '') + if empty(name) || !has_key(printed, name) + call append('$', line) + if !empty(name) + let printed[name] = 1 + if line[0] == 'x' && index(s:update.errors, name) < 0 + call add(s:update.errors, name) + end + endif + endif + endfor + finally + let &imd = imd + call s:update_finish() + endtry + else + call s:update_vim() + while use_job && sync + sleep 100m + if s:update.fin + break + endif + endwhile + endif +endfunction + +function! s:log4(name, msg) + call setline(4, printf('- %s (%s)', a:msg, a:name)) + redraw +endfunction + +function! s:update_finish() + if exists('s:git_terminal_prompt') + let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt + endif + if s:switch_in() + call append(3, '- Updating ...') | 4 + for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))')) + let [pos, _] = s:logpos(name) + if !pos + continue + endif + if has_key(spec, 'commit') + call s:log4(name, 'Checking out '.spec.commit) + let out = s:checkout(spec) + elseif has_key(spec, 'tag') + let tag = spec.tag + if tag =~ '\*' + let tags = s:lines(s:system('git tag --list '.s:shellesc(tag).' --sort -version:refname 2>&1', spec.dir)) + if !v:shell_error && !empty(tags) + let tag = tags[0] + call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) + call append(3, '') + endif + endif + call s:log4(name, 'Checking out '.tag) + let out = s:system('git checkout -q '.s:esc(tag).' -- 2>&1', spec.dir) + else + let branch = s:esc(get(spec, 'branch', 'master')) + call s:log4(name, 'Merging origin/'.branch) + let out = s:system('git checkout -q '.branch.' -- 2>&1' + \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only origin/'.branch.' 2>&1')), spec.dir) + endif + if !v:shell_error && filereadable(spec.dir.'/.gitmodules') && + \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) + call s:log4(name, 'Updating submodules. This may take a while.') + let out .= s:bang('git submodule update --init --recursive 2>&1', spec.dir) + endif + let msg = s:format_message(v:shell_error ? 'x': '-', name, out) + if v:shell_error + call add(s:update.errors, name) + call s:regress_bar() + silent execute pos 'd _' + call append(4, msg) | 4 + elseif !empty(out) + call setline(pos, msg[0]) + endif + redraw + endfor + silent 4 d _ + try + call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")')) + catch + call s:warn('echom', v:exception) + call s:warn('echo', '') + return + endtry + call s:finish(s:update.pull) + call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') + call s:switch_out('normal! gg') + endif +endfunction + +function! s:job_abort() + if (!s:nvim && !s:vim8) || !exists('s:jobs') + return + endif + + for [name, j] in items(s:jobs) + if s:nvim + silent! call jobstop(j.jobid) + elseif s:vim8 + silent! call job_stop(j.jobid) + endif + if j.new + call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir)) + endif + endfor + let s:jobs = {} +endfunction + +function! s:last_non_empty_line(lines) + let len = len(a:lines) + for idx in range(len) + let line = a:lines[len-idx-1] + if !empty(line) + return line + endif + endfor + return '' +endfunction + +function! s:job_out_cb(self, data) abort + let self = a:self + let data = remove(self.lines, -1) . a:data + let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]') + call extend(self.lines, lines) + " To reduce the number of buffer updates + let self.tick = get(self, 'tick', -1) + 1 + if !self.running || self.tick % len(s:jobs) == 0 + let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-') + let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) + call s:log(bullet, self.name, result) + endif +endfunction + +function! s:job_exit_cb(self, data) abort + let a:self.running = 0 + let a:self.error = a:data != 0 + call s:reap(a:self.name) + call s:tick() +endfunction + +function! s:job_cb(fn, job, ch, data) + if !s:plug_window_exists() " plug window closed + return s:job_abort() + endif + call call(a:fn, [a:job, a:data]) +endfunction + +function! s:nvim_cb(job_id, data, event) dict abort + return a:event == 'stdout' ? + \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) : + \ s:job_cb('s:job_exit_cb', self, 0, a:data) +endfunction + +function! s:spawn(name, cmd, opts) + let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''], + \ 'batchfile': (s:is_win && (s:nvim || s:vim8)) ? tempname().'.bat' : '', + \ 'new': get(a:opts, 'new', 0) } + let s:jobs[a:name] = job + let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd + if !empty(job.batchfile) + call writefile(["@echo off\r", cmd . "\r"], job.batchfile) + let cmd = job.batchfile + endif + let argv = add(s:is_win ? ['cmd', '/c'] : ['sh', '-c'], cmd) + + if s:nvim + call extend(job, { + \ 'on_stdout': function('s:nvim_cb'), + \ 'on_exit': function('s:nvim_cb'), + \ }) + let jid = jobstart(argv, job) + if jid > 0 + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = [jid < 0 ? argv[0].' is not executable' : + \ 'Invalid arguments (or job table is full)'] + endif + elseif s:vim8 + let jid = job_start(s:is_win ? join(argv, ' ') : argv, { + \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]), + \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]), + \ 'out_mode': 'raw' + \}) + if job_status(jid) == 'run' + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = ['Failed to start job'] + endif + else + let job.lines = s:lines(call('s:system', [cmd])) + let job.error = v:shell_error != 0 + let job.running = 0 + endif +endfunction + +function! s:reap(name) + let job = s:jobs[a:name] + if job.error + call add(s:update.errors, a:name) + elseif get(job, 'new', 0) + let s:update.new[a:name] = 1 + endif + let s:update.bar .= job.error ? 'x' : '=' + + let bullet = job.error ? 'x' : '-' + let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) + call s:log(bullet, a:name, empty(result) ? 'OK' : result) + call s:bar() + + if has_key(job, 'batchfile') && !empty(job.batchfile) + call delete(job.batchfile) + endif + call remove(s:jobs, a:name) +endfunction + +function! s:bar() + if s:switch_in() + let total = len(s:update.all) + call setline(1, (s:update.pull ? 'Updating' : 'Installing'). + \ ' plugins ('.len(s:update.bar).'/'.total.')') + call s:progress_bar(2, s:update.bar, total) + call s:switch_out() + endif +endfunction + +function! s:logpos(name) + for i in range(4, line('$')) + if getline(i) =~# '^[-+x*] '.a:name.':' + for j in range(i + 1, line('$')) + if getline(j) !~ '^ ' + return [i, j - 1] + endif + endfor + return [i, i] + endif + endfor + return [0, 0] +endfunction + +function! s:log(bullet, name, lines) + if s:switch_in() + let [b, e] = s:logpos(a:name) + if b > 0 + silent execute printf('%d,%d d _', b, e) + if b > winheight('.') + let b = 4 + endif + else + let b = 4 + endif + " FIXME For some reason, nomodifiable is set after :d in vim8 + setlocal modifiable + call append(b - 1, s:format_message(a:bullet, a:name, a:lines)) + call s:switch_out() + endif +endfunction + +function! s:update_vim() + let s:jobs = {} + + call s:bar() + call s:tick() +endfunction + +function! s:tick() + let pull = s:update.pull + let prog = s:progress_opt(s:nvim || s:vim8) +while 1 " Without TCO, Vim stack is bound to explode + if empty(s:update.todo) + if empty(s:jobs) && !s:update.fin + call s:update_finish() + let s:update.fin = 1 + endif + return + endif + + let name = keys(s:update.todo)[0] + let spec = remove(s:update.todo, name) + let new = !isdirectory(spec.dir) + + call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') + redraw + + let has_tag = has_key(spec, 'tag') + if !new + let [error, _] = s:git_validate(spec, 0) + if empty(error) + if pull + let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : '' + call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir }) + else + let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 } + endif + else + let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 } + endif + else + call s:spawn(name, + \ printf('git clone %s %s %s %s 2>&1', + \ has_tag ? '' : s:clone_opt, + \ prog, + \ s:shellesc(spec.uri), + \ s:shellesc(s:trim(spec.dir))), { 'new': 1 }) + endif + + if !s:jobs[name].running + call s:reap(name) + endif + if len(s:jobs) >= s:update.threads + break + endif +endwhile +endfunction + +function! s:update_python() +let py_exe = has('python') ? 'python' : 'python3' +execute py_exe "<< EOF" +import datetime +import functools +import os +try: + import queue +except ImportError: + import Queue as queue +import random +import re +import shutil +import signal +import subprocess +import tempfile +import threading as thr +import time +import traceback +import vim + +G_NVIM = vim.eval("has('nvim')") == '1' +G_PULL = vim.eval('s:update.pull') == '1' +G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 +G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) +G_CLONE_OPT = vim.eval('s:clone_opt') +G_PROGRESS = vim.eval('s:progress_opt(1)') +G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) +G_STOP = thr.Event() +G_IS_WIN = vim.eval('s:is_win') == '1' + +class PlugError(Exception): + def __init__(self, msg): + self.msg = msg +class CmdTimedOut(PlugError): + pass +class CmdFailed(PlugError): + pass +class InvalidURI(PlugError): + pass +class Action(object): + INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] + +class Buffer(object): + def __init__(self, lock, num_plugs, is_pull): + self.bar = '' + self.event = 'Updating' if is_pull else 'Installing' + self.lock = lock + self.maxy = int(vim.eval('winheight(".")')) + self.num_plugs = num_plugs + + def __where(self, name): + """ Find first line with name in current buffer. Return line num. """ + found, lnum = False, 0 + matcher = re.compile('^[-+x*] {0}:'.format(name)) + for line in vim.current.buffer: + if matcher.search(line) is not None: + found = True + break + lnum += 1 + + if not found: + lnum = -1 + return lnum + + def header(self): + curbuf = vim.current.buffer + curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) + + num_spaces = self.num_plugs - len(self.bar) + curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') + + with self.lock: + vim.command('normal! 2G') + vim.command('redraw') + + def write(self, action, name, lines): + first, rest = lines[0], lines[1:] + msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] + msg.extend([' ' + line for line in rest]) + + try: + if action == Action.ERROR: + self.bar += 'x' + vim.command("call add(s:update.errors, '{0}')".format(name)) + elif action == Action.DONE: + self.bar += '=' + + curbuf = vim.current.buffer + lnum = self.__where(name) + if lnum != -1: # Found matching line num + del curbuf[lnum] + if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): + lnum = 3 + else: + lnum = 3 + curbuf.append(msg, lnum) + + self.header() + except vim.error: + pass + +class Command(object): + CD = 'cd /d' if G_IS_WIN else 'cd' + + def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): + self.cmd = cmd + if cmd_dir: + self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd) + self.timeout = timeout + self.callback = cb if cb else (lambda msg: None) + self.clean = clean if clean else (lambda: None) + self.proc = None + + @property + def alive(self): + """ Returns true only if command still running. """ + return self.proc and self.proc.poll() is None + + def execute(self, ntries=3): + """ Execute the command with ntries if CmdTimedOut. + Returns the output of the command if no Exception. + """ + attempt, finished, limit = 0, False, self.timeout + + while not finished: + try: + attempt += 1 + result = self.try_command() + finished = True + return result + except CmdTimedOut: + if attempt != ntries: + self.notify_retry() + self.timeout += limit + else: + raise + + def notify_retry(self): + """ Retry required for command, notify user. """ + for count in range(3, 0, -1): + if G_STOP.is_set(): + raise KeyboardInterrupt + msg = 'Timeout. Will retry in {0} second{1} ...'.format( + count, 's' if count != 1 else '') + self.callback([msg]) + time.sleep(1) + self.callback(['Retrying ...']) + + def try_command(self): + """ Execute a cmd & poll for callback. Returns list of output. + Raises CmdFailed -> return code for Popen isn't 0 + Raises CmdTimedOut -> command exceeded timeout without new output + """ + first_line = True + + try: + tfile = tempfile.NamedTemporaryFile(mode='w+b') + preexec_fn = not G_IS_WIN and os.setsid or None + self.proc = subprocess.Popen(self.cmd, stdout=tfile, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, shell=True, + preexec_fn=preexec_fn) + thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) + thrd.start() + + thread_not_started = True + while thread_not_started: + try: + thrd.join(0.1) + thread_not_started = False + except RuntimeError: + pass + + while self.alive: + if G_STOP.is_set(): + raise KeyboardInterrupt + + if first_line or random.random() < G_LOG_PROB: + first_line = False + line = '' if G_IS_WIN else nonblock_read(tfile.name) + if line: + self.callback([line]) + + time_diff = time.time() - os.path.getmtime(tfile.name) + if time_diff > self.timeout: + raise CmdTimedOut(['Timeout!']) + + thrd.join(0.5) + + tfile.seek(0) + result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] + + if self.proc.returncode != 0: + raise CmdFailed([''] + result) + + return result + except: + self.terminate() + raise + + def terminate(self): + """ Terminate process and cleanup. """ + if self.alive: + if G_IS_WIN: + os.kill(self.proc.pid, signal.SIGINT) + else: + os.killpg(self.proc.pid, signal.SIGTERM) + self.clean() + +class Plugin(object): + def __init__(self, name, args, buf_q, lock): + self.name = name + self.args = args + self.buf_q = buf_q + self.lock = lock + self.tag = args.get('tag', 0) + + def manage(self): + try: + if os.path.exists(self.args['dir']): + self.update() + else: + self.install() + with self.lock: + thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) + except PlugError as exc: + self.write(Action.ERROR, self.name, exc.msg) + except KeyboardInterrupt: + G_STOP.set() + self.write(Action.ERROR, self.name, ['Interrupted!']) + except: + # Any exception except those above print stack trace + msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) + self.write(Action.ERROR, self.name, msg.split('\n')) + raise + + def install(self): + target = self.args['dir'] + if target[-1] == '\\': + target = target[0:-1] + + def clean(target): + def _clean(): + try: + shutil.rmtree(target) + except OSError: + pass + return _clean + + self.write(Action.INSTALL, self.name, ['Installing ...']) + callback = functools.partial(self.write, Action.INSTALL, self.name) + cmd = 'git clone {0} {1} {2} {3} 2>&1'.format( + '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], + esc(target)) + com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + + def repo_uri(self): + cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url' + command = Command(cmd, self.args['dir'], G_TIMEOUT,) + result = command.execute(G_RETRIES) + return result[-1] + + def update(self): + actual_uri = self.repo_uri() + expect_uri = self.args['uri'] + regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$') + ma = regex.match(actual_uri) + mb = regex.match(expect_uri) + if ma is None or mb is None or ma.groups() != mb.groups(): + msg = ['', + 'Invalid URI: {0}'.format(actual_uri), + 'Expected {0}'.format(expect_uri), + 'PlugClean required.'] + raise InvalidURI(msg) + + if G_PULL: + self.write(Action.UPDATE, self.name, ['Updating ...']) + callback = functools.partial(self.write, Action.UPDATE, self.name) + fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' + cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS) + com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + else: + self.write(Action.DONE, self.name, ['Already installed']) + + def write(self, action, name, msg): + self.buf_q.put((action, name, msg)) + +class PlugThread(thr.Thread): + def __init__(self, tname, args): + super(PlugThread, self).__init__() + self.tname = tname + self.args = args + + def run(self): + thr.current_thread().name = self.tname + buf_q, work_q, lock = self.args + + try: + while not G_STOP.is_set(): + name, args = work_q.get_nowait() + plug = Plugin(name, args, buf_q, lock) + plug.manage() + work_q.task_done() + except queue.Empty: + pass + +class RefreshThread(thr.Thread): + def __init__(self, lock): + super(RefreshThread, self).__init__() + self.lock = lock + self.running = True + + def run(self): + while self.running: + with self.lock: + thread_vim_command('noautocmd normal! a') + time.sleep(0.33) + + def stop(self): + self.running = False + +if G_NVIM: + def thread_vim_command(cmd): + vim.session.threadsafe_call(lambda: vim.command(cmd)) +else: + def thread_vim_command(cmd): + vim.command(cmd) + +def esc(name): + return '"' + name.replace('"', '\"') + '"' + +def nonblock_read(fname): + """ Read a file with nonblock flag. Return the last line. """ + fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) + buf = os.read(fread, 100000).decode('utf-8', 'replace') + os.close(fread) + + line = buf.rstrip('\r\n') + left = max(line.rfind('\r'), line.rfind('\n')) + if left != -1: + left += 1 + line = line[left:] + + return line + +def main(): + thr.current_thread().name = 'main' + nthreads = int(vim.eval('s:update.threads')) + plugs = vim.eval('s:update.todo') + mac_gui = vim.eval('s:mac_gui') == '1' + + lock = thr.Lock() + buf = Buffer(lock, len(plugs), G_PULL) + buf_q, work_q = queue.Queue(), queue.Queue() + for work in plugs.items(): + work_q.put(work) + + start_cnt = thr.active_count() + for num in range(nthreads): + tname = 'PlugT-{0:02}'.format(num) + thread = PlugThread(tname, (buf_q, work_q, lock)) + thread.start() + if mac_gui: + rthread = RefreshThread(lock) + rthread.start() + + while not buf_q.empty() or thr.active_count() != start_cnt: + try: + action, name, msg = buf_q.get(True, 0.25) + buf.write(action, name, ['OK'] if not msg else msg) + buf_q.task_done() + except queue.Empty: + pass + except KeyboardInterrupt: + G_STOP.set() + + if mac_gui: + rthread.stop() + rthread.join() + +main() +EOF +endfunction + +function! s:update_ruby() + ruby << EOF + module PlugStream + SEP = ["\r", "\n", nil] + def get_line + buffer = '' + loop do + char = readchar rescue return + if SEP.include? char.chr + buffer << $/ + break + else + buffer << char + end + end + buffer + end + end unless defined?(PlugStream) + + def esc arg + %["#{arg.gsub('"', '\"')}"] + end + + def killall pid + pids = [pid] + if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM + pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } + else + unless `which pgrep 2> /dev/null`.empty? + children = pids + until children.empty? + children = children.map { |pid| + `pgrep -P #{pid}`.lines.map { |l| l.chomp } + }.flatten + pids += children + end + end + pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } + end + end + + def compare_git_uri a, b + regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$} + regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1) + end + + require 'thread' + require 'fileutils' + require 'timeout' + running = true + iswin = VIM::evaluate('s:is_win').to_i == 1 + pull = VIM::evaluate('s:update.pull').to_i == 1 + base = VIM::evaluate('g:plug_home') + all = VIM::evaluate('s:update.todo') + limit = VIM::evaluate('get(g:, "plug_timeout", 60)') + tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 + nthr = VIM::evaluate('s:update.threads').to_i + maxy = VIM::evaluate('winheight(".")').to_i + vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/ + cd = iswin ? 'cd /d' : 'cd' + tot = VIM::evaluate('len(s:update.todo)') || 0 + bar = '' + skip = 'Already installed' + mtx = Mutex.new + take1 = proc { mtx.synchronize { running && all.shift } } + logh = proc { + cnt = bar.length + $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" + $curbuf[2] = '[' + bar.ljust(tot) + ']' + VIM::command('normal! 2G') + VIM::command('redraw') + } + where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } + log = proc { |name, result, type| + mtx.synchronize do + ing = ![true, false].include?(type) + bar += type ? '=' : 'x' unless ing + b = case type + when :install then '+' when :update then '*' + when true, nil then '-' else + VIM::command("call add(s:update.errors, '#{name}')") + 'x' + end + result = + if type || type.nil? + ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"] + elsif result =~ /^Interrupted|^Timeout/ + ["#{b} #{name}: #{result}"] + else + ["#{b} #{name}"] + result.lines.map { |l| " " << l } + end + if lnum = where.call(name) + $curbuf.delete lnum + lnum = 4 if ing && lnum > maxy + end + result.each_with_index do |line, offset| + $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) + end + logh.call + end + } + bt = proc { |cmd, name, type, cleanup| + tried = timeout = 0 + begin + tried += 1 + timeout += limit + fd = nil + data = '' + if iswin + Timeout::timeout(timeout) do + tmp = VIM::evaluate('tempname()') + system("(#{cmd}) > #{tmp}") + data = File.read(tmp).chomp + File.unlink tmp rescue nil + end + else + fd = IO.popen(cmd).extend(PlugStream) + first_line = true + log_prob = 1.0 / nthr + while line = Timeout::timeout(timeout) { fd.get_line } + data << line + log.call name, line.chomp, type if name && (first_line || rand < log_prob) + first_line = false + end + fd.close + end + [$? == 0, data.chomp] + rescue Timeout::Error, Interrupt => e + if fd && !fd.closed? + killall fd.pid + fd.close + end + cleanup.call if cleanup + if e.is_a?(Timeout::Error) && tried < tries + 3.downto(1) do |countdown| + s = countdown > 1 ? 's' : '' + log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type + sleep 1 + end + log.call name, 'Retrying ...', type + retry + end + [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] + end + } + main = Thread.current + threads = [] + watcher = Thread.new { + if vim7 + while VIM::evaluate('getchar(1)') + sleep 0.1 + end + else + require 'io/console' # >= Ruby 1.9 + nil until IO.console.getch == 3.chr + end + mtx.synchronize do + running = false + threads.each { |t| t.raise Interrupt } unless vim7 + end + threads.each { |t| t.join rescue nil } + main.kill + } + refresh = Thread.new { + while true + mtx.synchronize do + break unless running + VIM::command('noautocmd normal! a') + end + sleep 0.2 + end + } if VIM::evaluate('s:mac_gui') == 1 + + clone_opt = VIM::evaluate('s:clone_opt') + progress = VIM::evaluate('s:progress_opt(1)') + nthr.times do + mtx.synchronize do + threads << Thread.new { + while pair = take1.call + name = pair.first + dir, uri, tag = pair.last.values_at *%w[dir uri tag] + exists = File.directory? dir + ok, result = + if exists + chdir = "#{cd} #{iswin ? dir : esc(dir)}" + ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil + current_uri = data.lines.to_a.last + if !ret + if data =~ /^Interrupted|^Timeout/ + [false, data] + else + [false, [data.chomp, "PlugClean required."].join($/)] + end + elsif !compare_git_uri(current_uri, uri) + [false, ["Invalid URI: #{current_uri}", + "Expected: #{uri}", + "PlugClean required."].join($/)] + else + if pull + log.call name, 'Updating ...', :update + fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' + bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil + else + [true, skip] + end + end + else + d = esc dir.sub(%r{[\\/]+$}, '') + log.call name, 'Installing ...', :install + bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc { + FileUtils.rm_rf dir + } + end + mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok + log.call name, result, ok + end + } if running + end + end + threads.each { |t| t.join rescue nil } + logh.call + refresh.kill if refresh + watcher.kill +EOF +endfunction + +function! s:shellesc_cmd(arg) + let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g') + let escaped = substitute(escaped, '%', '%%', 'g') + let escaped = substitute(escaped, '"', '\\^&', 'g') + let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g') + return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"' +endfunction + +function! s:shellesc(arg) + if &shell =~# 'cmd.exe$' + return s:shellesc_cmd(a:arg) + endif + return shellescape(a:arg) +endfunction + +function! s:glob_dir(path) + return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)') +endfunction + +function! s:progress_bar(line, bar, total) + call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') +endfunction + +function! s:compare_git_uri(a, b) + " See `git help clone' + " https:// [user@] github.com[:port] / junegunn/vim-plug [.git] + " [git@] github.com[:port] : junegunn/vim-plug [.git] + " file:// / junegunn/vim-plug [/] + " / junegunn/vim-plug [/] + let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$' + let ma = matchlist(a:a, pat) + let mb = matchlist(a:b, pat) + return ma[1:2] ==# mb[1:2] +endfunction + +function! s:format_message(bullet, name, message) + if a:bullet != 'x' + return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] + else + let lines = map(s:lines(a:message), '" ".v:val') + return extend([printf('x %s:', a:name)], lines) + endif +endfunction + +function! s:with_cd(cmd, dir) + return printf('cd%s %s && %s', s:is_win ? ' /d' : '', s:shellesc(a:dir), a:cmd) +endfunction + +function! s:system(cmd, ...) + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let batchfile = tempname().'.bat' + call writefile(["@echo off\r", cmd . "\r"], batchfile) + let cmd = batchfile + endif + return system(s:is_win ? '('.cmd.')' : cmd) + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry +endfunction + +function! s:system_chomp(...) + let ret = call('s:system', a:000) + return v:shell_error ? '' : substitute(ret, '\n$', '', '') +endfunction + +function! s:git_validate(spec, check_branch) + let err = '' + if isdirectory(a:spec.dir) + let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url', a:spec.dir)) + let remote = result[-1] + if v:shell_error + let err = join([remote, 'PlugClean required.'], "\n") + elseif !s:compare_git_uri(remote, a:spec.uri) + let err = join(['Invalid URI: '.remote, + \ 'Expected: '.a:spec.uri, + \ 'PlugClean required.'], "\n") + elseif a:check_branch && has_key(a:spec, 'commit') + let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir)) + let sha = result[-1] + if v:shell_error + let err = join(add(result, 'PlugClean required.'), "\n") + elseif !s:hash_match(sha, a:spec.commit) + let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', + \ a:spec.commit[:6], sha[:6]), + \ 'PlugUpdate required.'], "\n") + endif + elseif a:check_branch + let branch = result[0] + " Check tag + if has_key(a:spec, 'tag') + let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) + if a:spec.tag !=# tag && a:spec.tag !~ '\*' + let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', + \ (empty(tag) ? 'N/A' : tag), a:spec.tag) + endif + " Check branch + elseif a:spec.branch !=# branch + let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', + \ branch, a:spec.branch) + endif + if empty(err) + let [ahead, behind] = split(s:lastline(s:system(printf( + \ 'git rev-list --count --left-right HEAD...origin/%s', + \ a:spec.branch), a:spec.dir)), '\t') + if !v:shell_error && ahead + if behind + " Only mention PlugClean if diverged, otherwise it's likely to be + " pushable (and probably not that messed up). + let err = printf( + \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n" + \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind) + else + let err = printf("Ahead of origin/%s by %d commit(s).\n" + \ .'Cannot update until local changes are pushed.', + \ a:spec.branch, ahead) + endif + endif + endif + endif + else + let err = 'Not found' + endif + return [err, err =~# 'PlugClean'] +endfunction + +function! s:rm_rf(dir) + if isdirectory(a:dir) + call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(a:dir)) + endif +endfunction + +function! s:clean(force) + call s:prepare() + call append(0, 'Searching for invalid plugins in '.g:plug_home) + call append(1, '') + + " List of valid directories + let dirs = [] + let errs = {} + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + if !s:is_managed(name) + call add(dirs, spec.dir) + else + let [err, clean] = s:git_validate(spec, 1) + if clean + let errs[spec.dir] = s:lines(err)[0] + else + call add(dirs, spec.dir) + endif + endif + let cnt += 1 + call s:progress_bar(2, repeat('=', cnt), total) + normal! 2G + redraw + endfor + + let allowed = {} + for dir in dirs + let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1 + let allowed[dir] = 1 + for child in s:glob_dir(dir) + let allowed[child] = 1 + endfor + endfor + + let todo = [] + let found = sort(s:glob_dir(g:plug_home)) + while !empty(found) + let f = remove(found, 0) + if !has_key(allowed, f) && isdirectory(f) + call add(todo, f) + call append(line('$'), '- ' . f) + if has_key(errs, f) + call append(line('$'), ' ' . errs[f]) + endif + let found = filter(found, 'stridx(v:val, f) != 0') + end + endwhile + + 4 + redraw + if empty(todo) + call append(line('$'), 'Already clean.') + else + let s:clean_count = 0 + call append(3, ['Directories to delete:', '']) + redraw! + if a:force || s:ask_no_interrupt('Delete all directories?') + call s:delete([6, line('$')], 1) + else + call setline(4, 'Cancelled.') + nnoremap d :set opfunc=delete_opg@ + nmap dd d_ + xnoremap d :call delete_op(visualmode(), 1) + echo 'Delete the lines (d{motion}) to delete the corresponding directories' + endif + endif + 4 + setlocal nomodifiable +endfunction + +function! s:delete_op(type, ...) + call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0) +endfunction + +function! s:delete(range, force) + let [l1, l2] = a:range + let force = a:force + while l1 <= l2 + let line = getline(l1) + if line =~ '^- ' && isdirectory(line[2:]) + execute l1 + redraw! + let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1) + let force = force || answer > 1 + if answer + call s:rm_rf(line[2:]) + setlocal modifiable + call setline(l1, '~'.line[1:]) + let s:clean_count += 1 + call setline(4, printf('Removed %d directories.', s:clean_count)) + setlocal nomodifiable + endif + endif + let l1 += 1 + endwhile +endfunction + +function! s:upgrade() + echo 'Downloading the latest version of vim-plug' + redraw + let tmp = tempname() + let new = tmp . '/plug.vim' + + try + let out = s:system(printf('git clone --depth 1 %s %s', s:plug_src, tmp)) + if v:shell_error + return s:err('Error upgrading vim-plug: '. out) + endif + + if readfile(s:me) ==# readfile(new) + echo 'vim-plug is already up-to-date' + return 0 + else + call rename(s:me, s:me . '.old') + call rename(new, s:me) + unlet g:loaded_plug + echo 'vim-plug has been upgraded' + return 1 + endif + finally + silent! call s:rm_rf(tmp) + endtry +endfunction + +function! s:upgrade_specs() + for spec in values(g:plugs) + let spec.frozen = get(spec, 'frozen', 0) + endfor +endfunction + +function! s:status() + call s:prepare() + call append(0, 'Checking plugins') + call append(1, '') + + let ecnt = 0 + let unloaded = 0 + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + let is_dir = isdirectory(spec.dir) + if has_key(spec, 'uri') + if is_dir + let [err, _] = s:git_validate(spec, 1) + let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] + else + let [valid, msg] = [0, 'Not found. Try PlugInstall.'] + endif + else + if is_dir + let [valid, msg] = [1, 'OK'] + else + let [valid, msg] = [0, 'Not found.'] + endif + endif + let cnt += 1 + let ecnt += !valid + " `s:loaded` entry can be missing if PlugUpgraded + if is_dir && get(s:loaded, name, -1) == 0 + let unloaded = 1 + let msg .= ' (not loaded)' + endif + call s:progress_bar(2, repeat('=', cnt), total) + call append(3, s:format_message(valid ? '-' : 'x', name, msg)) + normal! 2G + redraw + endfor + call setline(1, 'Finished. '.ecnt.' error(s).') + normal! gg + setlocal nomodifiable + if unloaded + echo "Press 'L' on each line to load plugin, or 'U' to update" + nnoremap L :call status_load(line('.')) + xnoremap L :call status_load(line('.')) + end +endfunction + +function! s:extract_name(str, prefix, suffix) + return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') +endfunction + +function! s:status_load(lnum) + let line = getline(a:lnum) + let name = s:extract_name(line, '-', '(not loaded)') + if !empty(name) + call plug#load(name) + setlocal modifiable + call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) + setlocal nomodifiable + endif +endfunction + +function! s:status_update() range + let lines = getline(a:firstline, a:lastline) + let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') + if !empty(names) + echo + execute 'PlugUpdate' join(names) + endif +endfunction + +function! s:is_preview_window_open() + silent! wincmd P + if &previewwindow + wincmd p + return 1 + endif +endfunction + +function! s:find_name(lnum) + for lnum in reverse(range(1, a:lnum)) + let line = getline(lnum) + if empty(line) + return '' + endif + let name = s:extract_name(line, '-', '') + if !empty(name) + return name + endif + endfor + return '' +endfunction + +function! s:preview_commit() + if b:plug_preview < 0 + let b:plug_preview = !s:is_preview_window_open() + endif + + let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}') + if empty(sha) + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) + return + endif + + if exists('g:plug_pwindow') && !s:is_preview_window_open() + execute g:plug_pwindow + execute 'e' sha + else + execute 'pedit' sha + wincmd P + endif + setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = 'cd '.s:shellesc(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha + if s:is_win + let batchfile = tempname().'.bat' + call writefile(["@echo off\r", cmd . "\r"], batchfile) + let cmd = batchfile + endif + execute 'silent %!' cmd + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry + setlocal nomodifiable + nnoremap q :q + wincmd p +endfunction + +function! s:section(flags) + call search('\(^[x-] \)\@<=[^:]\+:', a:flags) +endfunction + +function! s:format_git_log(line) + let indent = ' ' + let tokens = split(a:line, nr2char(1)) + if len(tokens) != 5 + return indent.substitute(a:line, '\s*$', '', '') + endif + let [graph, sha, refs, subject, date] = tokens + let tag = matchstr(refs, 'tag: [^,)]\+') + let tag = empty(tag) ? ' ' : ' ('.tag.') ' + return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date) +endfunction + +function! s:append_ul(lnum, text) + call append(a:lnum, ['', a:text, repeat('-', len(a:text))]) +endfunction + +function! s:diff() + call s:prepare() + call append(0, ['Collecting changes ...', '']) + let cnts = [0, 0] + let bar = '' + let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)') + call s:progress_bar(2, bar, len(total)) + for origin in [1, 0] + let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))')))) + if empty(plugs) + continue + endif + call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:') + for [k, v] in plugs + let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..' + let diff = s:system_chomp('git log --graph --color=never '.join(map(['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range], 's:shellesc(v:val)')), v.dir) + if !empty(diff) + let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : '' + call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)'))) + let cnts[origin] += 1 + endif + let bar .= '=' + call s:progress_bar(2, bar, len(total)) + normal! 2G + redraw + endfor + if !cnts[origin] + call append(5, ['', 'N/A']) + endif + endfor + call setline(1, printf('%d plugin(s) updated.', cnts[0]) + \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : '')) + + if cnts[0] || cnts[1] + nnoremap :silent! call preview_commit() + nnoremap o :silent! call preview_commit() + endif + if cnts[0] + nnoremap X :call revert() + echo "Press 'X' on each block to revert the update" + endif + normal! gg + setlocal nomodifiable +endfunction + +function! s:revert() + if search('^Pending updates', 'bnW') + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || + \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' + return + endif + + call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch).' --', g:plugs[name].dir) + setlocal modifiable + normal! "_dap + setlocal nomodifiable + echo 'Reverted' +endfunction + +function! s:snapshot(force, ...) abort + call s:prepare() + setf vim + call append(0, ['" Generated by vim-plug', + \ '" '.strftime("%c"), + \ '" :source this file in vim to restore the snapshot', + \ '" or execute: vim -S snapshot.vim', + \ '', '', 'PlugUpdate!']) + 1 + let anchor = line('$') - 3 + let names = sort(keys(filter(copy(g:plugs), + \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)'))) + for name in reverse(names) + let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir) + if !empty(sha) + call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha)) + redraw + endif + endfor + + if a:0 > 0 + let fn = expand(a:1) + if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?')) + return + endif + call writefile(getline(1, '$'), fn) + echo 'Saved as '.a:1 + silent execute 'e' s:esc(fn) + setf vim + endif +endfunction + +function! s:split_rtp() + return split(&rtp, '\\\@ +" +" Note: Based on the monokai theme for textmate +" by Wimer Hazenberg and its darker variant +" by Hamish Stuart Macpherson +" + +hi clear + +set background=dark +if version > 580 + " no guarantees for version 5.8 and below, but this makes it stop + " complaining + hi clear + if exists("syntax_on") + syntax reset + endif +endif +let g:colors_name="molokai" + +if exists("g:molokai_original") + let s:molokai_original = g:molokai_original +else + let s:molokai_original = 0 +endif + + +hi Boolean guifg=#AE81FF +hi Character guifg=#E6DB74 +hi Number guifg=#AE81FF +hi String guifg=#E6DB74 +hi Conditional guifg=#F92672 gui=bold +hi Constant guifg=#AE81FF gui=bold +hi Cursor guifg=#000000 guibg=#F35FBC +hi Debug guifg=#BCA3A3 gui=bold +hi Define guifg=#66D9EF +hi Delimiter guifg=#8F8F8F +hi DiffAdd guibg=#0F1D0B +hi DiffChange guifg=#89807D guibg=#322F2D +hi DiffDelete guifg=#960050 guibg=#1E0010 +hi DiffText guibg=#4A4340 gui=italic,bold + +hi Directory guifg=#A6E22E gui=bold +hi Error guifg=#960050 guibg=#1E0010 +hi ErrorMsg guifg=#F92672 guibg=#232526 gui=bold +hi Exception guifg=#A6E22E gui=bold +hi Float guifg=#AE81FF +hi FoldColumn guifg=#465457 guibg=#000000 +hi Folded guifg=#465457 guibg=#000000 +hi Function guifg=#A6E22E +hi Identifier guifg=#FD971F +hi Ignore guifg=#808080 guibg=bg +hi IncSearch guifg=#C4BE89 guibg=#000000 + +hi Keyword guifg=#F92672 gui=bold +hi Label guifg=#E6DB74 gui=none +hi Macro guifg=#C4BE89 gui=italic +hi SpecialKey guifg=#66D9EF gui=italic + +hi MatchParen guifg=#CD5907 guibg=#232728 gui=bold +hi ModeMsg guifg=#E6DB74 +hi MoreMsg guifg=#E6DB74 +hi Operator guifg=#F92672 + +" complete menu +hi Pmenu guifg=#66D9EF guibg=#000000 +hi PmenuSel guibg=#808080 +hi PmenuSbar guibg=#080808 +hi PmenuThumb guifg=#66D9EF + +hi PreCondit guifg=#A6E22E gui=bold +hi PreProc guifg=#A6E22E +hi Question guifg=#66D9EF +hi Repeat guifg=#F92672 gui=bold +hi Search guifg=#FFFFFF guibg=#455354 +" marks column +hi SignColumn guifg=#A6E22E guibg=#232526 +hi SpecialChar guifg=#F92672 gui=bold +hi SpecialComment guifg=#465457 gui=bold +hi Special guifg=#66D9EF guibg=bg gui=italic +hi SpecialKey guifg=#888A85 gui=italic +if has("spell") + hi SpellBad guisp=#FF0000 gui=undercurl + hi SpellCap guisp=#7070F0 gui=undercurl + hi SpellLocal guisp=#70F0F0 gui=undercurl + hi SpellRare guisp=#FFFFFF gui=undercurl +endif +hi Statement guifg=#F92672 gui=bold +hi StatusLine guifg=#CD5907 guibg=fg +hi StatusLineNC guifg=#808080 guibg=#080808 +hi StorageClass guifg=#FD971F gui=italic +hi Structure guifg=#66D9EF +hi Tag guifg=#F92672 gui=italic +hi Title guifg=#ef5939 +hi Todo guifg=#FFFFFF guibg=bg gui=bold + +hi Typedef guifg=#66D9EF +hi Type guifg=#66D9EF gui=none +hi Underlined guifg=#808080 gui=underline + +hi VertSplit guifg=#808080 guibg=#080808 gui=bold +hi VisualNOS guibg=#403D3D +hi Visual guibg=#403D3D +hi WarningMsg guifg=#FFFFFF guibg=#333333 gui=bold +hi WildMenu guifg=#66D9EF guibg=#000000 + + + +if s:molokai_original == 1 + hi Normal guifg=#F8F8F2 guibg=#272822 + hi Comment guifg=#75715E + hi CursorLine guibg=#3E3D32 + hi CursorColumn guibg=#3E3D32 + hi ColorColumn guibg=#3E3D32 + hi LineNr guifg=#AAAAAA guibg=#3B3A32 + hi NonText guifg=#BCBCBC guibg=#3B3A32 +else + hi Normal guifg=#F8F8F2 guibg=#1B1D1E + hi Folded guifg=#666666 guibg=#1B1D1E + hi Comment guifg=#465457 + hi CursorLine guibg=#232728 + hi CursorColumn guibg=#232728 + hi ColorColumn guibg=#232728 + hi LineNr guifg=#AAAAAA guibg=#1B1D1E + + " Invisible character colors + highlight NonText guifg=#444444 guibg=#1a1c1d + highlight SpecialKey guifg=#444444 guibg=#1a1c1d +end + +" +" Support for 256-color terminal +" +if &t_Co > 255 + hi Boolean ctermfg=135 + hi Character ctermfg=144 + hi Number ctermfg=135 + hi String ctermfg=144 + hi Conditional ctermfg=161 cterm=bold + hi Constant ctermfg=135 cterm=bold + hi Cursor ctermfg=16 ctermbg=253 + hi Debug ctermfg=225 cterm=bold + hi Define ctermfg=81 + hi Delimiter ctermfg=241 + + hi DiffAdd ctermbg=24 + hi DiffChange ctermfg=181 ctermbg=239 + hi DiffDelete ctermfg=162 ctermbg=53 + hi DiffText ctermbg=102 cterm=bold + + hi Directory ctermfg=118 cterm=bold + hi Error ctermfg=219 ctermbg=89 + hi ErrorMsg ctermfg=199 ctermbg=16 cterm=bold + hi Exception ctermfg=118 cterm=bold + hi Float ctermfg=135 + hi FoldColumn ctermfg=67 ctermbg=16 + hi Folded ctermfg=67 ctermbg=16 + hi Function ctermfg=118 + hi Identifier ctermfg=208 + hi Ignore ctermfg=244 ctermbg=232 + hi IncSearch ctermfg=193 ctermbg=16 + + hi Keyword ctermfg=161 cterm=bold + hi Label ctermfg=229 cterm=none + hi Macro ctermfg=193 + hi SpecialKey ctermfg=81 + + hi MatchParen ctermfg=16 ctermbg=208 cterm=bold + hi ModeMsg ctermfg=229 + hi MoreMsg ctermfg=229 + hi Operator ctermfg=161 + + " complete menu + hi Pmenu ctermfg=81 ctermbg=16 + hi PmenuSel ctermbg=244 + hi PmenuSbar ctermbg=232 + hi PmenuThumb ctermfg=81 + + hi PreCondit ctermfg=118 cterm=bold + hi PreProc ctermfg=118 + hi Question ctermfg=81 + hi Repeat ctermfg=161 cterm=bold + hi Search ctermfg=253 ctermbg=66 + + " marks column + hi SignColumn ctermfg=118 ctermbg=235 + hi SpecialChar ctermfg=161 cterm=bold + hi SpecialComment ctermfg=245 cterm=bold + hi Special ctermfg=81 ctermbg=232 + hi SpecialKey ctermfg=245 + + hi Statement ctermfg=161 cterm=bold + hi StatusLine ctermfg=238 ctermbg=253 + hi StatusLineNC ctermfg=244 ctermbg=232 + hi StorageClass ctermfg=208 + hi Structure ctermfg=81 + hi Tag ctermfg=161 + hi Title ctermfg=166 + hi Todo ctermfg=231 ctermbg=232 cterm=bold + + hi Typedef ctermfg=81 + hi Type ctermfg=81 cterm=none + hi Underlined ctermfg=244 cterm=underline + + hi VertSplit ctermfg=244 ctermbg=232 cterm=bold + hi VisualNOS ctermbg=238 + hi Visual ctermbg=235 + hi WarningMsg ctermfg=231 ctermbg=238 cterm=bold + hi WildMenu ctermfg=81 ctermbg=16 + + hi Normal ctermfg=252 ctermbg=233 + hi Comment ctermfg=59 + hi CursorLine ctermbg=234 cterm=none + hi CursorColumn ctermbg=234 + hi LineNr ctermfg=250 ctermbg=234 + hi NonText ctermfg=250 ctermbg=234 +end diff --git a/vim/colors/mustang.vim b/vim/colors/mustang.vim new file mode 100644 index 00000000..715605a0 --- /dev/null +++ b/vim/colors/mustang.vim @@ -0,0 +1,55 @@ +" Maintainer: Henrique C. Alves (hcarvalhoalves@gmail.com) +" Version: 1.0 +" Last Change: September 25 2008 + +set background=dark + +hi clear + +if exists("syntax_on") + syntax reset +endif + +let colors_name = "mustang" + +" Vim >= 7.0 specific colors +if version >= 700 + hi CursorLine guibg=#2d2d2d ctermbg=236 + hi CursorColumn guibg=#2d2d2d ctermbg=236 + hi MatchParen guifg=#d0ffc0 guibg=#2f2f2f gui=bold ctermfg=157 ctermbg=237 cterm=bold + hi Pmenu guifg=#ffffff guibg=#444444 ctermfg=255 ctermbg=238 + hi PmenuSel guifg=#000000 guibg=#b1d631 ctermfg=0 ctermbg=148 +endif + +" General colors +hi Cursor guifg=NONE guibg=#626262 gui=none ctermbg=241 +hi Normal guifg=#e2e2e5 guibg=#202020 gui=none ctermfg=253 ctermbg=234 +hi NonText guifg=#808080 guibg=#303030 gui=none ctermfg=244 ctermbg=235 +hi LineNr guifg=#808080 guibg=#000000 gui=none ctermfg=244 ctermbg=232 +hi StatusLine guifg=#d3d3d5 guibg=#444444 gui=italic ctermfg=253 ctermbg=238 cterm=italic +hi StatusLineNC guifg=#939395 guibg=#444444 gui=none ctermfg=246 ctermbg=238 +hi VertSplit guifg=#444444 guibg=#444444 gui=none ctermfg=238 ctermbg=238 +hi Folded guibg=#384048 guifg=#a0a8b0 gui=none ctermbg=4 ctermfg=248 +hi Title guifg=#f6f3e8 guibg=NONE gui=bold ctermfg=254 cterm=bold +hi Visual guifg=#faf4c6 guibg=#3c414c gui=none ctermfg=254 ctermbg=4 +hi SpecialKey guifg=#808080 guibg=#343434 gui=none ctermfg=244 ctermbg=236 + +" Syntax highlighting +hi Comment guifg=#808080 gui=italic ctermfg=244 +hi Todo guifg=#8f8f8f gui=italic ctermfg=245 +hi Boolean guifg=#b1d631 gui=none ctermfg=148 +hi String guifg=#b1d631 gui=italic ctermfg=148 +hi Identifier guifg=#b1d631 gui=none ctermfg=148 +hi Function guifg=#ffffff gui=bold ctermfg=255 +hi Type guifg=#7e8aa2 gui=none ctermfg=103 +hi Statement guifg=#7e8aa2 gui=none ctermfg=103 +hi Keyword guifg=#ff9800 gui=none ctermfg=208 +hi Constant guifg=#ff9800 gui=none ctermfg=208 +hi Number guifg=#ff9800 gui=none ctermfg=208 +hi Special guifg=#ff9800 gui=none ctermfg=208 +hi PreProc guifg=#faf4c6 gui=none ctermfg=230 +hi Todo guifg=#000000 guibg=#e6ea50 gui=italic + +" Code-specific colors +hi pythonOperator guifg=#7e8aa2 gui=none ctermfg=103 + diff --git a/vim/ftplugin/tex.vim b/vim/ftplugin/tex.vim new file mode 100644 index 00000000..8b399cd2 --- /dev/null +++ b/vim/ftplugin/tex.vim @@ -0,0 +1,4 @@ +setlocal wrap " Wrap lines +setlocal linebreak " Break lines at word boundaries + +map t :w\|!rubber -d % diff --git a/vim/snippets/ruby-rails.snippets b/vim/snippets/ruby-rails.snippets new file mode 100644 index 00000000..427bb526 --- /dev/null +++ b/vim/snippets/ruby-rails.snippets @@ -0,0 +1,3 @@ +snippet mcol add/remove column + add_column :${1:table}, :${2:column}, :${3:type} + remove_column :$1, :$2 diff --git a/vimrc b/vimrc new file mode 100644 index 00000000..e880369e --- /dev/null +++ b/vimrc @@ -0,0 +1,513 @@ +" Use vim settings, rather then vi settings (much better!) +" This must be first, because it changes other options as a side effect. +set nocompatible +set shell=bash +filetype off + +let mapleader = "," +let maplocalleader = "\\" + +call plug#begin('~/.vim/plugged') +" Vundle configuration, and config for each bundle {{{ +" Deps +Plug 'git://github.com/MarcWeber/vim-addon-mw-utils.git' +Plug 'git://github.com/tomtom/tlib_vim.git' + +" General +" Nice looking status line +Plug 'Lokaltog/vim-powerline' + +" Change surrounding stuff (e.g. cs'" to change ' to ") +Plug 'tpope/vim-surround' +" +" " Better repeats with . +Plug 'tpope/vim-repeat' +" +" " Rename the current file and update buffer (:Rename) +Plug 'tpope/vim-eunuch' +" +Plug 'sickill/vim-pasta' +"Plug 'jgdavey/tslime.vim' +"Plug 'jgdavey/vim-turbux' +" +" " ctrl-p to jump between files +Plug 'kien/ctrlp.vim' +let g:ctrlp_user_command = { + \ 'types': { + \ 1: ['.git/', 'cd %s && git ls-files'], + \ }, + \ 'fallback': 'find %s -type f' + \ } + +" " General Programming +Plug 'elixir-lang/vim-elixir' + +" " Live syntax checking +Plug 'scrooloose/syntastic' +Plug 'tpope/vim-endwise' +Plug 'tpope/vim-fugitive' + +Plug 'garbas/vim-snipmate' +Plug 'scrooloose/snipmate-snippets' + +" " Ruby +Plug 'tpope/vim-rails' +Plug 'tpope/vim-haml' + +" " JavaScript +Plug 'kchmck/vim-coffee-script' +Plug 'leafgarland/typescript-vim' + +" Clojure +" Plug 'VimClojure' +" let vimclojure#WantNailgun = 1 +" let vimclojure#UseErrorBuffer = 0 +" let vimclojure#SplitPos = "right" +" let g:vimclojure#HighlightBuiltins = 1 +" let g:vimclojure#ParenRainbow = 1 + +" Scala +Plug 'derekwyatt/vim-scala' +" +Plug 'tpope/vim-fireplace' +" +Plug 'scrooloose/nerdtree' +nmap n :NERDTreeClose:NERDTreeToggle +nmap N :NERDTreeClose +let NERDTreeBookmarksFile=expand("$HOME/.vim/NERDTreeBookmarks") " Store the bookmarks file +let NERDTreeShowBookmarks=1 " Show the bookmarks table on startup +let NERDTreeShowFiles=1 " Show hidden files, too +let NERDTreeShowHidden=1 +let NERDTreeQuitOnOpen=0 " Don't quit on opening files from the tree +let NERDTreeHighlightCursorline=1 " Highlight the selected entry in the tree +let NERDTreeMouseMode=1 " Use a double click to fold/unfold directories and a double click to open files +let NERDTreeIgnore=[ '\.pyc$', '\.pyo$', '\.py\$class$', '\.obj$', '\.o$', '\.so$', '\.egg$', '^\.git$' ] " Don't display these kinds of files + +Plug 'scrooloose/nerdcommenter' +" ,/ to invert comment on the current line/selection +nmap / :call NERDComment(0, "invert") +vmap / :call NERDComment(0, "invert") + +" Languages +" Plug 'sjl/threesome.vim' +" }}} + +call plug#end() + + + +filetype plugin indent on " enable detection, plugins and indenting in one step + + +" Editing behaviour {{{ +set showmode " always show what mode we're currently editing in +set nowrap " don't wrap lines +set tabstop=2 " a tab is four spaces +set softtabstop=2 " when hitting , pretend like a tab is removed, even if spaces +set shiftwidth=2 " number of spaces to use for autoindenting +set shiftround " use multiple of shiftwidth when indenting with '<' and '>' +set backspace=indent,eol,start " allow backspacing over everything in insert mode +set autoindent " always set autoindenting on +set copyindent " copy the previous indentation on autoindenting +set number " always show line numbers +set numberwidth=5 " good for 9999 lines before it starts to take on a different length +set showmatch " set show matching parenthesis +set ignorecase " ignore case when searching +set smartcase " ignore case if search pattern is all lowercase, case-sensitive otherwise +set smarttab " insert tabs on the start of a line according to shiftwidth, not tabstop +set scrolloff=4 " keep 4 lines off the edges of the screen when scrolling +set hlsearch " highlight search terms +set incsearch " show search matches as you type +set gdefault " search/replace "globally" (on a line) by default +"set listchars=tab▸\ ,trail:·,extends:#,nbsp:· + +set nolist " don't show invisible characters by default, but it is enabled for some file types (see later) +set pastetoggle= " when in insert mode, press to go to paste mode, where you can paste mass data that won't be autoindented +set mouse=a " enable using the mouse if terminal emulator supports it (xterm does) +set fileformats="unix,dos,mac" +set noeol " prevent vim from adding that stupid empty line at the end of every file +set binary " this needs to be on for the above to work +set expandtab " expand tabs by default (overloadable per file type later) +"set toolbar= " don't show the toolbar in gvim + +set completeopt=longest,menu,preview " Autocomplete to longest common substring, then show menu, and more info in preview window if available + +" Thanks to Steve Losh for this liberating tip +" See http://stevelosh.com/blog/2010/09/coming-home-to-vim +" nnoremap / /\v +" vnoremap / /\v + +" Speed up scrolling of the viewport slightly +nnoremap 2 +nnoremap 2 +" }}} + +" Folding rules {{{ +set foldenable " enable folding +set foldcolumn=2 " add a fold column +set foldmethod=marker " detect triple-{ style fold markers +set foldlevelstart=0 " start out with everything folded +set foldopen=block,hor,insert,jump,mark,percent,quickfix,search,tag,undo + " which commands trigger auto-unfold +" }}} + +" Editor layout {{ +set termencoding=utf-8 +set encoding=utf-8 +set lazyredraw " don't update the display while executing macros +set laststatus=2 " tell VIM to always put a status line in, even + " if there is only one window +set cmdheight=2 " use a status bar that is 2 rows high +set guioptions=aegiLt + +augroup ft_statuslinecolor + au! + + au InsertEnter * hi StatusLine ctermfg=196 guifg=#FF3145 + au InsertLeave * hi StatusLine ctermfg=130 guifg=#CD5907 +augroup END + +set statusline=%f " Path. +set statusline+=%m " Modified flag. +set statusline+=%r " Readonly flag. +set statusline+=%w " Preview window flag. + +set statusline+=\ " Space. + +set statusline+=%#redbar# " Highlight the following as a warning. +set statusline+=%{SyntasticStatuslineFlag()} " Syntastic errors. +set statusline+=%* " Reset highlighting. + +set statusline+=%= " Right align. + +" File format, encoding and type. Ex: "(unix/utf-8/python)" +set statusline+=( +set statusline+=%{&ff} " Format (unix/DOS). +set statusline+=/ +set statusline+=%{strlen(&fenc)?&fenc:&enc} " Encoding (utf-8). +set statusline+=/ +set statusline+=%{&ft} " Type (python). +set statusline+=) + +" Line and column position and counts. +set statusline+=\ (line\ %l\/%L,\ col\ %03c) +" }}} + +" Vim behaviour {{{ +set hidden " hide buffers instead of closing them this + " means that the current buffer can be put + " to background without being written; and + " that marks and undo history are preserved +set switchbuf=useopen " reveal already opened files from the + " quickfix window instead of opening new + " buffers +set history=1000 " remember more commands and search history +set undolevels=1000 " use many muchos levels of undo +if v:version >= 730 + set undofile " keep a persistent backup file + set undodir=~/tmp,/tmp +endif +set nobackup " do not keep backup files, it's 70's style cluttering +set noswapfile " do not write annoying intermediate swap files, + " who did ever restore from swap files anyway? +set directory=~/.vim/.tmp,~/tmp,/tmp + " store swap files in one of these directories + " (in case swapfile is ever turned on) +set viminfo='20,\"80 " read/write a .viminfo file, don't store more + " than 80 lines of registers +set wildmenu " make tab completion for files/buffers act like bash +set wildmode=list:full " show a list when pressing tab and complete + " first full match +set wildignore=.svn,CVS,.git,.hg,*.o,*.a,*.class,*.mo,*.la,*.so,*.obj,*.swp,*.jpg,*.png,*.xpm,*.gif + " ignore these files when completing names and in Explorer +set title " change the terminal's title +set visualbell " don't beep +set noerrorbells " don't beep +set showcmd " show (partial) command in the last line of the screen + " this also shows visual selection info +set nomodeline " disable mode lines (security measure) +set ttyfast " always use a fast terminal +"set cursorline " underline the current line, for quick orientation + +" Tame the quickfix window (open/close using ,f) +nmap f :QFix + +command! -bang -nargs=? QFix call QFixToggle(0) +function! QFixToggle(forced) + if exists("g:qfix_win") && a:forced == 0 + cclose + unlet g:qfix_win + else + copen 10 + let g:qfix_win = bufnr("$") + endif +endfunction + +augroup BWCCreateDir + au! + autocmd BufWritePre * if expand("")!~#'^\w\+:/' && !isdirectory(expand("%:h")) | execute "silent! !mkdir -p ".shellescape(expand('%:h'), 1) | redraw! | endif +augroup END +" }}} + +" Highlighting {{{ +if &t_Co >= 256 || $COLORTERM == 'gnome-terminal' || has("gui_running") + set t_Co=256 + set background=dark + colorscheme molokai + + function! ToggleBackground() + if (g:solarized_style=="dark") + let g:solarized_style="light" + colorscheme solarized + else + let g:solarized_style="dark" + colorscheme solarized + endif + endfunction + command! Togbg call ToggleBackground() + nnoremap :call ToggleBackground() + inoremap :call ToggleBackground()a + vnoremap :call ToggleBackground() +endif + +if &t_Co > 2 || has("gui_running") + syntax on " switch syntax highlighting on, when the terminal has colors +endif + +hi SpellErrors guibg=red guifg=black ctermbg=red ctermfg=black +" }}} + +" Shortcut mappings {{{ +" Since I never use the ; key anyway, this is a real optimization for almost +" all Vim commands, since we don't have to press that annoying Shift key that +" slows the commands down +nnoremap ; : + +" Why isn't this in by default, it's not like W does anything? +command! W w + +" Avoid accidental hits of while aiming for +map! + +" Quickly close the current window +nnoremap q :q + +" Use Q for formatting the current paragraph (or visual selection) +vmap Q gq +nmap Q gqap + +" make p in Visual mode replace the selected text with the yank register +vnoremap p :let current_reg = @"gvdi=current_reg + +" Swap implementations of ` and ' jump to markers +" By default, ' jumps to the marked line, ` jumps to the marked line and +" column, so swap them +nnoremap ' ` +nnoremap ` ' + +" ,e to switch between 2 last buffers +nmap e :b# + +" Please move through long wrapped lines logically +nnoremap j gj +nnoremap k gk + +" Easy window navigation +map h +map j +map k +map l + +" Use ,d (or ,dd or ,dj or 20,dd) to delete a line without adding it to the +" yanked stack (also, in visual mode) +nmap d "_d +vmap d "_d + +" Quick yanking to the end of the line +nmap Y y$ + +" Yank/paste to the OS clipboard with ,y and ,p +nmap y "+y +nmap Y "+yy +nmap p "+p +nmap P "+P +set clipboard=unnamed + +" YankRing stuff +let g:yankring_history_dir = '$HOME/.vim/.tmp' +nmap r :YRShow + +" Edit the vimrc file +nmap ev :e $MYVIMRC +nmap sv :so $MYVIMRC + +" Clears the search register +nmap / :nohlsearch + +" Quickly get out of insert mode without your fingers having to leave the +" home row ('jj' only, 'jk' doesn't work well with Dutch "-ijk"-words) +inoremap jj + +" Pull word under cursor into LHS of a substitute (for quick search and +" replace) +nmap z :%s#\<=expand("")\># + +" Scratch +nmap :Sscratchx + +" Sudo to write +cmap w!! w !sudo tee % >/dev/null + +" Jump to matching pairs easily, with Tab +nnoremap % +vnoremap % + +" Folding +nnoremap za +vnoremap za + +" Strip all trailing whitespace from a file, using ,w +nnoremap W :%s/\s\+$//:let @/='' + +" Run Ack fast (mind the trailing space here, wouldya?) +nnoremap a :Ack + +" Creating folds for tags in HTML +"nnoremap ft Vatzf + +" Reselect text that was just pasted with ,v +nnoremap v V`] + +function! InsertTabWrapper() + let col = col('.') - 1 + if !col || getline('.')[col - 1] !~ '\k' + return "\" + else + return "\" + endif +endfunction +inoremap =InsertTabWrapper() + +" }}} + +" For latex, enable wrapping +autocmd filetype latex set wrap +autocmd filetype latex map ,c :w\|!rubber -d % + + +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +"set autowriteall " Automatically save before commands like :next and :make +"set history=1000 + +"" highlight trailing whitespace +set listchars=tab:▷⋅,trail:·,eol:$ +nmap s :set nolist! + +"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +" Stolen from https://github.com/garybernhardt/dotfiles/blob/master/.vimrc + +" Map keys to go to specific files +map gr :topleft :split config/routes.rb +function! ShowRoutes() +" Requires 'scratch' plugin + :topleft 100 :split __Routes__ +" Make sure Vim doesn't write __Routes__ as a file + :set buftype=nofile +" Delete everything + :normal 1GdG +" Put routes output in buffer + :0r! rake -s routes +" Size window to number of lines (1 plus rake output length) + :exec ":normal " . line("$") . _ " +" Move cursor to bottom + :normal 1GG +" Delete empty trailing line + :normal dd +endfunction +map gR :call ShowRoutes() +"map ga :CommandTFlush\|:CommandT app/assets +"map gv :CommandTFlush\|:CommandT app/views +"map gc :CommandTFlush\|:CommandT app/controllers +"map gm :CommandTFlush\|:CommandT app/models +"map gh :CommandTFlush\|:CommandT app/helpers +"map gl :CommandTFlush\|:CommandT lib +"map gp :CommandTFlush\|:CommandT public +"map gs :CommandTFlush\|:CommandT spec +"map gf :CommandTFlush\|:CommandT features +"map gg :topleft 100 :split Gemfile +"map f :CommandTFlush\|:CommandT +"map F :CommandTFlush\|:CommandT %% + +nnoremap + +function! RunTests(filename) +" Write the file and run tests for the given filename + :w + :silent !echo;echo;echo;echo;echo;echo;echo;echo;echo;echo + " Run tests directly in current terminal + exec ":!soundcheck " . a:filename + + " Or send them to a tmux instance + "call Send_to_Tmux("soundcheck " . a:filename . "\n") + + " TODO: Write a switching function for this so I don't have to edit + " vimrc to switch. +endfunction + +function! SetTestFile() +" Set the spec file that tests will be run for. + let t:grb_test_file=@% +endfunction + +function! RunTestFile(...) + if a:0 + let command_suffix = a:1 + else + let command_suffix = "" + endif + +" Run the tests for the previously-marked file. + let in_test_file = match(expand("%"), '\(.feature\|_spec.rb\|_test.rb\)$') != -1 + if in_test_file + call SetTestFile() + elseif !exists("t:grb_test_file") + return + end + call RunTests(t:grb_test_file . command_suffix) +endfunction + +function! RunNearestTest() + let spec_line_number = line('.') + call RunTestFile(":" . spec_line_number) +endfunction + +noremap t :call RunTestFile() +map T :call RunNearestTest() +map a :call RunTests('') +map c :w\|:!cucumber +map C :w\|:!cucumber --profile wip + +function! PromoteToLet() + :normal! dd + :normal! P + :.s/\(\w\+\) = \(.*\)$/let(:\1) { \2 }/ + :normal == +endfunction +:command! PromoteToLet :call PromoteToLet() + +map p :call PromoteToLet() + +fun! StripTrailingWhitespaces() + let l = line(".") + let c = col(".") + %s/\s\+$//e + %s/\($\n\s*\)\+\%$//e + call cursor(l, c) +endfun + +autocmd FileType c,cpp,java,php,ruby,python autocmd BufWritePre :call StripTrailingWhitespaces() + +set winwidth=78 +"set winheight=5 +"set winminheight=5 +"set winheight=999 diff --git a/zsh/aliases b/zsh/aliases new file mode 100644 index 00000000..5847bcc6 --- /dev/null +++ b/zsh/aliases @@ -0,0 +1,13 @@ +alias ls='ls -FG' +alias em='/Applications/Emacs.app/Contents/MacOS/Emacs &' +alias e='emacsclient -n' +alias ssh='/usr/bin/ssh' + +# git +alias g='git' +alias gp='git pull --rebase' +alias gti=git + +# the silver searcher +alias ag="ag --path-to-agignore=$HOME/.agignore" +alias git-up="~/.rvm/wrappers/default/git-up" diff --git a/zsh/config.zsh b/zsh/config.zsh new file mode 100644 index 00000000..a0931317 --- /dev/null +++ b/zsh/config.zsh @@ -0,0 +1,32 @@ +autoload -U colors +colors + +setopt promptsubst +autoload -U promptinit +promptinit +prompt marten + +# Automatically pushd +setopt autopushd + +export GEOLOQI_TOKEN="6d67-6079eaaa6955cb62838be656e36cd00398d7ec55" + +export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH +export MAKEFLAGS="-j8" +export EDITOR=vim + +# Setup autojump if we have it (https://github.com/joelthelion/autojump/wiki/) +[[ -s /etc/profile.d/autojump.zsh ]] && source /etc/profile.d/autojump.zsh +if [ -f /usr/local/etc/autojump ]; then + . /usr/local/etc/autojump +fi + +# Setup nvm if we have it +[[ -s "$HOME/.nvm/nvm.sh" ]] && source "$HOME/.nvm/nvm.sh" + +# Setup my own bin as the first place to look +#export PATH=$HOME/bin:$PATH + +function dburl() { + dropbox puburl ~/Dropbox/Public/$1.png | pbcopy +} \ No newline at end of file diff --git a/zsh/functions/clean b/zsh/functions/clean new file mode 100644 index 00000000..2047dc6f --- /dev/null +++ b/zsh/functions/clean @@ -0,0 +1,9 @@ +# Remove useless files +clean () { + if [ "$1" = "-r" ]; then + find . \( -name '#*' -o -name '*~' -o -name '.*~' -o -name 'core*' \ + -o -name 'dead*' \) -ok rm '{}' ';' + else + rm -i \#* *~ .*~ core* dead* + fi +} diff --git a/zsh/functions/folsym b/zsh/functions/folsym new file mode 100644 index 00000000..40d59f99 --- /dev/null +++ b/zsh/functions/folsym @@ -0,0 +1,29 @@ +# Show the path from a symlink to its ultimate source. +if [[ -e $1 || -h $1 ]]; then +file=$1 +else +file=`which $1` +fi +if [[ -e $file || -L $file ]]; then +if [[ -L $file ]]; then + echo `ls -ld $file | perl -ane 'print $F[7]'` '->' + folsym `perl -le '$file = $ARGV[0]; + $dest = readlink $file; + if ($dest !~ m{^/}) { + $file =~ s{(/?)[^/]*$}{$1$dest}; + } else { + $file = $dest; + } + $file =~ s{/{2,}}{/}g; + while ($file =~ s{[^/]+/\.\./}{}) { + ; + } + $file =~ s{^(/\.\.)+}{}; + print $file' $file` + +else + ls -d $file +fi +else +echo $file +fi diff --git a/zsh/functions/prompt_grb_setup b/zsh/functions/prompt_grb_setup new file mode 100644 index 00000000..2c0894df --- /dev/null +++ b/zsh/functions/prompt_grb_setup @@ -0,0 +1,340 @@ +# grb prompt theme +# copied from wunjo prompt theme and modified + +autoload -U zgitinit +zgitinit + +prompt_grb_help () { + cat <<'EOF' + + prompt grb + +EOF +} + +revstring() { + git describe --always $1 2>/dev/null || + git rev-parse --short $1 2>/dev/null +} + +coloratom() { + local off=$1 atom=$2 + if [[ $atom[1] == [[:upper:]] ]]; then + off=$(( $off + 60 )) + fi + echo $(( $off + $colorcode[${(L)atom}] )) +} +colorword() { + local fg=$1 bg=$2 att=$3 + local -a s + + if [ -n "$fg" ]; then + s+=$(coloratom 30 $fg) + fi + if [ -n "$bg" ]; then + s+=$(coloratom 40 $bg) + fi + if [ -n "$att" ]; then + s+=$attcode[$att] + fi + + echo "%{"$'\e['${(j:;:)s}m"%}" +} + +function minutes_since_last_commit { + now=`date +%s` + last_commit=`git log --pretty=format:'%at' -1 2>/dev/null` + if $lastcommit ; then + seconds_since_last_commit=$((now-last_commit)) + minutes_since_last_commit=$((seconds_since_last_commit/60)) + echo $minutes_since_last_commit + else + echo "-1" + fi +} + +function prompt_grb_scm_time_since_commit() { + local -A pc + pc=(${(kv)wunjo_prompt_colors}) + + if zgit_inworktree; then + local MINUTES_SINCE_LAST_COMMIT=`minutes_since_last_commit` + if [ "$MINUTES_SINCE_LAST_COMMIT" -eq -1 ]; then + COLOR="$pc[scm_time_uncommitted]" + local SINCE_LAST_COMMIT="${COLOR}uncommitted$pc[reset]" + else + if [ "$MINUTES_SINCE_LAST_COMMIT" -gt 30 ]; then + COLOR="$pc[scm_time_long]" + elif [ "$MINUTES_SINCE_LAST_COMMIT" -gt 10 ]; then + COLOR="$pc[scm_time_medium]" + else + COLOR="$pc[scm_time_short]" + fi + local SINCE_LAST_COMMIT="${COLOR}$(minutes_since_last_commit)m$pc[reset]" + fi + echo $SINCE_LAST_COMMIT + fi +} + +function prompt_grb_scm_info() { + if zgit_inworktree; then + echo "($(prompt_wunjo_scm_branch))" + fi +} + +prompt_grb_setup() { + local verbose + if [[ $TERM == screen* ]] && [ -n "$STY" ]; then + verbose= + else + verbose=1 + fi + + typeset -A colorcode + colorcode[black]=0 + colorcode[red]=1 + colorcode[green]=2 + colorcode[yellow]=3 + colorcode[blue]=4 + colorcode[magenta]=5 + colorcode[cyan]=6 + colorcode[white]=7 + colorcode[default]=9 + colorcode[k]=$colorcode[black] + colorcode[r]=$colorcode[red] + colorcode[g]=$colorcode[green] + colorcode[y]=$colorcode[yellow] + colorcode[b]=$colorcode[blue] + colorcode[m]=$colorcode[magenta] + colorcode[c]=$colorcode[cyan] + colorcode[w]=$colorcode[white] + colorcode[.]=$colorcode[default] + + typeset -A attcode + attcode[none]=00 + attcode[bold]=01 + attcode[faint]=02 + attcode[standout]=03 + attcode[underline]=04 + attcode[blink]=05 + attcode[reverse]=07 + attcode[conceal]=08 + attcode[normal]=22 + attcode[no-standout]=23 + attcode[no-underline]=24 + attcode[no-blink]=25 + attcode[no-reverse]=27 + attcode[no-conceal]=28 + + local -A pc + pc[default]='default' + pc[date]='cyan' + pc[time]='Blue' + pc[host]='Green' + pc[user]='cyan' + pc[punc]='yellow' + pc[line]='magenta' + pc[hist]='green' + pc[path]='Cyan' + pc[shortpath]='default' + pc[rc]='red' + pc[scm_branch]='green' + pc[scm_commitid]='Yellow' + pc[scm_status_dirty]='Red' + pc[scm_status_staged]='Green' + pc[scm_time_short]='green' + pc[scm_time_medium]='yellow' + pc[scm_time_long]='red' + pc[scm_time_uncommitted]='Magenta' + pc[#]='Yellow' + for cn in ${(k)pc}; do + pc[${cn}]=$(colorword $pc[$cn]) + done + pc[reset]=$(colorword . . 00) + + typeset -Ag wunjo_prompt_colors + wunjo_prompt_colors=(${(kv)pc}) + + local p_date p_line p_rc + + p_date="$pc[date]%D{%Y-%m-%d} $pc[time]%D{%T}$pc[reset]" + + p_line="$pc[line]%y$pc[reset]" + + PROMPT= + if [ $verbose ]; then + PROMPT+="$pc[host]%m$pc[reset]" + fi + #PROMPT+="$pc[path]%(2~.%~.%/)$pc[reset]" + #PROMPT+="\$(prompt_wunjo_scm_status)" + #PROMPT+="%(?.. $pc[rc]exited %1v$pc[reset])" +# PROMPT+=" +#" + #PROMPT+="($pc[hist]%h$pc[reset])" + PROMPT+=":$pc[shortpath]%1~$pc[reset]" + PROMPT+="($pc[scm_branch]\$(prompt_wunjo_scm_branch)$pc[reset])" + PROMPT+=" $pc[#]\$$pc[reset] " + + #RPROMPT= + #if [ $verbose ]; then + # RPROMPT+="$p_date " + #fi + #RPROMPT+="$pc[user]%n$pc[reset]" + #RPROMPT+=" $p_line" + + export PROMPT RPROMPT + precmd_functions+='prompt_wunjo_precmd' +} + +prompt_wunjo_precmd() { + local ex=$? + psvar=() + + if [[ $ex -ge 128 ]]; then + sig=$signals[$ex-127] + psvar[1]="sig${(L)sig}" + else + psvar[1]="$ex" + fi +} + +prompt_wunjo_scm_status() { + zgit_isgit || return + local -A pc + pc=(${(kv)wunjo_prompt_colors}) + + head=$(zgit_head) + gitcommit=$(revstring $head) + + local -a commits + + if zgit_rebaseinfo; then + orig_commit=$(revstring $zgit_info[rb_head]) + orig_name=$(git name-rev --name-only $zgit_info[rb_head]) + orig="$pc[scm_branch]$orig_name$pc[punc]($pc[scm_commitid]$orig_commit$pc[punc])" + onto_commit=$(revstring $zgit_info[rb_onto]) + onto_name=$(git name-rev --name-only $zgit_info[rb_onto]) + onto="$pc[scm_branch]$onto_name$pc[punc]($pc[scm_commitid]$onto_commit$pc[punc])" + + if [ -n "$zgit_info[rb_upstream]" ] && [ $zgit_info[rb_upstream] != $zgit_info[rb_onto] ]; then + upstream_commit=$(revstring $zgit_info[rb_upstream]) + upstream_name=$(git name-rev --name-only $zgit_info[rb_upstream]) + upstream="$pc[scm_branch]$upstream_name$pc[punc]($pc[scm_commitid]$upstream_commit$pc[punc])" + commits+="rebasing $upstream$pc[reset]..$orig$pc[reset] onto $onto$pc[reset]" + else + commits+="rebasing $onto$pc[reset]..$orig$pc[reset]" + fi + + local -a revs + revs=($(git rev-list $zgit_info[rb_onto]..HEAD)) + if [ $#revs -gt 0 ]; then + commits+="\n$#revs commits in" + fi + + if [ -f $zgit_info[dotest]/message ]; then + mess=$(head -n1 $zgit_info[dotest]/message) + commits+="on $mess" + fi + elif [ -n "$gitcommit" ]; then + commits+="on $pc[scm_branch]$head$pc[punc]($pc[scm_commitid]$gitcommit$pc[punc])$pc[reset]" + local track_merge=$(zgit_tracking_merge) + if [ -n "$track_merge" ]; then + if git rev-parse --verify -q $track_merge >/dev/null; then + local track_remote=$(zgit_tracking_remote) + local tracked=$(revstring $track_merge 2>/dev/null) + + local -a revs + revs=($(git rev-list --reverse $track_merge..HEAD)) + if [ $#revs -gt 0 ]; then + local base=$(revstring $revs[1]~1) + local base_name=$(git name-rev --name-only $base) + local base_short=$(revstring $base) + local word_commits + if [ $#revs -gt 1 ]; then + word_commits='commits' + else + word_commits='commit' + fi + + local conj="since" + if [[ "$base" == "$tracked" ]]; then + conj+=" tracked" + tracked= + fi + commits+="$#revs $word_commits $conj $pc[scm_branch]$base_name$pc[punc]($pc[scm_commitid]$base_short$pc[punc])$pc[reset]" + fi + + if [ -n "$tracked" ]; then + local track_name=$track_merge + if [[ $track_remote == "." ]]; then + track_name=${track_name##*/} + fi + tracked=$(revstring $tracked) + commits+="tracking $pc[scm_branch]$track_name$pc[punc]" + if [[ "$tracked" != "$gitcommit" ]]; then + commits[$#commits]+="($pc[scm_commitid]$tracked$pc[punc])" + fi + commits[$#commits]+="$pc[reset]" + fi + fi + fi + fi + + gitsvn=$(git rev-parse --verify -q --short git-svn) + if [ $? -eq 0 ]; then + gitsvnrev=$(zgit_svnhead $gitsvn) + gitsvn=$(revstring $gitsvn) + if [ -n "$gitsvnrev" ]; then + local svninfo='' + local -a revs + svninfo+="$pc[default]svn$pc[punc]:$pc[scm_branch]r$gitsvnrev" + revs=($(git rev-list git-svn..HEAD)) + if [ $#revs -gt 0 ]; then + svninfo+="$pc[punc]@$pc[default]HEAD~$#revs" + svninfo+="$pc[punc]($pc[scm_commitid]$gitsvn$pc[punc])" + fi + commits+=$svninfo + fi + fi + + if [ $#commits -gt 0 ]; then + echo -n " ${(j: :)commits}" + fi +} + +prompt_wunjo_scm_branch() { + zgit_isgit || return + local -A pc + pc=(${(kv)wunjo_prompt_colors}) + + echo -n "$pc[punc]$pc[scm_branch]$(zgit_head)" + + if zgit_inworktree; then + if ! zgit_isindexclean; then + echo -n "$pc[scm_status_staged]+" + fi + + local -a dirty + if ! zgit_isworktreeclean; then + dirty+='!' + fi + + if zgit_hasunmerged; then + dirty+='*' + fi + + if zgit_hasuntracked; then + dirty+='?' + fi + + if [ $#dirty -gt 0 ]; then + echo -n "$pc[scm_status_dirty]${(j::)dirty}" + fi + fi + + echo $pc[reset] +} + +prompt_grb_setup "$@" + +# vim:set ft=zsh: diff --git a/zsh/functions/prompt_marten_setup b/zsh/functions/prompt_marten_setup new file mode 100644 index 00000000..8b63f35d --- /dev/null +++ b/zsh/functions/prompt_marten_setup @@ -0,0 +1,22 @@ +# marten prompt theme +local rvm_ruby='%{$fg[red]%}‹$(rvm-prompt i v g)›%{$reset_color%}' +local smiley="%(?,%{$fg[green]%}☺%{$reset_color%},%{$fg[red]%}☹%{$reset_color%})" + +prompt_marten_help () { + cat <<'EOF' +This is my prompt +EOF +} + +prompt_marten_setup () { + precmd () { prompt_marten_precmd } + preexec () { } +} + +prompt_marten_precmd () { + # *Very* important to use single quotes, not double quotes... WHY? + PROMPT='%{$fg_bold[red]%}➜ %{$fg_bold[green]%}%p %{$fg[cyan]%}%~ $(git_prompt_info)%{$reset_color%} ' + RPROMPT="%(?..%{$fg_bold[red]%}[%?]%{$reset_color%})" +} + +prompt_marten_setup "$@" diff --git a/zsh/functions/prompt_pilif_setup b/zsh/functions/prompt_pilif_setup new file mode 100644 index 00000000..6f1c2e4a --- /dev/null +++ b/zsh/functions/prompt_pilif_setup @@ -0,0 +1,52 @@ +# pilif prompt theme + +prompt_pilif_help () { + cat <<'EOF' +This prompt is color-scheme-able. You can invoke it thus: + + prompt pilif [ [ []]] + +This is heavily based on adam1 which is distributed with ZSH. In fact, +the only change from adam1 is support for displaying the current branch +of your git repository (if you are in one) +EOF +} + +prompt_pilif_setup () { + prompt_adam1_color1=${1:-'blue'} + prompt_adam1_color2=${2:-'cyan'} + prompt_adam1_color3=${3:-'green'} + + base_prompt="%{$bg_no_bold[$prompt_adam1_color1]%}%n@%m%{$reset_color%} " + post_prompt="%{$reset_color%}" + + base_prompt_no_color=$(echo "$base_prompt" | perl -pe "s/%{.*?%}//g") + post_prompt_no_color=$(echo "$post_prompt" | perl -pe "s/%{.*?%}//g") + + precmd () { prompt_pilif_precmd } + preexec () { } +} + +prompt_pilif_precmd () { + setopt noxtrace localoptions + local base_prompt_expanded_no_color base_prompt_etc + local prompt_length space_left + local git_branch + + git_branch=`git symbolic-ref HEAD|awk '{sub(/^refs\/heads\//, "", $1); print " ("$1")"}'` + base_prompt_expanded_no_color=$(print -P "$base_prompt_no_color") + base_prompt_etc=$(print -P "$base_prompt%(4~|...|)%3~") + prompt_length=${#base_prompt_etc} + if [[ $prompt_length -lt 40 ]]; then + path_prompt="%{$fg_bold[$prompt_adam1_color2]%}%(4~|...|)%3~%{$fg_bold[white]%}$git_branch" + else + space_left=$(( $COLUMNS - $#base_prompt_expanded_no_color - 2 )) + path_prompt="%{$fg_bold[$prompt_adam1_color3]%}%${space_left}<...<%~ %{$reset_color%}$git_branch%{$fg_bold[$prompt_adam1_color3]%} $prompt_newline%{$fg_bold_white%}" + fi + + PS1="$base_prompt$path_prompt %# $post_prompt" + PS2="$base_prompt$path_prompt %_> $post_prompt" + PS3="$base_prompt$path_prompt ?# $post_prompt" +} + +prompt_pilif_setup "$@" diff --git a/zsh/functions/start-ssh-agent b/zsh/functions/start-ssh-agent new file mode 100644 index 00000000..dc5035b8 --- /dev/null +++ b/zsh/functions/start-ssh-agent @@ -0,0 +1,11 @@ +# Check for the existence of an SSH agent. If none, start one. +if [[ "$UID" != 0 ]]; then + case `hostname` in + tigger*|pooh*) + ps -x | grep [s]sh-agent$ > /dev/null || { + echo -n "Starting ssh-agent..." + ssh-agent > /dev/null + echo " done" + } + esac +fi \ No newline at end of file diff --git a/zsh/lib/aliases.zsh b/zsh/lib/aliases.zsh new file mode 100644 index 00000000..f90ff369 --- /dev/null +++ b/zsh/lib/aliases.zsh @@ -0,0 +1,47 @@ +# Push and pop directories on directory stack +alias pu='pushd' +alias po='popd' + +# Basic directory operations +alias ...='cd ../..' +alias -- -='cd -' + +# Super user +alias _='sudo' + +#alias g='grep -in' + +# Show history +alias history='fc -l 1' + +# List direcory contents +alias lsa='ls -lah' +alias l='ls -la' +alias ll='ls -l' +alias sl=ls # often screw this up + +alias e='emacsclient -nw' + +alias git-up="~/.rvm/wrappers/default/git-up" +alias datagrip="/usr/local/datagrip/bin/datagrip.sh" + +alias afind='ack-grep -il' +alias awssh="docker run -it --rm --volume $HOME/.ssh:/root/.ssh -e AWS_DEFAULT_REGION -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN -e AWS_SECURITY_TOKEN astopy/awssh" + +alias psql-panoptes-staging='psql -h panoptes-staging-new.cezuuccr9cw6.us-east-1.rds.amazonaws.com --user panoptes panoptes' +alias psql-panoptes-production='psql -h panoptes-production.cezuuccr9cw6.us-east-1.rds.amazonaws.com --user readonly panoptes' +alias psql-panoptes-slave='psql -h panoptes-production-read1.cezuuccr9cw6.us-east-1.rds.amazonaws.com --user readonly panoptes' + +alias psql-talk-production='psql -h panoptes-microservices-production.cezuuccr9cw6.us-east-1.rds.amazonaws.com --user talk_production talk_production' + +alias psql-edu-staging='psql -h microservices-staging.cezuuccr9cw6.us-east-1.rds.amazonaws.com --user education_staging education_staging' +alias psql-edu-production='psql -h microservices-production.cezuuccr9cw6.us-east-1.rds.amazonaws.com --user education_production education_production' + +alias psql-caesar-staging='psql -h microservices-staging.cezuuccr9cw6.us-east-1.rds.amazonaws.com --user caesar_staging caesar_staging' +alias psql-caesar-production='psql -h microservices-production.cezuuccr9cw6.us-east-1.rds.amazonaws.com --user caesar_production caesar_production' + +alias psql-nero-production='psql -h microservices-production.cezuuccr9cw6.us-east-1.rds.amazonaws.com --user nero nero' + +alias psql-turk-production='psql -h microservices-production.cezuuccr9cw6.us-east-1.rds.amazonaws.com --user mechanical_zoo_production mechanical_zoo_production' + +alias sas='hermes exec StandaloneAppsSwarm -- docker ' diff --git a/zsh/lib/asdf-vm.zsh b/zsh/lib/asdf-vm.zsh new file mode 100644 index 00000000..5c8d7949 --- /dev/null +++ b/zsh/lib/asdf-vm.zsh @@ -0,0 +1,4 @@ +if [[ -s $HOME/.asdf/asdf.sh ]] ; then + . $HOME/.asdf/asdf.sh + . $HOME/.asdf/completions/asdf.bash +fi diff --git a/zsh/lib/autojump.zsh b/zsh/lib/autojump.zsh new file mode 100644 index 00000000..9ca09404 --- /dev/null +++ b/zsh/lib/autojump.zsh @@ -0,0 +1,5 @@ +if (( $+commands[brew] )) ; then + [[ -s `brew --prefix`/etc/autojump.sh ]] && . `brew --prefix`/etc/autojump.sh +fi + +[[ -s /usr/share/autojump/autojump.sh ]] && . /usr/share/autojump/autojump.sh diff --git a/zsh/lib/aws.zsh b/zsh/lib/aws.zsh new file mode 100644 index 00000000..f51e024e --- /dev/null +++ b/zsh/lib/aws.zsh @@ -0,0 +1,3 @@ +if [[ -a /usr/local/share/zsh/site-functions/_aws ]] ; then + source /usr/local/share/zsh/site-functions/_aws +fi diff --git a/zsh/lib/completion.zsh b/zsh/lib/completion.zsh new file mode 100644 index 00000000..b964595e --- /dev/null +++ b/zsh/lib/completion.zsh @@ -0,0 +1,72 @@ +# fixme - the load process here seems a bit bizarre + +unsetopt menu_complete # do not autoselect the first completion entry +unsetopt flowcontrol +setopt auto_menu # show completion menu on succesive tab press +setopt complete_in_word +setopt always_to_end + +WORDCHARS='' + +zmodload -i zsh/complist + +## case-insensitive (all),partial-word and then substring completion +if [ "x$CASE_SENSITIVE" = "xtrue" ]; then + zstyle ':completion:*' matcher-list 'r:|[._-]=* r:|=*' 'l:|=* r:|=*' + unset CASE_SENSITIVE +else + zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=*' 'l:|=* r:|=*' +fi + +zstyle ':completion:*' list-colors '' + +# should this be in keybindings? +bindkey -M menuselect '^o' accept-and-infer-next-history + +zstyle ':completion:*:*:*:*:*' menu select +zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#) ([0-9a-z-]#)*=01;34=0=01' +zstyle ':completion:*:*:*:*:processes' command "ps -u `whoami` -o pid,user,comm -w -w" + +# disable named-directories autocompletion +zstyle ':completion:*:cd:*' tag-order local-directories directory-stack path-directories +cdpath=(.) + +# use /etc/hosts and known_hosts for hostname completion +[ -r /etc/ssh/ssh_known_hosts ] && _global_ssh_hosts=(${${${${(f)"$( /dev/null) || return + + echo "%{$fg_bold[blue]%}git:(%{$reset_color%}\ +$(git_prompt_time_since_commit)\ +|\ +%{$fg_bold[blue]%}${ref#refs/heads/}%{$reset_color%}\ +$(git_prompt_dirty_or_clean)\ +%{$fg_bold[blue]%})%{$reset_color%}" +} + +# Checks if working tree is dirty +function git_prompt_dirty_or_clean() { + if [[ -n $(git status -s 2> /dev/null) ]]; then + echo "%{$fg[yellow]%}✗%{$reset_color%}" + else + echo "" + fi +} + +# Stolen from: https://github.com/garybernhardt/dotfiles/blob/master/.zsh/func/prompt_grb_setup +function git_minutes_since_last_commit() { + now=`date +%s` + last_commit=`git log --pretty=format:'%at' -1 2>/dev/null` + if $lastcommit ; then + seconds_since_last_commit=$((now-last_commit)) + minutes_since_last_commit=$((seconds_since_last_commit/60)) + echo $minutes_since_last_commit + else + echo "-1" + fi +} + +# Formats time since last commit, in minutes +function git_prompt_time_since_commit() { + local MINUTES_SINCE_LAST_COMMIT=`git_minutes_since_last_commit` + if [ "$MINUTES_SINCE_LAST_COMMIT" -eq -1 ]; then + local SINCE_LAST_COMMIT="%{$fg[magenta]%}uncommitted%{$reset_color%}" + else + if [ "$MINUTES_SINCE_LAST_COMMIT" -gt 30 ]; then + COLOR="%{$fg[red]%}" + elif [ "$MINUTES_SINCE_LAST_COMMIT" -gt 10 ]; then + COLOR="%{$fg[yellow]%}" + else + COLOR="%{$fg[green]%}" + fi + local SINCE_LAST_COMMIT="${COLOR}${MINUTES_SINCE_LAST_COMMIT}m%{$reset_color%}" + fi + echo $SINCE_LAST_COMMIT +} + +# Checks if there are commits ahead from remote +function git_prompt_ahead() { + if $(echo "$(git log origin/$(current_branch)..HEAD 2> /dev/null)" | grep '^commit' &> /dev/null); then + echo "$ZSH_THEME_GIT_PROMPT_AHEAD" + fi +} + +# Formats prompt string for current git commit short SHA +function git_prompt_short_sha() { + SHA=$(git rev-parse --short HEAD 2> /dev/null) && echo "$ZSH_THEME_GIT_PROMPT_SHA_BEFORE$SHA$ZSH_THEME_GIT_PROMPT_SHA_AFTER" +} + +# Formats prompt string for current git commit long SHA +function git_prompt_long_sha() { + SHA=$(git rev-parse HEAD 2> /dev/null) && echo "$ZSH_THEME_GIT_PROMPT_SHA_BEFORE$SHA$ZSH_THEME_GIT_PROMPT_SHA_AFTER" +} + +# Get the status of the working tree +git_prompt_status() { + INDEX=$(git status --porcelain 2> /dev/null) + STATUS="" + if $(echo "$INDEX" | grep '^?? ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_UNTRACKED$STATUS" + fi + if $(echo "$INDEX" | grep '^A ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_ADDED$STATUS" + elif $(echo "$INDEX" | grep '^M ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_ADDED$STATUS" + fi + if $(echo "$INDEX" | grep '^ M ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_MODIFIED$STATUS" + elif $(echo "$INDEX" | grep '^AM ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_MODIFIED$STATUS" + elif $(echo "$INDEX" | grep '^ T ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_MODIFIED$STATUS" + fi + if $(echo "$INDEX" | grep '^R ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_RENAMED$STATUS" + fi + if $(echo "$INDEX" | grep '^ D ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_DELETED$STATUS" + elif $(echo "$INDEX" | grep '^AD ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_DELETED$STATUS" + fi + if $(echo "$INDEX" | grep '^UU ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_UNMERGED$STATUS" + fi + echo $STATUS +} diff --git a/zsh/lib/grep.zsh b/zsh/lib/grep.zsh new file mode 100644 index 00000000..1d14cfb2 --- /dev/null +++ b/zsh/lib/grep.zsh @@ -0,0 +1,5 @@ +# +# Color grep results +# Examples: http://rubyurl.com/ZXv +# +export GREP_COLOR='1;32' \ No newline at end of file diff --git a/zsh/lib/history.zsh b/zsh/lib/history.zsh new file mode 100644 index 00000000..2a65cf3b --- /dev/null +++ b/zsh/lib/history.zsh @@ -0,0 +1,13 @@ +## Command history configuration +HISTFILE=$HOME/.zsh_history +HISTSIZE=10000000 +SAVEHIST=10000000 + +setopt append_history +setopt extended_history +setopt hist_expire_dups_first +setopt hist_ignore_dups # ignore duplication command history list +setopt hist_ignore_space +setopt hist_verify +setopt inc_append_history +setopt share_history # share command history data diff --git a/zsh/lib/key-bindings.zsh b/zsh/lib/key-bindings.zsh new file mode 100644 index 00000000..5c1b90bf --- /dev/null +++ b/zsh/lib/key-bindings.zsh @@ -0,0 +1,51 @@ +# TODO: Explain what some of this does.. + +bindkey -e +bindkey '\ew' kill-region +bindkey -s '\el' "ls\n" +bindkey -s '\e.' "..\n" +bindkey '^r' history-incremental-search-backward +bindkey "^[[5~" up-line-or-history +bindkey "^[[6~" down-line-or-history + +# make search up and down work, so partially type and hit up/down to find relevant stuff +bindkey '^[[A' up-line-or-search +bindkey '^[[B' down-line-or-search + +bindkey "^[[H" beginning-of-line +bindkey "^[[1~" beginning-of-line +bindkey "^[OH" beginning-of-line +bindkey "^[[F" end-of-line +bindkey "^[[4~" end-of-line +bindkey "^[OF" end-of-line +bindkey ' ' magic-space # also do history expansion on space + +bindkey "^[[1;5C" forward-word +bindkey "^[[1;5D" backward-word + +bindkey '^[[Z' reverse-menu-complete + +# Make the delete key (or Fn + Delete on the Mac) work instead of outputting a ~ +bindkey '^?' backward-delete-char +bindkey "^[[3~" delete-char +bindkey "^[3;5~" delete-char +bindkey "\e[3~" delete-char + +# consider emacs keybindings: + +#bindkey -e ## emacs key bindings +# +#bindkey '^[[A' up-line-or-search +#bindkey '^[[B' down-line-or-search +#bindkey '^[^[[C' emacs-forward-word +#bindkey '^[^[[D' emacs-backward-word +# +#bindkey -s '^X^Z' '%-^M' +#bindkey '^[e' expand-cmd-path +#bindkey '^[^I' reverse-menu-complete +#bindkey '^X^N' accept-and-infer-next-history +#bindkey '^W' kill-region +#bindkey '^I' complete-word +## Fix weird sequence that rxvt produces +#bindkey -s '^[[Z' '\t' +# diff --git a/zsh/lib/misc.zsh b/zsh/lib/misc.zsh new file mode 100644 index 00000000..88732e66 --- /dev/null +++ b/zsh/lib/misc.zsh @@ -0,0 +1,13 @@ +## smart urls +autoload -U url-quote-magic +zle -N self-insert url-quote-magic + +## file rename magick +bindkey "^[m" copy-prev-shell-word + +## jobs +setopt long_list_jobs + +## pager +export PAGER=less +export LC_CTYPE=$LANG diff --git a/zsh/lib/nvm.zsh b/zsh/lib/nvm.zsh new file mode 100644 index 00000000..d96fd37d --- /dev/null +++ b/zsh/lib/nvm.zsh @@ -0,0 +1,3 @@ +if (( $+commands[brew] )) ; then + source $(brew --prefix nvm)/nvm.sh +fi diff --git a/zsh/lib/rvm.zsh b/zsh/lib/rvm.zsh new file mode 100644 index 00000000..69360e25 --- /dev/null +++ b/zsh/lib/rvm.zsh @@ -0,0 +1,7 @@ +# # get the name of the branch we are on +# function rvm_prompt_info() { +# ruby_version=$(~/.rvm/bin/rvm-prompt 2> /dev/null) || return +# echo "($ruby_version)" +# } + + diff --git a/zsh/lib/spectrum.zsh b/zsh/lib/spectrum.zsh new file mode 100644 index 00000000..2fdf537e --- /dev/null +++ b/zsh/lib/spectrum.zsh @@ -0,0 +1,28 @@ +#! /bin/zsh +# A script to make using 256 colors in zsh less painful. +# P.C. Shyamshankar +# Copied from http://github.com/sykora/etc/blob/master/zsh/functions/spectrum/ + +typeset -Ag FX FG BG + +FX=( + reset "%{%}" + bold "%{%}" no-bold "%{%}" + italic "%{%}" no-italic "%{%}" + underline "%{%}" no-underline "%{%}" + blink "%{%}" no-blink "%{%}" + reverse "%{%}" no-reverse "%{%}" +) + +for color in {000..255}; do + FG[$color]="%{[38;5;${color}m%}" + BG[$color]="%{[48;5;${color}m%}" +done + +# Show all 256 colors with color number +function spectrum_ls() { + for code in {000..255}; do + print -P -- "$code: %F{$code}Test%f" + done +} + diff --git a/zsh/lib/termsupport.zsh b/zsh/lib/termsupport.zsh new file mode 100644 index 00000000..22198950 --- /dev/null +++ b/zsh/lib/termsupport.zsh @@ -0,0 +1,33 @@ +#usage: title short_tab_title looooooooooooooooooooooggggggg_windows_title +#http://www.faqs.org/docs/Linux-mini/Xterm-Title.html#ss3.1 +#Fully support screen, iterm, and probably most modern xterm and rxvt +#Limited support for Apple Terminal (Terminal can't set window or tab separately) +function title { + [ "$DISABLE_AUTO_TITLE" != "true" ] || return + if [[ "$TERM" == screen* ]]; then + print -Pn "\ek$1:q\e\\" #set screen hardstatus, usually truncated at 20 chars + elif [[ "$TERM" == xterm* ]] || [[ $TERM == rxvt* ]] || [[ "$TERM_PROGRAM" == "iTerm.app" ]]; then + print -Pn "\e]2;$2:q\a" #set window name + print -Pn "\e]1;$1:q\a" #set icon (=tab) name (will override window name on broken terminal) + fi +} + +ZSH_THEME_TERM_TAB_TITLE_IDLE="%15<..<%~%<<" #15 char left truncated PWD +ZSH_THEME_TERM_TITLE_IDLE="%n@%m: %~" + +#Appears when you have the prompt +function omz_termsupport_precmd { + title $ZSH_THEME_TERM_TAB_TITLE_IDLE $ZSH_THEME_TERM_TITLE_IDLE +} + +#Appears at the beginning of (and during) of command execution +function omz_termsupport_preexec { + emulate -L zsh + setopt extended_glob + local CMD=${1[(wr)^(*=*|sudo|ssh|-*)]} #cmd name only, or if this is sudo or ssh, the next cmd + title "$CMD" "%100>...>$2%<<" +} + +autoload -U add-zsh-hook +add-zsh-hook precmd omz_termsupport_precmd +add-zsh-hook preexec omz_termsupport_preexec diff --git a/zsh/lib/theme-and-appearance.zsh b/zsh/lib/theme-and-appearance.zsh new file mode 100644 index 00000000..aec67721 --- /dev/null +++ b/zsh/lib/theme-and-appearance.zsh @@ -0,0 +1,36 @@ +# ls colors +autoload colors; colors; +export LSCOLORS="Gxfxcxdxbxegedabagacad" +#export LS_COLORS + +# Enable ls colors +if [ "$DISABLE_LS_COLORS" != "true" ] +then + # Find the option for using colors in ls, depending on the version: Linux or BSD + ls --color -d . &>/dev/null 2>&1 && alias ls='ls --color=tty' || alias ls='ls -G' +fi + +#setopt no_beep +setopt auto_cd +setopt multios +setopt cdablevarS + +if [[ x$WINDOW != x ]] +then + SCREEN_NO="%B$WINDOW%b " +else + SCREEN_NO="" +fi + +# Apply theming defaults +PS1="%n@%m:%~%# " + +# git theming default: Variables for theming the git info prompt +ZSH_THEME_GIT_PROMPT_PREFIX="git:(" # Prefix at the very beginning of the prompt, before the branch name +ZSH_THEME_GIT_PROMPT_SUFFIX=")" # At the very end of the prompt +ZSH_THEME_GIT_PROMPT_DIRTY="*" # Text to display if the branch is dirty +ZSH_THEME_GIT_PROMPT_CLEAN="" # Text to display if the branch is clean + +# Setup the prompt with pretty colors +setopt prompt_subst + diff --git a/zsh/options b/zsh/options new file mode 100644 index 00000000..acc0393a --- /dev/null +++ b/zsh/options @@ -0,0 +1,66 @@ +### Options. See zshoptions(1) + +# History commands are appended to the existing file instead of overwriting it. +setopt append_history +# Don't do menu completion +setopt no_auto_menu +# Show possible matches if completion can't figure out what to do. +setopt auto_list +# Commands without arguments will first try to resume suspended programs of the same name. +setopt auto_resume +# Puts more info in the history file +setopt extended_history +# Turns off C-S/C-Q flow control +setopt no_flow_control +# Sequential duplicate commands only get one history entry. +setopt hist_ignore_dups +# Do completion on in foo= +setopt magic_equal_subst +# Don't error if globbing fails; just leave the globbing chars in. +setopt nonomatch +# Don't bug me about it if I type 'rm *'. +setopt rm_star_silent +# Think about adding this. +#setopt share_history +# Don't overwrite files automatically +setopt no_clobber +# Use Emacs-style bindings. +bindkey -e + +# These were added sometime after 3.0.6, based on empirical evidence. +if [[ $ZSH_VERSION > 3.0.6 ]]; then + # Print hex numbers like 0x7F instead of 16#7F + setopt c_bases + # Only the newest of a set of duplicates (regardless of sequence) is saved to file. + setopt hist_save_no_dups + # Commands are added to the history file as they are entered. + setopt inc_append_history + # Use variable width columns for completion options + setopt list_packed + # print octal numbers like 037 instead of 8#37 + setopt octal_zeroes +fi + +### Parameters. See zshparam(1) + +# Extensions to ignore for completion. +FIGNORE=".o:~" +# Where to save my command history +HISTFILE=~/.zsh_history +# Remember the last 5000000 commands. +HISTSIZE=50000000 +# Only ask if completion listing would scroll off screen +LISTMAX=0 +# Check for logins/logouts every 5 minutes +LOGCHECK=300 +# Never look at my mail spool. +MAILCHECK=0 +# Give timing statistics for programs that take longer than a minute to run. +REPORTTIME=60 +# Save the last 3000 commands. +SAVEHIST=3000000 +# Report on any log(in|out)s not from my username on systems that I administer. +case `hostname` in tigger*|pooh*|robinson*) + WATCH=notme + WATCHFMT='%n %a %l from %m at %T.' +esac diff --git a/zsh/prompt b/zsh/prompt new file mode 100644 index 00000000..33fdce3e --- /dev/null +++ b/zsh/prompt @@ -0,0 +1,180 @@ +function precmd { + + local TERMWIDTH + (( TERMWIDTH = ${COLUMNS} - 1 )) + + + ### + # Truncate the path if it's too long. + + PR_FILLBAR="" + PR_PWDLEN="" + + local promptsize + local pwdsize + if [[ $ZSH_VERSION > 3.0.6 ]]; then + promptsize=${#${(%):---(%n@%m:%l)---()--}} + pwdsize=${#${(%):-%~}} + else + promptsize=${#${:---($USER@${HOST%%.*}:${TTY#/dev/})---()--}} + pwdsize=${#$(pwd | perl -pe "s{$HOME}{~}")} + fi + + if [[ "$promptsize + $pwdsize" -gt $TERMWIDTH ]]; then + ((PR_PWDLEN=$TERMWIDTH - $promptsize)) + else + PR_FILLBAR="\${(l.(($TERMWIDTH - ($promptsize + $pwdsize)))..${PR_HBAR}.)}" + fi + + + ### + # Get APM info. + + if which ibam > /dev/null; then + PR_APM_RESULT=`ibam --percentbattery` + elif which apm > /dev/null; then + PR_APM_RESULT=`apm` + fi + +} + + +setopt extended_glob +preexec () { + if [[ "$TERM" == "screen" ]]; then + local CMD=${1[(wr)^(<*|*=*|sudo|exec|-*)]} + echo -n "\ek$CMD\e\\" + fi + + ### + # See if $DISPLAY needs to be fixed. + if [[ "$STY" != "" && "$DISPLAY" != "$(<~/.display)" ]]; then + DISPLAY=$(<~/.display) + fi +} + + +setprompt () { + ### + # Need this so the prompt will work. + + setopt prompt_subst + + + ### + # See if we can use colors. + + autoload colors zsh/terminfo + if [[ "$terminfo[colors]" -ge 8 ]]; then + colors + fi + for color in RED GREEN YELLOW BLUE MAGENTA CYAN WHITE; do + eval PR_$color='%{$terminfo[bold]$fg[${(L)color}]%}' + eval PR_LIGHT_$color='%{$fg[${(L)color}]%}' + (( count = $count + 1 )) + done + PR_NO_COLOUR="%{$terminfo[sgr0]%}" + + + ### + # See if we can use extended characters to look nicer. + +# Commented out because zsh currently does NOT handle UTF-8 properly. Count +# yourself lucky if the alternate character set line drawing works in your +# terminal. +# if [[ "`locale charmap`" = "UTF-8" ]]; then +# PR_SET_CHARSET="" +# PR_SHIFT_IN="" +# PR_SHIFT_OUT="" +# PR_HBAR="─" +# PR_ULCORNER="┌" +# PR_LLCORNER="└" +# PR_LRCORNER="┘" +# PR_URCORNER="┐" +# else + if [[ $ZSH_VERSION > 3.0.6 ]]; then + typeset -A altchar + fi + set -A altchar ${(s..)terminfo[acsc]} + PR_SET_CHARSET="%{$terminfo[enacs]%}" + PR_SHIFT_IN="%{$terminfo[smacs]%}" + PR_SHIFT_OUT="%{$terminfo[rmacs]%}" + PR_HBAR=${altchar[q]:--} + PR_ULCORNER=${altchar[l]:--} + PR_LLCORNER=${altchar[m]:--} + PR_LRCORNER=${altchar[j]:--} + PR_URCORNER=${altchar[k]:--} +# fi + + + ### + # Decide if we need to set titlebar text. + + case $TERM in + xterm*|mlterm) + PR_TITLEBAR=$'%{\e]0;%(0#.-=*[ROOT]*=- | .)%n@%m:%~ | ${COLUMNS}x${LINES} | %y\a%}' + ;; + screen) + PR_TITLEBAR=$'%{\e_screen \005 (\005t) | %(0#.-=[ROOT]=- | .)%n@%m:%~ | ${COLUMNS}x${LINES} | %y\e\\%}' + ;; + *) + PR_TITLEBAR='' + ;; + esac + + + ### + # Decide whether to set a screen title + if [[ "$TERM" == "screen" ]]; then + PR_STITLE=$'%{\ekzsh\e\\%}' + else + PR_STITLE='' + fi + + + ### + # APM detection + + case "$OSTYPE" in + linux*) + if which ibam > /dev/null; then + PR_APM='$PR_RED${${PR_APM_RESULT[(f)1]}[(w)-2]}%%(${${PR_APM_RESULT[(f)3]}[(w)-1]})$PR_LIGHT_BLUE:' + elif which apm > /dev/null; then + PR_APM='$PR_RED${PR_APM_RESULT[(w)5,(w)6]/\% /%%}$PR_LIGHT_BLUE:' + else + PR_APM='' + fi + ;; + *) + PR_APM='' + ;; + esac + + + ### + # Finally, the prompt. + + PROMPT='$PR_SET_CHARSET$PR_STITLE${(e)PR_TITLEBAR}\ +$PR_CYAN$PR_SHIFT_IN$PR_ULCORNER$PR_BLUE$PR_HBAR$PR_SHIFT_OUT(\ +$PR_GREEN%(0#.%SROOT%s.%n)$PR_GREEN@%m:%l\ +$PR_BLUE)$PR_SHIFT_IN$PR_HBAR$PR_CYAN$PR_HBAR${(e)PR_FILLBAR}$PR_BLUE$PR_HBAR$PR_SHIFT_OUT(\ +$PR_MAGENTA%$PR_PWDLEN<...<%~%<<\ +$PR_BLUE)$PR_SHIFT_IN$PR_HBAR$PR_CYAN$PR_URCORNER$PR_SHIFT_OUT\ + +$PR_CYAN$PR_SHIFT_IN$PR_LLCORNER$PR_BLUE$PR_HBAR$PR_SHIFT_OUT(\ +%(?..$PR_LIGHT_RED%?$PR_BLUE:)\ +${(e)PR_APM}$PR_YELLOW%D{%H:%M}\ +$PR_LIGHT_BLUE:%(0#.$PR_RED.$PR_WHITE)%#$PR_BLUE)$PR_SHIFT_IN$PR_HBAR$PR_SHIFT_OUT\ +$PR_CYAN$PR_SHIFT_IN$PR_HBAR$PR_SHIFT_OUT\ +$PR_NO_COLOUR ' + + RPROMPT=' $PR_CYAN$PR_SHIFT_IN$PR_HBAR$PR_BLUE$PR_HBAR$PR_SHIFT_OUT\ +($PR_YELLOW%D{%a,%b%d}$PR_BLUE)$PR_SHIFT_IN$PR_HBAR$PR_CYAN$PR_LRCORNER$PR_SHIFT_OUT$PR_NO_COLOUR' + + PS2='$PR_CYAN$PR_SHIFT_IN$PR_HBAR$PR_SHIFT_OUT\ +$PR_BLUE$PR_SHIFT_IN$PR_HBAR$PR_SHIFT_OUT(\ +$PR_LIGHT_GREEN%_$PR_BLUE)$PR_SHIFT_IN$PR_HBAR$PR_SHIFT_OUT\ +$PR_CYAN$PR_SHIFT_IN$PR_HBAR$PR_SHIFT_OUT$PR_NO_COLOUR ' +} + +setprompt diff --git a/zshenv b/zshenv new file mode 100644 index 00000000..c1b23429 --- /dev/null +++ b/zshenv @@ -0,0 +1,43 @@ +fpath=($HOME/.zsh/functions $HOME/.zsh/completions $fpath) +typeset -U fpath + +# Setup PATH +export PATH=/usr/local/Cellar/macvim/7.3-64/MacVim.app/Contents/MacOS:$PATH +export PATH=/usr/local/share/python:$PATH +export PATH=/usr/local/share/npm/bin:$PATH +export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH +export PATH=/usr/local/mysql/bin:$PATH +export PATH=/usr/local/git/bin:$PATH +export PATH=/usr/local/go/bin:$PATH +export PATH=/var/lib/gems/1.8/bin:$PATH +export PATH=/opt/vagrant/bin:$PATH +export PATH=/usr/local/spark-1.5.1-bin-hadoop2.6/bin:$PATH +export PATH=$HOME/.emacs.d/bin:$PATH +export PATH=$PATH:/usr/local/opt/go/libexec/bin +export PATH=/anaconda2/bin:$PATH +export PATH="$HOME/.cask/bin:$PATH" +export PATH=$HOME/.local/bin:$PATH +export PATH=$HOME/bin:$HOME/dotfiles/bin:$PATH +export PATH="$PATH:$HOME/src/xtensa-esp32-elf/bin" +export PATH="$HOME/.cargo/bin:$PATH" +export PATH=$PATH:$HOME/dev/esp/xtensa-esp32-elf/bin + +export PATH="$PATH:$HOME/.rvm/bin" # Add RVM to PATH for scripting +export PATH="$HOME/.rbenv/bin:$PATH" + +export ROQUA_ROOT=$HOME/work/code/deployer +export RAILS_LOG_LEVEL="debug" + +export PATH=$HOME/bin:$PATH + +export LC_ALL=en_US.UTF-8 +export LANG=en_US.UTF-8 + +export ANDROID_HOME=/usr/local/opt/android-sdk +export IDF_PATH=$HOME/dev/esp/esp-idf +export ERL_AFLAGS="-kernel shell_history enabled" + +if (( $+commands[java_home] )) ; then + export JAVA_HOME="$(/usr/libexec/java_home)" +fi + diff --git a/zshrc b/zshrc new file mode 100644 index 00000000..9082aafb --- /dev/null +++ b/zshrc @@ -0,0 +1,46 @@ +# shortcut to this dotfiles path is $ZSH +export ZSH=$HOME/dotfiles + +# your project folder that we can `c [tab]` to +export PROJECTS=~/prj + +# source every .zsh file in this rep +for config_file ($ZSH/**/*.zsh) source $config_file + +# use .localrc for SUPER SECRET CRAP that you don't +# want in your public, versioned repo. +if [[ -a ~/.localrc ]] +then + source ~/.localrc +fi + +# initialize autocomplete here, otherwise functions won't be loaded +autoload -U compinit +compinit + +# load every completion after autocomplete loads +for config_file ($ZSH/**/completion.sh) source $config_file + +eval "$(rbenv init -)" +eval "$(direnv hook zsh)" + +bindkey -e +bindkey '^[[1;9C' forward-word +bindkey '^[[1;9D' backward-word + +test -e "${HOME}/.iterm2_shell_integration.zsh" && source "${HOME}/.iterm2_shell_integration.zsh" + +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm +[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion + + +export PATH=/anaconda2/bin:$PATH +# added by travis gem +[ -f /Users/marten/.travis/travis.sh ] && source /Users/marten/.travis/travis.sh + +# added by travis gem +[ -f /home/marten/.travis/travis.sh ] && source /home/marten/.travis/travis.sh + +. $HOME/.asdf/asdf.sh +. $HOME/.asdf/completions/asdf.bash