Skip to content
Draft
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6997c66
poking at a 'mawl server' mode for live reloading changes.
phillmv Feb 18, 2024
b5dc0d8
WIP: isolated some AdhocMarkdown import logic into EntryImporter to e…
phillmv Feb 18, 2024
aff262d
wip uh experimenting with loading stylesheet dynamically
phillmv Mar 23, 2024
c452cfa
silence sqlite warning, i get it
phillmv Mar 30, 2024
15cdac0
don't rely on any secrets not in the codebase
phillmv Mar 30, 2024
b495acf
always run db:prepare?
phillmv Mar 30, 2024
38de6b2
since we provide the SECRET_KEY we don't need this anymore
phillmv Mar 30, 2024
f3d3dd6
wip: let's avoid dynamic stylesheets for just now, when dynamically l…
phillmv Mar 30, 2024
feae41c
reintroduced concept of a template entry whose body is rendered as erb
phillmv Apr 14, 2024
88b11ee
prepare db on mawl import and generate
phillmv Apr 25, 2024
a05b4c1
entry renderer now supplies default_url_options
phillmv Apr 25, 2024
636a56d
AdHoc importer now also identifies erb files.
phillmv Apr 25, 2024
2831c15
always link to archive with .html extension
phillmv Apr 25, 2024
01f4a34
Swapped the active_storage routes prefix to be '_'
phillmv May 14, 2024
7817e46
Added LinkRelativizer to EntryRenderer pipeline.
phillmv May 14, 2024
46dd8d3
wip, separated mawl out from start-arquivo, fixed some settings
phillmv Nov 9, 2024
04ea2dd
start-arquivo should load the current folder
phillmv Nov 17, 2024
57dd488
Merge branch 'master' into tidy-up-mawl
phillmv Nov 17, 2024
5783036
Merge branch 'master' into live-reload-mawl
phillmv Nov 17, 2024
1c764fc
Merge branch 'tidy-up-mawl' into live-reload-mawl
phillmv Nov 17, 2024
3d30600
added mawl console
phillmv Nov 29, 2024
74d1ec3
wip: Hackily added the ability to live-reload stylesheets in static m…
phillmv Nov 29, 2024
0d9f5fe
don't try to divine timestamps from c/mtime, which git does not set.
phillmv Dec 4, 2025
c08bcb9
Going to avoid this for now, think of a better solution down the road.
phillmv Dec 4, 2025
8d5d20b
backed out file attachment experiment in non static controller
phillmv Dec 18, 2025
23c2257
what if we fflag this for now?
phillmv Dec 18, 2025
6911fe6
i did figure out a better way to handle templates: in the renderer!
phillmv Dec 18, 2025
d16750b
minor cleanup
phillmv Dec 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions app/controllers/entries_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class EntriesController < ApplicationController
before_action :find_or_build_entry, only: [:show, :edit]
before_action :find_entry, only: [:update, :destroy, :files, :copy]
before_action :find_entry, only: [:update, :destroy, :copy]

# GET /entries
# GET /entries.json
Expand Down Expand Up @@ -159,7 +159,22 @@ def destroy
end

def files
blob = @entry.files.blobs.find_by!(filename: params[:filename])
# TODO: test this obviously insane behaviour

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think i did this in order to support not-yet-important files as entries. but why did i do this in the normal entries controller as opposed to the static entries controller?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh maybe i was trying to make this work in the non-static mode eh

# are we dealing with a document entry with `files` in the identifier?
doc_identifier = File.join(params["id"], "files", params["filename"])
@entry = current_notebook.entries.find_by(identifier: doc_identifier)

if @entry.nil?
@entry = current_notebook.entries.find_by!(identifier: params[:id])
end

# TODO: undo this when we move documents to just reading the file off disk
if @entry.document?
blob = @entry.files.blobs.first
else
blob = @entry.files.blobs.find_by(filename: params[:filename])
end

