EMACS-DOCUMENT

=============>随便,谢谢

通过org-mode追踪租金收入

我最近一直在享受探索 org-mode 的过程,因为我总是在其他地方看到它。经常都有人问我:为什么你不直接使用“org-mode”而使用自己管理的这个疯狂玩意呢?

这里有一个具体的例子,没有双关语的意思,我管理着一些公寓,并且每个月都更新记录来跟踪一些事情。 之前,我都是通过使用一组markdown文件来记录租户、收入、维修等详细信息,每个属性一个。 现在我将它们移植到 org-mode 文件中。我用org-mode文件做的第一件事是为每一年都创建一个表,今年的表格类似这样的:

#+NAME: 2020
| Month    | Tenant |   Rent | Mortgage | Housing Company |
|----------+--------+--------+----------+-----------------|
| January  | Bob    |    250 |   150.00 |           75.00 |
| February | Bob    |    250 |   150.00 |           75.00 |
| March    |        |        |          |                 |
| April    |        |        |          |                 |
| May      |        |        |          |                 |
| ..       | ...    |    ... |     .... |             ... |
|----------+--------+--------+----------+-----------------|
| Totals   |        | 500.00 |   300.00 |          150.00 |
#+TBLFM: @>$3=vsum(@I..@II);%0.2f::@>$4=vsum(@I..@II);%0.2f::@>$5=vsum(@I..@II);%0.2f

在这里你明显可以看出:

  • 我声明了一个名为2020的表。
  • 我在表格底部计算各列的总数。
  • 我只会在确定我可爱的租户支付了租金的那一天才会填写那一行。
  • 因此,当前月之后的所有行都是空的。

org-mode 用户应该非常熟悉这样的操作,让我们更进一步,我们有一个名为 2020 的表,让我们创建一个名为 2020-total 的新表来显示更多有用的数据:

#+NAME: 2020-totals
| Year | Income | Expenses | Profit |
|------+---------+----------+--------|
| 2020 | 500.00 | 450.00 | 50.00 |
#+TBLFM: @2$2=remote(2020,@>$3);%.2f::@2$3=remote(2020,@>$4)+remote(2020,@>$5);%.2f::@>$4=@>$2-@$3;%.2f

也没有什么特殊的东西:

  • 我们引用的是“远程”值(即来自不同的表的值)。
  • 我们会查找类似“row:@>”,“column:$3”的内容,他们的意思是“最后一行,第三列(row:last column:3rd)”。
  • 最后一行应该 :: 分割,比如:
  • @2$2=remote(2020,@>$3);%.2f
  • %.2f 是一个格式字符串,用于控制要显示的小数位数。
  • @2$3=remote(2020,@>$4) + remote(2020,@>$5);%.2f
  • 费用包含“按揭”+“房屋公司”两部分
  • 即第四和第五栏的内容。
  • @>$4=@>$2-@$3;%.2f
  • 最终的结果是我们得到了收入和费用的总和,它们之间的差额就是利润(或损失)。

自然而然的,我已经记录有一段时间了,所以我们真正想要的是一个完整的/全局的收入/支出和利润表(如果是负数,就是亏损)。 我们假设在文档中有 多个 表,每年有一对 “=2019=”、“=2019-total=” 这样的表格。 要生成全局的收入/费用/属性表,我们只需将名称与模式 "*-total" 匹配的每个表的列相加。我们开始吧:

#+NAME: income-expenses-profit
|----------+-----------|
| Income | €10000.00 |
| Expenses | € 8000.00 |
| Profit | € 2000.00 |
|----------+-----------|
#+TBLFM: @1$2='(car (sum-field-in-tables "-totals$" 1));€%.2f::@2$2='(car (sum-field-in-tables "-totals$" 2));€%.2f::@3$2='(car (sum-field-in-tables "-totals$" 3));€%.2f

同样,这里有三个值,通过 :: 分隔使它们更具可读性:

  • @1$2'(car (sum-field-in-tables "-totals$" 1));€%.2f=
  • @2$2'(car (sum-field-in-tables "-totals$" 2));€%.2f=
  • @3$2'(car (sum-field-in-tables "-totals$" 3));€%.2f=

简而言之,我们将第二列各行的值设置为计算Emacs lisp表达式 (car (sum-field-in-tables .. 的值,而不是使用来自 org-mode 的内置表格支持。 我确实需要自己编写这个函数来执行必要的表格迭代和字段求和,同时还添加了简单的过滤支持,这非常有用,我们将在后面看到:

(defun sum-field-in-tables (pattern field &optional filter)
  "For every table in the current document which has a name matching the
supplied pattern perform a sum of the specified column.

If the optional filter is present then the summing will ignore any rows
which do not match the given filter-pattern.

The return value is a list containing the sum, and a count of those
rows which were summed."

通过该函数,我成功地实现了我想要的功能,而且还不止这些,我可以做一些很厉害的事情,比如显示给定租户的总收入/付款。 如果您看回到 2020 年的表,你会看到有一列中列出的是租户的名称。这样我就可以计算出租户的总收入,以及支付的金额总和:

  • 所有表格名称的模式为"^:digit:$"
  • 第三列(即租金)
  • 行需要匹配过滤器“Bob”

最终结果是这样的另一个表:

#+NAME: tenants-paid
| Tenant | Rent Paid | Rented Months |
|--------+-----------+---------------|
| Alice  | €1500.00  |             6 |
| Bob    | €1500.00  |             6 |
| Chris  | €1500.00  |             6 |
#+TBLFM: $2='(car (sum-field-in-tables "^[0-9]*$" 2 $1));€%0.2f::$3='(cdr (sum-field-in-tables "^[0-9]*$" 2 $1))

这个公式能正常工作是因为table-sum函数返回两项内容,即行的实际求和已求和的行数:

  • 因此 (car (sum-field-in-tables .. 返回实际的和。这个人付的总房租。
  • (cdr (sum-field-in-tables .. 返回指定租户支付金额的次数。

唯一需要显式执行的操作是将 Alice, BobChris 的行添加到这个表中。

无论如何,这些操作相当酷的,我挺满意的,不过如果我可以避免写lisp我会更满意一点。现在我想我需要在两种方法中选择一种:

  • 我把lisp函数嵌入报告文件本身中?
  • 当文件被加载时就会执行这些lisp函数- 这个方案很简单
  • 或者我把它放在~/.emacs/init.md 中?-这对我来说很好,但这意味着这个文件对其他人来说不是自包含的.

在接下来的几天或几周内,我可能会继续倒腾这个玩意。我只要稍微配置一下,然后在文档的某些部分设置 :noexport: 标记,将org文件导出成HTML和PDF就变得很有魅力的一件事情了。