discourse_cli_tool 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/exe/dsc +5 -0
- data/lib/discourse_cli/cli.rb +16 -0
- data/lib/discourse_cli/client_factory.rb +21 -0
- data/lib/discourse_cli/commands/base.rb +66 -0
- data/lib/discourse_cli/commands/categories.rb +61 -0
- data/lib/discourse_cli/commands/config.rb +58 -0
- data/lib/discourse_cli/commands/posts.rb +52 -0
- data/lib/discourse_cli/commands/topics.rb +90 -0
- data/lib/discourse_cli/config.rb +56 -0
- data/lib/discourse_cli/editor.rb +29 -0
- data/lib/discourse_cli/formatter.rb +44 -0
- data/lib/discourse_cli/version.rb +5 -0
- data/lib/discourse_cli.rb +13 -0
- metadata +137 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 688cddf126e1f109caa8824d80fc514566742cff6a5d1a535ccb7a27d8c0bae6
|
|
4
|
+
data.tar.gz: 37e886fb922d7481d80dfe8370419c03f6f45d4a6a6f0eee1086a01c151b7614
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b1f8f285a6d0138b95c991713cca48e47f61a42afb633a6e5fc3d9720674a43d78d61deb3308b9511203f5a458dc5cececffada7d5d9162ef811583da32a9177
|
|
7
|
+
data.tar.gz: bd8734e7f6f47197a8a65b7faa2b47d23dd3bbf6d3b7113702a0e3b1c405f8475968215aa76bee4098449457413c925895a803cd10a9d718ad5e3a2fc303c264
|
data/exe/dsc
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
|
|
5
|
+
module DiscourseCli
|
|
6
|
+
class CLI < Thor
|
|
7
|
+
def self.exit_on_failure?
|
|
8
|
+
true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
register(Commands::Config, "config", "config <command>", "Manage site configuration")
|
|
12
|
+
register(Commands::Categories, "categories", "categories <command>", "Manage categories")
|
|
13
|
+
register(Commands::Topics, "topics", "topics <command>", "Manage topics")
|
|
14
|
+
register(Commands::Posts, "posts", "posts <command>", "Manage posts")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "discourse_api"
|
|
4
|
+
|
|
5
|
+
module DiscourseCli
|
|
6
|
+
class ClientFactory
|
|
7
|
+
def self.build(config)
|
|
8
|
+
missing = []
|
|
9
|
+
missing << "host" unless config.host
|
|
10
|
+
missing << "api_key" unless config.api_key
|
|
11
|
+
missing << "api_username" unless config.api_username
|
|
12
|
+
|
|
13
|
+
if missing.any?
|
|
14
|
+
raise "Missing required config: #{missing.join(", ")}. " \
|
|
15
|
+
"Run `dsc config set` or set #{missing.map { |k| "DISCOURSE_#{k.upcase}" }.join(", ")}."
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
DiscourseApi::Client.new(config.host, config.api_key, config.api_username)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
|
|
5
|
+
module DiscourseCli
|
|
6
|
+
module Commands
|
|
7
|
+
class Base < Thor
|
|
8
|
+
class_option :site, type: :string, desc: "Named site from config file"
|
|
9
|
+
class_option :host, type: :string, desc: "Discourse host URL"
|
|
10
|
+
class_option :"api-key", type: :string, desc: "API key"
|
|
11
|
+
class_option :"api-username", type: :string, desc: "API username"
|
|
12
|
+
class_option :json, type: :boolean, default: false, desc: "Output JSON"
|
|
13
|
+
class_option :quiet, type: :boolean, default: false, desc: "Suppress output, exit code only"
|
|
14
|
+
|
|
15
|
+
def self.exit_on_failure?
|
|
16
|
+
true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def client
|
|
22
|
+
@client ||= ClientFactory.build(config)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def config
|
|
26
|
+
@config ||= DiscourseCli::Config.new(
|
|
27
|
+
site: options[:site],
|
|
28
|
+
host: options[:host],
|
|
29
|
+
api_key: options[:"api-key"],
|
|
30
|
+
api_username: options[:"api-username"],
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def formatter
|
|
35
|
+
@formatter ||= Formatter.new(json: options[:json], quiet: options[:quiet])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def resolve_raw(given_raw)
|
|
39
|
+
return given_raw if given_raw
|
|
40
|
+
return $stdin.read unless $stdin.tty?
|
|
41
|
+
Editor.new.open
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def handle_error(error)
|
|
45
|
+
message =
|
|
46
|
+
case error
|
|
47
|
+
when DiscourseApi::UnauthenticatedError
|
|
48
|
+
"Authentication failed — check your api_key and api_username"
|
|
49
|
+
when DiscourseApi::NotFoundError
|
|
50
|
+
"Not found"
|
|
51
|
+
when DiscourseApi::UnprocessableEntity
|
|
52
|
+
errors = error.response&.dig(:body, "errors")
|
|
53
|
+
errors ? Array(errors).join("\n") : error.message
|
|
54
|
+
when DiscourseApi::TooManyRequests
|
|
55
|
+
"Rate limited — try again shortly"
|
|
56
|
+
when DiscourseApi::Timeout
|
|
57
|
+
"Request timed out"
|
|
58
|
+
else
|
|
59
|
+
error.message
|
|
60
|
+
end
|
|
61
|
+
$stderr.puts message
|
|
62
|
+
exit 1
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DiscourseCli
|
|
4
|
+
module Commands
|
|
5
|
+
class Categories < Base
|
|
6
|
+
desc "list", "List all categories"
|
|
7
|
+
def list
|
|
8
|
+
formatter.print_list(client.categories)
|
|
9
|
+
rescue DiscourseApi::DiscourseError => e
|
|
10
|
+
handle_error(e)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
desc "show ID", "Show a category"
|
|
14
|
+
def show(id)
|
|
15
|
+
formatter.print_item(client.category(id.to_i))
|
|
16
|
+
rescue DiscourseApi::DiscourseError => e
|
|
17
|
+
handle_error(e)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "create", "Create a category"
|
|
21
|
+
option :name, type: :string, required: true
|
|
22
|
+
option :color, type: :string
|
|
23
|
+
option :"text-color", type: :string
|
|
24
|
+
option :description, type: :string
|
|
25
|
+
def create
|
|
26
|
+
params = { name: options[:name] }
|
|
27
|
+
params[:color] = options[:color] if options[:color]
|
|
28
|
+
params[:text_color] = options[:"text-color"] if options[:"text-color"]
|
|
29
|
+
params[:description] = options[:description] if options[:description]
|
|
30
|
+
formatter.print_item(client.create_category(params))
|
|
31
|
+
rescue DiscourseApi::DiscourseError => e
|
|
32
|
+
handle_error(e)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
desc "update ID", "Update a category"
|
|
36
|
+
option :name, type: :string, required: true
|
|
37
|
+
option :color, type: :string
|
|
38
|
+
option :"text-color", type: :string
|
|
39
|
+
option :description, type: :string
|
|
40
|
+
def update(id)
|
|
41
|
+
params = { id: id.to_i, name: options[:name] }
|
|
42
|
+
params[:color] = options[:color] if options[:color]
|
|
43
|
+
params[:text_color] = options[:"text-color"] if options[:"text-color"]
|
|
44
|
+
params[:description] = options[:description] if options[:description]
|
|
45
|
+
result = client.update_category(params)
|
|
46
|
+
formatter.print_item(result) if result
|
|
47
|
+
formatter.print_success("Updated category #{id}")
|
|
48
|
+
rescue DiscourseApi::DiscourseError => e
|
|
49
|
+
handle_error(e)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
desc "delete ID", "Delete a category"
|
|
53
|
+
def delete(id)
|
|
54
|
+
client.delete_category(id.to_i)
|
|
55
|
+
formatter.print_success("Deleted category #{id}")
|
|
56
|
+
rescue DiscourseApi::DiscourseError => e
|
|
57
|
+
handle_error(e)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module DiscourseCli
|
|
7
|
+
module Commands
|
|
8
|
+
class Config < Base
|
|
9
|
+
def self.config_path
|
|
10
|
+
DiscourseCli::Config.config_path
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
desc "set", "Save connection details for a named site"
|
|
14
|
+
option :site, type: :string, required: true, desc: "Site name"
|
|
15
|
+
option :host, type: :string, required: true, desc: "Discourse host URL"
|
|
16
|
+
option :"api-key", type: :string, required: true, desc: "API key"
|
|
17
|
+
option :"api-username", type: :string, required: true, desc: "API username"
|
|
18
|
+
def set
|
|
19
|
+
data = load_file
|
|
20
|
+
data["sites"] ||= {}
|
|
21
|
+
data["default"] ||= options[:site]
|
|
22
|
+
data["sites"][options[:site]] = {
|
|
23
|
+
"host" => options[:host],
|
|
24
|
+
"api_key" => options[:"api-key"],
|
|
25
|
+
"api_username" => options[:"api-username"],
|
|
26
|
+
}
|
|
27
|
+
FileUtils.mkdir_p(File.dirname(self.class.config_path))
|
|
28
|
+
File.write(self.class.config_path, YAML.dump(data))
|
|
29
|
+
formatter.print_success("Saved config for site '#{options[:site]}'")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
desc "list", "List configured sites"
|
|
33
|
+
def list
|
|
34
|
+
data = load_file
|
|
35
|
+
sites = data["sites"] || {}
|
|
36
|
+
default_site = data["default"]
|
|
37
|
+
if sites.empty?
|
|
38
|
+
puts "No sites configured. Run `dsc config set` to add one."
|
|
39
|
+
return
|
|
40
|
+
end
|
|
41
|
+
sites.each do |name, cfg|
|
|
42
|
+
marker = name == default_site ? " (default)" : ""
|
|
43
|
+
puts "#{name}#{marker}"
|
|
44
|
+
puts " host: #{cfg["host"]}"
|
|
45
|
+
puts " api_username: #{cfg["api_username"]}"
|
|
46
|
+
puts
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def load_file
|
|
53
|
+
path = self.class.config_path
|
|
54
|
+
File.exist?(path) ? YAML.safe_load(File.read(path)) || {} : {}
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DiscourseCli
|
|
4
|
+
module Commands
|
|
5
|
+
class Posts < Base
|
|
6
|
+
desc "list", "List recent posts"
|
|
7
|
+
def list
|
|
8
|
+
response = client.posts
|
|
9
|
+
items = response["latest_posts"] || []
|
|
10
|
+
formatter.print_list(items)
|
|
11
|
+
rescue DiscourseApi::DiscourseError => e
|
|
12
|
+
handle_error(e)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
desc "show ID", "Show a post"
|
|
16
|
+
def show(id)
|
|
17
|
+
formatter.print_item(client.get_post(id.to_i))
|
|
18
|
+
rescue DiscourseApi::DiscourseError => e
|
|
19
|
+
handle_error(e)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
desc "create", "Create a post (opens $EDITOR if --raw not given)"
|
|
23
|
+
option :"topic-id", type: :numeric, required: true
|
|
24
|
+
option :raw, type: :string
|
|
25
|
+
def create
|
|
26
|
+
raw = resolve_raw(options[:raw])
|
|
27
|
+
result = client.create_post(topic_id: options[:"topic-id"], raw: raw)
|
|
28
|
+
formatter.print_item(result)
|
|
29
|
+
rescue DiscourseApi::DiscourseError => e
|
|
30
|
+
handle_error(e)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
desc "update ID", "Update a post (opens $EDITOR if --raw not given)"
|
|
34
|
+
option :raw, type: :string
|
|
35
|
+
def update(id)
|
|
36
|
+
raw = resolve_raw(options[:raw])
|
|
37
|
+
client.edit_post(id.to_i, raw)
|
|
38
|
+
formatter.print_success("Updated post #{id}")
|
|
39
|
+
rescue DiscourseApi::DiscourseError => e
|
|
40
|
+
handle_error(e)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
desc "delete ID", "Delete a post"
|
|
44
|
+
def delete(id)
|
|
45
|
+
client.delete_post(id.to_i)
|
|
46
|
+
formatter.print_success("Deleted post #{id}")
|
|
47
|
+
rescue DiscourseApi::DiscourseError => e
|
|
48
|
+
handle_error(e)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DiscourseCli
|
|
4
|
+
module Commands
|
|
5
|
+
class Topics < Base
|
|
6
|
+
desc "list", "List latest topics"
|
|
7
|
+
option :category, type: :string, desc: "Filter by category slug"
|
|
8
|
+
def list
|
|
9
|
+
params = {}
|
|
10
|
+
params[:category] = options[:category] if options[:category]
|
|
11
|
+
formatter.print_list(client.latest_topics(params))
|
|
12
|
+
rescue DiscourseApi::DiscourseError => e
|
|
13
|
+
handle_error(e)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
desc "show ID", "Show a topic"
|
|
17
|
+
def show(id)
|
|
18
|
+
formatter.print_item(client.topic(id.to_i))
|
|
19
|
+
rescue DiscourseApi::DiscourseError => e
|
|
20
|
+
handle_error(e)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
desc "create", "Create a topic (opens $EDITOR if --raw not given)"
|
|
24
|
+
option :title, type: :string, required: true
|
|
25
|
+
option :category, type: :string
|
|
26
|
+
option :tags, type: :string, desc: "Comma-separated tags"
|
|
27
|
+
option :raw, type: :string
|
|
28
|
+
def create
|
|
29
|
+
raw = resolve_raw(options[:raw])
|
|
30
|
+
params = { title: options[:title], raw: raw }
|
|
31
|
+
params[:category] = options[:category] if options[:category]
|
|
32
|
+
params[:tags] = options[:tags].split(",").map(&:strip) if options[:tags]
|
|
33
|
+
formatter.print_item(client.create_topic(params))
|
|
34
|
+
rescue DiscourseApi::DiscourseError => e
|
|
35
|
+
handle_error(e)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
desc "update ID", "Update a topic"
|
|
39
|
+
option :title, type: :string
|
|
40
|
+
option :category, type: :string, desc: "Category slug or name"
|
|
41
|
+
option :raw, type: :string, desc: "Replace first post content (opens $EDITOR if not given and no other options)"
|
|
42
|
+
def update(id)
|
|
43
|
+
updated_anything = false
|
|
44
|
+
|
|
45
|
+
if options[:title]
|
|
46
|
+
client.rename_topic(id.to_i, options[:title])
|
|
47
|
+
updated_anything = true
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if options[:category]
|
|
51
|
+
cats = client.categories
|
|
52
|
+
cat = cats.find { |c| c["slug"] == options[:category] || c["name"] == options[:category] }
|
|
53
|
+
unless cat
|
|
54
|
+
$stderr.puts "Category not found: #{options[:category]}"
|
|
55
|
+
exit 1
|
|
56
|
+
end
|
|
57
|
+
client.recategorize_topic(id.to_i, cat["id"])
|
|
58
|
+
updated_anything = true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
raw =
|
|
62
|
+
if options[:raw]
|
|
63
|
+
options[:raw]
|
|
64
|
+
elsif !updated_anything && !$stdin.tty?
|
|
65
|
+
$stdin.read
|
|
66
|
+
elsif !updated_anything
|
|
67
|
+
Editor.new.open
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
if raw
|
|
71
|
+
topic_data = client.topic(id.to_i)
|
|
72
|
+
first_post_id = topic_data["post_stream"]["posts"][0]["id"]
|
|
73
|
+
client.edit_post(first_post_id, raw)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
formatter.print_success("Updated topic #{id}")
|
|
77
|
+
rescue DiscourseApi::DiscourseError => e
|
|
78
|
+
handle_error(e)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
desc "delete ID", "Delete a topic"
|
|
82
|
+
def delete(id)
|
|
83
|
+
client.delete_topic(id.to_i)
|
|
84
|
+
formatter.print_success("Deleted topic #{id}")
|
|
85
|
+
rescue DiscourseApi::DiscourseError => e
|
|
86
|
+
handle_error(e)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module DiscourseCli
|
|
6
|
+
class Config
|
|
7
|
+
def self.config_path
|
|
8
|
+
File.expand_path("~/.config/dsc/config.yml")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(overrides = {})
|
|
12
|
+
@overrides = overrides
|
|
13
|
+
@config_path_snapshot = self.class.config_path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def host
|
|
17
|
+
@overrides[:host] || ENV["DISCOURSE_HOST"] || site_config["host"]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def api_key
|
|
21
|
+
@overrides[:api_key] || ENV["DISCOURSE_API_KEY"] || site_config["api_key"]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def api_username
|
|
25
|
+
@overrides[:api_username] || ENV["DISCOURSE_API_USERNAME"] || site_config["api_username"]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def sites
|
|
29
|
+
file_config["sites"] || {}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def default_site
|
|
33
|
+
file_config["default"]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def site_name
|
|
39
|
+
@overrides[:site] || ENV["DISCOURSE_SITE"] || file_config["default"]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def site_config
|
|
43
|
+
return {} unless site_name
|
|
44
|
+
(file_config["sites"] || {})[site_name] || {}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def file_config
|
|
48
|
+
@file_config ||=
|
|
49
|
+
if File.exist?(@config_path_snapshot)
|
|
50
|
+
YAML.safe_load(File.read(@config_path_snapshot)) || {}
|
|
51
|
+
else
|
|
52
|
+
{}
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tempfile"
|
|
4
|
+
require "shellwords"
|
|
5
|
+
|
|
6
|
+
module DiscourseCli
|
|
7
|
+
class Editor
|
|
8
|
+
def initialize(spawn: nil)
|
|
9
|
+
@spawn = spawn || method(:default_spawn)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def open(initial_content = "")
|
|
13
|
+
Tempfile.create(["dsc", ".md"]) do |f|
|
|
14
|
+
f.write(initial_content)
|
|
15
|
+
f.flush
|
|
16
|
+
@spawn.call(f.path)
|
|
17
|
+
f.rewind
|
|
18
|
+
f.read
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def default_spawn(path)
|
|
25
|
+
editor = ENV.fetch("EDITOR", "vi")
|
|
26
|
+
system("#{editor} #{Shellwords.escape(path)}")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module DiscourseCli
|
|
6
|
+
class Formatter
|
|
7
|
+
def initialize(output: $stdout, json: false, quiet: false)
|
|
8
|
+
@output = output
|
|
9
|
+
@json = json
|
|
10
|
+
@quiet = quiet
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def print_list(items)
|
|
14
|
+
return if @quiet
|
|
15
|
+
if @json
|
|
16
|
+
@output.puts JSON.pretty_generate(items)
|
|
17
|
+
else
|
|
18
|
+
items.each { |item| @output.puts format_row(item) }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def print_item(item)
|
|
23
|
+
return if @quiet
|
|
24
|
+
if @json
|
|
25
|
+
@output.puts JSON.pretty_generate(item)
|
|
26
|
+
else
|
|
27
|
+
item.each { |k, v| @output.puts "#{k}: #{v}" }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def print_success(message)
|
|
32
|
+
return if @quiet || @json
|
|
33
|
+
@output.puts message
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def format_row(item)
|
|
39
|
+
id = item["id"] || item[:id]
|
|
40
|
+
label = item["title"] || item["name"] || item["slug"] || item.values.compact.first
|
|
41
|
+
"#{id}\t#{label}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "discourse_cli/version"
|
|
4
|
+
require_relative "discourse_cli/config"
|
|
5
|
+
require_relative "discourse_cli/client_factory"
|
|
6
|
+
require_relative "discourse_cli/editor"
|
|
7
|
+
require_relative "discourse_cli/formatter"
|
|
8
|
+
require_relative "discourse_cli/commands/base"
|
|
9
|
+
require_relative "discourse_cli/commands/config"
|
|
10
|
+
require_relative "discourse_cli/commands/categories"
|
|
11
|
+
require_relative "discourse_cli/commands/topics"
|
|
12
|
+
require_relative "discourse_cli/commands/posts"
|
|
13
|
+
require_relative "discourse_cli/cli"
|
metadata
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: discourse_cli_tool
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tim Case
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: base64
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: discourse_api
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.1'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.1'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: thor
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: minitest
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '5.0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '5.0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rake
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '13.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '13.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: webmock
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '3.0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '3.0'
|
|
96
|
+
email:
|
|
97
|
+
- tim@2drops.net
|
|
98
|
+
executables:
|
|
99
|
+
- dsc
|
|
100
|
+
extensions: []
|
|
101
|
+
extra_rdoc_files: []
|
|
102
|
+
files:
|
|
103
|
+
- exe/dsc
|
|
104
|
+
- lib/discourse_cli.rb
|
|
105
|
+
- lib/discourse_cli/cli.rb
|
|
106
|
+
- lib/discourse_cli/client_factory.rb
|
|
107
|
+
- lib/discourse_cli/commands/base.rb
|
|
108
|
+
- lib/discourse_cli/commands/categories.rb
|
|
109
|
+
- lib/discourse_cli/commands/config.rb
|
|
110
|
+
- lib/discourse_cli/commands/posts.rb
|
|
111
|
+
- lib/discourse_cli/commands/topics.rb
|
|
112
|
+
- lib/discourse_cli/config.rb
|
|
113
|
+
- lib/discourse_cli/editor.rb
|
|
114
|
+
- lib/discourse_cli/formatter.rb
|
|
115
|
+
- lib/discourse_cli/version.rb
|
|
116
|
+
homepage: https://github.com/timcase/discourse-cli-tool
|
|
117
|
+
licenses:
|
|
118
|
+
- MIT
|
|
119
|
+
metadata: {}
|
|
120
|
+
rdoc_options: []
|
|
121
|
+
require_paths:
|
|
122
|
+
- lib
|
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
124
|
+
requirements:
|
|
125
|
+
- - ">="
|
|
126
|
+
- !ruby/object:Gem::Version
|
|
127
|
+
version: 2.7.0
|
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
|
+
requirements:
|
|
130
|
+
- - ">="
|
|
131
|
+
- !ruby/object:Gem::Version
|
|
132
|
+
version: '0'
|
|
133
|
+
requirements: []
|
|
134
|
+
rubygems_version: 4.0.10
|
|
135
|
+
specification_version: 4
|
|
136
|
+
summary: CLI tool for managing Discourse forums from the command line
|
|
137
|
+
test_files: []
|