Skip to content

Commit

Permalink
feat: Add org id module
Browse files Browse the repository at this point in the history
  • Loading branch information
kristijanhusak committed Jan 20, 2024
1 parent 651078a commit 84c986d
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 1 deletion.
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- master
- feat/org-id
pull_request:
branches:
- master
Expand Down
5 changes: 5 additions & 0 deletions lua/orgmode/config/defaults.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---@class DefaultConfig
---@field org_id_method 'uuid' | 'ts' | 'org'
local DefaultConfig = {
org_agenda_files = '',
org_default_notes_file = '',
Expand Down Expand Up @@ -42,6 +43,10 @@ local DefaultConfig = {
},
org_src_window_setup = 'top 16new',
org_edit_src_content_indentation = 0,
org_id_uuid_program = 'uuidgen',
org_id_ts_format = '%Y%m%d%H%M%S',
org_id_method = 'uuid',
org_id_prefix = nil,
win_split_mode = 'horizontal',
win_border = 'single',
notifications = {
Expand Down
35 changes: 35 additions & 0 deletions lua/orgmode/org/id.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
local config = require('orgmode.config')
local utils = require('orgmode.utils')
local state = require('orgmode.state.state')

local OrgId = {}

function OrgId.new()
return OrgId._generate()
end

---@private
---@return string
function OrgId._generate()
if config.org_id_method == 'uuid' then
if vim.fn.executable(config.org_id_uuid_program) ~= 1 then
utils.echo_error('org_id_uuid_program is not executable: ' .. config.org_id_uuid_program)
return ''
end
return tostring(vim.fn.system(config.org_id_uuid_program):gsub('%s+', ''))
end

if config.org_id_method == 'ts' then
return tostring(os.date(config.org_id_ts_format))
end

if config.org_id_method == 'org' then
math.randomseed(os.clock() * 100000000000)
return ('%s%s'):format(vim.trim(config.org_id_prefix or ''), math.random(100000000000000))
end

utils.echo_error('Invalid org_id_method: ' .. config.org_id_method)
return ''
end

return OrgId
13 changes: 12 additions & 1 deletion lua/orgmode/state/state.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ local utils = require('orgmode.utils')
local Promise = require('orgmode.utils.promise')

---@class OrgState
local OrgState = { data = {}, _ctx = { loaded = false, saved = false, curr_loader = nil, savers = 0 } }
local OrgState = { data = {}, _ctx = { loaded = false, saved = false, curr_loader = nil, savers = 0, dirty = false } }

local cache_path = vim.fs.normalize(vim.fn.stdpath('cache') .. '/org-cache.json', { expand_env = false })

Expand All @@ -16,6 +16,9 @@ function OrgState.new()
return tbl.data[key]
end,
__newindex = function(tbl, key, value)
if tbl.data[key] ~= value then
tbl._ctx.dirty = true
end
tbl.data[key] = value
end,
})
Expand All @@ -28,6 +31,9 @@ end
---Save the current state to cache
---@return Promise
function OrgState:save()
if not OrgState._ctx.dirty then
return Promise.resolve(self)
end
OrgState._ctx.saved = false
--- We want to ensure the state was loaded before saving.
self:load()
Expand All @@ -38,6 +44,7 @@ function OrgState:save()
self._ctx.savers = self._ctx.savers - 1
if self._ctx.savers == 0 then
OrgState._ctx.saved = true
OrgState._ctx.dirty = false
end
end)
:catch(function(err_msg)
Expand Down Expand Up @@ -86,6 +93,7 @@ function OrgState:load()
vim.schedule(function()
utils.echo_warning('OrgState cache load failure, error: ' .. vim.inspect(err_msg))
-- Try to 'repair' the cache by saving the current state
self._ctx.dirty = true
self:save()
end)
end
Expand All @@ -107,6 +115,7 @@ function OrgState:load()
-- If the file didn't exist then go ahead and save
-- our current cache and as a side effect create the file
if type(err) == 'string' and err:match([[^ENOENT.*]]) then
self._ctx.dirty = true
self:save()
return self
end
Expand All @@ -116,6 +125,7 @@ function OrgState:load()
:finally(function()
self._ctx.loaded = true
self._ctx.curr_loader = nil
self._ctx.dirty = false
end)

return self._ctx.curr_loader
Expand Down Expand Up @@ -160,6 +170,7 @@ function OrgState:wipe(overwrite)
self._ctx.curr_loader = nil
self._ctx.loaded = false
self._ctx.saved = false
self._ctx.dirty = true
if overwrite then
state:save_sync()
end
Expand Down
10 changes: 10 additions & 0 deletions lua/orgmode/treesitter/headline.lua
Original file line number Diff line number Diff line change
Expand Up @@ -582,4 +582,14 @@ function Headline:_apply_indent(text)
return config:apply_indent(text, self:level() + 1)
end

function Headline:id_get_or_create()
local id_prop = self:get_property('ID')
if id_prop then
return vim.trim(id_prop.value)
end
local org_id = require('orgmode.org.id').new()
self:set_property('ID', org_id)
return org_id
end

return Headline
8 changes: 8 additions & 0 deletions test.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
* TODO Test

* TODO Test1
:PROPERTIES:
:ID: 1717a012-6ca3-42bd-b471-4e6855618096
:END:

* TODO Test2
52 changes: 52 additions & 0 deletions tests/plenary/org/id_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
describe('org id', function()
local org_id = require('orgmode.org.id')
it('should generate an id using uuid method', function()
local uuid = org_id.new()
assert.are.same(36, #uuid)
assert.is.True(uuid:match('%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x') ~= nil)
end)

it('should generate an id using "ts" method', function()
require('orgmode').setup({
org_id_method = 'ts'
})
local ts_id = org_id.new()
assert.is.True(ts_id:match('%d%d%d%d%d%d%d%d%d%d%d%d%d%d') ~= nil)
assert.is.True(ts_id:match(os.date('%Y%m%d%H')) ~= nil)
end)

it('should generate an id using "ts" method and custom format', function()
require('orgmode').setup({
org_id_method = 'ts',
org_id_ts_format = '%Y_%m_%d_%H_%M_%S'
})
local ts_id = org_id.new()
assert.is.True(ts_id:match('%d%d%d%d_%d%d_%d%d_%d%d_%d%d_%d%d') ~= nil)
assert.is.True(ts_id:match(os.date('%Y_%m_%d_%H')) ~= nil)
end)

it('should generate an id using "org" format', function()
require('orgmode').setup({
org_id_method = 'org'
})

local oid = org_id.new()
-- Ensure it does not generate a timestamp format
assert.is.Nil(oid:match(os.date('%Y%m%d%H')))
assert.is.True(oid:match('%d+') ~= nil)
assert.is.True(oid:len() >= 1)
end)

it('should generate an id using "org" format with custom prefix', function()
require('orgmode').setup({
org_id_method = 'org',
org_id_prefix = 'org_tests_'
})

local oid = org_id.new()
-- Ensure it does not generate a timestamp format
assert.is.Nil(oid:match('org_tests_'..os.date('%Y%m%d%H')))
assert.is.True(oid:match('org_tests_%d+') ~= nil)
assert.is.True(oid:len() >= 11)
end)
end)
30 changes: 30 additions & 0 deletions tests/plenary/state/state_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local utils = require('orgmode.utils')
---@type OrgState
local state = nil
local cache_path = vim.fs.normalize(vim.fn.stdpath('cache') .. '/org-cache.json', { expand_env = false })
local spy = require('luassert.spy')

describe('State', function()
before_each(function()
Expand Down Expand Up @@ -76,6 +77,35 @@ describe('State', function()
assert.are.equal('hello world', state.my_var)
end)

it('should set the dirty state when a variable is set', function()
-- By default it's dirty
assert.is.True(state._ctx.dirty)
state:save_sync()
assert.is.False(state._ctx.dirty)

state.my_var = 'hello world'
assert.is.True(state._ctx.dirty)
state:save_sync()
assert.is.False(state._ctx.dirty)

-- Ensure writefile is not called if state is not dirty
local s = spy.on(utils, 'writefile')
state:save_sync()
assert.spy(s).was.called(0)

-- Ensure writefile is not called if state was not changed
state:save_sync()
state.my_var = 'hello world'
assert.spy(s).was.called(0)

-- Ensure writefile is called if state prop was changed
state.my_var = 'hello worlds'
state:save_sync()
assert.spy(s).was.called(1)

s:revert()
end)

it('should be able to self-heal from an invalid state file', function()
state:save_sync()

Expand Down

0 comments on commit 84c986d

Please sign in to comment.