github_members 0.0.1

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: 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: []