#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""PyLNP main library."""
import os
import sys
from . import log
from .json_config import JSONConfiguration
VERSION = 'dev'
[docs]
class UI(object):
"""
Specifies the interface required by the core PyLNP library for communicating
with the user. Provided for reference; UIs do not need to inherit from this.
"""
[docs]
def start(self):
"""Notifies the UI to start. On return, PyLNP will terminate."""
[docs]
def on_update_available(self):
"""
Called when an update is available. Use this to show a notification
and prompt the user for further action.
"""
[docs]
def on_program_running(self, path, is_df):
"""
Called when attempting to launch a program that is already running.
<path> provides the path to the program that is being launched, so you
can request a forced launch.
<is_df> specifies if the program is DF (True) or a utility (False).
"""
[docs]
def on_invalid_config(self, errors):
"""
Called before running DF if an invalid configuration is detected.
<errors> contains a list of discovered problems, which should be shown
to the user.
A true return value will launch DF anyway; a false return value cancels.
"""
[docs]
def on_request_update_permission(self, interval):
"""
Called when PyLNP.json specifies a desired update interval but the
user configuration does not hold a value for this.
<interval> contains the number of days between update checks.
A true return value will change the configuration to use the specified
interval. A false return value will turn off automatic update checks.
"""
[docs]
def on_query_migration(self):
"""
When no saves are detected, this function will be called.
This should provide the user with an option to import a previous
DF install or starter pack into the newly selected DF version.
"""
lnp = None
[docs]
class PyLNP(object):
"""
PyLNP library class.
Acts as an abstraction layer between the UI and the Dwarf Fortress
instance.
"""
# pylint: disable=too-many-instance-attributes
def __init__(self):
"""Constructor for the PyLNP library."""
# pylint:disable=global-statement
global lnp
lnp = self
self.args = self.parse_commandline()
self.BASEDIR = '.'
if sys.platform == 'win32':
self.os = 'win'
elif sys.platform.startswith('linux'):
self.os = 'linux'
elif sys.platform == 'darwin':
self.os = 'osx'
self.bundle = ''
if hasattr(sys, 'frozen'):
self.bundle = self.os
os.chdir(os.path.dirname(sys.executable))
if self.bundle == 'osx':
# OS X bundles start in different directory
os.chdir('../../..')
else:
os.chdir(os.path.join(os.path.dirname(__file__), '..'))
from . import update
self.folders = []
self.df_info = None
self.settings = None
self.running = {}
self.autorun = []
self.updater = None
self.config = None
self.userconfig = None
self.ui = None
self.initialize_program()
self.initialize_df()
self.new_version = None
self.initialize_ui()
update.check_update()
from . import paths
save_dir = paths.get('save')
saves_exist = os.path.isdir(save_dir) and os.listdir(save_dir)
if paths.get('df') and not saves_exist:
self.ui.on_query_migration()
self.ui.start()
[docs]
def initialize_program(self):
"""Initializes the main program (errorlog, path registration, etc.)."""
from . import errorlog, paths, utilities
self.BASEDIR = '.'
self.detect_basedir()
paths.clear()
paths.register('root', self.BASEDIR)
errorlog.start()
paths.register('lnp', self.BASEDIR, 'LNP')
if not os.path.isdir(paths.get('lnp')):
log.w('LNP folder is missing!')
paths.register('keybinds', paths.get('lnp'), 'Keybinds')
paths.register('graphics', paths.get('lnp'), 'Graphics')
paths.register('utilities', paths.get('lnp'), 'Utilities')
paths.register('colors', paths.get('lnp'), 'Colors')
paths.register('embarks', paths.get('lnp'), 'Embarks')
paths.register('tilesets', paths.get('lnp'), 'Tilesets')
paths.register('baselines', paths.get('lnp'), 'Baselines')
paths.register('mods', paths.get('lnp'), 'Mods')
config_file = 'PyLNP.json'
if os.access(paths.get('lnp', 'PyLNP.json'), os.F_OK):
config_file = paths.get('lnp', 'PyLNP.json')
default_config = {
"folders": [
["Savegame folder", "<df>/data/save"],
["Utilities folder", "LNP/Utilities"],
["Graphics folder", "LNP/Graphics"],
["-", "-"],
["Main folder", ""],
["LNP folder", "LNP"],
["Dwarf Fortress folder", "<df>"],
["Init folder", "<df>/data/init"]
],
"links": [
["DF Homepage", "https://www.bay12games.com/dwarves/"],
["DF Wiki", "https://dwarffortresswiki.org/"],
["DF Forums", "http://www.bay12forums.com/smf/"]
],
"to_import": [
['text_prepend', '<df>/gamelog.txt'],
['text_prepend', '<df>/ss_fix.log'],
['text_prepend', '<dfhack_config>/dfhack.history'],
['copy_add', '<df>/data/save'],
['copy_add', '<df>/soundsense',
'LNP/Utilities/Soundsense/packs'],
['copy_add', 'LNP/Utilities/Soundsense/packs'],
['copy_add', 'User Generated Content']
],
"hideUtilityPath": False,
"hideUtilityExt": False,
"updates": {
"updateMethod": ""
}
}
self.config = JSONConfiguration(config_file, default_config)
self.userconfig = JSONConfiguration('PyLNP.user')
self.autorun = []
utilities.load_autorun()
if self.args.terminal_test_parent:
from . import terminal
errorlog.stop()
sys.exit(terminal.terminal_test_parent(
self.args.terminal_test_parent[0]))
if self.args.terminal_test_child:
from . import terminal
errorlog.stop()
sys.exit(terminal.terminal_test_child(
self.args.terminal_test_child[0]))
[docs]
def initialize_df(self):
"""Initializes the DF folder and related variables."""
from . import df
self.df_info = None
self.folders = []
self.settings = None
df.find_df_folder()
[docs]
def initialize_ui(self):
"""Instantiates the UI object."""
from tkgui.tkgui import TkGui
self.ui = TkGui()
[docs]
def reload_program(self):
"""Reloads the program to allow the user to change DF folders."""
self.args.df_folder = None
self.initialize_program()
self.initialize_df()
self.initialize_ui()
self.ui.start()
[docs]
def parse_commandline(self):
"""Parses and acts on command line options."""
args = self.get_commandline_args()
if args.debug == 1:
log.set_level(log.DEBUG)
elif args.debug is not None and args.debug > 1:
log.set_level(log.VERBOSE)
if args.release_prep:
args.raw_lint = True
log.d(args)
return args
[docs]
@staticmethod
def get_commandline_args():
"""Responsible for the actual parsing of command line options."""
import argparse
parser = argparse.ArgumentParser(
description="PyLNP " + VERSION)
parser.add_argument(
'-d', '--debug', action='count',
help='Turn on debugging output (use twice for extra verbosity)')
parser.add_argument(
'--raw-lint', action='store_true',
help='Verify contents of raw files and exit')
parser.add_argument(
'df_folder', nargs='?',
help='Dwarf Fortress folder to use (if it exists)')
parser.add_argument(
'--version', action='version', version="PyLNP " + VERSION)
parser.add_argument(
'--df-executable', action='store',
help='Override DF/DFHack executable name')
parser.add_argument(
'--release-prep', action='store_true',
help=argparse.SUPPRESS)
parser.add_argument(
'--terminal-test-parent', nargs=1,
help=argparse.SUPPRESS)
parser.add_argument(
'--terminal-test-child', nargs=1,
help=argparse.SUPPRESS)
return parser.parse_known_args()[0]
[docs]
def save_config(self):
"""Saves LNP configuration."""
self.userconfig.save_data()
[docs]
def macos_check_translocated(self):
"""Verify that macOS isn't isolating our application."""
assert self.os == 'osx'
if '/AppTranslocation/' in sys.executable:
try:
from tkinter import messagebox
except ImportError:
import tkMessageBox as messagebox
messagebox.showinfo(
'Cannot launch PyLNP',
'PyLNP cannot be run from a disk image or from the Downloads '
'folder. Please copy the PyLNP app and its other Dwarf '
'Fortress files elsewhere, such as to the Applications folder.')
[docs]
def detect_basedir(self):
"""Detects the location of Dwarf Fortress by walking up the directory
tree."""
prev_path = '.'
from . import df
try:
while os.path.abspath(self.BASEDIR) != prev_path:
df.find_df_folders()
if len(self.folders) != 0:
return
prev_path = os.path.abspath(self.BASEDIR)
self.BASEDIR = os.path.join(self.BASEDIR, '..')
except UnicodeDecodeError:
# This seems to no longer be an issue, but leaving in the check
# just in case
log.e(
"PyLNP is being stored in a path containing non-ASCII "
"characters, and cannot continue. Folder names may only use "
"the characters A-Z, 0-9, and basic punctuation.\n"
"Alternatively, you may run PyLNP from source using Python 3.")
sys.exit(1)
log.e("Could not find any Dwarf Fortress installations.")
if self.os == 'osx':
self.macos_check_translocated()
sys.exit(2)
# vim:expandtab