cani 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bbc79d5e5ddb3a2d40c39165c898d32ffa657a68dec23636f1b2b15da5bb9857
4
+ data.tar.gz: b93cf69ff6740b4fbd11010d5d3c05c196eb2e63edcdc5efbe09dba51279ba12
5
+ SHA512:
6
+ metadata.gz: 688f7a52ab214dfeb46e46a1b93c8ab16e9ecc2fecd8d7e2a2de2c57b4cb0588e088bb24e313e8f9bb08e0e218842ea3b16439983357ad51dca35a778bacf706
7
+ data.tar.gz: eae2981b38417c952878e3dd9fd7309881f07fe39bf726bd7986a74bb5d7a9f053f4453d4e909e12927b8d5d46104570ba0a450781ebff66932ee390ff279990
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.0
5
+ before_install: gem install bundler -v 1.16.2
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at sidneyliebrand@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in cani.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,45 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ cani (0.1.0)
5
+ colorize
6
+ json
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ coderay (1.1.2)
12
+ colorize (0.8.1)
13
+ diff-lcs (1.3)
14
+ json (2.1.0)
15
+ method_source (0.9.0)
16
+ pry (0.11.3)
17
+ coderay (~> 1.1.0)
18
+ method_source (~> 0.9.0)
19
+ rake (10.5.0)
20
+ rspec (3.7.0)
21
+ rspec-core (~> 3.7.0)
22
+ rspec-expectations (~> 3.7.0)
23
+ rspec-mocks (~> 3.7.0)
24
+ rspec-core (3.7.1)
25
+ rspec-support (~> 3.7.0)
26
+ rspec-expectations (3.7.0)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.7.0)
29
+ rspec-mocks (3.7.0)
30
+ diff-lcs (>= 1.2.0, < 2.0)
31
+ rspec-support (~> 3.7.0)
32
+ rspec-support (3.7.1)
33
+
34
+ PLATFORMS
35
+ ruby
36
+
37
+ DEPENDENCIES
38
+ bundler (~> 1.16)
39
+ cani!
40
+ pry
41
+ rake (~> 10.0)
42
+ rspec (~> 3.0)
43
+
44
+ BUNDLED WITH
45
+ 1.16.2
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Sidney Liebrand
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # Cani
2
+
3
+ ![cani cli](/assets/cani.png)
4
+
5
+ Cani is a small command-line wrapper around the data of [caniuse](https://caniuse.com).
6
+ It uses [fzf](https://github.com/junegunn/fzf) to display results.
7
+ This wrapper aims to be easy to use out of the box. To achieve this it ships with completions
8
+ for `bash`, `fish`, and `zsh`. [Caniuse data (1.7MB)](https://github.com/Fyrd/caniuse/blob/master/data.json) is fetched and updated automatically
9
+ on a regular interval together with completions.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'cani'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install cani
26
+
27
+ ## Configuration
28
+
29
+ After installation, running the command (`cani`) for the first time will create some files and directories:
30
+
31
+ - `~/.config/cani/config.yml` - default configuration
32
+ - `~/.config/cani/caniuse.json` - caniuse api data
33
+ - `~/.config/cani/completions/_cani.bash` - bash completion
34
+ - `~/.config/cani/completions/_cani.zsh` - zsh completion
35
+ - `~/.config/fish/completions/cani.fish` - fish completions
36
+
37
+ Some existing files will also be modified:
38
+
39
+ - `~/.bashrc` - A source line to bash completions will be added, or updated if it exists
40
+ - `~/.zshrc` - A source line to zsh completions will be added, or updated if it exists
41
+
42
+ After running the command for the first time, please restart your shell or `source` your `~/.*rc` file to load completions.
43
+ There are some commented settings that can be adjusted in the `~/.config/cani/config.yml` file.
44
+
45
+ ## Usage
46
+
47
+ Running `cani` without arguments yields the help description.
48
+ Cani supports the following actions:
49
+
50
+ - [`use`](#use) - show browser support for all features
51
+ - [`show BROWSER VERSION`](#show) - show feature support based on selected browser / version
52
+ - [`help`](#help) - show help
53
+ - [`version`](#version) - print the version number
54
+ - [`update`](#update) - force update data and completions
55
+ - [`install_completions`](#install_completions) - install shell completions
56
+ - [`purge`](#purge) - purge files and directories created by `cani`
57
+
58
+ ### use
59
+
60
+ ```sh
61
+ cani use
62
+ ```
63
+
64
+ Show a list of features with fzf. Features are shown with their current W3C status, percentage of support, title and
65
+ each individual browser's support on a single row.
66
+
67
+ ### show
68
+
69
+ ```sh
70
+ cani show
71
+ ```
72
+
73
+ Show a list of browsers. Selecting a browser will take you to the versions for that browser.
74
+ Selecting a version shows the final window with feature support for that specific browser version.
75
+
76
+ This command can also be invoked directly with a browser and version:
77
+
78
+ ```sh
79
+ # show all versions of chrome
80
+ cani show chr
81
+
82
+ # show all supported features in chrome 70
83
+ cani show chr 70
84
+ ```
85
+
86
+ ### help
87
+
88
+ ```sh
89
+ cani help
90
+ ```
91
+
92
+ Displays short help for the `cani` command
93
+
94
+ ### version
95
+
96
+ ```sh
97
+ cani version
98
+ ```
99
+
100
+ Displays the current version e.g: `0.1.0`
101
+
102
+ ### update
103
+
104
+ ```sh
105
+ cani update
106
+ ```
107
+
108
+ Force update dataset and completions.
109
+
110
+ ### install_completions
111
+
112
+ ```sh
113
+ cani install_completions
114
+ ```
115
+
116
+ Completions are supported for `zsh`, `bash` and `fish` shells (currently).
117
+ They are automatically installed upon first invocation of the `cani` command.
118
+ This command is only a fallback in case there were any issues with permissions etc..
119
+
120
+ ### purge
121
+
122
+ ```sh
123
+ cani purge
124
+ ```
125
+
126
+ Purges all files created by this command, removing every trace except the executable itself.
127
+ It will also remove source lines added that pointed to the completions in `~/.zshrc` and `~/.bashrc`.
128
+ After running a `purge`, all that remains is running `gem uninstall cani` to completely purge it.
129
+
130
+ ## Contributing
131
+
132
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sidofc/cani. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
133
+
134
+ ## License
135
+
136
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
137
+
138
+ ## Code of Conduct
139
+
140
+ Everyone interacting in the Cani project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/cani/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'pry'
6
+ require 'cani'
7
+
8
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/cani.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'cani/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'cani'
7
+ spec.version = Cani::VERSION
8
+ spec.authors = ['Sidney Liebrand']
9
+ spec.email = ['sidneyliebrand@gmail.com']
10
+
11
+ spec.summary = 'A simple caniuse CLI.'
12
+ spec.description = 'A rework of the ruby script from my medium post: https://medium.com/@sidneyliebrand/combining-caniuse-with-fzf-fb93ad235bae'
13
+ spec.homepage = 'https://github.com/SidOfc/cani'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
17
+ `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features|assets)/})
19
+ end
20
+ end
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_runtime_dependency 'colorize'
26
+ spec.add_runtime_dependency 'json'
27
+
28
+ spec.add_development_dependency 'bundler', '~> 1.16'
29
+ spec.add_development_dependency 'rake', '~> 10.0'
30
+ spec.add_development_dependency 'pry'
31
+ spec.add_development_dependency 'rspec', '~> 3.0'
32
+ end
data/exe/cani ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'cani'
6
+
7
+ # extract first non-option (options starting with '-' or '--')
8
+ # argument in ARGV.
9
+ cmd ||= Cani.api.config.args.first
10
+
11
+ # refresh all completions after new data has been fetched
12
+ Cani::Completions.install! if Cani.api.updated?
13
+
14
+ if cmd && Cani.respond_to?(cmd)
15
+ # if the command is recognized, execute it
16
+ Cani.send cmd
17
+ else
18
+ # otherwise, display help message
19
+ Cani.help
20
+ end
data/lib/cani.rb ADDED
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'colorize'
4
+ require 'json'
5
+ require 'yaml'
6
+
7
+ require 'cani/version'
8
+ require 'cani/api'
9
+ require 'cani/fzf'
10
+ require 'cani/completions'
11
+
12
+ # Cani
13
+ module Cani
14
+ def self.api
15
+ @api ||= Api.new
16
+ end
17
+
18
+ def self.help
19
+ puts "Cani #{VERSION} <https://github.com/SidOfc/cani>"
20
+ puts ''
21
+ puts 'Usage: cani [COMMAND [ARGUMENTS]]'
22
+ puts ''
23
+ puts 'Commands:'
24
+ puts ' use FEATURE show browser support for FEATURE'
25
+ puts ' show BROWSER show information about specific BROWSER'
26
+ puts ' install_completions installs completions for bash, zsh and fish'
27
+ puts ' update force update api data and completions'
28
+ puts ' purge remove all completion, configuration and data'
29
+ puts ' stored by this cani'
30
+ puts ' '
31
+ puts ' help show this help'
32
+ puts ' version print the version number'
33
+ puts ''
34
+ puts 'Examples:'
35
+ puts ' cani use'
36
+ puts ' cani show ie'
37
+ puts ' cani show chr.and'
38
+ end
39
+
40
+ def self.version
41
+ puts VERSION
42
+ end
43
+
44
+ def self.install_completions
45
+ Completions.install!
46
+ end
47
+
48
+ def self.purge
49
+ Completions.remove!
50
+ api.remove!
51
+ api.config.remove!
52
+ end
53
+
54
+ def self.update
55
+ api.update! && Completions.install! || exit(1) unless api.updated?
56
+ end
57
+
58
+ def self.edit
59
+ system ENV.fetch('EDITOR', 'vim'), api.config.file
60
+ end
61
+
62
+ def self.use
63
+ puts Fzf.pick Fzf.feature_rows,
64
+ header: 'use] [' + Api::Feature.support_legend,
65
+ colors: %i[green light_black light_white light_black]
66
+ end
67
+
68
+ def self.show(brws = api.config.args[1], version = api.config.args[2])
69
+ browser = api.find_browser brws
70
+
71
+ if browser
72
+ if version
73
+ Fzf.pick Fzf.browser_feature_rows(browser, version),
74
+ header: "show:#{browser.title.downcase}:#{version}] [#{Api::Feature.support_legend}",
75
+ colors: [:green, :light_black, :light_white]
76
+
77
+ show browser.title, nil
78
+ else
79
+ if (version = Fzf.pick(Fzf.browser_usage_rows(browser),
80
+ header: [:show, browser.title],
81
+ colors: %i[white light_black]).first)
82
+ show browser.title, version
83
+ else
84
+ show nil, nil
85
+ end
86
+ end
87
+ else
88
+ browser = api.find_browser Fzf.pick(Fzf.browser_rows,
89
+ header: [:show],
90
+ colors: %i[white light_black]).first
91
+
92
+ show browser.title, nil unless browser.nil?
93
+ end
94
+ end
95
+ end
data/lib/cani/api.rb ADDED
@@ -0,0 +1,87 @@
1
+ require 'net/http'
2
+
3
+ require_relative 'api/config'
4
+ require_relative 'api/browser'
5
+ require_relative 'api/feature'
6
+
7
+ module Cani
8
+ class Api
9
+ def initialize
10
+ @data = load_data
11
+ end
12
+
13
+ def load_data(fetch: false)
14
+ @upd = false
15
+ data_file = File.join config.directory, 'caniuse.json'
16
+ data_exists = File.exist? data_file
17
+ data_up_to_date = data_exists ? (Time.now.to_i - File.mtime(data_file).to_i < config.expire.to_i)
18
+ : false
19
+
20
+ if !fetch && data_exists && data_up_to_date
21
+ # data is available and up to date
22
+ JSON.parse File.read(data_file)
23
+ elsif (data = raw)
24
+ @upd = true
25
+ # data either doesn't exist or isn't up to date
26
+ # if we can fetch new data, attempt to update
27
+ FileUtils.mkdir_p File.dirname(data_file)
28
+ File.open(data_file, 'w') { |f| f << data }
29
+ JSON.parse data
30
+ elsif data_exists
31
+ # if we are unable fetch new data, fall back
32
+ # to existing data if it exists
33
+ JSON.parse File.read(data_file)
34
+ else
35
+ # no other option but fail since we have no data
36
+ # and no way of fetching the data to display
37
+ puts 'fatal: no data available for display'
38
+ exit 1
39
+ end
40
+ end
41
+
42
+ def remove!
43
+ data_file = File.join config.directory, 'caniuse.json'
44
+
45
+ File.unlink data_file if File.exist? data_file
46
+ end
47
+
48
+ def update!
49
+ @data = load_data fetch: true
50
+ end
51
+
52
+ def updated?
53
+ @upd
54
+ end
55
+
56
+ def config(**opts)
57
+ @settings ||= Config.new(**opts)
58
+ end
59
+
60
+ def find_browser(name)
61
+ name = name.to_s.downcase
62
+ idx = browsers.find_index do |bwsr|
63
+ [bwsr.title, bwsr.name, bwsr.abbr].include? name
64
+ end
65
+
66
+ browsers[idx] if idx
67
+ end
68
+
69
+ def browsers
70
+ @browsers ||= @data['agents'].map do |(name, info)|
71
+ Browser.new info.merge(name: name)
72
+ end
73
+ end
74
+
75
+ def features
76
+ @features ||= @data['data'].values.map(&Feature.method(:new))
77
+ end
78
+
79
+ def raw
80
+ begin
81
+ Net::HTTP.get URI(config.source)
82
+ rescue
83
+ nil
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,50 @@
1
+ module Cani
2
+ class Api
3
+ class Browser
4
+ attr_reader :name, :title, :prefix, :type, :versions, :usage, :abbr, :label
5
+
6
+ ABBR_MAP = { 'ios' => 'saf.ios' }.freeze
7
+ LABEL_MAP = {
8
+ 'ie' => 'Internet Explorer',
9
+ 'edge' => 'Edge',
10
+ 'ff' => 'Firefox',
11
+ 'chr' => 'Chrome',
12
+ 'saf' => 'Safari',
13
+ 'op' => 'Opera',
14
+ 'saf.ios' => 'IOS Safari',
15
+ 'o.mini' => 'Opera Mini',
16
+ 'and' => 'Android Browser',
17
+ 'bb' => 'BlackBerry Browser',
18
+ 'o.mob' => 'Opera Mobile',
19
+ 'chr.and' => 'Chrome for Android',
20
+ 'ff.and' => 'Firefox for Android',
21
+ 'ie.mob' => 'Internet Explorer Mobile',
22
+ 'uc' => 'UC Browser for android',
23
+ 'ss' => 'Samsung Internet',
24
+ 'qq' => 'QQ Browser',
25
+ 'baidu' => 'Baidu Browser'
26
+ }.freeze
27
+
28
+ def initialize(attributes = {})
29
+ abbr = attributes['abbr'].downcase.gsub(/^\.+|\.+$/, '').tr '/', '.'
30
+
31
+ @abbr = ABBR_MAP.fetch abbr, abbr
32
+ @label = LABEL_MAP.fetch abbr, abbr
33
+ @name = attributes[:name].downcase
34
+ @title = attributes['browser'].downcase
35
+ @prefix = attributes['prefix'].downcase
36
+ @type = attributes['type'].downcase
37
+ @usage = attributes['usage_global']
38
+ @versions = @usage.keys
39
+ end
40
+
41
+ def features_for(version)
42
+ @features ||= Cani.api.features.each_with_object({}) do |ft, h|
43
+ type = ft.support_in name, version
44
+ (h[type] ||= []) << { support: type, title: ft.title,
45
+ status: ft.status, percent: ft.percent }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,121 @@
1
+ module Cani
2
+ class Api
3
+ class Config
4
+ attr_reader :settings
5
+
6
+ FILE = File.join(Dir.home, '.config', 'cani', 'config.yml').freeze
7
+ DIRECTORY = File.dirname(FILE).freeze
8
+ COMP_DIR = File.join(DIRECTORY, 'completions').freeze
9
+ FISH_DIR = File.join(Dir.home, '.config', 'fish').freeze
10
+ FISH_COMP_DIR = File.join(FISH_DIR, 'completions').freeze
11
+ DEFAULTS = {
12
+ # data settings
13
+ 'expire' => 86_400,
14
+ 'source' => 'https://raw.githubusercontent.com/Fyrd/caniuse/master/data.json',
15
+
16
+ # usage settings
17
+ 'versions' => 1,
18
+ 'browsers' => %w[chrome firefox edge ie safari ios_saf opera android bb]
19
+ }.freeze
20
+
21
+ def initialize(**opts)
22
+ @settings = DEFAULTS.merge opts
23
+
24
+ if File.exist? file
25
+ if (yml = YAML.load_file(file))
26
+ @settings.merge! yml
27
+ end
28
+ else
29
+ install!
30
+ end
31
+ end
32
+
33
+ def file
34
+ FILE
35
+ end
36
+
37
+ def directory
38
+ DIRECTORY
39
+ end
40
+
41
+ def comp_dir
42
+ COMP_DIR
43
+ end
44
+
45
+ def fish_dir
46
+ FISH_DIR
47
+ end
48
+
49
+ def fish_comp_dir
50
+ FISH_COMP_DIR
51
+ end
52
+
53
+ def flags
54
+ @flags ||= ARGV.select { |arg| arg.start_with? '-' }
55
+ end
56
+
57
+ def args
58
+ @args ||= ARGV.reject { |arg| arg.start_with? '-' }
59
+ end
60
+
61
+ def remove!
62
+ File.unlink file if File.exist? file
63
+ FileUtils.rm_rf directory if Dir.exist? directory
64
+ end
65
+
66
+ def install!
67
+ hrs = (DEFAULTS['expire'] / 3600.to_f).round 2
68
+ days = (hrs / 24.to_f).round 2
69
+ wk = (days / 7.to_f).round 2
70
+ mo = (days / 30.to_f).round 2
71
+ tstr = if mo >= 1
72
+ "#{mo == mo.to_i ? mo.to_i : mo} month#{mo != 1 ? 's' : ''}"
73
+ elsif wk >= 1
74
+ "#{wk == wk.to_i ? wk.to_i : wk} week#{wk != 1 ? 's' : ''}"
75
+ elsif days >= 1
76
+ "#{days == days.to_i ? days.to_i : days} day#{days != 1 ? 's' : ''}"
77
+ else
78
+ "#{hrs == hrs.to_i ? hrs.to_i : hrs} hour#{hrs != 1 ? 's' : ''}"
79
+ end
80
+
81
+ FileUtils.mkdir_p directory
82
+ File.open file, 'w' do |f|
83
+ f << "---\n"
84
+ f << "# this is the default configuration file for the \"Cani\" RubyGem.\n"
85
+ f << "# it contains some options to control what is shown, when new data\n"
86
+ f << "# is fetched, where it should be fetched from.\n"
87
+ f << "# documentation: https://github.com/sidofc/cani\n"
88
+ f << "# rubygems: https://rubygems.org/gems/cani\n\n"
89
+ f << "# the \"expire\" key defines the interval at which new data is\n"
90
+ f << "# fetched from \"source\". It's value is passed in as seconds\n"
91
+ f << "# default value: #{DEFAULTS['expire']} # => #{tstr}\n"
92
+ f << "expire: #{expire}\n\n"
93
+ f << "# the \"source\" key is used to fetch the data required for\n"
94
+ f << "# this command to work.\n"
95
+ f << "source: #{source}\n\n"
96
+ f << "# the \"versions\" key defines how many versions of support\n"
97
+ f << "# will be shown in the \"use\" command\n"
98
+ f << "# e.g. `-ie +edge` becomes `--ie ++edge` when this is set to 2, etc..."
99
+ f << "versions: #{versions}\n\n"
100
+ f << "# the \"browsers\" key defines which browsers are shown\n"
101
+ f << "# in the \"use\" command\n"
102
+ f << "browsers:\n"
103
+ f << " # enabled:\n"
104
+ f << browsers.map { |bn| " - #{bn}" }.join("\n") + "\n"
105
+ f << " # others:\n"
106
+ f << (Cani.api.browsers.map(&:name) - browsers).map { |bn| " # - #{bn}" }.join("\n")
107
+ end
108
+
109
+ Completions.install!
110
+ end
111
+
112
+ def method_missing(mtd, *args, &block)
113
+ settings.key?(mtd.to_s) ? settings[mtd.to_s] : super
114
+ end
115
+
116
+ def respond_to_missing?(mtd, include_private = false)
117
+ settings.key? mtd.to_s
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,54 @@
1
+ module Cani
2
+ class Api
3
+ class Feature
4
+ attr_reader :title, :status, :spec, :stats, :percent
5
+
6
+ STATUSES = {
7
+ 'rec' => 'rc',
8
+ 'unoff' => 'un',
9
+ 'other' => 'ot'
10
+ }.freeze
11
+
12
+ TYPES = {
13
+ 'y' => {symbol: '+', name: :default, short: :def},
14
+ 'a' => {symbol: '~', name: :partial, short: :part},
15
+ 'n' => {symbol: '-', name: :unsupported, short: :unsupp},
16
+ 'p' => {symbol: '#', name: :polyfill, short: :poly},
17
+ 'x' => {symbol: '@', name: :prefix, short: :prefix},
18
+ 'd' => {symbol: '!', name: :flag, short: :flag},
19
+ 'u' => {symbol: '?', name: :unknown, short: :unknown}
20
+ }.freeze
21
+
22
+ def initialize(attributes = {})
23
+ @title = attributes['title']
24
+ @status = STATUSES.fetch attributes['status'], attributes['status']
25
+ @spec = attributes['spec']
26
+ @percent = attributes['usage_perc_y']
27
+ @stats = attributes['stats'].each_with_object({}) do |(k, v), h|
28
+ h[k] = v.to_a.last(Cani.api.config.versions)
29
+ .map { |(vv, s)| [vv.downcase, s.to_s[0] || ''] }.to_h
30
+ end
31
+ end
32
+
33
+ def current_support
34
+ @current_support ||= Cani.api.config.browsers.map do |browser|
35
+ bridx = Cani.api.browsers.find_index { |brs| brs.name == browser }
36
+ brwsr = Cani.api.browsers[bridx] unless bridx.nil?
37
+ syms = stats[browser].values.map { |s| TYPES[s][:symbol] || '' }
38
+ .join.rjust Cani.api.config.versions
39
+
40
+ syms + brwsr.abbr
41
+ end
42
+ end
43
+
44
+ def support_in(browser, version)
45
+ TYPES.fetch(stats[browser.to_s][version.to_s], {})
46
+ .fetch :name, :unknown
47
+ end
48
+
49
+ def self.support_legend
50
+ TYPES.map { |_, v| "#{v[:short]}(#{v[:symbol]})" }.join ' '
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,107 @@
1
+ module Cani
2
+ module Completions
3
+ def self.generate_fish
4
+ gem_root = File.join File.dirname(__FILE__), '../../'
5
+ tpl = File.read File.join(gem_root, 'shell/completions/functions.fish')
6
+ shw = Cani.api.browsers.reduce String.new do |acc, browser|
7
+ [acc, "complete -f -c cani -n '__fish_cani_using_command show' -a '#{browser.abbr}' -d '#{browser.label}'",
8
+ "complete -f -c cani -n '__fish_cani_showing_browser #{browser.abbr}' -a '#{browser.versions.reverse.join(' ')}'"].join("\n")
9
+ end
10
+
11
+ tpl + shw
12
+ end
13
+
14
+ def self.generate_zsh
15
+ gem_root = File.join File.dirname(__FILE__), '../../'
16
+ tpl = File.read File.join(gem_root, 'shell/completions/functions.zsh')
17
+ indent = 10
18
+ versions = Cani.api.browsers.reduce String.new do |acc, browser|
19
+ acc + (' ' * (indent - 2)) + browser.abbr + ")\n" +
20
+ (' ' * indent) + "_arguments -C \"1: :(#{browser.versions.join(' ')})\"\n" +
21
+ (' ' * indent) + ";;\n"
22
+ end.strip
23
+
24
+ tpl.gsub('{{names}}', Cani.api.browsers.map(&:abbr).join(' '))
25
+ .gsub '{{versions}}', versions
26
+ end
27
+
28
+ def self.generate_bash
29
+ gem_root = File.join File.dirname(__FILE__), '../../'
30
+ tpl = File.read File.join(gem_root, 'shell/completions/functions.bash')
31
+ indent = 10
32
+ versions = Cani.api.browsers.reduce String.new do |acc, browser|
33
+ acc + (' ' * (indent - 2)) + '"' + browser.abbr + "\")\n" +
34
+ (' ' * indent) + "COMPREPLY=($(compgen -W \"#{browser.versions.join(' ')}\" ${COMP_WORDS[COMP_CWORD]}))\n" +
35
+ (' ' * indent) + ";;\n"
36
+ end.strip
37
+
38
+ tpl.gsub('{{names}}', Cani.api.browsers.map(&:abbr).join(' '))
39
+ .gsub '{{versions}}', versions
40
+ end
41
+
42
+ def self.install!
43
+ # create all parent folders
44
+ FileUtils.mkdir_p Cani.api.config.fish_comp_dir
45
+ FileUtils.mkdir_p Cani.api.config.comp_dir
46
+
47
+ # write each completion file
48
+ File.open File.join(Cani.api.config.fish_comp_dir, 'cani.fish'), 'w' do |file|
49
+ file << generate_fish
50
+ end
51
+
52
+ %w[bash zsh].each do |shell|
53
+ File.open File.join(Cani.api.config.comp_dir, "_cani.#{shell}"), 'w' do |file|
54
+ file << send("generate_#{shell}")
55
+ end
56
+ end
57
+
58
+ # append source lines to configurations
59
+ insert_source_lines!
60
+ end
61
+
62
+ def self.remove!
63
+ fish_comp = File.join Cani.api.config.fish_comp_dir, 'cani.fish'
64
+
65
+ File.unlink fish_comp if File.exist? fish_comp
66
+
67
+ %w[bash zsh].each do |shell|
68
+ shell_comp = File.join Cani.api.config.comp_dir, "_cani.#{shell}"
69
+
70
+ File.unlink shell_comp if File.exist? shell_comp
71
+ end
72
+
73
+ # remove source lines to configurations
74
+ delete_source_lines!
75
+ end
76
+
77
+ def self.delete_source_lines!
78
+ %w[bash zsh].each do |shell|
79
+ shellrc = File.join Dir.home, ".#{shell}rc"
80
+ lines = File.read(shellrc).split "\n"
81
+ comp_path = File.join Cani.api.config.comp_dir, "_cani.#{shell}"
82
+ rm_idx = lines.find_index { |l| l.match? comp_path }
83
+
84
+ lines.delete_at rm_idx unless rm_idx.nil?
85
+ File.write shellrc, lines.join("\n")
86
+ end
87
+ end
88
+
89
+ def self.insert_source_lines!
90
+ %w[bash zsh].each do |shell|
91
+ shellrc = File.join Dir.home, ".#{shell}rc"
92
+ lines = File.read(shellrc).split "\n"
93
+ comp_path = File.join Cani.api.config.comp_dir, "_cani.#{shell}"
94
+ slidx = lines.find_index { |l| l.match? comp_path }
95
+
96
+ if slidx
97
+ lines[slidx] =
98
+ "#{lines[slidx][/^\s+/]}[ -f #{comp_path} ] && source #{comp_path}"
99
+ else
100
+ lines << "[ -f #{comp_path} ] && source #{comp_path}"
101
+ end
102
+
103
+ File.write shellrc, lines.join("\n")
104
+ end
105
+ end
106
+ end
107
+ end
data/lib/cani/fzf.rb ADDED
@@ -0,0 +1,88 @@
1
+ module Cani
2
+ module Fzf
3
+ def self.pick(rows, **opts)
4
+ unless executable?
5
+ puts 'fatal: command "fzf" not found, is it installed?'
6
+ exit 1
7
+ end
8
+
9
+ if STDOUT.tty?
10
+ rows = tableize_rows(rows, **opts).join "\n"
11
+ ohdr = opts.fetch :header, []
12
+ header = ohdr.is_a?(Array) ? [:cani, *ohdr].map { |v| v.to_s.downcase }.join(':')
13
+ : 'cani:' + ohdr.to_s
14
+
15
+ `echo "#{rows}" | fzf --ansi --header="[#{header}]"`.split ' '
16
+ else
17
+ # when output of any initial command is being piped
18
+ # print results and exit this command.
19
+ puts tableize_rows(rows).join "\n"
20
+ exit
21
+ end
22
+ end
23
+
24
+ def self.executable?
25
+ @exe ||= begin
26
+ `command -v fzf`
27
+ $?.success?
28
+ end
29
+ end
30
+
31
+ def self.feature_rows
32
+ Cani.api.features.map do |ft|
33
+ pc = format('%.2f%%', ft.percent).rjust 6
34
+ tt = format('%-24s', ft.title.size > 24 ? ft.title[0..23].strip + '..'
35
+ : ft.title)
36
+
37
+ ["[#{ft.status}]", pc, tt, *ft.current_support]
38
+ end
39
+ end
40
+
41
+ def self.browser_rows
42
+ Cani.api.browsers.map do |bwsr|
43
+ [bwsr.title, 'usage: ' + format('%.4f%%', bwsr.usage.values.sum)]
44
+ end
45
+ end
46
+
47
+ def self.browser_usage_rows(brwsr)
48
+ brwsr.usage.map { |(v, u)| [v, 'usage: ' + format('%.4f%%', u)] }.reverse
49
+ end
50
+
51
+ def self.browser_feature_rows(brwsr, version)
52
+ features_by_support = brwsr.features_for version
53
+
54
+ Api::Feature::TYPES.flat_map do |(status, type)|
55
+ if (features = features_by_support.fetch(type[:name], nil))
56
+ features.map do |feature|
57
+ ["[#{feature[:status]}]", "[#{type[:symbol]}]", feature[:title]]
58
+ end
59
+ end
60
+ end.compact
61
+ end
62
+
63
+ def self.tableize_rows(rows, **opts)
64
+ col_widths = []
65
+ colors = opts.fetch :colors, []
66
+
67
+ rows.each do |row|
68
+ row.each.with_index do |column, i|
69
+ col_width = column.size
70
+ col_widths[i] = col_width if col_width > col_widths[i].to_i
71
+ end
72
+ end
73
+
74
+ rows.map do |row|
75
+ row.map.with_index do |col, i|
76
+ result = col.to_s.ljust col_widths[i]
77
+
78
+ if STDOUT.tty?
79
+ result.colorize(colors[i] || colors[-1] || :default)
80
+ .gsub '"', '\"'
81
+ else
82
+ result
83
+ end
84
+ end.join(' ').rstrip
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,3 @@
1
+ module Cani
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,26 @@
1
+ # This is a template file that adds the most basic top level completions.
2
+ # It is used during installation of completions via the CLI.
3
+ # Unlike the functions.fish file, this one cannot be run directly.
4
+ #
5
+ # The invalid syntax will be replaced with valid BASH code
6
+ # during the installation process.
7
+
8
+ _cani_completions() {
9
+ case "${COMP_WORDS[1]}" in
10
+ "show")
11
+ case "${COMP_WORDS[2]}" in
12
+ {{versions}}
13
+ *)
14
+ COMPREPLY=($(compgen -W "{{names}}" "${COMP_WORDS[COMP_CWORD]}"))
15
+ ;;
16
+ esac
17
+ ;;
18
+ *)
19
+ COMPREPLY=($(compgen -W "use show help version" "${COMP_WORDS[COMP_CWORD]}"))
20
+ ;;
21
+ esac
22
+ }
23
+
24
+ complete -F _cani_completions cani
25
+
26
+
@@ -0,0 +1,39 @@
1
+ # This is a template file that adds the most basic top level completions.
2
+ # It is used during installation of completions via the CLI.
3
+ #
4
+ # Extra completions are added in the installation process.
5
+ # These include completions for browsers and versions.
6
+
7
+ function __fish_cani_needs_command
8
+ set -l cmd (commandline -opc)
9
+ set -q cmd[2]
10
+ or return 0
11
+
12
+ echo $cmd[2..-1]
13
+ return 1
14
+ end
15
+
16
+ function __fish_cani_showing_browser
17
+ set -l cmd (__fish_cani_needs_command | string split ' ')
18
+
19
+ test "$cmd[1]" = "show"
20
+ and contains -- "$cmd[2]" $argv
21
+ and return 0
22
+ end
23
+
24
+ function __fish_cani_using_command
25
+ set -l cmd (__fish_cani_needs_command)
26
+
27
+ test -z "$cmd"
28
+ and return 1
29
+
30
+ contains -- $cmd $argv
31
+ and return 0
32
+ end
33
+
34
+ complete -f -c cani
35
+
36
+ complete -f -c cani -n '__fish_cani_needs_command' -a 'use' -d 'Display an overview of features including support'
37
+ complete -f -c cani -n '__fish_cani_needs_command' -a 'show' -d 'Display feature support for a specific browser'
38
+ complete -f -c cani -n '__fish_cani_needs_command' -a 'help' -d 'Show command help'
39
+ complete -f -c cani -n '__fish_cani_needs_command' -a 'version' -d 'Print the version number'
@@ -0,0 +1,26 @@
1
+ # This is a template file that adds the most basic top level completions.
2
+ # It is used during installation of completions via the CLI.
3
+ # Unlike the functions.fish file, this one cannot be run directly.
4
+ #
5
+ # The invalid syntax will be replaced with valid ZSH code
6
+ # during the installation process.
7
+
8
+ function _cani {
9
+ local line
10
+
11
+ _arguments -C "1: :(use show help version)" \
12
+ "*::arg:->args"
13
+
14
+ case $line[1] in
15
+ show)
16
+ _arguments -C "1: :({{names}})" \
17
+ "*::arg:->args"
18
+
19
+ case $line[1] in
20
+ {{versions}}
21
+ esac
22
+ ;;
23
+ esac
24
+ }
25
+
26
+ compdef _cani cani
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cani
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sidney Liebrand
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-07-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.16'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.16'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ description: 'A rework of the ruby script from my medium post: https://medium.com/@sidneyliebrand/combining-caniuse-with-fzf-fb93ad235bae'
98
+ email:
99
+ - sidneyliebrand@gmail.com
100
+ executables:
101
+ - cani
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".rspec"
107
+ - ".travis.yml"
108
+ - CODE_OF_CONDUCT.md
109
+ - Gemfile
110
+ - Gemfile.lock
111
+ - LICENSE.txt
112
+ - README.md
113
+ - Rakefile
114
+ - bin/console
115
+ - bin/setup
116
+ - cani.gemspec
117
+ - exe/cani
118
+ - lib/cani.rb
119
+ - lib/cani/api.rb
120
+ - lib/cani/api/browser.rb
121
+ - lib/cani/api/config.rb
122
+ - lib/cani/api/feature.rb
123
+ - lib/cani/completions.rb
124
+ - lib/cani/fzf.rb
125
+ - lib/cani/version.rb
126
+ - shell/completions/functions.bash
127
+ - shell/completions/functions.fish
128
+ - shell/completions/functions.zsh
129
+ homepage: https://github.com/SidOfc/cani
130
+ licenses:
131
+ - MIT
132
+ metadata: {}
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubyforge_project:
149
+ rubygems_version: 2.7.6
150
+ signing_key:
151
+ specification_version: 4
152
+ summary: A simple caniuse CLI.
153
+ test_files: []