Follow by Email

Friday, August 17, 2012

Administering TeX Live on Ubuntu

The TeX that comes in the Ubuntu package manager is a couple of years old (I have Ubuntu 12.04 LTS here in 2012 and the package manager has TeX Live 2009).  For many people that won't matter but for my text Linear Algebra I needed some more recent packages, which needed a more recent LaTeX kernel ... . So on the urging of a number of smart folks I installed TeX Live 2012 from tug.org.  Here is my experience.

I kept the package manager's packages installed.  One reason is that I know from past experience that if I drop those packages then when I want to install related materials from the package manager, such as AUCtex or Asymptote, then the package manager will complain about unmet dependencies, and I'll try a --force install  and later there will be an update ... and anyway at some point so that chaos sets in.  (Lars Madsen posted a way to avoid this with his knowledge of the package system but of the choices available I went another way, partly because I didn't entirely understand his process and so would have trouble adjusting it if I needed to.)

A first go


I downloaded TeX Live.  It put things in the default of /usr/local/texlive.   My local files go in /usr/local/texlive/texmf-local.  I changed the PATH variable in /etc/profile (maybe I should have used /etc/environment instead, or in addition?) and logged out and back in again.

To check  my setup I copied some personal styles and classes.

    ftpmaint@millstone:~$ ls /usr/local/texlive/texmf-local/tex/latex/local
    charis.sty  gentium.sty  memo.cls  present.sty
    conc.sty    jh.sty       mins.cls  pyweb.cls

I tried to remake the file database


    ftpmaint at millstone:~$ sudo texhash
    [sudo] password for ftpmaint: 
    texhash: Updating /usr/local/share/texmf/ls-R... 
    texhash: Updating /var/lib/texmf/ls-R-TEXMFMAIN... 
    texhash: Updating /var/lib/texmf/ls-R-TEXLIVE... 
    texhash: Updating /var/lib/texmf/ls-R... 
    texhash: Done. 
 
but after then issuing kpsewhich conc.sty failed.  Why?

Turns out (I am skipping over a great deal of head-scratching and googling here), on Ubuntu the sudo command was using the Ubuntu tex's texhash
 
    ftpmaint at millstone:~$ which texhash
    /usr/local/texlive/2012/bin/i386-linux/texhash
    ftpmaint at millstone:~$ sudo which texhash
    /usr/bin/texhash

rather than tug.org's because it has its own PATH.

    ftpmaint at millstone:~$ echo $PATH
    /usr/local/texlive/2012/bin/i386-linux:/usr/lib/lightdm/lightdm:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
    ftpmaint at millstone:~$ echo 'echo $PATH' | sudo sh
    /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Oops.

I got around it by calling the program with its full path.

    ftpmaint at millstone:~$ sudo /usr/local/texlive/2012/bin/i386-linux/texhash
    texhash: Updating /usr/local/texlive/2012/texmf/ls-R... 
    texhash: Updating /usr/local/texlive/2012/texmf-config/ls-R... 
    texhash: Updating /usr/local/texlive/2012/texmf-dist/ls-R... 
    texhash: Updating /usr/local/texlive/2012/../texmf-local/ls-R... 
    texhash: Updating /usr/local/texlive/2012/texmf-var/ls-R... 
    texhash: Done.

The answer


The above was an answer, but it obviously wasn't the answer.  Lars commented, "On my systems I have a specific LaTeX user who owns the LaTeX installation, thus I do not have to run anything as root."  This seemed right.

I created a new user texlive to own the material in /usr/local/texlive.

    ftpmaint at millstone:~$ cd /usr/local    
    ftpmaint at millstone:~$ sudo chown -R ftpmaint:ftpmaint texlive

A comment about this user: I don't want texlive to appear on the choices on the login screen so I gave this account a user id below 500: sudo usermod -u 499 texlive (I got the parameter 500 from /etc/lightdm/users.conf).

Several people cautioned me to watch the umask of this user but because the system sets the default at 022 I was OK. 

 Lars mentioned that he may have multiple TeX Live trees under /usr/local/texlive, say /usr/local/texlive/2011 and /usr/local/texlive2012.  He has a soft link that points to the current one.

    texlive at millstone:~$ ln -s 2012 current

I often have trouble remembering what command options to run, etc., so I like to put that stuff in .sh files.  Here the soft link does its magic; no need to change the script when I add another tree.

     ftpmaint@millstone:/usr/local/texlive$ ls -l
    total 12
    drwxr-xr-x 10 texlive texlive 4096 Aug 17 07:05 2012
    lrwxrwxrwx  1 root    root       4 Aug 16 17:47 current -> 2012
    drwxr-xr-x 10 texlive texlive 4096 Aug 17 07:05 texmf-local
    -rwxr--r--  1 texlive texlive  181 Aug 16 17:48 update.sh
 
    ftpmaint@millstone:/usr/local/texlive$ cat update.sh
    #!/bin/bash
    # Update the current TeX Live installation
    # 2012-Aug-16 Jim Hefferon jhefferon@smcvt.edu
   
    PATH=/usr/local/texlive/current/bin/i386-linux:$PATH
    tlmgr update --all --self

The only remaining hiccup is to remember the password for texlive.  This is the kind of account that a person only uses every couple of months where I could easily forget.  But I use a password keeper program KeePassX to store my passwords so I put this user's information in there.

    
 

Wednesday, March 21, 2012

Looking for a new Linux dist

Too bad.  Ubuntu was good for a couple of years.  Now, I just spent an hour online trying to spelunk how to disable the bootup sound.  No one on the interwebs knows.  It clearly should be in a menu.  It was in a menu.  But now it is not.

I often get up at 4:30 and turn on the ham set, and the computer.  My wife doesn't have to hear the bootup sound at that hour.  But there appears to be no reasonable way to turn it off.

This is not the first time I have spent a frustrating hour on such a task.  I must have spent twenty of them in the last couple of months. Ubuntu was good, but now it is not.

Ah well.  Gotta get a new distro.

Thursday, September 8, 2011

Installing MicroPress's Computer Concrete fonts onto TeX Live under Linux

 I own the Computer Concrete fonts from MicroPress. These are outline fonts so they look nice at any magnification. Because I use a concrete font in my Beamer presentations, the characters get blown up to a good size and I'd like them to show well, so I wanted to install these.

I have Ubuntu 11.04 with a current TeX Live that I installed off the web.  I looked in my local TeX directory /usr/local/share/texmf/fonts (I used this location instead of /usr/share/texmf because it will survive if I install a later version of TeX Live.)

I loaded the vendor's CD into the machine, where it showed up as /media/CFonts/CFONTS.dir.  I see directories for afm, inf, pfb, pfm, setup, and tfm.  There are also some .zip files containing LaTeX styles that I don't think I need as they come with TeX Live.

1) As sudo, I copied the files from /media/CFonts/CFONTS.dir/afm and /media/CFonts/CFONTS.dir/inf into /usr/local/share/texmf/fonts/afm/public/cfonts after making that directory. I was told to put the inf stuff in there also by the TeX Directory Standard document. I made the file permissions rw for the owner root, r for the group, and r for others.

