vita 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3d91b8ed0e012e5d41566ed51182c9c0626be2129e0ee942276f6ca845b43035
4
+ data.tar.gz: 5e28af902f3f09d9d4096f21401c45c3d39a20120e82f04f1ea4df8af15abb23
5
+ SHA512:
6
+ metadata.gz: d8bfef7a32f9ebfd417ca52f14838fe3edc9dcdb506a276b6d8476e3967840d15f1f6b95e9749026a420c5b97c17e16e07d34f445b5720b2a3936455dd88c714
7
+ data.tar.gz: cae831f65056900dcb9c439916b23fb09d9d48c9977bdad674099398d4fe558e8ffbb337057070970acd9b21f660eb526e18bafd1a34f8eff28f817c0b4d28bd
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Alec Cursley
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,68 @@
1
+ # Vita 🌱
2
+
3
+ Vita is a tool for [digital gardening](https://maggieappleton.com/garden-history): creating, tending and publishing a collection of notes and ideas.
4
+
5
+ Vita takes a folder of plain text files and links them together to form a ~~graph~~ garden of knowledge. Vita takes influence from note-taking software like [Obsidian](https://obsidian.md/) and [Roam Research](https://roamresearch.com/).
6
+
7
+ An example note shown using Vita:
8
+
9
+ <img src="doc/notes.png" style="width: 100%" alt="Screenshot of a note titled &quot;Cohesion&quot;">
10
+
11
+ ## Installation
12
+
13
+ Vita requires [Ruby 3.3.0](https://www.ruby-lang.org/en/news/2023/12/25/ruby-3-3-0-released/).
14
+
15
+ To install:
16
+
17
+ ```
18
+ $ gem install vita
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ In a folder containing text files, run `vita` to view your notes in a web browser:
24
+
25
+ ```
26
+ $ vita
27
+ _ _
28
+ __ _(_) |_ __ _
29
+ \ \ / / | __/ _` |
30
+ \ V /| | || (_| |
31
+ \_/ |_|\__\__,_|
32
+
33
+ Starting Vita at http://localhost:9000
34
+ ```
35
+
36
+ To publish:
37
+
38
+ ```
39
+ $ vita publish
40
+ ```
41
+
42
+ Vita creates a `publish` folder containing HTML files for publishing to a web server.
43
+
44
+ ## Links between notes
45
+
46
+ Vita looks for connections between notes.
47
+
48
+ When a note's content includes the title of another note, Vita creates a link between the two notes.
49
+
50
+ Links are clickable. Wherever a note's title appears in another note's content, clicking on it navigates to that note:
51
+
52
+ <img src="doc/outlinks.png" alt="Screenshot of a note in Vita, showing hyperlinks from a note's content to other notes">
53
+
54
+ Each note lists the other notes that link to it, along with an excerpt from the source note. Clicking on any of these navigates to the source note:
55
+
56
+ <img src="doc/backlinks.png" alt="Screenshot of the &quot;links to this note&quot; panel in Vita, showing notes that reference the currently-displayed note">
57
+
58
+ ## Home note
59
+
60
+ If a note titled Home exists, it is treated as a home page and shown when you open your notes. Other notes cannot link to the Home note.
61
+
62
+ ## File format
63
+
64
+ Vita uses plain text files. You can create them using any text editor and save them with any file extension. You can optionally use Markdown or [Org-mode](https://orgmode.org/) syntax (`.org` files only) to add formatting to your notes.
65
+
66
+ ## Name
67
+
68
+ Vita is named for Vita Sackville-West, a twentieth-century author and garden designer and creator of [Sissinghurst Castle Garden](https://www.nationaltrust.org.uk/visit/kent/sissinghurst-castle-garden), a favourite place of mine.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require "standard/rake"
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task default: [:spec, :standard]
data/bin/vita ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/vita"
4
+ require "vita/main"
5
+
6
+ begin
7
+ garden = Vita::Garden.read(Dir.pwd)
8
+ main = Vita::Main.new(garden, $stdout)
9
+ status = main.call(*ARGV)
10
+ main.join
11
+ exit status
12
+ rescue => e
13
+ puts e.message
14
+ exit 1
15
+ end
data/doc/backlinks.png ADDED
Binary file
data/doc/notes.png ADDED
Binary file
data/doc/outlinks.png ADDED
Binary file
data/lib/vita/error.rb ADDED
@@ -0,0 +1,4 @@
1
+ module Vita
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,21 @@
1
+ require "singleton"
2
+ require "redcarpet"
3
+
4
+ module Vita
5
+ module Format
6
+ class Markdown
7
+ include Singleton
8
+ def initialize
9
+ @markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML)
10
+ end
11
+
12
+ def name
13
+ "markdown"
14
+ end
15
+
16
+ def to_html(source)
17
+ @markdown.render(source)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ require "singleton"
2
+ require "org-ruby"
3
+
4
+ module Vita
5
+ module Format
6
+ class Org
7
+ include Singleton
8
+
9
+ def name
10
+ "org"
11
+ end
12
+
13
+ def to_html(source)
14
+ Orgmode::Parser.new(source).to_html
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ require "vita/format/markdown"
2
+ require "vita/format/org"
3
+
4
+ module Vita
5
+ module Format
6
+ def self.for_filename(filename)
7
+ case File.extname(filename.downcase)
8
+ when ".org"
9
+ Format::Org.instance
10
+ else
11
+ Format::Markdown.instance
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,71 @@
1
+ require "vita/note"
2
+ require "vita/garden_note"
3
+ require "vita/error"
4
+
5
+ module Vita
6
+ class Garden
7
+ attr_reader :root, :title, :notes
8
+
9
+ def self.read(root)
10
+ new(root, Garden.get_notes(root))
11
+ end
12
+
13
+ def self.get_notes(root)
14
+ unless File.directory? root
15
+ raise Vita::Error.new("Directory not found at #{root}")
16
+ end
17
+
18
+ Dir[File.join(root, "*.*")].map { |filename| Note.new(filename) }
19
+ end
20
+
21
+ def initialize(root, notes)
22
+ @root = File.expand_path(root)
23
+ @title = File.basename(root)
24
+ self.notes = notes
25
+ end
26
+
27
+ def update!
28
+ self.notes = Garden.get_notes(root)
29
+ end
30
+
31
+ def empty?
32
+ @notes.empty?
33
+ end
34
+
35
+ def [](title)
36
+ @notes_hash[title.downcase]
37
+ end
38
+
39
+ def note_at_path(path)
40
+ @notes.find { |note| note.path == path }
41
+ end
42
+
43
+ def linkable_notes
44
+ @notes - [home_note]
45
+ end
46
+
47
+ def home_note
48
+ self["home"]
49
+ end
50
+
51
+ def links
52
+ notes.flat_map(&:outlinks)
53
+ end
54
+
55
+ def links_to(note)
56
+ links.filter { |link| link.to_note == note }
57
+ end
58
+
59
+ private
60
+
61
+ def notes=(notes)
62
+ @notes = notes.map { |note| GardenNote.new(self, note) }.sort_by(&:title)
63
+ @notes_hash = @notes.map { |note| [note.title.downcase, note] }.to_h
64
+
65
+ if home_note
66
+ @notes.delete(home_note)
67
+ @notes.insert(0, home_note)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,79 @@
1
+ require "vita/link"
2
+ require "vita/note_scanner"
3
+
4
+ module Vita
5
+ class GardenNote
6
+ EXCERPT_LENGTH = 160
7
+
8
+ attr_reader :garden
9
+
10
+ def initialize(garden, note)
11
+ @garden = garden
12
+ @note = note
13
+ end
14
+
15
+ def title
16
+ @note.title
17
+ end
18
+
19
+ def title_regexp
20
+ /\b#{Regexp.quote(title)}\b/i
21
+ end
22
+
23
+ def path
24
+ if home?
25
+ "index.html"
26
+ else
27
+ @note.title.downcase.gsub(/\s+/, "-") + ".html"
28
+ end
29
+ end
30
+
31
+ def content
32
+ @note.content
33
+ end
34
+
35
+ def html
36
+ Format.for_filename(@note.filename).to_html(content)
37
+ end
38
+
39
+ def excerpt
40
+ excerpt = content[0, EXCERPT_LENGTH]
41
+ excerpt += "…" if excerpt.length < content.length
42
+ excerpt
43
+ end
44
+
45
+ def outlinks
46
+ @outlinks ||= create_outlinks
47
+ end
48
+
49
+ def backlinks
50
+ garden.links_to(self)
51
+ end
52
+
53
+ def links
54
+ outlinks + backlinks
55
+ end
56
+
57
+ def home?
58
+ garden.home_note == self
59
+ end
60
+
61
+ private
62
+
63
+ def create_outlinks
64
+ notes = garden.linkable_notes - [self]
65
+ regexp = Regexp.union(notes.map(&:title_regexp))
66
+ matches = NoteScanner.new(content).scan(regexp)
67
+
68
+ matches.map do |match|
69
+ Link.new(
70
+ from_note: self,
71
+ to_note: garden[match.text],
72
+ text: match.text,
73
+ context: match.context,
74
+ context_position: match.context_position
75
+ )
76
+ end
77
+ end
78
+ end
79
+ end
data/lib/vita/html.rb ADDED
@@ -0,0 +1,17 @@
1
+ require "cgi"
2
+
3
+ module Vita
4
+ module Html
5
+ def link_to(text, href)
6
+ "<a href=\"#{h(href)}\">#{h(text)}</a>"
7
+ end
8
+
9
+ def h(text)
10
+ if text
11
+ CGI.escape_html(text.to_s)
12
+ else
13
+ ""
14
+ end
15
+ end
16
+ end
17
+ end
data/lib/vita/link.rb ADDED
@@ -0,0 +1,21 @@
1
+ module Vita
2
+ class Link < Struct.new(:from_note, :to_note, :text, :context, :context_position)
3
+ def highlights
4
+ [
5
+ Highlight.new(context[0, context_position], false),
6
+ Highlight.new(context[context_position, text.length], true),
7
+ Highlight.new(context[(context_position + text.length)..], false)
8
+ ].reject(&:empty?)
9
+ end
10
+ end
11
+
12
+ class Highlight < Struct.new(:text, :match?)
13
+ def to_s
14
+ text
15
+ end
16
+
17
+ def empty?
18
+ text.empty?
19
+ end
20
+ end
21
+ end
data/lib/vita/main.rb ADDED
@@ -0,0 +1,86 @@
1
+ require "launchy"
2
+ require "fileutils"
3
+ require "vita/server"
4
+
5
+ module Vita
6
+ class Main
7
+ attr_reader :garden
8
+
9
+ def initialize(garden, stdout)
10
+ @garden = garden
11
+ @stdout = stdout
12
+ end
13
+
14
+ def call(*args)
15
+ if garden.empty?
16
+ puts "Your garden is empty. Create a text file in this directory first."
17
+ return 1
18
+ end
19
+
20
+ case args.first
21
+ when "open", nil
22
+ start_server
23
+ open_browser
24
+ 0
25
+ when "serve"
26
+ start_server
27
+ 0
28
+ when "publish"
29
+ publish
30
+ 0
31
+ else
32
+ puts "Usage: vita [open|serve|publish]"
33
+ 1
34
+ end
35
+ end
36
+
37
+ def join
38
+ @server_thread&.join
39
+ end
40
+
41
+ def stop_server
42
+ @server_thread&.kill
43
+ end
44
+
45
+ private
46
+
47
+ def publish
48
+ renderer = Renderer.new
49
+
50
+ FileUtils.remove_dir(publish_directory, force: true)
51
+ Dir.mkdir(publish_directory)
52
+ garden.notes.each do |note|
53
+ File.write(
54
+ File.join(publish_directory, note.path),
55
+ renderer.render_html(note)
56
+ )
57
+ puts "#{note.title} (#{note.path})"
58
+ end
59
+ end
60
+
61
+ def start_server
62
+ puts Vita::BANNER
63
+ puts "Starting Vita #{Vita::VERSION} at #{server_url}"
64
+ Server.set(:garden, garden)
65
+ @server_thread = Thread.new do
66
+ Server.run!
67
+ end
68
+ end
69
+
70
+ def open_browser
71
+ Launchy.open(server_url)
72
+ end
73
+
74
+ def server_url
75
+ "http://localhost:9000"
76
+ end
77
+
78
+ def publish_directory
79
+ File.join(garden.root, "publish")
80
+ end
81
+
82
+ def puts(s)
83
+ @stdout.puts(s)
84
+ end
85
+ end
86
+ end
data/lib/vita/note.rb ADDED
@@ -0,0 +1,17 @@
1
+ module Vita
2
+ class Note
3
+ attr_reader :filename
4
+
5
+ def initialize(filename)
6
+ @filename = filename
7
+ end
8
+
9
+ def title
10
+ File.basename(@filename, File.extname(@filename))
11
+ end
12
+
13
+ def content
14
+ @content ||= File.read(@filename)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,42 @@
1
+ module Vita
2
+ class NoteScanner
3
+ CONTEXT_BOUNDARY = /(?<=\A|[.!?])|\n\n|\z/
4
+ NOT_WHITESPACE = /\S/
5
+
6
+ Match = Struct.new(:position, :text, :context, :context_position)
7
+
8
+ attr_reader :string
9
+
10
+ def initialize(string)
11
+ @string = string
12
+ end
13
+
14
+ def scan(regexp)
15
+ matches = string.to_enum(:scan, regexp).map { Regexp.last_match }
16
+
17
+ matches.map do |match|
18
+ link_text = match[0]
19
+ match_start, match_end = match.offset(0)
20
+
21
+ prev_context_boundary = string.rindex(CONTEXT_BOUNDARY, match_start)
22
+ next_context_boundary = string.index(CONTEXT_BOUNDARY, match_end)
23
+
24
+ context_start = string.index(NOT_WHITESPACE, prev_context_boundary)
25
+ context_end = string.rindex(NOT_WHITESPACE, next_context_boundary)
26
+
27
+ context_around_match = string[context_start..context_end]
28
+
29
+ Match.new(
30
+ position: match_start,
31
+ text: link_text,
32
+ context: flatten_newlines(context_around_match),
33
+ context_position: match_start - context_start
34
+ )
35
+ end
36
+ end
37
+
38
+ def flatten_newlines(string)
39
+ string.tr("\n", " ")
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,21 @@
1
+ require "erb"
2
+ require "vita/rendering_context"
3
+
4
+ module Vita
5
+ class Renderer
6
+ def render_html(note)
7
+ context = RenderingContext.new(note)
8
+ template.result(context.get_binding)
9
+ end
10
+
11
+ private
12
+
13
+ def template
14
+ @template ||= ERB.new(File.read(template_path))
15
+ end
16
+
17
+ def template_path
18
+ File.join(Vita::BASE_DIRECTORY, "templates", "note.html.erb")
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ require "vita/html"
2
+
3
+ module Vita
4
+ class RenderingContext
5
+ include Html
6
+
7
+ attr_reader :note
8
+
9
+ def initialize(note)
10
+ @note = note
11
+ end
12
+
13
+ def garden
14
+ note.garden
15
+ end
16
+
17
+ def content_html
18
+ note.outlinks.reduce(note.html) { |html, link|
19
+ html.sub(
20
+ /(?<=\s)#{Regexp.quote(link.text)}\b/,
21
+ link_to(link.text, link.to_note.path)
22
+ )
23
+ }
24
+ end
25
+
26
+ def link_to_note(note)
27
+ link_to note.title, note.path
28
+ end
29
+
30
+ def get_binding
31
+ binding
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,44 @@
1
+ require "sinatra"
2
+
3
+ module Vita
4
+ class Server < Sinatra::Base
5
+ configure do
6
+ enable :quiet
7
+ set :port, 9000
8
+ end
9
+
10
+ before do
11
+ garden.update!
12
+ end
13
+
14
+ get "/" do
15
+ note = garden.notes.first
16
+
17
+ if note
18
+ redirect to note.path
19
+ else
20
+ status 404
21
+ end
22
+ end
23
+
24
+ get "/:path.html" do |path|
25
+ note = garden.note_at_path("#{path}.html")
26
+
27
+ if note
28
+ renderer.render_html(note)
29
+ else
30
+ status 404
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def garden
37
+ settings.garden
38
+ end
39
+
40
+ def renderer
41
+ @renderer ||= Vita::Renderer.new
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module Vita
2
+ VERSION = "0.1.0"
3
+ end
data/lib/vita.rb ADDED
@@ -0,0 +1,23 @@
1
+ $LOAD_PATH.unshift(File.expand_path(__dir__))
2
+
3
+ require "vita/format"
4
+ require "vita/garden"
5
+ require "vita/garden_note"
6
+ require "vita/html"
7
+ require "vita/link"
8
+ require "vita/note"
9
+ require "vita/renderer"
10
+ require "vita/rendering_context"
11
+ require "vita/version"
12
+
13
+ module Vita
14
+ BASE_DIRECTORY = File.expand_path("..", __dir__)
15
+ BANNER = <<~'BANNER'
16
+ _ _
17
+ __ _(_) |_ __ _
18
+ \ \ / / | __/ _` |
19
+ \ V /| | || (_| |
20
+ \_/ |_|\__\__,_|
21
+
22
+ BANNER
23
+ end
@@ -0,0 +1,79 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>
5
+ <%= h note.title %> &ndash; <%= h garden.title %>
6
+ </title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
8
+ <style>
9
+ .line-clamp-2 {
10
+ overflow: hidden;
11
+ display: -webkit-box;
12
+ -webkit-box-orient: vertical;
13
+ -webkit-line-clamp: 2;
14
+ }
15
+ </style>
16
+ <script defer src="https://unpkg.com/htmx.org@1.9.10"></script>
17
+ </head>
18
+ <body>
19
+ <nav class="navbar bg-body-tertiary mb-3">
20
+ <div class="container">
21
+ <a class="navbar-brand" href="/">
22
+ <%= h garden.title %>
23
+ </a>
24
+ </div>
25
+ </nav>
26
+
27
+ <div class="container mb-5">
28
+ <div class="row row-gap-3">
29
+ <div class="col">
30
+ <article class="mb-4">
31
+ <h1 class="mb-3"><%= h note.title %></h1>
32
+
33
+ <%= content_html %>
34
+ </article>
35
+
36
+ <% if note.backlinks.any? %>
37
+ <aside class="card">
38
+ <div class="card-header">
39
+ <% if note.backlinks.one? %>
40
+ 1 link
41
+ <% else %>
42
+ <%= note.backlinks.size %> links
43
+ <% end %>
44
+ to this note
45
+ </div>
46
+ <div role="list" class="list-group list-group-flush">
47
+ <% note.backlinks.each do |link| %>
48
+ <a role="listitem" class="list-group-item list-group-item-action" href="<%= h link.from_note.path %>">
49
+ <h3 class="h6 mb-1 text-truncate"><%= link.from_note.title %></h3>
50
+ <div class="text-body-secondary line-clamp-2">
51
+ <% link.highlights.each do |text| %><% if text.match? %><mark><%= h text %></mark><% else %><%= h text %><% end %><% end %>
52
+ </div>
53
+ </a>
54
+ <% end %>
55
+ </div>
56
+ </aside>
57
+ <% end %>
58
+ </div>
59
+
60
+ <div class="col-md-3">
61
+ <nav>
62
+ <div role="list" class="list-group">
63
+ <% garden.notes.each do |garden_note| %>
64
+ <a role="listitem" class="list-group-item list-group-item-action<% if garden_note == note %> active<% end %> text-truncate" href="<%= h garden_note.path %>">
65
+ <%= garden_note.title %>
66
+ </a>
67
+ <% end %>
68
+ </div>
69
+ </nav>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ <script defer>
74
+ if (location.protocol !== "file:") {
75
+ document.body.setAttribute('hx-boost', true);
76
+ }
77
+ </script>
78
+ </body>
79
+ </html>
metadata ADDED
@@ -0,0 +1,239 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vita
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alec Cursley
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-02-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redcarpet
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 3.6.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 3.6.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: org-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.12
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.9.12
41
+ - !ruby/object:Gem::Dependency
42
+ name: launchy
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.5'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '4.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rackup
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.1'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: puma
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '6.4'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '6.4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '13.1'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '13.1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.13'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.13'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec-collection_matchers
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.2'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.2'
139
+ - !ruby/object:Gem::Dependency
140
+ name: simplecov
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 0.22.0
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 0.22.0
153
+ - !ruby/object:Gem::Dependency
154
+ name: rack-test
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '2.1'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '2.1'
167
+ - !ruby/object:Gem::Dependency
168
+ name: standard
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: 1.34.0
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: 1.34.0
181
+ description:
182
+ email:
183
+ - alec@cursley.net
184
+ executables:
185
+ - vita
186
+ extensions: []
187
+ extra_rdoc_files: []
188
+ files:
189
+ - ".ruby-version"
190
+ - LICENSE
191
+ - README.md
192
+ - Rakefile
193
+ - bin/vita
194
+ - doc/backlinks.png
195
+ - doc/notes.png
196
+ - doc/outlinks.png
197
+ - lib/vita.rb
198
+ - lib/vita/error.rb
199
+ - lib/vita/format.rb
200
+ - lib/vita/format/markdown.rb
201
+ - lib/vita/format/org.rb
202
+ - lib/vita/garden.rb
203
+ - lib/vita/garden_note.rb
204
+ - lib/vita/html.rb
205
+ - lib/vita/link.rb
206
+ - lib/vita/main.rb
207
+ - lib/vita/note.rb
208
+ - lib/vita/note_scanner.rb
209
+ - lib/vita/renderer.rb
210
+ - lib/vita/rendering_context.rb
211
+ - lib/vita/server.rb
212
+ - lib/vita/version.rb
213
+ - templates/note.html.erb
214
+ homepage: https://github.com/cursley/vita
215
+ licenses:
216
+ - MIT
217
+ metadata:
218
+ homepage_uri: https://github.com/cursley/vita
219
+ source_code_uri: https://github.com/cursley/vita
220
+ post_install_message:
221
+ rdoc_options: []
222
+ require_paths:
223
+ - lib
224
+ required_ruby_version: !ruby/object:Gem::Requirement
225
+ requirements:
226
+ - - ">="
227
+ - !ruby/object:Gem::Version
228
+ version: 3.0.0
229
+ required_rubygems_version: !ruby/object:Gem::Requirement
230
+ requirements:
231
+ - - ">="
232
+ - !ruby/object:Gem::Version
233
+ version: '0'
234
+ requirements: []
235
+ rubygems_version: 3.5.5
236
+ signing_key:
237
+ specification_version: 4
238
+ summary: A tool for tending and publishing a digital garden
239
+ test_files: []