expires_in ActiveStorage.service_urls_expire_in
redirect_to rails_blob_path(blob, disposition: params[:disposition])
end
Expand Down
25 changes: 22 additions & 3 deletions app/controllers/static_site/entries_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ def show
# TODO: make up my mind on how to handle templates.
# elsif @entry.template?
# don't love it but fix later, lol do not deploy this to untrusted user contexts???
# render inline: File.read(File.join(current_notebook.import_path, @entry.source)), layout: "application"
# render inline: @entry.body, layout: "application"

elsif @entry.note? || @entry.bookmark?
elsif @entry.note? || @entry.bookmark? || @entry.template?
@show_thread = params[:thread].present?
@renderer = EntryRenderer.new(@entry, remove_subject: true)
@current_date = @entry.occurred_at.strftime("%Y-%m-%d")
Expand All @@ -38,6 +38,25 @@ def self.controller_path

private
def set_entry

# TODO: live-reloading
# see if the file exists and if it does, import it

# step 1: does it exist as a file?
# step 2: is it markdown or yaml?
# step 2.1: actually this is harder to untangle
# will have to think about how i want to support the "normal" dump o yaml
# vs the "adhoc" markdown
# step 3: parse it & add it.

if Rails.env.development?
@entry = EntryImporter.new(current_notebook).resolve_and_import!(params[:id])

if @entry
return
end
end

# quick terrible hack for routing document type entries
if params[:format]
identifier = "#{params[:id]}.#{params[:format]}"
Expand Down Expand Up @@ -70,7 +89,7 @@ def serve_blob(blob)
end

response.headers["Content-Type"] = content_type || ActiveStorage::BaseController::DEFAULT_SEND_FILE_TYPE
response.headers["Content-Disposition"] = disposition || ActiveStorage::BaseController::DEFAULT_SEND_FILE_DISPOSITION
# response.headers["Content-Disposition"] = disposition || ActiveStorage::BaseController::DEFAULT_SEND_FILE_DISPOSITION

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

????

end
end
end
Expand Down
89 changes: 4 additions & 85 deletions app/models/ad_hoc_markdown_importer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,19 @@ def process_import_path(notebook)

identifier = path_to_relative_identifier(file_path, notebook.import_path)

entry_importer = EntryImporter.new(notebook)
if skip_file_path?(identifier, file_path)
# Do nothing.
elsif looks_like_text?(file_path)
# Files that become normal entries, the content in our site.
puts "processing #{file_path}" if Rails.env.development?
entry_attributes = entry_attributes_from_markdown(identifier, file_path)

entry = add_entry!(notebook, entry_attributes)
entry_importer.import!(identifier, file_path)
else
# everything that is not markdown is treated a bit differently.
puts "processing #{file_path}" if Rails.env.development?
entry_attributes = entry_attributes_from_document(identifier, file_path)

entry = add_entry!(notebook, entry_attributes)
entry = entry_importer.add_entry!(entry_attributes)

filename = File.basename(identifier)
if !entry.files.blobs.find_by(filename: filename)
Expand All @@ -91,72 +90,7 @@ def skip_file_path?(identifier, file_path)
end

def looks_like_text?(file_path)
file_path =~ /\.(md|markdown|html)$/
end

def entry_attributes_from_markdown(identifier, md_path)
loader = FrontMatterParser::Loader::Yaml.new(allowlist_classes: [Time, Date, DateTime])
md_parser = FrontMatterParser::SyntaxParser::Md.new
parsed_file = FrontMatterParser::Parser.new(md_parser, loader: loader).call(File.read(md_path))

occurred_at = parsed_file["occurred_at"]
if occurred_at.blank?
# let's try to guess it from the file
basename = File.basename(md_path)
date = basename.match(/([0-9]{4}-*[0-9]{2}-*[0-9]{2}-*)/).to_a[0]

# most of the time this does the right thing but it does have the habit
# of sometimes throwing an exception
begin
occurred_at = DateTime.parse(date.to_s)
rescue ArgumentError
end

# if still nil, let's look at the file itself
if occurred_at.nil?
occurred_at = File.ctime(md_path)
end
end