2) I copied the files from the CD's pfb and pfm directories into /usr/local/share/texmf/fonts/type1/public/cfonts after making that directory. As above, I combined the file types in the one directory on the advice of the TDS document, and changed the permissions as above.

3) I copied the files from the CD's tfm directory into /usr/local/share/texmf/fonts/tfm/public/cfonts after making the directory, and I changed the file permissions as above.

4) I copied the CD's /media/CFonts/CFONTS.dir/setup/dvips/CONCRETE.MAP into /usr/local/share/texmf/fonts/map/dvips/cfonts/oncrete.map after making that directory.  I changed its file permissions.

5) On the advice of TUG's font installation page, from the directory /usr/local/share/texmf/fonts/map/dvips/cfonts I ran "sudo texhash" and also "sudo updmap-sys --enable Map=concrete.map"

It worked. The next time I compiled my .tex to a .pdf I could see in the File > Properties > Fonts tab that the cc fonts were included as Type 1.

By the way, I use this LaTeX style to get the concrete fonts.

% From Walter Schmidt post on ctt Jan 10 2005
\renewcommand{\rmdefault}{ccr}
\usepackage[euler-digits]{eulervm}
\linespread{1.04} % approximately
\renewcommand{\bfdefault}{sbc}
% \usepackage{mathdots}

