-
Notifications
You must be signed in to change notification settings - Fork 0
/
orgtbl-edit.el
180 lines (140 loc) · 6.96 KB
/
orgtbl-edit.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
;;; orgtbl-edit.el --- Edit spreadsheet or text-delimited file as an Org table -*- lexical-binding: t; -*-
;; Copyright (C) 2022 Shankar Rao
;; Author: Shankar Rao <[email protected]>
;; URL: https://github.com/shankar2k/orgtbl-edit
;; Version: 0.2
;; Package-Requires: ((emacs "26.1"))
;; Keywords: convenience, data, wp, org, orgtbl, xls, ods, csv, tsv
;; This file is not part of GNU Emacs.
;;; Commentary:
;; This package provides the function orgtbl-edit that opens a spreadsheet or
;; text-limited file is a special buffer where it can be edited as an Org
;; table using orgtbl-mode. In particular, all org-table- and orgtbl- commands
;; will work in the buffer. When the buffer is saved, the table is exported
;; back to the original spreadsheet or text-delimited file in its original
;; format.
;;
;; See documentation on https://github.com/shankar2k/orgtbl-edit.
;;; License:
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; History:
;; Version 0.2 (2022-09-18):
;; - Fixed bug with CSV files that have quoted fields with commas
;; Version 0.1 (2021-10-27):
;; - Initial version
;;; Code:
;;;; Requirements
(require 'pcase)
(require 'subr-x)
(require 'org-table)
(require 'ox-odt)
;;;; Variables
(defvar orgtbl-edit-spreadsheet-formats
'("xlsx" "xls" "ods")
"List of spreadsheet formats that can be edited using ``orgtbl-edit''.")
(defvar orgtbl-edit-header-lines 1
"Number of header lines to skip when detecting field separator in ``orgtbl-edit''.")
(defvar-local orgtbl-edit-separator '(4)
"Field separator between the columns.
It can have the following values:
(4) Use the comma as a field separator
(16) Use a TAB as field separator
\" \" Use a space as field separator")
(defvar-local orgtbl-edit-filename ""
"Name of spreadsheet or text-delimited file to save this table to.")
(defvar-local orgtbl-edit-is-spreadsheet-file nil
"Non-nil if current file being edited by orgtbl-edit is a spreadsheet.")
(defvar-local orgtbl-edit-temp-file ""
"Name of temporary CSV file to use when exporting table to a spreadsheet file.")
;;;; Functions
(defun orgtbl-edit-guess-separator ()
"Detect the field separator for text in the current buffer.
This moves forward ``orgtbl-edit-header-lines'' line from the
beginning of the buffer before detecting the field separator."
(save-excursion
(goto-char (point-min))
(forward-line orgtbl-edit-header-lines)
(cond
((not (re-search-forward "^[^\n\t]+$" nil t)) '(16))
((not (re-search-forward "^[^\n,]+$" nil t)) '(4))
(t " "))))
(defun orgtbl-edit-save-function ()
"Save table back to the original spreadsheet or text-delimited file.
This function is used by ``write-contents-functions''."
(if-let* ((save-cmd (pcase orgtbl-edit-separator
('(16) "orgtbl-to-tsv")
('(4) "orgtbl-to-csv")
(" " "orgtbl-to-generic")))
(export-file (if orgtbl-edit-is-spreadsheet-file
orgtbl-edit-temp-file
orgtbl-edit-filename))
(result (org-table-export export-file save-cmd))
(ext orgtbl-edit-is-spreadsheet-file))
(org-odt-convert export-file (car ext))
result))
;;;; Commands
;;;###autoload
(defun orgtbl-edit (filename)
"Edit a spreadsheet or text-delimited FILENAME as an Org table.
The contents of FILENAME are displayed in a special buffer as an
Org table, and all usual ``org-table-'' and ``orgtbl-'' commands
will work in the buffer. When the buffer is saved, its contents
are exported back to FILENAME in its original format."
(interactive "fSpreadsheet or CSV file: ")
(let ((table-buffer-name (format "*orgtbl: %s*" filename)))
(catch 'dont-overwrite
(unless (get-buffer table-buffer-name)
(let ((is-spreadsheet (member (file-name-extension filename)
orgtbl-edit-spreadsheet-formats)))
(when (and is-spreadsheet
(not (yes-or-no-p
(format (concat "Warning: Editing %s as an Org table"
" will destroy formatting and "
"formulas! Continue?")
(file-name-nondirectory filename)))))
(throw 'dont-overwrite t))
(set-buffer (get-buffer-create table-buffer-name))
(orgtbl-mode +1)
(setq truncate-lines t
orgtbl-edit-filename filename
orgtbl-edit-is-spreadsheet-file is-spreadsheet)
(add-hook 'write-contents-functions #'orgtbl-edit-save-function nil t)
;; Mark the buffer as unmodified after saving, because if this is
;; done in write-contents-functions, then after exporting the table,
;; emacs will try to save the buffer as an ordinary file.
(add-hook 'after-save-hook #'(lambda () (set-buffer-modified-p nil))
nil t)
(if is-spreadsheet
(let ((temp-filename (concat (file-name-sans-extension filename)
".csv")))
(org-odt-convert filename "csv")
(unless (file-readable-p temp-filename)
(kill-buffer table-buffer-name)
(error (concat "Unable to convert %s to intermediate CSV "
"format.\nPlease verify that %s%s is properly "
"installed and its path is\n set correctly")
(file-name-nondirectory filename)
org-odt-convert-process
(if (equal org-odt-convert-process "LibreOffice")
" Calc" "")))
(insert-file-contents temp-filename)
(setq orgtbl-edit-temp-file temp-filename
orgtbl-edit-separator '(4)))
(insert-file-contents filename)
(setq orgtbl-edit-separator (orgtbl-edit-guess-separator)))
(org-table-convert-region (point-min) (point-max)
orgtbl-edit-separator)
(set-buffer-modified-p nil)))
(switch-to-buffer table-buffer-name))))
;;;; Footer
(provide 'orgtbl-edit)
;;; orgtbl-edit.el ends here