Skip to content
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

Updated markdown script to work with Steam version #898

Closed
wants to merge 10 commits into from
316 changes: 140 additions & 176 deletions markdown.lua
Original file line number Diff line number Diff line change
@@ -1,227 +1,191 @@
-- Save a text screen in markdown (eg for reddit)
-- Save selected unit/item' description in markdown (e.g., for reddit)
-- This script extracts descriptions of selected units or items and saves them in markdown format.
-- This is a derivatiwe work based upon scripts/forum-dwarves.lua by Caldfir and expwnent
-- Adapted for markdown by Mchl https://github.com/Mchl
-- Updated to work with Steam version by Glotov4 https://github.com/glotov4

local helpstr = [====[
glotov4 marked this conversation as resolved.
Show resolved Hide resolved

markdown
========
Save a copy of a text screen in markdown (useful for Reddit, among other sites).
See `forum-dwarves` for BBCode export (for e.g. the Bay12 Forums).
===========
Tags: fort | inspection | units

Save a description of selected unit or item to a markdown file.

This script will attempt to get description of selected unit or item.
For units, script will collect:
- Name, race, age, profession
- Description, as seen at the Unit/Health/Description screen
- Traits, as seen at the Unit/Personality/Traits

For items:
- Decorated name ("☼«☼Chalk Statue of Dakas☼»☼")
- Full description, as seen when clicking "View this item's sheet"

Then the script will append marked-down version of this data
to the target file (for easy pasting on reddit for example).

This script doesn't work with the data from other screens.

This script will attempt to read the current df-screen, and if it is a
text-viewscreen (such as the dwarf 'thoughts' screen or an item / creature
'description') or an announcement list screen (such as announcements and
combat reports) then append a marked-down version of this text to the
target file (for easy pasting on reddit for example).
Previous entries in the file are not overwritten, so you
may use the``markdown`` command multiple times to create a single
document containing the text from multiple screens (eg: text screens
from several dwarves, or text screens from multiple artifacts/items,
or some combination).
may use the ``markdown`` command multiple times to create a single
document containing the description of multiple items & units.

Usage::
By default, data is stored in markdown_/YourWorldName/_export.md

See `forum-dwarves` for BBCode export (for e.g. the Bay12 Forums).

Usage
-----

markdown [-n] [filename]

:-n: overwrites contents of output file
:filename:
if provided, save to :file:`md_{filename}.md` instead
of the default :file:`md_export.md`
if provided, save to :file:`markdown_{filename}.md` instead
of the default :file:`markdown_/worldName/_export.md`
:help: show help

Examples
-----

### -chalk statue of Bìlalo Bandbeach-

#### Description:
This is a well-crafted chalk statue of Bìlalo Bandbeach. The item is a well-designed image of Bìlalo Bandbeach the elf and Lani Lyricmonks the Learned the ettin in chalk by Domas Uthmiklikot. Lani Lyricmonks the Learned is striking down Bìlalo Bandbeach. The artwork relates to the killing of the elf Bìlalo Bandbeach by the ettin Lani Lyricmonks the Learned with Hailbite in The Forest of Indignation in 147.

The screens which have been tested and known to function properly with
this script are:
---

#. dwarf/unit 'thoughts' screen
#. item/art 'description' screen
#. individual 'historical item/figure' screens
#. manual
#. announements screen
#. combat reports screen
#. latest news (when meeting with liaison)
### Lokum Alnisendok, dwarf, 27 years old Presser.

There may be other screens to which the script applies. It should be
safe to attempt running the script with any screen active, with an
error message to inform you when the selected screen is not appropriate
for this script.
#### Description:
A short, sturdy creature fond of drink and industry.

He is very quick to tire.

His very long beard is neatly combed. His very long sideburns are braided. His very long moustache is neatly combed. His hair is clean-shaven. He is average in size. His nose is sharply hooked. His nose bridge is convex. His gold eyes are slightly wide-set. His somewhat tall ears are somewhat narrow. His hair is copper. His skin is copper.

#### Personality:
He has an amazing memory, but he has a questionable spatial sense and poor focus.

He doesn't generally think before acting. He feels a strong need to reciprocate any favor done for him. He enjoys the company of others. He does not easily hate or develop negative feelings. He generally finds himself quite hopeful about the future. He tends to be swayed by the emotions of others. He finds obligations confining, though he is conflicted by this for more than one reason. He doesn't tend to hold on to grievances. He has an active imagination.

He needs alcohol to get through the working day.

---
]====]

local args = {...}
local utils = require('utils')
local gui = require('gui')