Put it in /usr/local/share/texmf/tex/latex/local/conc.sty and run "sudo texhash" to have it be available as a normal LaTeX style.

Thursday, September 24, 2009

Suckerfish menus in Django

I have some data that I keep in a tree, and I wanted to see if people could browse it in a tree-like interface. All in Django, of course.

(Let me apologize for any errors in transcription below; this is my first time using blogger.)

I'm very wary of cross-browser incompatibilities. So any solution with a lot of Javascript, etc., leaves me worried that a year from now Safari will become incompatible and I'll be left racing to catch up. That'd be bad.

But suckerfish menus are based on CSS which is at least a fairly wide standard. I used a number of web sites as guides, but one that was especially handy was A List Apart.

Let me first cover what the data is. CTAN has perhaps four thousand packages. People should be able to browse them by some reasonable characterization: for instance, maybe "Document Styles > Books > Publisher Styles" would pop up a bunch of packages in that category.

I'd like to allow a number of different characterizations. For instance, Juergen Fenn has a good one that he uses in the TeX Catalogue and I have another. (Actually, my idea is to give each package a primary characterization and then a number of secondary characterizations, where the tree of primary characterizations is the same as the tree of secondary characterizations. For instance, a linguistics package may also contain a font of special symbols so the primary would be by subject area, but the secondary would be in fonts.)

I choose to store the characterization data using mptt (because at the time I was looking it seemed to be all that fit, but it has worked for me so I stuck with it). I call the different characterizations "dimensions" and so my models.py has this.

class Dimen(models.Model):
dimen=models.CharField(
max_length=32,
null=False,
blank=False,
primary_key=True,
help_text="What dimension is the package being characterized by?")
description=models.CharField(
max_length=72,
null=False,
blank=False,
db_index=False,
help_text="Description of this dimension.")

def __unicode__(self):
return self.dimen

class Meta:
pass


and also this.


class Characterizations(models.Model):
parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
dimen=models.ForeignKey(
Dimen,
related_name='characterizations_fk_dimen',
null=True,
blank=False,
db_index=True,
help_text="Dimension of characterization")
title=models.CharField(
max_length=72,
null=False,
blank=False,
# unique=True,
db_index=True,
help_text="Title of this node (short)")
full_title=models.TextField( # denormalize for speed
null=False,
blank=False,
# unique=True,
db_index=True,
help_text="Title of this node, from the root")
description=models.CharField(
max_length=72*24,
null=False,
blank=False,
db_index=True,
help_text="Description of this node (a paragraph)")
path=models.CharField(
max_length=100,
null=True,
blank=True,
db_index=True,
help_text="Where full_title is a > b > c this is a/b/c"
)

def __unicode__(self):
return repr(unicode(self.full_title))

class Meta:
ordering=['id']


mptt.register(Characterizations)

(note that last line; it is not part of the class definition). Note the denormalization fields in the model class-- searching by url, for instance, is a help.

Finally, the packages are characterized in this way.

class Characterization(models.Model):
characterization=models.ForeignKey(
Characterizations,
related_name='characterization_fk_char_id',
null=False,
blank=False,
db_index=True,
help_text="Identifier of node in tree")
pkg_id=models.ForeignKey(
Package,
related_name='characterization_fk_pkg_id',
null=True,
blank=False,
db_index=True,
help_text="Identifier of package")

def __unicode__(self):
return self.pkg_id.pkg_id

class Meta:
pass

To populate the database, I can't put a .sql file in the sql/ directory of my project because mptt has fields that it uses for bookkeeping and I don't know what values it likes there. So I wrote a script characterizations.py to populate it. I have to import the relevant Django stuff


import django.core, django.core.management
import az.settings
import django.db
from django.db import models
from django import forms

and some stuff from my project

