zhuzh 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: a8023b517bd18b09affd30ac54d42b5245021c0cee130f9de6490f758e64b378
4
+ data.tar.gz: 8551adc0c2205d4ff3640afe090239740158fea6c2f5876e38313b78e786d185
5
+ SHA512:
6
+ metadata.gz: 9a7875c2e0855f3e00f6a8a607d9635c8e2342dabeeeb90334b8a91448f193ee3c5ab7d890488ce8339e9c680a3d3d9dad1745bc7601f1b259f1805eeadc96eb
7
+ data.tar.gz: 63ba8328c156ac24b395440e5b603eaf458da623aaca25900f70f86763221727b33dbf0722b62829a659bd054e084a37a3e07db2aa3267115693081b768fc1a1
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # Zhuzh
2
+
3
+ A tool for easily creating and managing markdown notes.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ gem install zhuzh
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Basic Usage
14
+
15
+ Open your editor:
16
+ ```bash
17
+ zz
18
+ ```
19
+
20
+ When you save and close the editor, the note will be automatically saved.
21
+
22
+ ### Add a note directly:
23
+
24
+ ```bash
25
+ zz "This is my super important note I need to write it quick before I forget"
26
+ ```
27
+
28
+ ### Pipe/redirect content to zz:
29
+
30
+ ```bash
31
+ cat blog_post.txt | zz
32
+ ```
33
+
34
+ or
35
+
36
+ ```bash
37
+ zz < blog_post.txt
38
+ ```
39
+
40
+ ### Configuration
41
+
42
+ By default, notes are stored in `$HOME/Zarchive`. You can change this by setting the `ZZ_DIR` environment variable:
43
+
44
+ ```bash
45
+ export ZZ_DIR="$HOME/my_notes"
46
+ ```
47
+
48
+ ### Note Organization
49
+
50
+ Notes are organized based on their content:
51
+ - The first word becomes the subdirectory name
52
+ - Words 2-5 are used in the filename, followed by a timestamp
53
+ - For example, if you start a note with "Project ideas for tomorrow", it will be saved as `$ZZ_DIR/project/ideas_for_tomorrow_TIMESTAMP.md`
54
+
55
+ ## Neovim Integration
56
+
57
+ Add this to your Neovim configuration:
58
+
59
+ ```vim
60
+ " Save current buffer with Zhuzh
61
+ function! SaveToZhuzh()
62
+ let output = system('zz', join(getline(1, '$'), "\n"))
63
+ echo "Saved to: " . trim(output)
64
+ endfunction
65
+ command! Zz call SaveToZhuzh()
66
+ ```
67
+
68
+ Then you can use `:Zz` to save the current buffer as a Zhuzh note.
69
+
70
+ ## Development
71
+
72
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
73
+
74
+ ## License
75
+
76
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/bin/tapioca ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'tapioca' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
12
+
13
+ bundle_binstub = File.expand_path('bundle', __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?('This file was generated by Bundler')
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require 'rubygems'
25
+ require 'bundler/setup'
26
+
27
+ load Gem.bin_path('tapioca', 'tapioca')
data/bin/zz ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'zhuzh'
5
+
6
+ # Handle command line arguments directly
7
+ if ARGV.size.positive?
8
+ # Add explicit newline at the end of command line input
9
+ text = "#{ARGV.join(' ')}\n"
10
+ config = Zhuzh::Config.new
11
+ note = Zhuzh::Note.new(text, config)
12
+
13
+ if note.save
14
+ puts note.filepath
15
+ exit 0
16
+ else
17
+ exit 1
18
+ end
19
+ else
20
+ # No arguments provided, use the CLI handler for stdin/editor
21
+ exit Zhuzh::CLI.start(ARGV) || 0
22
+ end
data/lib/zhuzh/cli.rb ADDED
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ require 'sorbet-runtime'
5
+
6
+ module Zhuzh
7
+ # Command line interface for Zhuzh
8
+ class CLI
9
+ extend T::Sig
10
+
11
+ sig { params(args: T::Array[String]).returns(Integer) }
12
+ def self.start(args)
13
+ new.run(args)
14
+ end
15
+
16
+ sig { params(args: T::Array[String]).returns(Integer) }
17
+ def run(args)
18
+ content = if args.empty?
19
+ if $stdin.tty?
20
+ # No text, open editor
21
+ open_editor
22
+ else
23
+ # Text piped from stdin
24
+ $stdin.read
25
+ end
26
+ else
27
+ # Combine all arguments as note text
28
+ args.join(' ')
29
+ end
30
+
31
+ process_content(content) ? 0 : 1
32
+ end
33
+
34
+ private
35
+
36
+ sig { params(content: T.nilable(String)).returns(T::Boolean) }
37
+ def process_content(content)
38
+ return false if content.nil? || content.strip.empty?
39
+
40
+ config = Config.new
41
+ note = Note.new(content, config)
42
+
43
+ if note.save
44
+ puts note.filepath
45
+ true
46
+ else
47
+ false
48
+ end
49
+ end
50
+
51
+ sig { returns(T.nilable(String)) }
52
+ def open_editor
53
+ editor = ENV['EDITOR'] || 'vim'
54
+ temp_file = "/tmp/zz_#{Time.now.to_i}.md"
55
+
56
+ # Open editor and wait for it to close
57
+ system("#{editor} #{temp_file}")
58
+
59
+ if File.exist?(temp_file)
60
+ content = File.read(temp_file)
61
+ File.unlink(temp_file)
62
+ return content
63
+ end
64
+
65
+ nil
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ require 'sorbet-runtime'
5
+ require 'fileutils'
6
+
7
+ module Zhuzh
8
+ # Manages configuration and directory settings
9
+ class Config
10
+ extend T::Sig
11
+
12
+ sig { returns(String) }
13
+ attr_reader :zz_dir
14
+
15
+ sig { void }
16
+ def initialize
17
+ @zz_dir = T.let(determine_zz_dir, String)
18
+ Zhuzh::Utils.ensure_directory_exists(@zz_dir)
19
+ end
20
+
21
+ private
22
+
23
+ sig { returns(String) }
24
+ def determine_zz_dir
25
+ ENV['ZZ_DIR'] || File.join(Dir.home, 'Zarchive')
26
+ end
27
+ end
28
+ end
data/lib/zhuzh/note.rb ADDED
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ require 'sorbet-runtime'
5
+ require 'fileutils'
6
+ require 'time'
7
+
8
+ module Zhuzh
9
+ # Handles creation and storage of notes
10
+ class Note
11
+ extend T::Sig
12
+
13
+ sig { returns(String) }
14
+ attr_reader :content, :filepath
15
+
16
+ sig { params(content: String, config: Zhuzh::Config).void }
17
+ def initialize(content, config)
18
+ # Add trailing newline if not present for command line input
19
+ @content = T.let(content.end_with?("\n") ? content : "#{content}\n", String)
20
+ @config = T.let(config, Zhuzh::Config)
21
+ @filepath = T.let(generate_filepath, String)
22
+ end
23
+
24
+ sig { returns(T::Boolean) }
25
+ def save
26
+ Zhuzh::Utils.ensure_directory_exists(File.dirname(@filepath))
27
+ File.write(@filepath, @content)
28
+ true
29
+ rescue StandardError => e
30
+ warn "Error saving note: #{e.message}"
31
+ false
32
+ end
33
+
34
+ private
35
+
36
+ sig { returns(String) }
37
+ def generate_filepath
38
+ text = strip_frontmatter(@content)
39
+ words = text.strip.split(/\s+/)
40
+
41
+ dir_name = extract_directory_name(words)
42
+ file_base = extract_file_base(words)
43
+ timestamp = Time.now.strftime('%Y-%m-%d-%H-%M-%S')
44
+ base_path = File.join(@config.zz_dir, dir_name)
45
+
46
+ create_unique_filepath(base_path, file_base, timestamp)
47
+ end
48
+
49
+ sig { params(words: T::Array[String]).returns(String) }
50
+ def extract_directory_name(words)
51
+ words.empty? ? 'blank' : normalize_path_component(T.must(words[0]))
52
+ end
53
+
54
+ sig { params(words: T::Array[String]).returns(String) }
55
+ def extract_file_base(words)
56
+ file_words = words[1..4]&.compact || []
57
+ if file_words.empty?
58
+ 'blank'
59
+ else
60
+ file_words.map { |w| normalize_path_component(w) }.join('_')
61
+ end
62
+ end
63
+
64
+ sig { params(base_path: String, file_base: String, timestamp: String).returns(String) }
65
+ def create_unique_filepath(base_path, file_base, timestamp)
66
+ file_path = File.join(base_path, "#{file_base}_#{timestamp}.md")
67
+
68
+ counter = 1
69
+ while File.exist?(file_path)
70
+ file_path = File.join(base_path, "#{file_base}_#{counter}_#{timestamp}.md")
71
+ counter += 1
72
+ end
73
+
74
+ file_path
75
+ end
76
+
77
+ sig { params(text: String).returns(String) }
78
+ def strip_frontmatter(text)
79
+ if text.start_with?("---\n")
80
+ # Ignore everything between the first and second "---"
81
+ parts = text.split("---\n", 3)
82
+ return parts[2] || '' if parts.size >= 3
83
+ end
84
+ text
85
+ end
86
+
87
+ sig { params(component: String).returns(String) }
88
+ def normalize_path_component(component)
89
+ # Convert to lowercase, remove non-alphanumeric chars, replace spaces with underscore
90
+ normalized = component.downcase.gsub(/[^a-z0-9\s]/, '').gsub(/\s+/, '_')
91
+ # Truncate to avoid filesystem issues
92
+ T.must(normalized[0..63])
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ require 'sorbet-runtime'
5
+
6
+ module Zhuzh
7
+ extend T::Sig
8
+
9
+ VERSION = T.let('0.1.0', String)
10
+ end
data/lib/zhuzh.rb ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ require 'sorbet-runtime'
5
+ require_relative 'zhuzh/version'
6
+ require_relative 'zhuzh/config'
7
+ require_relative 'zhuzh/note'
8
+ require_relative 'zhuzh/cli'
9
+
10
+ module Zhuzh
11
+ class Error < StandardError; end
12
+
13
+ # Common utility methods for Zhuhz
14
+ module Utils
15
+ extend T::Sig
16
+
17
+ # Creates a directory and its parent directories if they don't exist
18
+ sig { params(dir: String).void }
19
+ def self.ensure_directory_exists(dir)
20
+ FileUtils.mkdir_p(dir)
21
+ end
22
+ end
23
+ end
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zhuzh
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Fuzz Leonard
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-04-14 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: sorbet-runtime
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.5'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.5'
26
+ - !ruby/object:Gem::Dependency
27
+ name: minitest
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '5.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rubocop
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.21'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.21'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rubocop-minitest
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.38'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.38'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rubocop-rake
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.7'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.7'
96
+ - !ruby/object:Gem::Dependency
97
+ name: sorbet
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.5'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.5'
110
+ - !ruby/object:Gem::Dependency
111
+ name: tapioca
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.16'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '0.16'
124
+ description: A tool for easily creating and managing markdown notes
125
+ email:
126
+ - ink@fuzz.ink
127
+ executables:
128
+ - zz
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - README.md
133
+ - bin/tapioca
134
+ - bin/zz
135
+ - lib/zhuzh.rb
136
+ - lib/zhuzh/cli.rb
137
+ - lib/zhuzh/config.rb
138
+ - lib/zhuzh/note.rb
139
+ - lib/zhuzh/version.rb
140
+ homepage: https://github.com/fuzz/zhuhz
141
+ licenses:
142
+ - MIT
143
+ metadata:
144
+ rubygems_mfa_required: 'true'
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 2.7.0
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubygems_version: 3.6.3
160
+ specification_version: 4
161
+ summary: Simple markdown note management
162
+ test_files: []