CSS, 132 116 115 bytes
a:not(:last-child):nth-child(n+2):after,a:nth-last-child(n+3):after{content:","}a+:last-child:before{content:"and "
<p>
<a>one</a>
</p>
<p>
<a>one</a>
<a>two</a>
</p>
<p>
<a>one</a>
<a>two</a>
<a>three</a>
</p>
<p>
<a>one</a>
<a>two</a>
<a>three</a>
<a>four</a>
</p>
a:not(:last-child):nth-child(n+2):after,a:nth-last-child(n+3):after{content:","}a+:last-child:before{content:"and "
CSS is not seen too often in code golf because it can only format text, but it actually works for this challenge and I thought it would be fun to do. See it in action using the snippet above (click "Show code snippet").
List should be in a linked HTML file with each element surrounded by <a>
tags and separated by line breaks. The list items should be the only elements in their parent element, e.g.
<a>one</a>
<a>two</a>
<a>three</a>
Explanation
a:not(:last-child):nth-child(n+2)::after,
a:nth-last-child(n+3)::after {
content: ",";
}
a + :last-child::before {
content: "and ";
}
Let's consider the ungolfed version above. If you're not familiar with how CSS works, everything outside the curly braces is a selector that determines the set of HTML elements to which the declarations inside the braces apply. Each selector-declaration pair is called a rule. (It's more complicated than that but will suffice for this explanation.) Before any styling is applied, the list appears separated by only spaces.
We want to add commas after every word except the last, except for two-word lists, which get no commas. The first selector, a:not(:last-child):nth-child(n+2):after
, selects all elements except the first and the last. :nth-child(n+2)
is a shorter way of saying :not(:first-child)
, and it basically works by selecting elements whose index (starting at 1) is greater than or equal to 2. (Yes, it still confuses me a little. The MDN docs might help.)
Now we just need to select the first element to get a comma if there are three or more elements total. a:nth-last-child(n+3):after
works like :nth-child
, but counting from the back, so it selects all elements except the last two. The comma takes the union of the two sets, and we use the :after
pseudo-element to add content
immediately after each selected element.
The second rule is easier. We need to add "and" before the last element in the list, unless it is a single element. In other words, we need to select the last element that is preceded by another element. +
is the adjacent sibling selector in CSS.
['we invited the stripper','JFK','Stalin']
\$\endgroup\$