import az, az.pkgs
from az.pkgs.models import LicenseTypes, Languages, Package, Maintainers, PackageHistory, PackageText, License, OnCtan, OffCtan, RelatedPackages, Keywords, Keyword, Authors, Author, Distributions, Distribution, TexLiveTypes, TexLivePackages, TexLiveCatalogue, Upload, Dimen, Characterizations, Characterization
from az.util import util

and then I make use of a couple of add-a-node routines. The idea is to run code that does this (I'm showing just a small part).

def buildPrimaryByFunctionTree():
"""Build the primary by-function trees
"""
p=Dimen.objects.get(dimen__exact='primary')
# ------------------ Top -------------------------
pRoot=Characterizations.objects.create(
dimen=p,
title='',
full_title='',
description=p.description)
pRoot.parent=None
pRoot.save()
# ----------- Top > Document types -----------------
Document_types=addNode(pRoot,'Document types',
'Everything from books and articles to memos and letters. Also here are documents for specific publishers.')
# ----------- Top > Document types > Books -----------------
Books=addNode(Document_types,'Books',
'Books.')
Publisher_styles=addNode(Books,'Publisher styles',
'Styles for books from specific publishers.')
Others_books=addNode(Books,'Others',
'Other packages.')

The add-a-node routines were weird:

BRANCH_SEPARATOR=' > ' # separate parts of a branch; should not occur in titles
def getBranchTitle(n):
"""From the node, get the branch title, like 'Top > Fonts' (assumes
n.full_title is not available).
"""
# Like: return n.full_title but get it for setting in the dB
ancestorTitles=[a.title for a in n.get_ancestors(ascending=False)]
ancestorTitles.append(n.title)
branchTitle=BRANCH_SEPARATOR.join(ancestorTitles[1:])
return branchTitle

and

def getRootNode(dimen_str):
"""Get the root node of the tree with the given dimen
"""
nodeQ=Characterizations.objects.filter(dimen__exact=dimen_str)
if not(nodeQ):
return None
return nodeQ[0].get_root()

and

def getNodeFromBranchTitle(branchTitle,dimen_str):
"""From the branch title, like 'Top > Fonts', get the node.
""" n=Characterizations.objects.filter(dimen__dimen__exact=dimen_str).get(full_title__exact=branchTitle)
return n

were reasonable, but this one

def addNode(parentNode,title,description):
# I don't get this. I appear to have to fetch the parent node so that
# it is the most recent thing on mptt's mind. See mptt's doctests.py
# in the section marked "Creation"
pN=Characterizations.objects.get(
id=parentNode.id)
childNode=Characterizations(parent=pN,
dimen=pN.dimen,
title=title,
full_title=title,
description=description)
childNode.insert_at(pN,
position='first-child',
commit=False)
childNode.full_title=getBranchTitle(childNode)
childNode.path=util.path_components_to_url('',childNode.full_title.split(BRANCH_SEPARATOR),'')
childNode.save()
return childNode

was so strange that I thought I was doing it wrong. Anyway, it correctly populates the tree.

So now I have a tree. I need to offer the visitor the chance to pick a dimension, and then display the resulting tree using suckerfish menus.

In urls.py I put

urlpatterns += patterns('az.views',
(r'^characterization/choose_dimen/?$','characterization.choose_dimen'),
(r'^characterization/?(?P<pth>.*)$','characterization.main'),

and then I needed a views/characterization.py . I also needed to make a widget, a field, and a form.

The widget required some fiddling. I feed it choices that are pairs
(url_path, [list of tree node labels]) . It uses the length of the list in the second entry to decide if it needs to go to a child node, or if this is a sibling node, etc. Then it renders a simple html structure like this (based on the discussion from A List Apart; see above).

<ul id="sfish">
<li><a href="/characterization/primary/">pick one</a>
<ul><li><a href="/characterization/primary/"></a>
</li><li><a href="/characterization/primary/document-types/">Document types</a>
<ul><li><a href="/characterization/primary/document-types/books/">Books</a>
<ul><li><a href="/characterization/primary/document-types/books/publisher-styles/">Publisher styles</a></li>
<li><a href="/characterization/primary/document-types/books/others/">Others</a>
</li></ul>
</li><li><a href="/characterization/primary/document-types/articles/">Articles</a>

Here is the widget code.

class PickCharacterizationWidget(forms.RadioSelect):
def render(self,name,value,attrs=None,choices=()):
"""Display the tree in a form suitable for suckerfish menus
choices list of pairs (string, list of strings) where the first
is a link to the page you go to if you choose this, and
list of strings is a list like ['a','b','c'] to represent
'a > b > c'
"""
INDENT=" "*4
r=["<ul id="'sfish'">"]
prior_ft_list=[None]
for lnk,ft_list in self.choices:
# print "ft is ",ft
# r.append("\n++"+ft)
t,s=len(ft_list),len(prior_ft_list)
if t>s:
# r.append("")
r.append("\n"+INDENT*t+"<ul>")
elif t==s:
if prior_ft_list[-1]: # not very first entry
r.append("")
r.append("\n"+INDENT*t)
elif t<s:
for i in range(s-t):
r.append("\n"+INDENT*(s-i)+"</li></ul>")
r.append("\n"+INDENT*t)
r.append("</li><a href="http://www.blogger.com/%s">>" % (lnk,)+ft_list[-1]+"</a>")
prior_ft_list=ft_list
for i in range(len(prior_ft_list)-len(ft_list)):
if i>1:
r.append("</li></ul>")
else:
r.append("")
r.append("\n"+INDENT+"\n")
return "".join(r)

Note that this all needs CSS to work. Here is the relevant section of mine (it ain't beautiful).

/* See http://htmldog.com/articles/suckerfish/dropdowns/ */
#sfish, #sfish ul { /* all lists */
padding: 0;
margin: 0;
list-style: none;
float : left;
width : 11em;
}

#sfish li { /* all list items */
position : relative;
float : left;
line-height : 1.25em;
margin-bottom : -1px;
width: 11em;
}

#sfish li ul { /* second-level lists */
position : absolute;
left: -999em;
margin-left : 21.05em;
margin-top : -1.35em;
}

#sfish li ul ul { /* third-and-above-level lists */
left: -999em;
}