created_at = parsed_file["created_at"] || File.ctime(md_path)
updated_at = parsed_file["updated_at"] || File.mtime(md_path)

# if we're parsing front-mattered markdown, you don't get to define an
# identifier separate from the file's relative path, don't want to deal
# with collisions etc, too confusing, the ad hoc markdown is for ad hoc
# files, loosely slapped together!
#
# then, we lop off `.md`, `.markdown` and `html` from the suffix
entry_source = identifier # store the unmodified identifier as the "source"
identifier = identifier.gsub(/\.(md|markdown|html)/, "")

entry_attributes = parsed_file.front_matter.merge({
"identifier" => identifier,
"source" => entry_source,
"occurred_at" => occurred_at,
"body" => parsed_file.content,
"created_at" => created_at,
"updated_at" => updated_at,
skip_local_sync: true
}).slice(*Entry.accepted_attributes)

# handle metadata!
metadata_keys = (parsed_file.front_matter.keys - Entry.accepted_attributes)
if metadata_keys.any?
# if the user has specified a non Hash value, ie "metadata: foo", then
# throw a slightly easier to understand error here, instead of later on
# when we try to instantiate the Entry object & the error gets thrown there.
if entry_attributes["metadata"] && !entry_attributes["metadata"].is_a?(Hash)
raise "I expected the 'metadata' key on #{identifier} to be Hash, but something else is going on."
end

entry_attributes["metadata"] ||= {}
metadata_keys.each do |mkey|
entry_attributes["metadata"][mkey] ||= parsed_file.front_matter[mkey]
end
end

entry_attributes
file_path =~ /\.(md|markdown|html|erb)$/
end

def entry_attributes_from_document(identifier, file_path)
Expand Down Expand Up @@ -236,19 +170,4 @@ def process_templates(notebook)
def path_to_relative_identifier(file_path, import_path)
Pathname.new(file_path).relative_path_from(import_path).to_s
end

def add_entry!(notebook, entry_attributes)
identifier = entry_attributes["identifier"]

# find or update the entry
entry = notebook.entries.find_by(identifier: identifier)

if entry
entry.update!(entry_attributes)
else
entry = notebook.entries.create(entry_attributes)
end

entry
end
end
169 changes: 169 additions & 0 deletions app/models/entry_importer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
class EntryImporter
attr_reader :current_notebook, :root_path
def initialize(current_notebook)
@current_notebook = current_notebook
@root_path = current_notebook.import_path
end

def resolve_and_import!(identifier)