-- Argument processing
local args = {...}
if args[1] == 'help' then
print(helpstr)
glotov4 marked this conversation as resolved.
Show resolved Hide resolved
return
end

local writemode = 'a'
-- Determine file write mode and filename
local writemode = 'a' -- append (default)
local filename
local worldName = dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name)):gsub(" ", "_")

-- check if we want to append to an existing file (default) or overwrite previous contents
if args[1] == '-n' or args[1] == '/n' then
writemode = 'w'
writemode = 'w' -- overwrite
table.remove(args, 1)
end

local filename

if args[1] ~= nil then
filename = 'md_' .. table.remove(args, 1) .. '.md'
filename = 'markdown_' .. table.remove(args, 1) .. '.md'
else
filename = 'md_export.md'
filename = 'markdown_' .. worldName .. '_export.md'
end

local utils = require 'utils'
local gui = require 'gui'
local dialog = require 'gui.dialogs'

local scrn = dfhack.gui.getCurViewscreen()
local flerb = dfhack.gui.getFocusString(scrn)

local months = {
[1] = 'Granite',
[2] = 'Slate',
[3] = 'Felsite',
[4] = 'Hematite',
[5] = 'Malachite',
[6] = 'Galena',
[7] = 'Limestone',
[8] = 'Sandstone',
[9] = 'Timber',
[10] = 'Moonstone',
[11] = 'Opal',
[12] = 'Obsidian',
}

-- Utility functions
local function getFileHandle()
return io.open(filename, writemode)
return assert(io.open(filename, writemode), "Error opening file: " .. filename)
end

local function closeFileHandle(handle)
handle:write('\n***\n\n')
handle:write('\n---\n\n')
handle:close()
print ('Data exported to "' .. filename .. '"')
print ('\nData exported to "' .. filename .. '"')
end

local function reformat(strin)
local strout = strin

-- [P] tags seem to indicate a new paragraph
local newline_idx = string.find(strout, '[P]', 1, true)
while newline_idx ~= nil do
strout = string.sub(strout, 1, newline_idx - 1) .. '\n***\n\n' .. string.sub(strout, newline_idx + 3)
newline_idx = string.find(strout, '[P]', 1, true)
end

-- [R] tags seem to indicate a new 'section'. Let's mark it with a horizontal line.
newline_idx = string.find(strout, '[R]', 1, true)
while newline_idx ~= nil do
strout = string.sub(strout, 1, newline_idx - 1) .. '\n***\n\n' .. string.sub(strout,newline_idx + 3)
newline_idx = string.find(strout, '[R]', 1, true)
end

-- No idea what [B] tags might indicate. Just removing them seems to work fine
newline_idx = string.find(strout, '[B]', 1, true)
while newline_idx ~= nil do
strout = string.sub(strout, 1, newline_idx - 1) .. string.sub(strout,newline_idx + 3)
newline_idx = string.find(strout, '[B]', 1, true)
end

-- Reddit doesn't support custom colors in markdown. We need to remove all color information :(
local color_idx = string.find(strout, '[C:', 1, true)
while color_idx ~= nil do
strout = string.sub(strout, 1, color_idx - 1) .. string.sub(strout, color_idx + 9)
color_idx = string.find(strout, '[C:', 1, true)
end

return strout
local function reformat(str)
-- [B] tags seem to indicate a new paragraph
-- [R] tags seem to indicate a sub-blocks of text.Treat them as paragraphs.
-- [P] tags seem to be redundant
-- [C] tags indicate color. Remove all color information
return str:gsub('%[B%]', '\n\n')
:gsub('%[R%]', '\n\n')
:gsub('%[P%]', '')
:gsub('%[C:%d+:%d+:%d+%]', '')
:gsub('\n\n+', '\n\n')
end

local function formattime(year, ticks)
-- Dwarf Mode month is 33600 ticks long
local month = math.floor(ticks / 33600)
local dayRemainder = ticks - month * 33600

-- Dwarf Mode day is 1200 ticks long
local day = math.floor(dayRemainder / 1200)
local timeRemainder = dayRemainder - day * 1200

-- Assuming a 24h day each Dwarf Mode tick corresponds to 72 seconds
local seconds = timeRemainder * 72

local H = string.format("%02.f", math.floor(seconds / 3600));
local m = string.format("%02.f", math.floor(seconds / 60 - (H * 60)));
local i = string.format("%02.f", math.floor(seconds - H * 3600 - m * 60));

day = day + 1
if (day == 1 or day == 21) then
day = day .. 'st' --luacheck: retype
elseif (day == 2 or day == 22) then
day = day .. 'nd' --luacheck: retype
elseif (day == 3 or day == 23) then
day = day .. 'rd' --luacheck: retype
else
day = day .. 'th' --luacheck: retype
end