#sfish li a {
width: 20em;
w\idth : 20em;
display : block;
color : black;
font-weight : normal;
text-decoration : none;
background-color : white;
border : 1px solid black;
padding : 0 0.5em;
}

#sfish li a:hover {
color : black;
background-color : #bababa;
}

#sfish li:hover ul ul,
#sfish li:hover ul ul ul,
#sfish li.sfhover ul ul,
#sfish li.sfhover ul ul ul
#sfish li.sfhover ul ul ul ul
{
left: -999em;
}

#sfish li:hover ul,
#sfish li li:hover ul,
#sfish li li li:hover ul,
#sfish li li li li:hover ul,
#sfish li.sfhover ul,
#sfish li li.sfhover ul,
#sfish li li li.sfhover ul
#sfish li li li li.sfhover ul
{ /* lists nested under hovered list items */
left: auto;
}

#content {
margin-left : 12em;
}

There is also a little bit of Javascript.


sfHover = function() {
var sfEls = document.getElementById("nav").getElementsByTagName("LI");
for (var i=0; i<sfEls.length; i++) {
sfEls[i].onmouseover=function() {
this.className+=" sfhover";
}
sfEls[i].onmouseout=function() {
this.className=this.className.replace(new RegExp(" sfhover\\b"), "");
}
}
}
if (window.attachEvent) window.attachEvent("onload", sfHover);


I don't understand JavaScript so I can't vouch for it, but it came from A List Apart so I threw it in.

Here is the field that makes the choices.

from az.pkgs.data.characterizations import BRANCH_SEPARATOR
DEFAULT_CHARACTERIZATION_DIMENSION='primary'
class PickCharacterizationField(forms.ChoiceField):
"""The user picks one characterization to look at.
"""
def __init__(self, dimen=None, choices=(), required=True, widget=widgets.PickCharacterizationWidget, label=None, initial=None, help_text=None):
util.printFlush("forms.py PickCharacterizationField() dimen="+repr(dimen))
if dimen:
self.dimen=dimen
else:
try:
self.dimen=initial['dimen']
except:
self.dimen=DEFAULT_CHARACTERIZATION_DIMENSION
if not(choices):
choices=self.get_characterizations(self.dimen)
super(PickCharacterizationField, self).__init__(choices=choices, required=required, widget=widget, label=label, initial=initial, help_text=help_text)
self.widget.choices=self.choices

