-
-
Notifications
You must be signed in to change notification settings - Fork 171
Support sheband and tangle mode header tag #939
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Support sheband and tangle mode header tag #939
Conversation
113cc16 to
67667a8
Compare
67667a8 to
160fe5f
Compare
kristijanhusak
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR! Generally looks good, but I have some questions and suggestions.
lua/orgmode/babel/tangle.lua
Outdated
| local shebang = info.header_args[':shebang'] | ||
| if shebang then | ||
| shebang = shebang:gsub('[\'"]', '') | ||
| table.insert(parsed_content, 1, shebang) | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic is already done above on line 64. Is there a reason why it's needed twice?
lua/orgmode/babel/tangle.lua
Outdated
| if mode_str and mode_str:sub(1, 1) == 'o' then | ||
| mode_str = mode_str:sub(2) | ||
| local mode_num = tonumber(mode_str, 8) | ||
| vim.loop.fs_chmod(filename, mode_num) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of doing chmod after the file is created, lets pass down the mode as an argument to utils.writefile and pass it there when creating the file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sound good, I'm make the changes
e84d8a8 to
fdfb3c8
Compare
|
The orgmode docs describe 3 styles of tangle-mode values: ls-style, chmod style, and octal. All three style are now supported, however, I'm still using the example of the three modes |
Nice! 👍🏻
That's fine, I don't think it will cause any issues. Could you add few tests that test these new tangle options? I'm not sure if CI will cause some issues testing the permissions, but you can add 1 test that modifies the perrmission and push to see how CI behaves. It's somewhat sensitive stuff so I want to make sure we cover it with tests as much as possible. |
ae38aa8 to
de701e8
Compare
Add the ability to include a shebang line at the beginning of tangled code files by supporting the `:shebang` header argument. The shebang value is cleaned of quotes and inserted as the first line of the tangled content.
This header allow the creator to change the generated file permissions.
When tangling code blocks to files, automatically create parent directories when the :mkdirp header argument is set to 'yes'.
Coding style requires single quotes
Add the ability to include a shebang line at the beginning of tangled code files by supporting the `:shebang` header argument. The shebang value is cleaned of quotes and inserted as the first line of the tangled content.
They keep showing up.
Add Plenary specs to verify shebang prepending, default 0755 behavior, and :tangle-mode parsing/override (octal, chmod, ls styles).
6b920eb to
44ac60d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds support for :shebang and :tangle-mode source block header tags to enable org-mode babel tangling features. When a shebang is specified, it's prepended to the tangled file and the file permissions default to 0755. The :tangle-mode tag allows explicit file permission control using octal (e.g., "o644"), chmod-style (e.g., "u=rw,go=r"), or ls-style (e.g., "rwxr-x---") formats.
Changes:
- Added helper functions to convert permission strings (ls-style and chmod-style) to octal format
- Modified tangle_info structure from a simple array to an object with
contentandmodefields - Implemented shebang prepending with automatic 0755 permission default
- Added file permission modification using vim.loop.fs_chmod
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| tests/plenary/babel/tangle_shebang_mode_spec.lua | New test file with comprehensive coverage of shebang and tangle-mode features including octal, chmod-style, and ls-style permission formats |
| lua/orgmode/babel/tangle.lua | Core implementation adding permission conversion functions and integrating shebang/mode handling into the tangle workflow |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| function ls_style_to_octal(rwx_string) | ||
| local result = 0 | ||
| local value = 0 | ||
|
|
||
| for i = 1, #rwx_string, 3 do | ||
| local chunk = rwx_string:sub(i, i + 2) | ||
| value = 0 | ||
|
|
||
| if chunk:sub(1, 1) == 'r' then | ||
| value = value + 4 | ||
| end | ||
| if chunk:sub(2, 2) == 'w' then | ||
| value = value + 2 | ||
| end | ||
| if chunk:sub(3, 3) == 'x' then | ||
| value = value + 1 | ||
| end | ||
|
|
||
| result = result * 8 + value | ||
| end | ||
|
|
||
| return result | ||
| end |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These helper functions should be declared as local function instead of global functions. This is consistent with the codebase convention seen throughout the project (e.g., in lua/orgmode/api/agenda.lua, lua/orgmode/colors/colors.lua, and many other files where helper functions are prefixed with local).
| function chmod_style_to_octal(chmod_string) | ||
| local owner, group, other = 0, 0, 0 | ||
|
|
||
| for part in chmod_string:gmatch('[^,]+') do | ||
| local who, what = part:match('(%a+)[=+](.+)') | ||
| if not who or not what then | ||
| return nil | ||
| end | ||
|
|
||
| local perm = 0 | ||
| if what:find('r') then | ||
| perm = perm + 4 | ||
| end | ||
| if what:find('w') then | ||
| perm = perm + 2 | ||
| end | ||
| if what:find('x') then | ||
| perm = perm + 1 | ||
| end | ||
|
|
||
| if who:find('u') or who:find('a') then | ||
| owner = bit.bor(owner, perm) | ||
| end | ||
| if who:find('g') or who:find('a') then | ||
| group = bit.bor(group, perm) | ||
| end | ||
| if who:find('o') or who:find('a') then | ||
| other = bit.bor(other, perm) | ||
| end | ||
| end | ||
|
|
||
| return owner * 64 + group * 8 + other | ||
| end |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This helper function should be declared as local function instead of a global function, following the codebase convention seen throughout the project.
| if info.header_args[':mkdirp'] == 'yes' then | ||
| local path = vim.fn.fnamemodify(info.filename, ':h') | ||
| vim.fn.mkdir(path, 'p') | ||
| end | ||
|
|
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The :mkdirp check is duplicated here. It already appears at lines 117-120, so this duplicate block at lines 136-139 should be removed.
| if info.header_args[':mkdirp'] == 'yes' then | |
| local path = vim.fn.fnamemodify(info.filename, ':h') | |
| vim.fn.mkdir(path, 'p') | |
| end |
| if mode then | ||
| utils.echo_info(('change mode %s mode %o'):format(filename, mode)) | ||
| vim.loop.fs_chmod(filename, mode) | ||
| end |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a race condition here. The file mode is set with vim.loop.fs_chmod synchronously after the async utils.writefile is added to the promises array but before Promise.all(promises):wait() completes. This means fs_chmod may execute before the file is fully written, which could fail or set permissions on a partially written file. The fs_chmod call should happen after the write promise is resolved.
|
|
||
| if mode then | ||
| utils.echo_info(('change mode %s mode %o'):format(filename, mode)) | ||
| vim.loop.fs_chmod(filename, mode) |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The codebase consistently uses vim.uv for libuv bindings (see lua/orgmode/utils/init.lua:2, lua/orgmode/files/file.lua:46, lua/orgmode/export/init.lua:85, etc.). This should be changed to vim.uv.fs_chmod for consistency.
| assert.are.equal(tonumber("0750", 8), stat_mode(tangled_file)) | ||
| end) | ||
|
|
||
| it('should keep 0755 when :shebang is set even if :tangle-mode is also set (mode is overridden if :tangle-mode present)', function() |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test description says "should keep 0755" but the test actually expects 0700 and verifies that :tangle-mode overrides the shebang default. The description should be corrected to match what the test actually verifies, something like "should apply :tangle-mode even when :shebang is set (mode is overridden if :tangle-mode present)".
| it('should keep 0755 when :shebang is set even if :tangle-mode is also set (mode is overridden if :tangle-mode present)', function() | |
| it('should apply :tangle-mode even when :shebang is set (mode is overridden if :tangle-mode present)', function() |
| end | ||
| vim.list_extend(tangle_info[info.filename], parsed_content) | ||
| vim.list_extend(tangle_info[info.filename]['content'], parsed_content) | ||
| tangle_info[info.filename]['mode'] = filemode |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When multiple blocks tangle to the same file, there's a logic issue with file mode handling. If the first block sets a shebang (which defaults the mode to 0755), and a subsequent block has neither :shebang nor :tangle-mode, the filemode variable will become nil and override the previously set mode on line 145. This means the final file mode will be nil instead of the expected 0755. The mode should only be updated if a new mode is explicitly specified, not reset to nil.
| tangle_info[info.filename]['mode'] = filemode | |
| if filemode ~= nil then | |
| tangle_info[info.filename]['mode'] = filemode | |
| end |
| function ls_style_to_octal(rwx_string) | ||
| local result = 0 | ||
| local value = 0 | ||
|
|
||
| for i = 1, #rwx_string, 3 do | ||
| local chunk = rwx_string:sub(i, i + 2) | ||
| value = 0 | ||
|
|
||
| if chunk:sub(1, 1) == 'r' then | ||
| value = value + 4 | ||
| end | ||
| if chunk:sub(2, 2) == 'w' then | ||
| value = value + 2 | ||
| end | ||
| if chunk:sub(3, 3) == 'x' then | ||
| value = value + 1 | ||
| end | ||
|
|
||
| result = result * 8 + value | ||
| end | ||
|
|
||
| return result | ||
| end |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ls_style_to_octal function expects a 9-character string (like "rwxr-xr-x") but doesn't validate the input length. If a malformed ls-style permission string is provided (e.g., "rwx" or "rwxr-xr-x-extra"), the function will either process incomplete chunks or process extra characters incorrectly. Consider adding validation to ensure the string is exactly 9 characters long, or handle variable-length strings appropriately.
Summary
Add shebang and tangle-mode source block header tags
Related Issues
New features to enable features in org-mode
Related #
https://orgmode.org/manual/Extracting-Source-Code.html
Closes #
Changes
Changes the structure of the tangle_info table.