spreen-wiki 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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +114 -0
- data/exe/spreen +6 -0
- data/lib/spreen/wiki/application.rb +151 -0
- data/lib/spreen/wiki/cli.rb +159 -0
- data/lib/spreen/wiki/configuration.rb +217 -0
- data/lib/spreen/wiki/home.rb +102 -0
- data/lib/spreen/wiki/sidebar.rb +50 -0
- data/lib/spreen/wiki/templates/category/english.md +8 -0
- data/lib/spreen/wiki/templates/category/japanese.md +8 -0
- data/lib/spreen/wiki/templates/owner/english.md +8 -0
- data/lib/spreen/wiki/templates/owner/japanese.md +8 -0
- data/lib/spreen/wiki/unknown_wiki_count_list_exporter.rb +61 -0
- data/lib/spreen/wiki/unknown_wiki_list_exporter_for_llm.rb +53 -0
- data/lib/spreen/wiki/version.rb +8 -0
- data/lib/spreen/wiki.rb +20 -0
- data/lib/spreen-wiki.rb +6 -0
- data/sig/generated/spreen/wiki/application.rbs +93 -0
- data/sig/generated/spreen/wiki/cli.rbs +70 -0
- data/sig/generated/spreen/wiki/configuration.rbs +150 -0
- data/sig/generated/spreen/wiki/home.rbs +54 -0
- data/sig/generated/spreen/wiki/sidebar.rbs +31 -0
- data/sig/generated/spreen/wiki/unknown_wiki_count_list_exporter.rbs +40 -0
- data/sig/generated/spreen/wiki/unknown_wiki_list_exporter_for_llm.rbs +37 -0
- data/sig/generated/spreen/wiki/version.rbs +7 -0
- data/sig/generated/spreen/wiki.rbs +10 -0
- data/sig/generated/spreen-wiki.rbs +2 -0
- data/sig/generated/test/application_test.rbs +30 -0
- data/sig/generated/test/configuration_test.rbs +38 -0
- data/sig/generated/test/home_test.rbs +60 -0
- data/sig/generated/test/sidebar_test.rbs +42 -0
- data/sig/generated/test/unknown_wiki_count_list_exporter_test.rbs +161 -0
- data/sig/generated/test/unknown_wiki_list_exporter_for_llm_test.rbs +53 -0
- metadata +80 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: ae1421288cd76d1d43326a72cdfd2c2c2dd9bb617becd061468e7a2789f80b31
|
|
4
|
+
data.tar.gz: 3ab8bc5f1d779379fe2137cd0869071150c06fc74ce4566cda84c57a03a6d5af
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d2658be24738c7ad2a5a9738f45dcb5505b8fa1c4054554e9c8384e5782df2c01027e5aff529ae59715123343f472ec4dab0e1cac36008a66be0128401311090
|
|
7
|
+
data.tar.gz: 56454356e70d3d8d6a7c3013084d5c3babf65d9e4042ce1f8f55a306b90169ff9184753d65e20ff058a6f4bdf7e1f6f74b64d2c28f0d73f5a994082f43e65211
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 - present @hayat01sh1da
|
|
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,114 @@
|
|
|
1
|
+
# spreen-wiki (Ruby)
|
|
2
|
+
|
|
3
|
+
The RubyGems implementation of [spreen-wiki](../README.md): generates `Home.md` and `_Sidebar.md` for a GitHub wiki grouped by the Owner/Category declared on the first line of each page, and exports reports of the pages whose owner or category is unknown.
|
|
4
|
+
|
|
5
|
+
## 1. Installation
|
|
6
|
+
|
|
7
|
+
```command
|
|
8
|
+
$ gem install spreen-wiki
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Ruby 3.4+.
|
|
12
|
+
|
|
13
|
+
## 2. CLI Usage
|
|
14
|
+
|
|
15
|
+
```command
|
|
16
|
+
$ cd path/to/your-repo.wiki
|
|
17
|
+
$ spreen update --path . --org your-org --repo your-repo
|
|
18
|
+
Updated Home.md and _Sidebar.md.
|
|
19
|
+
Check them out at 'https://github.com/your-org/your-repo/wiki' !!
|
|
20
|
+
|
|
21
|
+
$ spreen count-report --path .
|
|
22
|
+
Unknown Owner nor Necessity: 1
|
|
23
|
+
Unowned but Necessary: 1
|
|
24
|
+
Unowned: 10
|
|
25
|
+
Exported the unknown wiki count report to './unknown_wiki_count_list_by_namespace.txt'.
|
|
26
|
+
|
|
27
|
+
$ spreen llm-export --path .
|
|
28
|
+
Exported the unknown wiki list for LLM to './unknown_wiki_list_for_llm.txt'.
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Common flags (see `spreen <command> --help`): `--group-by Owner|Category`, `--language English|Japanese`, `--overflow` (split Home into per-namespace pages under `wikis-by-owner/`), `--template-dir`, `--config`, `--exclude`, `--output`, `--wiki-url`.
|
|
32
|
+
|
|
33
|
+
Persistent settings, custom labels and additional languages go into a [`.spreen.yml`](../README.md#4-configuration) at the wiki root.
|
|
34
|
+
|
|
35
|
+
## 3. Library Usage
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
require 'spreen/wiki'
|
|
39
|
+
|
|
40
|
+
Spreen::Wiki::Home.run(base_path: '.', organisation: 'your-org', repository: 'your-repo')
|
|
41
|
+
Spreen::Wiki::Sidebar.run(base_path: '.', organisation: 'your-org', repository: 'your-repo')
|
|
42
|
+
Spreen::Wiki::UnknownWikiCountListExporter.run(base_path: '.', output: 'report.txt')
|
|
43
|
+
Spreen::Wiki::UnknownWikiListExporterForLLM.run(base_path: '.', group_by: 'Category', language: 'Japanese')
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
All classes accept `base_path:`, `group_by:`, `language:`, `home_overflow:` plus the configuration keywords `organisation:`, `repository:`, `wiki_url:`, `owner_base_url:`, `template_dir:`, `excluded_dirs:`, `config_path:` (and `output:` on the exporters).
|
|
47
|
+
|
|
48
|
+
## 4. Development
|
|
49
|
+
|
|
50
|
+
### 4-1. Environment
|
|
51
|
+
|
|
52
|
+
- Ruby 4.0.5
|
|
53
|
+
- Bundler 4.0.x
|
|
54
|
+
|
|
55
|
+
```command
|
|
56
|
+
$ bundle install
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 4-2. Interactive Rake Tasks (this repository's own automation)
|
|
60
|
+
|
|
61
|
+
The wiki cron workflows drive these stdin-prompted tasks; they pin this repository's settings and read `ORGANISATION_NAME` from the environment.
|
|
62
|
+
|
|
63
|
+
```command
|
|
64
|
+
$ rake update_wiki_list_on_home_and_sidebar
|
|
65
|
+
$ rake export_unknown_wiki_count_list_by_namespace
|
|
66
|
+
$ rake export_unknown_wiki_list_for_llm
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 4-3. Unit Tests
|
|
70
|
+
|
|
71
|
+
```command
|
|
72
|
+
$ bundle exec rake
|
|
73
|
+
Run options: --seed 33975
|
|
74
|
+
|
|
75
|
+
# Running:
|
|
76
|
+
|
|
77
|
+
.........................................................................................................................................................................
|
|
78
|
+
|
|
79
|
+
Finished in 9.889896s, 17.0881 runs/s, 32.7607 assertions/s.
|
|
80
|
+
|
|
81
|
+
174 runs, 323 assertions, 0 failures, 0 errors, 0 skips
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 4-4. Static Code Analysis
|
|
85
|
+
|
|
86
|
+
```command
|
|
87
|
+
$ bundle exec rubocop
|
|
88
|
+
Inspecting 21 files
|
|
89
|
+
....................
|
|
90
|
+
|
|
91
|
+
20 files inspected, no offenses detected
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 4-5. Type Checks
|
|
95
|
+
|
|
96
|
+
```command
|
|
97
|
+
$ bundle exec rbs-inline --output sig/generated/ lib test
|
|
98
|
+
🎉 Generated 16 RBS files under sig/generated
|
|
99
|
+
$ bundle exec steep check
|
|
100
|
+
# Type checking files:
|
|
101
|
+
|
|
102
|
+
..............................
|
|
103
|
+
|
|
104
|
+
No type error detected. 🫖
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 4-6. Build & Release
|
|
108
|
+
|
|
109
|
+
```command
|
|
110
|
+
$ gem build spreen-wiki.gemspec
|
|
111
|
+
$ gem push spreen-wiki-x.y.z.gem
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Releases are tagged `ruby-vX.Y.Z`; update [CHANGELOG.md](../CHANGELOG.md) with every release.
|
data/exe/spreen
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require_relative 'configuration'
|
|
5
|
+
|
|
6
|
+
module Spreen
|
|
7
|
+
module Wiki
|
|
8
|
+
# Base class for the wiki-organising commands. Loads the wiki tree under
|
|
9
|
+
# base_path, groups it by Owner or Category in either English or Japanese,
|
|
10
|
+
# and exposes the resulting maps to its subclasses. Grouping criteria,
|
|
11
|
+
# languages and labels are resolved through Configuration and can be
|
|
12
|
+
# extended via a `.spreen.yml` file.
|
|
13
|
+
class Application
|
|
14
|
+
class NotImplementedError < StandardError; end
|
|
15
|
+
|
|
16
|
+
# @rbs base_path: String
|
|
17
|
+
# @rbs group_by: String
|
|
18
|
+
# @rbs language: String
|
|
19
|
+
# @rbs home_overflow: (bool | String)
|
|
20
|
+
# @rbs **options: untyped
|
|
21
|
+
# @rbs return: untyped
|
|
22
|
+
def self.run(base_path: Dir.pwd, group_by: 'Owner', language: 'English', home_overflow: 'false', **)
|
|
23
|
+
instance = new(base_path:, group_by:, language:, home_overflow:, **)
|
|
24
|
+
instance.validate!
|
|
25
|
+
instance.run
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @rbs base_path: String
|
|
29
|
+
# @rbs group_by: String
|
|
30
|
+
# @rbs language: String
|
|
31
|
+
# @rbs home_overflow: (bool | String)
|
|
32
|
+
# @rbs config: Configuration?
|
|
33
|
+
# @rbs **options: untyped
|
|
34
|
+
# @rbs return: void
|
|
35
|
+
def initialize(base_path: Dir.pwd, group_by: 'Owner', language: 'English', home_overflow: 'false',
|
|
36
|
+
config: nil, **)
|
|
37
|
+
@base_path = base_path
|
|
38
|
+
@group_by = group_by
|
|
39
|
+
@language = language
|
|
40
|
+
@home_overflow = parse_home_overflow(home_overflow)
|
|
41
|
+
@config = config || Configuration.new(base_path:, **)
|
|
42
|
+
@path_to_home = File.join(base_path, 'Home.md')
|
|
43
|
+
@path_to_sidebar = File.join(base_path, '_Sidebar.md')
|
|
44
|
+
@paths_to_wikis = Dir[File.join(base_path, '**', '*.md')]
|
|
45
|
+
@excluded_paths = @config.excluded_dirs.flat_map { |dir| Dir[File.join(base_path, dir, '**', '*.md')] }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @rbs return: void
|
|
49
|
+
def validate!
|
|
50
|
+
raise ArgumentError, "Invalid group_by: `#{group_by}`" unless config.group_bys.include?(group_by)
|
|
51
|
+
raise ArgumentError, "Invalid language: `#{language}`" unless config.languages(group_by).include?(language)
|
|
52
|
+
|
|
53
|
+
validate_home_overflow!
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @rbs return: untyped
|
|
57
|
+
def run
|
|
58
|
+
raise NotImplementedError, 'This method must be implemented in each subclass.'
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
attr_reader :base_path,
|
|
64
|
+
:group_by,
|
|
65
|
+
:language,
|
|
66
|
+
:home_overflow,
|
|
67
|
+
:config,
|
|
68
|
+
:path_to_home,
|
|
69
|
+
:path_to_sidebar,
|
|
70
|
+
:paths_to_wikis,
|
|
71
|
+
:excluded_paths
|
|
72
|
+
|
|
73
|
+
# @rbs return: void
|
|
74
|
+
def validate_home_overflow!
|
|
75
|
+
return if [true, false].include?(home_overflow)
|
|
76
|
+
|
|
77
|
+
raise ArgumentError, "Invalid home_overflow: `#{home_overflow}` must be boolean"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @rbs raw: (bool | String)
|
|
81
|
+
# @rbs return: (bool | String)
|
|
82
|
+
def parse_home_overflow(raw)
|
|
83
|
+
case raw
|
|
84
|
+
when 'true' then true
|
|
85
|
+
when 'false' then false
|
|
86
|
+
else raw
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @rbs return: Array[String]
|
|
91
|
+
def target_paths
|
|
92
|
+
@target_paths ||= paths_to_wikis.reject do |path_to_wiki|
|
|
93
|
+
path_to_wiki == path_to_home ||
|
|
94
|
+
path_to_wiki == path_to_sidebar ||
|
|
95
|
+
excluded_paths.include?(path_to_wiki)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @rbs return: Regexp
|
|
100
|
+
def target_regexp
|
|
101
|
+
@target_regexp ||= config.target_regexp(group_by)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @rbs return: String
|
|
105
|
+
def no_declaration
|
|
106
|
+
@no_declaration ||= config.no_declaration(group_by, language)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @rbs return: Hash[String, Array[String]]
|
|
110
|
+
def wiki_maps_with_namespace
|
|
111
|
+
@wiki_maps_with_namespace ||= begin
|
|
112
|
+
named = Hash.new { |hash, namespace| hash[namespace] = [] } #: Hash[String, Array[String]]
|
|
113
|
+
undeclared = Hash.new { |hash, namespace| hash[namespace] = [] } #: Hash[String, Array[String]]
|
|
114
|
+
target_paths.each { |target_path| populate_namespace(target_path, named, undeclared) }
|
|
115
|
+
named.sort.to_h.merge(undeclared)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# @rbs target_path: String
|
|
120
|
+
# @rbs named: Hash[String, Array[String]]
|
|
121
|
+
# @rbs undeclared: Hash[String, Array[String]]
|
|
122
|
+
# @rbs return: void
|
|
123
|
+
def populate_namespace(target_path, named, undeclared)
|
|
124
|
+
return unless File.exist?(target_path)
|
|
125
|
+
|
|
126
|
+
namespace = namespace_for(target_path)
|
|
127
|
+
bucket = namespace == no_declaration ? undeclared : named
|
|
128
|
+
bucket[namespace] << File.basename(target_path)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @rbs target_path: String
|
|
132
|
+
# @rbs return: String
|
|
133
|
+
def namespace_for(target_path)
|
|
134
|
+
declaration = File.open(target_path, &:gets)
|
|
135
|
+
return no_declaration unless declaration&.start_with?(target_regexp)
|
|
136
|
+
|
|
137
|
+
declaration.chomp.gsub(target_regexp, '')
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @rbs return: Hash[String, Array[String]]
|
|
141
|
+
def owned_wiki_maps
|
|
142
|
+
@owned_wiki_maps ||= wiki_maps_with_namespace.select { |namespace, _| namespace.include?('@') }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# @rbs return: Hash[String, Array[String]]
|
|
146
|
+
def plain_wiki_maps
|
|
147
|
+
@plain_wiki_maps ||= wiki_maps_with_namespace.reject { |namespace, _| namespace.include?('@') }
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require 'optparse'
|
|
5
|
+
require_relative 'version'
|
|
6
|
+
require_relative 'home'
|
|
7
|
+
require_relative 'sidebar'
|
|
8
|
+
require_relative 'unknown_wiki_count_list_exporter'
|
|
9
|
+
require_relative 'unknown_wiki_list_exporter_for_llm'
|
|
10
|
+
|
|
11
|
+
module Spreen
|
|
12
|
+
module Wiki
|
|
13
|
+
# Command line interface behind the `spreen` executable:
|
|
14
|
+
# `spreen <update|count-report|llm-export> [options]`.
|
|
15
|
+
class CLI
|
|
16
|
+
HELP = <<~HELP
|
|
17
|
+
Usage: spreen <command> [options]
|
|
18
|
+
|
|
19
|
+
Commands:
|
|
20
|
+
update Regenerate Home.md and _Sidebar.md from the wiki tree
|
|
21
|
+
count-report Export a count report of the unknown-namespace wikis
|
|
22
|
+
llm-export Export the unknown-namespace wiki list for an LLM
|
|
23
|
+
version Print the version
|
|
24
|
+
|
|
25
|
+
Run `spreen <command> --help` to list the command's options.
|
|
26
|
+
HELP
|
|
27
|
+
|
|
28
|
+
# Flags shared by every command, mapped to the keyword arguments of the
|
|
29
|
+
# underlying classes. --exclude and the per-command flags are wired up
|
|
30
|
+
# separately because they need a type or a fixed value.
|
|
31
|
+
COMMON_OPTIONS = {
|
|
32
|
+
'--path PATH' => [:base_path, 'Path to the wiki clone (default: current directory)'],
|
|
33
|
+
'--group-by GROUP_BY' => [:group_by, 'Grouping criteria (default: Owner)'],
|
|
34
|
+
'--language LANGUAGE' => [:language, 'Language of the labels (default: English)'],
|
|
35
|
+
'--org ORGANISATION' => [:organisation, 'GitHub organisation/user hosting the wiki'],
|
|
36
|
+
'--repo REPOSITORY' => [:repository, 'GitHub repository whose wiki is organised'],
|
|
37
|
+
'--wiki-url URL' => [:wiki_url, 'Wiki URL (overrides the one derived from --org/--repo)'],
|
|
38
|
+
'--template-dir DIR' => [:template_dir, 'Directory of <group_by>/<language>.md Home templates'],
|
|
39
|
+
'--config FILE' => [:config_path, 'Config file path (default: <path>/.spreen.yml)']
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
# @rbs argv: Array[String]
|
|
43
|
+
# @rbs return: Integer
|
|
44
|
+
def self.start(argv = ARGV)
|
|
45
|
+
new(argv).run
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @rbs argv: Array[String]
|
|
49
|
+
# @rbs return: void
|
|
50
|
+
def initialize(argv)
|
|
51
|
+
@argv = argv.dup
|
|
52
|
+
@options = { base_path: Dir.pwd, group_by: 'Owner', language: 'English', home_overflow: 'false' }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @rbs return: Integer
|
|
56
|
+
def run
|
|
57
|
+
command = argv.shift
|
|
58
|
+
case command
|
|
59
|
+
when 'update', 'count-report', 'llm-export' then execute(command)
|
|
60
|
+
when 'version', '--version', '-v' then print_version
|
|
61
|
+
when nil, 'help', '--help', '-h' then print_help(command)
|
|
62
|
+
else unknown_command(command)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
attr_reader :argv, :options
|
|
69
|
+
|
|
70
|
+
# @rbs command: String
|
|
71
|
+
# @rbs return: Integer
|
|
72
|
+
def execute(command)
|
|
73
|
+
parser(command).parse!(argv)
|
|
74
|
+
__send__(command.tr('-', '_'))
|
|
75
|
+
0
|
|
76
|
+
rescue ArgumentError, OptionParser::ParseError => e
|
|
77
|
+
warn e.message
|
|
78
|
+
1
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @rbs return: Integer
|
|
82
|
+
def print_version
|
|
83
|
+
puts VERSION
|
|
84
|
+
0
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @rbs command: String?
|
|
88
|
+
# @rbs return: Integer
|
|
89
|
+
def print_help(command)
|
|
90
|
+
puts HELP
|
|
91
|
+
command.nil? ? 1 : 0
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @rbs command: String
|
|
95
|
+
# @rbs return: Integer
|
|
96
|
+
def unknown_command(command)
|
|
97
|
+
warn "Unknown command: `#{command}`"
|
|
98
|
+
warn HELP
|
|
99
|
+
1
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @rbs return: void
|
|
103
|
+
def update
|
|
104
|
+
wiki_url = Home.run(**options)
|
|
105
|
+
Sidebar.run(**options)
|
|
106
|
+
puts 'Updated Home.md and _Sidebar.md.'
|
|
107
|
+
puts "Check them out at '#{wiki_url}' !!" if wiki_url
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @rbs return: void
|
|
111
|
+
def count_report
|
|
112
|
+
count_list_by_namespace, path_to_export = UnknownWikiCountListExporter.run(**options)
|
|
113
|
+
puts count_list_by_namespace
|
|
114
|
+
puts "Exported the unknown wiki count report to '#{path_to_export}'."
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# @rbs return: void
|
|
118
|
+
def llm_export
|
|
119
|
+
path_to_export = UnknownWikiListExporterForLLM.run(**options)
|
|
120
|
+
puts "Exported the unknown wiki list for LLM to '#{path_to_export}'."
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# @rbs command: String
|
|
124
|
+
# @rbs return: OptionParser
|
|
125
|
+
def parser(command)
|
|
126
|
+
OptionParser.new("Usage: spreen #{command} [options]") do |opt|
|
|
127
|
+
add_common_options(opt)
|
|
128
|
+
add_command_options(opt, command)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @rbs opt: OptionParser
|
|
133
|
+
# @rbs return: void
|
|
134
|
+
def add_common_options(opt)
|
|
135
|
+
COMMON_OPTIONS.each do |flag, (key, description)|
|
|
136
|
+
opt.on(flag, description) { |value| options[key] = value }
|
|
137
|
+
end
|
|
138
|
+
opt.on('--exclude DIR1,DIR2', Array, 'Directories to skip while scanning') do |dirs|
|
|
139
|
+
options[:excluded_dirs] = dirs
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @rbs opt: OptionParser
|
|
144
|
+
# @rbs command: String
|
|
145
|
+
# @rbs return: void
|
|
146
|
+
def add_command_options(opt, command)
|
|
147
|
+
if command == 'update'
|
|
148
|
+
opt.on('--overflow', 'Split Home into per-namespace pages under wikis-by-owner/') do
|
|
149
|
+
options[:home_overflow] = 'true'
|
|
150
|
+
end
|
|
151
|
+
else
|
|
152
|
+
opt.on('--output FILENAME', 'Export filename (relative to --path unless absolute)') do |filename|
|
|
153
|
+
options[:output] = filename
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require 'yaml'
|
|
5
|
+
|
|
6
|
+
module Spreen
|
|
7
|
+
module Wiki
|
|
8
|
+
# Resolves the runtime configuration for a wiki checkout. Every value is
|
|
9
|
+
# looked up with the same precedence: explicit keyword argument, then the
|
|
10
|
+
# `.spreen.yml` file under base_path (or config_path), then the
|
|
11
|
+
# ORGANISATION_NAME environment variable (organisation only), then the
|
|
12
|
+
# built-in defaults.
|
|
13
|
+
class Configuration
|
|
14
|
+
CONFIG_FILENAME = '.spreen.yml'
|
|
15
|
+
EMPTY_HASH = {}.freeze #: Hash[String, untyped]
|
|
16
|
+
DEFAULT_TEMPLATE_DIR = File.expand_path(File.join(__dir__.to_s, 'templates'))
|
|
17
|
+
DEFAULT_EXCLUDED_DIRS = %w[spreen-wiki wikis-by-owner].freeze
|
|
18
|
+
|
|
19
|
+
# Built-in labels: how the first line of a wiki page is parsed (regexp),
|
|
20
|
+
# which namespace collects pages without a declaration (no_declaration),
|
|
21
|
+
# which namespaces the count report covers (unknown_namespaces) and which
|
|
22
|
+
# namespace the LLM export targets (llm_target_namespace). A config file
|
|
23
|
+
# can override any of these, add languages or add group_by criteria.
|
|
24
|
+
DEFAULT_LABELS = {
|
|
25
|
+
'Owner' => {
|
|
26
|
+
'regexp' => '[Oo]wner:\s?',
|
|
27
|
+
'languages' => {
|
|
28
|
+
'English' => {
|
|
29
|
+
'no_declaration' => 'Unowned',
|
|
30
|
+
'unknown_namespaces' => ['Unknown Owner nor Necessity', 'Unowned but Necessary', 'Unowned'],
|
|
31
|
+
'llm_target_namespace' => 'Unknown Owner nor Necessity'
|
|
32
|
+
},
|
|
33
|
+
'Japanese' => {
|
|
34
|
+
'no_declaration' => 'Owner記名なし',
|
|
35
|
+
'unknown_namespaces' => [
|
|
36
|
+
'Ownerチームが不明だが必要なページ群',
|
|
37
|
+
'Ownerチーム・要or不要が不明なページ群',
|
|
38
|
+
'Owner記名なし'
|
|
39
|
+
],
|
|
40
|
+
'llm_target_namespace' => 'Ownerチーム・要or不要が不明なページ群'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
'Category' => {
|
|
45
|
+
'regexp' => '[Cc]ategory:\s?',
|
|
46
|
+
'languages' => {
|
|
47
|
+
'English' => {
|
|
48
|
+
'no_declaration' => 'Uncategorised',
|
|
49
|
+
'unknown_namespaces' => ['Uncategorised'],
|
|
50
|
+
'llm_target_namespace' => 'Uncategorised'
|
|
51
|
+
},
|
|
52
|
+
'Japanese' => {
|
|
53
|
+
'no_declaration' => 'Category記載なし',
|
|
54
|
+
'unknown_namespaces' => ['Category記載なし'],
|
|
55
|
+
'llm_target_namespace' => 'Category記載なし'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}.freeze
|
|
60
|
+
|
|
61
|
+
attr_reader :organisation, :repository, :wiki_url, :owner_base_url, :excluded_dirs, :template_dir
|
|
62
|
+
|
|
63
|
+
# @rbs!
|
|
64
|
+
# @organisation: String?
|
|
65
|
+
# @repository: String?
|
|
66
|
+
# @wiki_url: String?
|
|
67
|
+
# @owner_base_url: String?
|
|
68
|
+
# @excluded_dirs: Array[String]
|
|
69
|
+
# @template_dir: String
|
|
70
|
+
# @file: Hash[String, untyped]
|
|
71
|
+
# @labels: Hash[String, untyped]
|
|
72
|
+
|
|
73
|
+
# @rbs base_path: String
|
|
74
|
+
# @rbs config_path: String?
|
|
75
|
+
# @rbs organisation: String?
|
|
76
|
+
# @rbs repository: String?
|
|
77
|
+
# @rbs wiki_url: String?
|
|
78
|
+
# @rbs owner_base_url: String?
|
|
79
|
+
# @rbs excluded_dirs: Array[String]?
|
|
80
|
+
# @rbs template_dir: String?
|
|
81
|
+
# @rbs return: void
|
|
82
|
+
def initialize(base_path:, config_path: nil, organisation: nil, repository: nil, wiki_url: nil,
|
|
83
|
+
owner_base_url: nil, excluded_dirs: nil, template_dir: nil)
|
|
84
|
+
@file = load_file(config_path || File.join(base_path, CONFIG_FILENAME))
|
|
85
|
+
@organisation = resolve_organisation(organisation)
|
|
86
|
+
@repository = resolve(repository, 'repository')
|
|
87
|
+
@wiki_url = resolve_wiki_url(wiki_url)
|
|
88
|
+
@owner_base_url = resolve_owner_base_url(owner_base_url)
|
|
89
|
+
@excluded_dirs = excluded_dirs || file.fetch('exclude', DEFAULT_EXCLUDED_DIRS)
|
|
90
|
+
@template_dir = template_dir || file.fetch('template_dir', DEFAULT_TEMPLATE_DIR)
|
|
91
|
+
@labels = deep_merge(DEFAULT_LABELS, file.fetch('labels', EMPTY_HASH))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @rbs return: Array[String]
|
|
95
|
+
def group_bys
|
|
96
|
+
labels.keys
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @rbs group_by: String
|
|
100
|
+
# @rbs return: Array[String]
|
|
101
|
+
def languages(group_by)
|
|
102
|
+
label_for(group_by).fetch('languages', EMPTY_HASH).keys
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @rbs group_by: String
|
|
106
|
+
# @rbs return: Regexp
|
|
107
|
+
def target_regexp(group_by)
|
|
108
|
+
Regexp.new(label_for(group_by).fetch('regexp', ''))
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @rbs group_by: String
|
|
112
|
+
# @rbs language: String
|
|
113
|
+
# @rbs return: String
|
|
114
|
+
def no_declaration(group_by, language)
|
|
115
|
+
language_labels(group_by, language).fetch('no_declaration', '')
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @rbs group_by: String
|
|
119
|
+
# @rbs language: String
|
|
120
|
+
# @rbs return: Array[String]
|
|
121
|
+
def unknown_namespaces(group_by, language)
|
|
122
|
+
language_labels(group_by, language).fetch('unknown_namespaces', [])
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# @rbs group_by: String
|
|
126
|
+
# @rbs language: String
|
|
127
|
+
# @rbs return: String
|
|
128
|
+
def llm_target_namespace(group_by, language)
|
|
129
|
+
language_labels(group_by, language).fetch('llm_target_namespace', '')
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @rbs group_by: String
|
|
133
|
+
# @rbs language: String
|
|
134
|
+
# @rbs return: String
|
|
135
|
+
def path_to_template(group_by, language)
|
|
136
|
+
File.join(template_dir, group_by.downcase, "#{language.downcase}.md")
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
attr_reader :file, :labels
|
|
142
|
+
|
|
143
|
+
# Treats empty strings the same as nil so that callers can pass values
|
|
144
|
+
# straight from optional environment variables or CLI flags.
|
|
145
|
+
# @rbs value: String?
|
|
146
|
+
# @rbs return: String?
|
|
147
|
+
def presence(value)
|
|
148
|
+
value unless value.nil? || value.empty?
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# @rbs explicit: String?
|
|
152
|
+
# @rbs key: String
|
|
153
|
+
# @rbs return: String?
|
|
154
|
+
def resolve(explicit, key)
|
|
155
|
+
presence(explicit) || presence(file[key])
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @rbs explicit: String?
|
|
159
|
+
# @rbs return: String?
|
|
160
|
+
def resolve_organisation(explicit)
|
|
161
|
+
resolve(explicit, 'organisation') || presence(ENV.fetch('ORGANISATION_NAME', nil))
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# @rbs explicit: String?
|
|
165
|
+
# @rbs return: String?
|
|
166
|
+
def resolve_wiki_url(explicit)
|
|
167
|
+
resolve(explicit, 'wiki_url') || default_wiki_url
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# @rbs explicit: String?
|
|
171
|
+
# @rbs return: String?
|
|
172
|
+
def resolve_owner_base_url(explicit)
|
|
173
|
+
resolve(explicit, 'owner_base_url') || default_owner_base_url
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# @rbs path: String
|
|
177
|
+
# @rbs return: Hash[String, untyped]
|
|
178
|
+
def load_file(path)
|
|
179
|
+
return {} unless File.exist?(path)
|
|
180
|
+
|
|
181
|
+
YAML.safe_load_file(path) || {}
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# @rbs group_by: String
|
|
185
|
+
# @rbs return: Hash[String, untyped]
|
|
186
|
+
def label_for(group_by)
|
|
187
|
+
labels.fetch(group_by, EMPTY_HASH)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# @rbs group_by: String
|
|
191
|
+
# @rbs language: String
|
|
192
|
+
# @rbs return: Hash[String, untyped]
|
|
193
|
+
def language_labels(group_by, language)
|
|
194
|
+
label_for(group_by).fetch('languages', EMPTY_HASH).fetch(language, EMPTY_HASH)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# @rbs return: String?
|
|
198
|
+
def default_wiki_url
|
|
199
|
+
"https://github.com/#{organisation}/#{repository}/wiki" if organisation && repository
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# @rbs return: String?
|
|
203
|
+
def default_owner_base_url
|
|
204
|
+
"https://github.com/orgs/#{organisation}/teams/" if organisation
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# @rbs base: Hash[String, untyped]
|
|
208
|
+
# @rbs override: Hash[String, untyped]
|
|
209
|
+
# @rbs return: Hash[String, untyped]
|
|
210
|
+
def deep_merge(base, override)
|
|
211
|
+
base.merge(override) do |_key, old_value, new_value|
|
|
212
|
+
old_value.is_a?(Hash) && new_value.is_a?(Hash) ? deep_merge(old_value, new_value) : new_value
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|