return (day .. " " .. months[month + 1] .. " " .. year .. " " .. H .. ":" .. m..":" .. i)
local function getNameRaceAgeProf(unit)
--%s is a placeholder for a string, and %d is a placeholder for a number.
return string.format("%s, %d years old %s.", dfhack.units.getReadableName(unit), df.global.cur_year - unit.birth_year, dfhack.units.getProfessionName(unit))
end

if flerb == 'textviewer' then
local scrn = scrn --as:df.viewscreen_textviewerst

local lines = scrn.src_text

if lines ~= nil then

local log = getFileHandle()
log:write('### ' .. dfhack.df2utf(scrn.title) .. '\n')
-- Main logic for item and unit processing
local item = dfhack.gui.getSelectedItem(true)
local unit = dfhack.gui.getSelectedUnit(true)

print('Exporting ' .. dfhack.df2console(scrn.title) .. '\n')
if not item and not unit then
print([[
Error: No unit or item is currently selected.
- To select a unit, click on it.
- For items that are installed as buildings (like statues or beds),
open the building's interface in the game and click the magnifying glass icon.

for n,x in ipairs(lines) do
log:write(reformat(dfhack.df2utf(x.value)).." ")
-- debug output
-- print(x.value)
end
closeFileHandle(log)
end

elseif flerb == 'announcelist' then
local scrn = scrn --as:df.viewscreen_announcelistst

local lines = scrn.reports

if lines ~= nil then
local log = getFileHandle()
local lastTime = ""

for n,x in ipairs(lines) do
local currentTime = formattime(x.year, x.time)
if (currentTime ~= lastTime) then
lastTime = currentTime
log:write('\n***\n\n')
log:write('## ' .. currentTime .. '\n')
end
-- debug output
-- print(x.text)
log:write(x.text .. '\n')
end
closeFileHandle(log)
end


elseif flerb == 'topicmeeting' then
local lines = scrn.text --hint:df.viewscreen_topicmeetingst

if lines ~= nil then
local log = getFileHandle()
Please select a valid target in the game and try running the script again.]])
-- Early return to avoid proceeding further
return
end

for n,x in ipairs(lines) do
-- debug output
-- print(x.value)
log:write(x.value .. '\n')
local log = getFileHandle()

if item then
-- Item processing
local item_raw_name = dfhack.items.getDescription(item, 0, true)
local item_raw_description = df.global.game.main_interface.view_sheets.raw_description
log:write('### ' .. dfhack.df2utf(item_raw_name) .. '\n\n#### Description: \n' .. reformat(dfhack.df2utf(item_raw_description)) .. '\n')
print('Exporting description of the ' .. item_raw_name)

elseif unit then
-- Unit processing
-- Simulate UI interactions to load data into memory (click through tabs)
local screen = dfhack.gui.getDFViewscreen()
-- Click "Personality"
df.global.gps.mouse_x = 145
glotov4 marked this conversation as resolved.
Show resolved Hide resolved
df.global.gps.mouse_y = 11
gui.simulateInput(screen, '_MOUSE_L')

-- Click "Health"
df.global.gps.mouse_x = 118
df.global.gps.mouse_y = 13
gui.simulateInput(screen, '_MOUSE_L')

-- Click "Health/Description"
df.global.gps.mouse_x = 142
df.global.gps.mouse_y = 15
gui.simulateInput(screen, '_MOUSE_L')

local unit_description_raw = df.global.game.main_interface.view_sheets.unit_health_raw_str[0].value
local unit_personality_raw = df.global.game.main_interface.view_sheets.personality_raw_str

if unit_description_raw or unit_personality_raw then
glotov4 marked this conversation as resolved.
Show resolved Hide resolved
log:write('### ' .. dfhack.df2utf(getNameRaceAgeProf(unit)) .. '\n\n#### Description: \n' .. reformat(dfhack.df2utf(unit_description_raw)) .. '\n\n#### Personality: \n')
for _, unit_personality in ipairs(unit_personality_raw) do
log:write(reformat(dfhack.df2utf(unit_personality.value)) .. '\n')
end
closeFileHandle(log)
print('Exporting Health/Description & Personality/Traits data for: \n' .. dfhack.df2console(getNameRaceAgeProf(unit)))
else
print("Unit has no data in Description & Personality tabs")
end
else
print 'This is not a textview, announcelist or topicmeeting screen. Can\'t export data, sorry.'
else
end

closeFileHandle(log)