blog-tools 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: 6162eaac9b16ffe613140a0c3153a05d195f7e9fea588437d460c7dfa8cbc30e
4
+ data.tar.gz: 354be29df9a617cb1c1e4cf4ef0428d5085048c25d977dd310e0f560d132e6cc
5
+ SHA512:
6
+ metadata.gz: a8c2914c2d4328faf4bea4234a47c7257170a90a21dd85b788a638068c3152546b5b62d24397d046effa0d7cee5f346f8b8922f53025aefd9b9e131a7bae8a77
7
+ data.tar.gz: 5b891802c32e5ec9ced9e069fd5f81b6f3f37a4b3bd13f0c6374845fec440dabdfa095b046d806705772a5923e8296ea7a298e83afe755d6106c40184300a32a
data/bin/blog-tools ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/blog_tools'
5
+
6
+ BlogTools::CLI.start(ARGV)
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ require_relative 'commands/generate'
6
+ require_relative 'commands/lists'
7
+ require_relative 'commands/config'
8
+
9
+ require_relative 'storage'
10
+
11
+ # BlogTools is the main namespace for the blog-tools CLI application
12
+ # It contains submodules for command logic, file storage, and CLI Interfaces
13
+ module BlogTools
14
+ # CLI handles all top-level commands for the blog-tools application
15
+ # It delegates to subcommands
16
+ class CLI < Thor
17
+ def initialize(*args)
18
+ super
19
+ Storage.setup!
20
+ end
21
+
22
+ desc 'generate', 'Create a new post'
23
+ subcommand 'generate', Commands::Generate
24
+
25
+ desc 'lists', 'Lists tools'
26
+ subcommand 'lists', Commands::Lists
27
+
28
+ desc 'config', 'Configure blog-tools'
29
+ subcommand 'config', Commands::Config
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BlogTools
4
+ module Commands
5
+ # Config deals with all commands that configure how blog-tools works
6
+ class Config < Thor
7
+ desc 'TITLE', 'Configure blog-tools'
8
+
9
+ def default(test)
10
+ puts test
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
4
+ require 'date'
5
+ require_relative '../storage'
6
+
7
+ module BlogTools
8
+ module Commands
9
+ # Generate handles all of the subcommands related to generating posts
10
+ class Generate < Thor
11
+ desc 'post TITLE', 'Generate a new blog post'
12
+ method_option :template, type: :string, desc: 'Specify a template file'
13
+ method_option :tags, type: :array, desc: 'Specify tags (space separated)'
14
+ method_option :author, type: :string, desc: 'Specify author'
15
+ method_option :output, type: :string, desc: 'Output path or filename (default: title.md)'
16
+ method_option :content, type: :string, desc: 'Path to content of post'
17
+ def post(title)
18
+ config = File.exist?(Storage::CONFIG_FILE) ? YAML.load_file(Storage::CONFIG_FILE) : {}
19
+
20
+ template_file = options[:template] || config['default_template'] || 'post.md'
21
+ template_path = File.expand_path(File.join(Storage::TEMPLATES_DIR + template_file))
22
+
23
+ return puts("[!] Template file not found: #{template_path}") unless File.exist?(template_path)
24
+
25
+ template = File.read(template_path)
26
+ renderer = ERB.new(template)
27
+
28
+ result = renderer.result_with_hash(
29
+ title: title,
30
+ date: Date.today.to_s,
31
+ author: options[:author] || config['author'] || ENV['USER'] || 'unknown',
32
+ tags: options[:tags] || config['tags'] || [],
33
+ content: options[:content] ? File.read(File.expand_path(options[:content])) : ''
34
+ )
35
+
36
+ output_filename = options[:output] || "#{title.downcase.strip.gsub(/\s+/, '_')}.md"
37
+ File.write(output_filename, result)
38
+
39
+ puts "[✓] Post generated at #{output_filename}"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../storage'
4
+
5
+ module BlogTools
6
+ module Commands
7
+ # List handles all of the subcommands dealing with makes, editing, and
8
+ # creating lists of blog post ideas
9
+ #
10
+ # TODO option to add file path to the post
11
+ class Lists < Thor
12
+ def initialize(*args)
13
+ super
14
+ @lists = Storage.read_lists || {}
15
+ end
16
+
17
+ desc 'list LIST', 'View all posts in a list'
18
+ method_option :completed, type: :boolean, default: false, desc: 'Show only completed posts'
19
+ method_option :in_progress, type: :boolean, default: false, desc: 'show only in-progress posts'
20
+ method_option :status, type: :boolean, default: false, desc: 'Show post status as well'
21
+ def list(list)
22
+ return puts('[!] List not found') unless @lists[list]
23
+
24
+ puts list.upcase
25
+ @lists[list][:posts].each do |item, data|
26
+ next if options[:completed] && !data[:completed]
27
+ next if options[:in_progress] && !data[:in_progress]
28
+
29
+ if options[:status]
30
+ status =
31
+ if data[:completed]
32
+ '[✓]'
33
+ elsif data[:in_progress]
34
+ '[~]'
35
+ else
36
+ '[ ]'
37
+ end
38
+ puts "- #{status} #{item}"
39
+ else
40
+ puts "- #{item}"
41
+ end
42
+ end
43
+ end
44
+
45
+ desc 'create NAME', 'Create a list'
46
+ def create(name)
47
+ @lists[name] = { posts: {} }
48
+ Storage.write_lists(@lists)
49
+ puts "[✓] Created list: #{name}"
50
+ end
51
+
52
+ desc 'add LIST POST', 'Add a post to a list'
53
+ def add(list, post_name)
54
+ return puts('[!] List not found') unless @lists[list]
55
+
56
+ @lists[list][:posts][post_name] = { completed: false, in_progress: false }
57
+ puts "[✓] Added #{post_name} to #{list} list"
58
+ Storage.write_lists(@lists)
59
+ end
60
+
61
+ desc 'delete LIST', 'Delete a list'
62
+ def delete(list)
63
+ return puts('[!] List not found') unless @lists[list]
64
+
65
+ puts "[?] Are you sure you want to delete the '#{list}' list?"
66
+ print "[?] This action cannot be undone. Proceed? (y/N)\n> "
67
+ input = $stdin.gets.chomp.strip
68
+
69
+ case input.downcase
70
+ when 'y'
71
+ @lists.delete(list)
72
+ Storage.write_lists(@lists)
73
+ puts "[✓] Deleted '#{list}' list"
74
+ when 'n', ''
75
+ puts '[i] Cancelled.'
76
+ else
77
+ puts '[!] Invalid input. Not deleting.'
78
+ end
79
+ end
80
+
81
+ desc 'remove LIST POST', 'Remove a post from a list'
82
+ def remove(list, post_name)
83
+ return puts('[!] List not found') unless @lists[list]
84
+
85
+ puts "[?] Are you sure you want to delete the post '#{post_name}'?"
86
+ print "[?] This action cannot be undone. Proceed? (y/N)\n> "
87
+ input = $stdin.gets.chomp.strip
88
+
89
+ case input.downcase
90
+ when 'y'
91
+ @lists[list][:posts].delete(post_name)
92
+ Storage.write_lists(@lists)
93
+ puts "[✓] Deleted '#{post_name}' post"
94
+ when 'n', ''
95
+ puts '[i] Cancelled.'
96
+ else
97
+ puts '[!] Invalid input. Not deleting.'
98
+ end
99
+ end
100
+
101
+ desc 'update LIST POST', 'Update the status of a blog post idea (use --complete or --in_progress)'
102
+ method_option :complete, type: :boolean, default: false, desc: 'Mark as complete'
103
+ method_option :in_progress, type: :boolean, default: false, desc: 'Mark as in progress'
104
+ def update(list, post_name)
105
+ return puts('[!] List not found') unless @lists[list]
106
+ return puts('[!] Post not found') unless @lists[list][:posts].key?(post_name)
107
+
108
+ post = @lists[list][:posts][post_name]
109
+
110
+ if options[:complete]
111
+ if post[:completed]
112
+ puts('[!] Post already marked as complete')
113
+ else
114
+ post[:completed] = true
115
+ puts("[✓] Marked '#{post_name}' as complete")
116
+ end
117
+ end
118
+
119
+ if options[:in_progress]
120
+ if post[:in_progress]
121
+ puts('[!] Post already marked as in progress')
122
+ else
123
+ post[:in_progress] = true
124
+ puts("[✓] Marked '#{post_name}' as in progress")
125
+ end
126
+ end
127
+
128
+ Storage.write_lists(@lists)
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'yaml'
5
+
6
+ module BlogTools
7
+ # Handles paths and file initialization for blog-tools
8
+ # configuration and templates.
9
+ module Storage
10
+ CONFIG_DIR = ENV['BLOG_TOOLS_DIR'] || File.join(Dir.home, '.config', 'blog-tools')
11
+ LISTS_FILE = File.join(CONFIG_DIR, 'lists.yml')
12
+ CONFIG_FILE = File.join(CONFIG_DIR, 'config.yml')
13
+ TEMPLATES_DIR = File.join(CONFIG_DIR, 'templates/')
14
+ DEFAULT_TEMPLATE_FILE = File.join(TEMPLATES_DIR, 'post.md')
15
+
16
+ # Ensure required directories and files exist.
17
+ def self.setup!
18
+ create_directories
19
+ create_lists_file
20
+ create_config_file
21
+ create_default_template
22
+ end
23
+
24
+ # Path to the config file
25
+ def self.config_path
26
+ CONFIG_FILE
27
+ end
28
+
29
+ def self.create_directories
30
+ FileUtils.mkdir_p(CONFIG_DIR)
31
+ FileUtils.mkdir_p(TEMPLATES_DIR)
32
+ end
33
+
34
+ def self.create_lists_file
35
+ FileUtils.touch(LISTS_FILE) unless File.exist?(LISTS_FILE)
36
+ end
37
+
38
+ def self.write_lists(data)
39
+ File.write(LISTS_FILE, data.to_yaml)
40
+ end
41
+
42
+ def self.read_lists
43
+ return {} unless File.exist?(LISTS_FILE)
44
+
45
+ data = YAML.safe_load_file(LISTS_FILE, permitted_classes: [Symbol, Date, Time], aliases: true)
46
+ data.is_a?(Hash) ? data : {}
47
+ rescue Psych::SyntaxError => e
48
+ warn "[!] Failed to parse lists.yml: #{e.message}"
49
+ {}
50
+ end
51
+
52
+ def self.create_config_file
53
+ return if File.exist?(CONFIG_FILE)
54
+
55
+ puts '[!] No configuration file found, generating now...'
56
+
57
+ default_config = {
58
+ 'author' => ENV['USER'] || 'changeme',
59
+ 'default_template' => 'post.md',
60
+ 'tags' => ['blog']
61
+ }
62
+
63
+ File.write(CONFIG_FILE, default_config.to_yaml)
64
+ end
65
+
66
+ def self.create_default_template
67
+ return unless Dir.empty?(TEMPLATES_DIR)
68
+
69
+ default_template = <<~MARKDOWN
70
+ ---
71
+ title: "<%= title %>"
72
+ date: <%= date %>
73
+ author: <%= author %>
74
+ tags: [<%= tags.map { |t| ""#{t}"" }.join(", ") %>]
75
+ ---
76
+
77
+ # <%= title %>
78
+
79
+ <%= content %>
80
+ MARKDOWN
81
+
82
+ File.write(DEFAULT_TEMPLATE_FILE, default_template)
83
+ puts "[+] Created default template: #{DEFAULT_TEMPLATE_FILE}"
84
+ end
85
+ end
86
+ end
data/lib/blog_tools.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'blog-tools/cli'
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blog-tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Slavetomints
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: thor
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
+ email:
27
+ - slavetomints@protonmail.com
28
+ executables:
29
+ - blog-tools
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - bin/blog-tools
34
+ - lib/blog-tools/cli.rb
35
+ - lib/blog-tools/commands/config.rb
36
+ - lib/blog-tools/commands/generate.rb
37
+ - lib/blog-tools/commands/lists.rb
38
+ - lib/blog-tools/storage.rb
39
+ - lib/blog_tools.rb
40
+ licenses: []
41
+ metadata:
42
+ rubygems_mfa_required: 'true'
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 3.4.0
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubygems_version: 3.6.7
58
+ specification_version: 4
59
+ summary: CLI tools for managing blog posts
60
+ test_files: []