github_members 0.0.1

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: f27c99dd181831eb919dead2f515dbdfe8677732df33a529681f3218523fd045
4
+ data.tar.gz: 2a8315bd3492878505168b9d6bb1b77f58e5aa50cd7780eece32d48c8fd0e45c
5
+ SHA512:
6
+ metadata.gz: ad928b3fad1ff61336a0c435e113ab534538465144c38f0bd056898018d2ee29b98d52cd63f75191610af477e548cbb197894d159a2f3f0c422f07086ec4a736
7
+ data.tar.gz: 90a313c90fa74106671683b6143e84c7189bee49b1262df2ba88062a4b3a7ee6ebb119df07a2255675adb53bdbe2f3e60c15748c7c4a0f0e118ec73d63a195c3
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.0.1
4
+
5
+ Initial release.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Masafumi Koba
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # GitHub Members
2
+
3
+ Manage GitHub members who belong to an organization.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your `Gemfile`:
8
+
9
+ ```ruby
10
+ gem 'github_members', require: false
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Run `bundle exec github_members --help`:
16
+
17
+ <!-- HELP:BEGIN -->
18
+
19
+ ```
20
+ Usage: github_members [options] <org>
21
+ -g, --github-token GITHUB_TOKEN A GitHub access token. The `GITHUB_TOKEN` environment variable
22
+ is also available.
23
+ -m, --markdown-file [FILE] A Markdown file path. Default: `members.md`
24
+ -y, --yaml-file [FILE] A YAML file path. Default: `members.yml`
25
+ -f, --fields [FIELDS] A comma-separated list of a member additional fields
26
+ -h, --help Show help
27
+ -v, --version Show version
28
+
29
+ Examples:
30
+ # By default
31
+ github_members <org>
32
+
33
+ # Set a token
34
+ GITHUB_TOKEN=*** github_members <org>
35
+ github_members <org> -g ***
36
+
37
+ # Specify files
38
+ github_members <org> -m README.md -y company_members.yml
39
+
40
+ # Add fields
41
+ github_members <org> -f slack,note
42
+ ```
43
+
44
+ <!-- HELP:END -->
45
+
46
+ ### Prepare GitHub access token
47
+
48
+ By default, only public members of a specified organization is fetched.
49
+ Perhaps, you may need to [generate an access token](https://github.com/settings/tokens/new?scopes=read:org&description=github_members) to fetch all members.
50
+
51
+ ### Generate YAML file including members
52
+
53
+ Fetched organization members are saved in a YAML file. You can add any attributes for a member.
54
+
55
+ ### Insert members table into Markdown file
56
+
57
+ Prepare the begin/end tags in your Markdown file like this:
58
+
59
+ ```markdown
60
+ <!-- GITHUB_MEMBERS:BEGIN -->
61
+ <!-- GITHUB_MEMBERS:END -->
62
+ ```
63
+
64
+ When running `github_members`, a table of a specified organization members is inserted into the markdown file:
65
+
66
+ ```markdown
67
+ <!-- GITHUB_MEMBERS:BEGIN -->
68
+
69
+ {inserted_table}
70
+
71
+ <!-- GITHUB_MEMBERS:END -->
72
+ ```
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/github_members"
4
+
5
+ exit GithubMembers::CLI.new(ARGV).run
@@ -0,0 +1,85 @@
1
+ module GithubMembers
2
+ class CLI
3
+ EXIT_SUCCESS = 0
4
+ EXIT_FAILURE = 1
5
+
6
+ attr_reader :argv
7
+ attr_reader :options
8
+
9
+ def initialize(argv)
10
+ @argv = argv
11
+ end
12
+
13
+ def run
14
+ begin
15
+ @options = Options.new(argv)
16
+ rescue Options::ParseError => e
17
+ warn e.message
18
+ return EXIT_FAILURE
19
+ end
20
+
21
+ case
22
+ when options.help_shown
23
+ puts options.help
24
+ return EXIT_SUCCESS
25
+ when options.version
26
+ puts options.version
27
+ return EXIT_SUCCESS
28
+ when options.github_org.nil?
29
+ warn "Error: Organization is missing"
30
+ warn ""
31
+ warn options.help
32
+ return EXIT_FAILURE
33
+ end
34
+
35
+ members = read_members
36
+
37
+ Client.new(options).fetch_members(options.github_org).each do |new_member|
38
+ github = new_member.fetch(:github)
39
+ fullname = new_member.fetch(:fullname)
40
+ avatar = new_member.fetch(:avatar)
41
+
42
+ if members.key?(github)
43
+ members[github].tap do |m|
44
+ m.fullname = fullname
45
+ m.avatar = avatar
46
+ m.updated = true
47
+ end
48
+ else
49
+ members[github] = member_class.new(
50
+ github: github,
51
+ fullname: fullname,
52
+ avatar: avatar,
53
+ updated: true
54
+ )
55
+ end
56
+ end
57
+
58
+ members.delete_if { |_, m| !m.updated }
59
+
60
+ write_members(members.values)
61
+
62
+ MarkdownWriter.new.write(members: members.values, file: options.markdown_file)
63
+
64
+ EXIT_SUCCESS
65
+ end
66
+
67
+ private def member_class
68
+ @member_class ||= Member.new.define_class(*options.fields)
69
+ end
70
+
71
+ private def read_members
72
+ file = options.yaml_file
73
+ return {} unless file.exist?
74
+
75
+ YAML.safe_load(File.read(file)).each_with_object({}) do |member, hash|
76
+ m = member_class.new(**member, updated: false)
77
+ hash[m.github] = m
78
+ end
79
+ end
80
+
81
+ private def write_members(members)
82
+ options.yaml_file.write(members.map(&:to_h).to_yaml)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,31 @@
1
+ require "octokit"
2
+
3
+ module GithubMembers
4
+ class Client
5
+ attr_reader :options
6
+
7
+ def initialize(options)
8
+ @options = options
9
+
10
+ Octokit.configure do |c|
11
+ token = options.github_token
12
+ c.access_token = token unless token.empty?
13
+ c.auto_paginate = true
14
+ c.per_page = 100
15
+ end
16
+ end
17
+
18
+ def fetch_members(org)
19
+ Octokit.organization_members(org).map do |gh_member|
20
+ gh_user = Octokit.user(gh_member.login)
21
+ fullname = gh_user.name.then { |name| name.to_s.empty? ? "" : name }
22
+
23
+ {
24
+ github: gh_member.login,
25
+ fullname: fullname,
26
+ avatar: gh_member.avatar_url
27
+ }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,6 @@
1
+ module GithubMembers
2
+ module Defaults
3
+ MARKDOWN_FILE = "members.md"
4
+ YAML_FILE = "members.yml"
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module GithubMembers
2
+ class Error < StandardError; end
3
+ end
@@ -0,0 +1,73 @@
1
+ require "markdown-tables"
2
+
3
+ module GithubMembers
4
+ class MarkdownWriter
5
+ TAG_BEGIN = "<!-- GITHUB_MEMBERS:BEGIN -->"
6
+ TAG_END = "<!-- GITHUB_MEMBERS:END -->"
7
+
8
+ def write(members:, file:)
9
+ unless file.exist?
10
+ file.write(<<~MARKDOWN)
11
+ # Members
12
+
13
+ #{TAG_BEGIN}
14
+ #{TAG_END}
15
+ MARKDOWN
16
+ end
17
+
18
+ table = render_table(members)
19
+ write_file(table, members.size, file)
20
+ end
21
+
22
+ private def render_table(members)
23
+ return "" if members.empty?
24
+
25
+ fields = members.first.additional_fields
26
+ labels = ["", "GitHub", "Fullname"]
27
+ align = ["c", "l", "l"]
28
+
29
+ fields.each do |field|
30
+ labels << field.capitalize
31
+ align << "l"
32
+ end
33
+
34
+ data = members.map do |member|
35
+ # NOTE: GitHub's default avatar doesn't support `&s=<size>`, so this uses the `<img>` tag.
36
+ avatar_url = URI(member.avatar).tap { |u| u.query = "s=64" }.to_s
37
+ avatar = %(<img src="#{avatar_url}" alt="@#{member.github}" width="32" height="32">)
38
+ github = "[@#{member.github}](#{member.github_url})"
39
+ fullname = member.fullname.to_s.then { |v| v.empty? ? "-" : v }
40
+
41
+ [avatar, github, fullname].tap do |d|
42
+ fields.each do |field|
43
+ d << member[field].to_s.then { |v| v.empty? ? "-" : v }
44
+ end
45
+ end
46
+ end
47
+
48
+ MarkdownTables.make_table(labels, data, is_rows: true, align: align)
49
+ end
50
+
51
+ private def write_file(table, member_count, file)
52
+ content = file.read
53
+
54
+ unless content.include?(TAG_BEGIN)
55
+ raise MissingTagInMarkdown, "#{file} must include `#{TAG_BEGIN}`"
56
+ end
57
+
58
+ unless content.include?(TAG_END)
59
+ raise MissingTagInMarkdown, "#{file} must include `#{TAG_END}`"
60
+ end
61
+
62
+ content.sub!(/#{TAG_BEGIN}.+#{TAG_END}/mo, <<~MARKDOWN.strip)
63
+ #{TAG_BEGIN}
64
+ **#{member_count}** member#{member_count == 1 ? "" : "s"}
65
+
66
+ #{table}
67
+ #{TAG_END}
68
+ MARKDOWN
69
+
70
+ file.write(content)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,21 @@
1
+ module GithubMembers
2
+ class Member
3
+ DEFAULT_FIELDS = [:github, :fullname, :avatar, :updated].freeze
4
+
5
+ def define_class(*fields)
6
+ Struct.new(*DEFAULT_FIELDS, *fields, keyword_init: true) do
7
+ def github_url
8
+ "https://github.com/#{github}"
9
+ end
10
+
11
+ def additional_fields
12
+ members - DEFAULT_FIELDS
13
+ end
14
+
15
+ def to_h
16
+ super.to_h.transform_keys(&:to_s).reject { |k, _| k == "updated" }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,79 @@
1
+ require "optparse"
2
+
3
+ module GithubMembers
4
+ class Options
5
+ class ParseError < Error; end
6
+
7
+ attr_accessor :github_org
8
+ attr_accessor :github_token
9
+ attr_accessor :markdown_file
10
+ attr_accessor :yaml_file
11
+ attr_accessor :fields
12
+ attr_accessor :help
13
+ attr_accessor :help_shown
14
+ attr_accessor :version
15
+
16
+ def initialize(argv)
17
+ self.github_token = ENV["GITHUB_TOKEN"].to_s
18
+
19
+ args = parser.parse!(argv)
20
+ self.help = parser.help + help_examples
21
+ self.github_org = args.first
22
+ self.markdown_file = Pathname(markdown_file || Defaults::MARKDOWN_FILE)
23
+ self.yaml_file = Pathname(yaml_file || Defaults::YAML_FILE)
24
+ rescue OptionParser::ParseError => e
25
+ raise ParseError, e.message
26
+ end
27
+
28
+ private def parser
29
+ @parser ||= OptionParser.new do |opts|
30
+ opts.banner = "Usage: github_members [options] <org>"
31
+
32
+ opts.on("-g", "--github-token GITHUB_TOKEN",
33
+ "A GitHub access token. The `GITHUB_TOKEN` environment variable",
34
+ "is also available.") do |v|
35
+ self.github_token = v
36
+ end
37
+
38
+ opts.on("-m", "--markdown-file [FILE]", "A Markdown file path. Default: `#{Defaults::MARKDOWN_FILE}`") do |v|
39
+ self.markdown_file = v
40
+ end
41
+
42
+ opts.on("-y", "--yaml-file [FILE]", "A YAML file path. Default: `#{Defaults::YAML_FILE}`") do |v|
43
+ self.yaml_file = v
44
+ end
45
+
46
+ opts.on("-f", "--fields [FIELDS]", Array, "A comma-separated list of a member additional fields") do |v|
47
+ self.fields = v
48
+ end
49
+
50
+ opts.on_tail("-h", "--help", "Show help") do
51
+ self.help_shown = true
52
+ end
53
+
54
+ opts.on_tail("-v", "--version", "Show version") do
55
+ self.version = VERSION
56
+ end
57
+ end
58
+ end
59
+
60
+ private def help_examples
61
+ <<~HELP
62
+
63
+ Examples:
64
+ # By default
65
+ github_members <org>
66
+
67
+ # Set a token
68
+ GITHUB_TOKEN=*** github_members <org>
69
+ github_members <org> -g ***
70
+
71
+ # Specify files
72
+ github_members <org> -m README.md -y company_members.yml
73
+
74
+ # Add fields
75
+ github_members <org> -f slack,note
76
+ HELP
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,3 @@
1
+ module GithubMembers
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,17 @@
1
+ require "json"
2
+ require "pathname"
3
+ require "yaml"
4
+
5
+ require_relative "github_members/errors"
6
+
7
+ require_relative "github_members/client"
8
+ require_relative "github_members/defaults"
9
+ require_relative "github_members/markdown_writer"
10
+ require_relative "github_members/member"
11
+ require_relative "github_members/options"
12
+ require_relative "github_members/version"
13
+
14
+ require_relative "github_members/cli"
15
+
16
+ module GithubMembers
17
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: github_members
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Masafumi Koba
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-08-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: markdown-tables
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: octokit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.21'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.21'
41
+ description:
42
+ email:
43
+ - ybiquitous@gmail.com
44
+ executables:
45
+ - github_members
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - CHANGELOG.md
50
+ - LICENSE
51
+ - README.md
52
+ - exe/github_members
53
+ - lib/github_members.rb
54
+ - lib/github_members/cli.rb
55
+ - lib/github_members/client.rb
56
+ - lib/github_members/defaults.rb
57
+ - lib/github_members/errors.rb
58
+ - lib/github_members/markdown_writer.rb
59
+ - lib/github_members/member.rb
60
+ - lib/github_members/options.rb
61
+ - lib/github_members/version.rb
62
+ homepage: https://github.com/ybiquitous/github_members
63
+ licenses:
64
+ - MIT
65
+ metadata:
66
+ homepage_uri: https://github.com/ybiquitous/github_members
67
+ source_code_uri: https://github.com/ybiquitous/github_members
68
+ changelog_uri: https://github.com/ybiquitous/github_members/blob/main/CHANGELOG.md
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 2.7.0
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubygems_version: 3.2.22
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Manage GitHub members who belong to an organization.
88
+ test_files: []