def get_characterizations(self,dimen,separator=BRANCH_SEPARATOR):
"""Return a list from a traversal of the tree:
(node (subnode1, (..)), (subnode2, ())..)
dimen string The Dimen.dimen field pointed to by this characterization
separator string How fields are separated in the full_title's
"""
try:
d=Dimen.objects.get(dimen__exact=dimen)
nodes=Characterizations.objects.filter(dimen=d)
except:
nodes=[]
node_list=[n.full_title.split(separator) for n in nodes] choices=[(util.path_components_to_url(u'/characterization/'+dimen+u'/',ft_list,u'/'),ft_list) for ft_list in node_list]
choices=[(x,['pick one']+y) for x,y in choices]
choices=[(u'/characterization/'+dimen+u'/',['pick one'])]+choices
return choices

Now came the main question: how to get the choices into the widget? I had to ask on the Django Users list and a nice person there helped me out (thanks to you, Daniel Roseman!). I made a form where the initialization function reaches out and touches the field.

class PickCharacterizationForm(forms.Form):
def __init__(self,*args,**kwargs):
dimen=kwargs.pop('dimen',None)
super(PickCharacterizationForm,self).__init__(*args,**kwargs)
pick_field=self.fields['pick']
if dimen:
pick_field.dimen = dimen
pick_field.choices=pick_field.get_characterizations(dimen)

pick=PickCharacterizationField(required=True,help_text='Select a characterization',widget=widgets.PickCharacterizationWidget)

class Media:
js=('js/sfish/sfhover.js',)

And it works! I have two routines in characterization.py
that either search for the list of dimensions.
def choose_dimen(req,tmplt=CHARACTERIZATION_TEMPLATE):
"""Allow a person to choose a dimension for the characterization
dimen string Default choice
"""
pageis=PageNameMap(PAGES,initialPageName='choose_dimen')
dI=Dimen.objects.all()
dimen_triples=[(d.dimen,d.dimen.capitalize(),d.description) for d in dI]
d={'pageis':pageis,
'dimen_triples':dimen_triples,
'breadcrumbs':breadcrumbs(None,None),
}
ctext=Context(d)
return render_to_response(tmplt,ctext)

or else invoke the form.

def main(req,pth=None,tmplt=CHARACTERIZATION_TEMPLATE):
pageis=PageNameMap(PAGES,initialPageName='main')
d={'pageis':pageis,
'breadcrumbs':breadcrumbs(None,None),
}
ctext=Context(d)
parts=pth.split('/')
try:
dimen=parts[0].strip()
cdr=parts[1:]
except:
dimen,cdr=None,None
if not(dimen):
return HttpResponseRedirect('/characterization/choose_dimen/')
try:
dI=Dimen.objects.get(dimen__exact=dimen)
dimen_description=dI.description
except Exception, err:
dimen=re.sub('[a-zA-Z0-9_\-]','',dimen) # safe for display on page
dimen_description=None
if not(dI):
return HttpResponseRedirect('/characterization/choose_dimen/')
f0=PickCharacterizationForm(dimen=dimen)
forms=[f0]
# See which packages are characterized in this way
pkg_list=[]
if cdr:
cQ=Characterization.objects.filter(characterization__path__exact='/'.join(cdr))
for cI in cQ:
pkg_list.append(cI.pkg_id.pkg_id)
# Cash out
ctext['forms']=forms
ctext['dimen']=dimen
ctext['dimen_description']=dI.description
ctext['full_title']=' > '.join(cdr)
ctext['pkg_list']=pkg_list
# Finish
return render_to_response(tmplt,ctext)

It is all still rough, and I haven't said what some of the routines do (such as PageNameMap), but perhaps this might help someone who is looking for a place to start with suckerfish and Django.

In the end, though, my menus proved to be too long for the page. The slide off the bottom of the page and so are very hard to use. Oh well.





Link

Wednesday, November 26, 2008

First blog post

This is a test of blogger.