# case identifier
# when "stylesheets/application"
# return render_stylesheet
# end
# TODO: replace hacky & brittle with more well-defined search
# i.e. exact lookups vs just fuzzy searching
# ALSO: need to think about how this would interact with scss templates.
file_path = build_file_path(identifier)
glob_path = file_path + "*"
file_path = Dir[glob_path].select { |f| f =~ /#{file_path}(\.html|\.md|\.markdown)+/ }.reject {|f| File.directory?(f) }.first
if file_path && looks_like_text?(file_path)
# TODO: hrm, should the identifier come from the request? this will blow up somehow
import!(identifier, file_path)
else
puts "couldn't find nothin' to import"
return nil
end
end

def import!(identifier, file_path)
entry_attributes = entry_attributes_from_markdown(identifier, file_path)

entry = add_entry!(entry_attributes)
end

def looks_like_text?(file_path)
file_path =~ /\.(md|markdown|html|erb)$/
end

def build_file_path(path_info)
clean_path_info = Rack::Utils.clean_path_info(path_info)
::File.join(@root_path, clean_path_info)
end

def entry_attributes_from_markdown(identifier, md_path)
loader = FrontMatterParser::Loader::Yaml.new(allowlist_classes: [Time, Date, DateTime])
md_parser = FrontMatterParser::SyntaxParser::Md.new
parsed_file = FrontMatterParser::Parser.new(md_parser, loader: loader).call(File.read(md_path))

occurred_at = parsed_file["occurred_at"]
if occurred_at.blank?
# let's try to guess it from the file
basename = File.basename(md_path)
date = basename.match(/([0-9]{4}-*[0-9]{2}-*[0-9]{2}-*)/).to_a[0]

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like, c'mon, isn't this nuts? this is a cute hack but if i give up on being able to parse random files in a random folder structure, it's obvious i should just define this in the frontmatter 🤔


# most of the time this does the right thing but it does have the habit
# of sometimes throwing an exception
begin
occurred_at = DateTime.parse(date.to_s)
rescue ArgumentError
end

# if still nil, let's look at the file itself
if occurred_at.nil?
occurred_at = File.ctime(md_path)
end
end

# TODO: DON'T DO THIS GIT DOESN"T SET CTIME!!!!!!
created_at = parsed_file["created_at"] || File.ctime(md_path)
updated_at = parsed_file["updated_at"] || File.mtime(md_path)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this actually has caused all sorts of annoying issues when regenerating the site, cos the updated at will reflect the disk mtime & not the last time the file was actually modified.


# if we're parsing front-mattered markdown, you don't get to define an
# identifier separate from the file's relative path, don't want to deal
# with collisions etc, too confusing, the ad hoc markdown is for ad hoc
# files, loosely slapped together!
#
# then, we lop off `.md`, `.markdown` and `html` from the suffix
entry_source = md_path # store the unmodified identifier as the "source"
identifier = identifier.gsub(/\.(md|markdown|html|erb)/, "")

# TODO: test this behaviour
if entry_source =~ /\.erb$/
entry_kind = "template"
else
entry_kind = nil
end

entry_attributes = parsed_file.front_matter.merge({
"identifier" => identifier,
"source" => entry_source,
"occurred_at" => occurred_at,
"body" => parsed_file.content,
"kind" => entry_kind,
"created_at" => created_at,
"updated_at" => updated_at,
skip_local_sync: true
}).slice(*Entry.accepted_attributes)

# handle metadata!
metadata_keys = (parsed_file.front_matter.keys - Entry.accepted_attributes)
if metadata_keys.any?
# if the user has specified a non Hash value, ie "metadata: foo", then
# throw a slightly easier to understand error here, instead of later on
# when we try to instantiate the Entry object & the error gets thrown there.
if entry_attributes["metadata"] && !entry_attributes["metadata"].is_a?(Hash)
raise "I expected the 'metadata' key on #{identifier} to be Hash, but something else is going on."
end

entry_attributes["metadata"] ||= {}
metadata_keys.each do |mkey|
entry_attributes["metadata"][mkey] ||= parsed_file.front_matter[mkey]
end
end

entry_attributes
end

def add_entry!(entry_attributes)
identifier = entry_attributes["identifier"]

# find or update the entry
entry = current_notebook.entries.find_by(identifier: identifier)

if entry
entry.update!(entry_attributes)
else
entry = current_notebook.entries.create(entry_attributes)
end

entry
end

def render_stylesheet
# okay lets do the dumbest thing possible
# reload the file everytime!!!!

scss_path = build_file_path("stylesheets/application.css.scss")

rendered_stylesheet = nil
if File.exist?(scss_path)
load_path = File.join(current_notebook.import_path, "stylesheets")

rendered_css = SassC::Engine.new(File.read(scss_path), {
filename: "application.css.scss",
syntax: :scss,
load_paths: [load_path],
}).render

# there can only be ONE application.css
if to_delete = current_notebook.entries.find_by(identifier: "stylesheets/application.css")
puts "Destroying extraneous stylesheets/application.css, so it can be replaced."
to_delete.destroy
end

rendered_stylesheet = current_notebook.entries.new
rendered_stylesheet.identifier = "stylesheets/application.css"
rendered_stylesheet.kind = :document
rendered_stylesheet.save!

blob = ActiveStorage::Blob.create_and_upload!(io: StringIO.new(rendered_css),
metadata: { analyzed: true },
filename: "application.css")
# blob.analyze
rendered_stylesheet.files.create(blob_id: blob.id, created_at: blob.created_at)
end

rendered_stylesheet
end
end
Loading