cani 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []