g | x | w | all
Bytes Lang Time Link
195Python 3.8 prerelease230805T212937ZDonat
257AWK230817T142533ZMarius_C
276Java JDK230824T181610ZDonat
271BASH230828T095017ZIvan
177JavaScript Node.js230821T204343ZDonat
289Haskell230818T174535ZDonat
190Perl 5230817T070117ZDonat
247Python 3.8230804T162400ZCursorCo
08605AB1E230811T121922ZKevin Cr
nanPython 3230803T235855Zanderium
235Javascript230803T172940Zccprog
244Perl 5230805T150004Zgood old
188JavaScript ES12230804T175339ZArnauld
213K ngn/k230803T095734Zdoug
425Python3230803T162027ZAjax1234
078Pyth230803T221935ZCursorCo
061Charcoal230803T153908ZNeil

Python 3.8 (pre-release), 239 209 205 204 199 197 196 195 bytes

def f(s,n):
 i=n%-2;g="--.\n<-'>"[i:7-i]
 while(i:=i+2)<n:m="--"*i;g=m+f"--.\n.-%s |\n'{m}-'"%g.replace("\n"," |\n| ");z=d=0;*a,=g
 for c in s:d+=a[z]in".'";a[z]=c;z+=2*(1,n,-1,-n)[d%4]
 return a

Try it online!

I was able to simplify again by a significant amount. Now the output is a simple array of characters (this is better than before).

The recursive function g makes an empty snake, function f fills in the given string s.

Here are my older versions:

Python 3.8 (pre-release), 271 269 257 254 bytes

This new version (built from the 275 bytes solution) stretches the output rules to the maximum, or even beyond? The output array contains iterables as elements, either strings or arrays of strings. By "".join(e) we always get a string out of element e.

g=lambda n:[(m:="-"*(2*n-3))+"-."]+[".|- "[i>0::2]+g(n-2)[i]+" |"for i in range(n-2)]+[f"'{m}'"]if n>2else["--.",">","<-'"][n<2::2]
def f(s,n):
 z=d=0;a=g(n)
 for c in s:l=list(a[z//n]);d+=l[z%n*2]in".'";l[z%n*2]=c;a[z//n]=l;z+=(1,n,-1,-n)[d%4]
 return a

Try it online!

Python 3.8 (pre-release), 361 ... 275 bytes

g=lambda n:[(m:="-"*(2*n-3))+"-."]+[".|- "[i>0::2]+g(n-2)[i]+" |"for i in range(n-2)]+[f"'{m}'"]if n>2else["--.",">","<-'"][n<2::2]
def f(s,n):
 x,y,k,a=0,0,7,[list(s)for s in g(n)]
 for c in s:k=(7-5*(k>6),-5*(k>1),k)[".'".find(a[y][x])];a[y][x]=c;x+=k//3;y+=k%3-1
 return a

Try it online!

Recursive, but not shortest, unfortunately.

Function g makes an empty snake, function f fills in the string.

AWK, 332 321 317 281 273 257 bytes

func R(C){r=++c<A*A?length($1)<c?i<A?C:S%2?"'":".":substr($1,c,1):S%2?"<":">";g[x,y]=S%2?G r:r G}func f(){for(x=c=S=i=0;i<A=$2;R(B="-",G=++i<A?B:""))y=i;while(S++<=A){for(i=S;i++<A;R("|",G=i<A?" ":B))x+=t=S%2*2-1;for(i=S;i++<A;R(B,G=i<A?B:""))y-=t}return G}

Try it online!


Ungolfed

function result(string2)
{
    rep = string2
    if ( i == $2-1 ) step % 2 ? rep = "'" : rep = "."
    if ( length($1) > cpt ) rep = substr($1, cpt+1, 1)
    if ( cpt == ($2 * $2)-1 ) step % 2 ? rep = "<" : rep = ">"
    return rep
}

function snakearound()
{
    x = 0
    y = 0
    cpt = 0
    step = 0
    for(i = 0; i < $2; i++)
    {
        grid[x, y] = result("-")
        if (i+1 < $2) grid[x, y] = grid[x, y] "-"
        cpt++
        y++
    }
    y--
    while(step <= $2)
    {
        step++
        step % 2 ? tmp = 1 : tmp = -1
        for(i = step; i < $2; i++)
        {
            x += tmp
            grid[x, y] = result("|")
            i == $2-1 ? string = "-" : string = " "
            step % 2 ? grid[x, y] = string grid[x, y] : grid[x, y] = grid[x, y] string
            cpt++
        }
        for(i = step; i < $2; i++)
        {
            y -= tmp
            grid[x, y] = result("-")
            i == $2-1 ? string = "" : string = "-"
            step % 2 ? grid[x, y] = string grid[x, y] : grid[x, y] = grid[x, y] string
            cpt++
        }
    }

    for(i = 0; i < $2; i++){
        for(j = 0; j < $2; j++) printf("%s", grid[i, j])
        printf("\n")
    }
}
```

Java (JDK), 279 278 276 bytes

char[]f(String s,int n){var a=g(n).toCharArray();int z=0,d=0;for(var c:s.toCharArray()){d+=a[z]/2&1;a[z]=c;z+=(d%2<1?1:n)*(d%4<2?2:-2);}return a;}String g(int n){String m;return n>1?(m="--".repeat(n-2))+"--.\n"+(n>2?".-"+g(n-2).replace("\n"," |\n| ")+" |\n'"+m:"<")+"-'":">";}

Try it online!

I have derived this from my JavaScript answer. Java is not really good for code golf, but I think, the result is not too bad.

BASH , 580 349 308 303 291 286 283 277 275 272 271 bytes

Found this very entertaining, thanks! First variant was 580 bytes, then 349, ..., now 271:

s=$1 n=$2 N=$[n+n-1];f(){ b=$1;for((j=1;j<n;j++));{((c<N))&&T+=%s;[[ $4 ]]&&h=$4 S[i+$2/2]=-;t=${s:c:1} S[i]=${t:-$b} b=$3;$[i+=$2,c++];S[i-($2/N)]=\ ;};};while((n>0));do f . 2 - \>;$[n-=a];f . $N \|;f \' -2 - \<;$[n--];f \' -$N \|;a=1;done;S[i]=$h;printf $T\\n "${S[@]}"

Try it online!

Squeezed to 271(with 2>/dev/null):

#!/bin/bash
s=$1 n=$2 N=$[n+n-1]
f(){ 
    b=$1
    for((j=1;j<n;j++)); {
        ((c<N))&&T+=%s
        [[ $4 ]]&&h=$4 S[i+$2/2]=-
        t=${s:c:1} S[i]=${t:-$b} b=$3
        $[i+=$2,c++]
        S[i-($2/N)]=\ 
    }
}
while((n>0));do
    f .  2    - \>; $[n-=a]
    f .  $N  \|
    f \' -2   - \<; $[n--]
    f \' -$N \|   ;   a=1
done;S[i]=$h;printf $T\\n "${S[@]}"

First variant was:

#!/bin/bash

s=$1
n=$2
l=${#s}
N=$((n+n-1)) # items per row

# Check if n is in range
(( (n*n)>=(l+1) )) || { echo "n must be 'n^2≥l+1'"; exit 1; }

# Fill the 'area' with spaces
for ((i=0; i<n*N; i++)); { snake[$i]=" "; }

# Create printing template
printf -v row "%${N}s"
row=${row// /%s}

# Setcounters
i=0
c=0
a=0


forward(){
    b='.'
    for ((j=1; j<n; j++)); do
        cut=${s:c:1}
        snake[$i]=${cut:-$b}
        snake[$((i+1))]="-"
        b='-'
        h='>'
        ((i+=2))
        ((c++))
    done
    ((n-=a))
}


down(){
    b='.'
    for ((j=1; j<n; j++)); do
        cut=${s:c:1}
        snake[$i]=${cut:-$b}
        b='|'
        ((i+=N))
        ((c++))
    done
}


back(){
    b="'"
    for ((j=1; j<n; j++)); do
        cut=${s:c:1}
        snake[$i]=${cut:-$b}
        snake[$((i-1))]="-"
        h='<'
        b='-'
        ((i-=2))
        ((c++))
    done
    ((n--))
}


up(){
    b="'"
    for ((j=1; j<n; j++)); do
        cut=${s:c:1}
        snake[$i]=${cut:-$b}
        b='|'
        ((i-=N))
        ((c++))
    done
    a=1
}


print(){
    printf "$row\n" "${snake[@]}"
#    echo $i,$c,$n
}


# Create snake
while ((n>0)); do
    forward
    down
    back
    up
done
snake[$i]=$h; printf "$row\n" "${snake[@]}"

Try it online!

JavaScript (Node.js), 193 192 190 188 187 182 177 bytes

s=>n=>s.map(c=>a[d*=/[.']/.test(a[z-=d])?d*d<5?n:-1/n:1,z]=c,d=z=-2,a=[...g(n)])&&a
g=(n,m)=>--n?(m="--".repeat(--n))+`--.
${n?`.-${g(n).split`
`.join` |
| `} |
'`+m:"<"}-'`:">"

Try it online!

Now even shorter than Arnauld's excellent answer!

This answer has been derived from my Python solution.

Function g makes an empty snake and function f updates it with the chars from string s. The function has to be called by f(s,n) and returns a plain array of single char strings.

Haskell, 333 289 bytes

f s n=h s n 0 0$concatMap(++"\n").g$n
h(c:t)n d z m=take z l++c:drop(z+1)l where e=mod(d+sum[1|a<-".'",m!!z==a])4;l=h t n e(z+2*[1,n,-1,-n]!!e)m
h _ _ _ _ m=m
g 1=[">"]
g 2=["--.","<-'"]
g n=(m++"--."):(map(++" |").h)b++["'-"++m++"'"]where b=g(n-2);m=b>>"--";h(l:k)=[".-"++l]++map("| "++)k

Try it online!

Function g makes an empty snake as a list of strings. Function h fills in the given string s while converting the list of strings to a single string. Function f is the resulting target function.

In function h, d is the direction where to go for the next character of string s and z is the position of the character to replace.

Perl 5, 236 224 222 214 190 bytes

sub g{my$m="--"x($n=-2+pop);$m?"$m--.\n.-@{[g($n)=~s/\n/ |\n| /gr]} |\n'$m-'":$n?">":"--.\n<-'"}sub f{$a=g$p=pop;$z+=(1,$p,-1,-$p)[($#_+=(substr$a,2*$z,1,$_)=~/[.']/)%4]for$_[$z=0]=~/./g;$a}

Try it online!

I have derived this from my Python answer. Function g makes an empty snake, function f fills in the string.

Here is the same code with a little more spacing for better readability:

sub g{
  my$m="--"x($n=-2+pop);
  $m?"$m--.\n.-@{[ g($n)=~s/\n/ |\n| /gr ]} |\n'$m-'":$n?">":"--.\n<-'"
 }
sub f{
  $a=g$p=pop;
  $z+=(1,$p,-1,-$p)[($#_+=(substr$a,2*$z,1,$_)=~/[.']/)%4] for $_[$z=0]=~/./g;
  $a
}

Python 3.8, 254 247 bytes

lambda n,s:(r:=range,G:=["<>"[n%2]],[(K:=(s+''.join((2*[(n+~h//4)*"-",(n-2-h//4)*"|"],".''.")[h%2][h//2%4]for h in r(4*n))[len(s):])[~(f+1)**2:],G:=['-'.join(K[:f+1])]+[G[~g][::-1]+"- "[f-g>1]+K[f-~g]for g in r(f)])for f in r(1,n)])and'\n'.join(G)

Try it online!

Essentially a port of my Pyth answer.

I'll hand wave a bit of an explanation. We start by assigning G to the center of the spiral, n being even or odd determines if the arrow points left or right. Next we construct the string for the spiral as if s was an empty string, then replace the beginning of that string with s. We then build the rest of the square, each iteration we flip G 180 degrees and add to the top and the right side. After n-1 iterations of this, we return G joined on newlines.

05AB1E, 86 bytes

LR·<€D¬šIõ¹·nи«'-ýŽ9¦S.Λ¶¡ιн»¶«„ -‚Dð¶:«D'-'|::2äε„|-‚D'|„.'Nè::¹N+ÉiNi'''<.;ë¨'>«]J

Inputs in the order \$n,S\$, where \$S\$ is a list of characters.
Outputs with two trailing newlines. If this is not allowed, a trailing ¨ or ? could be added so it'll output a single trailing newline instead.

Try it online or verify all test cases.

Explanation:

Step 1: Prepare the input character-list by joining by - and adding additional trailing -. Then use the (modifiable) Canvas builtin to put this string itself and all spaces at the correct positions in the output rectangle:

LR·<€D¬š         # Create the list of lengths for the Canvas builtin:
L                #  Push a list in the range [1, first (implicit) input-integer]
 R               #  Reverse it to range [n,1]
  ·              #  Double each inner value
   <             #  Decrease each by 1
    €D           #  Duplicate each value within the list
      ¬š         #  And prepend an additional copy of the first/largest item
Iõ¹·nи«'-ý      '# Create the string to draw for the Canvas builtin:
I                #  Push the second input-list of characters
  ¹·n            #  Push the first input, double it, take the square of that: 4n²
 õ   и           #  Create a list with that many empty strings ""
      «          #  Append it to the input-list
       '-ý      '#  Join this list with "-" delimiter
Ž9¦S             # Create the list of directions for the Canvas builtin:
Ž9¦              #  Push compressed integer 2460
   S             #  Convert it to a list of digits: [2,4,6,0]
.Λ               # Use the (modifiable) Canvas builtin with these three options
¶¡ιн»            # Remove all (0-based) odd-indexed rows
¶¡               # Split it on newlines
  ι              # Uninterleave it into two parts
   н             # Pop and only leave the first part
    »            # Join it back together by newlines

Try just step 1 online.

For more information on how the Canvas builtin works, see this 05AB1E tip of mine.
See this 05AB1E tip of mine (section How to compress large integers?) to understand why Ž9¦ is 2460.

Step 2: Replace all - that are currently at edge or corner positions with |:

¶«               # Append a trailing newline
„ -              # Push string " -"
   Â             # Bifurcate, short for Duplicate & Reverse copy: "- "
    ‚            # Pair them together: [" -","- "]
     D           # Duplicate it
      ð¶:        # Replace all spaces with newlines in the copy: ["\n-","-\n"]
         «       # Merge the two together: [" -","- ","\n-","-\n"]
D                # Duplicate it
 '-'|:           # Replace all "-" with "|" in the copy: [" |","| ","\n|","|\n"]
:                # Replace all [" -","- ","\n-","-\n"] respectively with
                 # [" |","| ","\n|","|\n"]

Try the first two steps online.

Step 3: Replace all | at corner positions with either . (for the top halve) or ' (for the bottom halve):

2ä               # Split the string into two equal-sized parts
  ε              # Map over both parts:
   „|-           #  Push string "|-"
      ‚         #  Bifurcate and pair again: ["|-","-|"]
   D             #  Duplicate it
    '|     :    '#  Replace all "|" with the following in the copy:
      „.'Nè     '#   The map-index'th character of string ".'"
   :             #  Replace all ["|-","-|"] respectively with
                 #  [".-","-."] for the first iteration of the map
                 #  or ["'-","-'"] for the second iteration of the map

Try the first three steps online.

Step 4: And finally, fix the > or <, and output the result:

    i            #   If
¹N+              #   the first input + the (0-based) map-index
   É             #   is odd:
     Ni          #    If it's the second iteration of the map:
       '' '<.;  '#     Replace the first "'" with "<"
      ë          #    Else (it's the first iteration of the map)
       ¨         #     Remove the last character of the first part
        '>«     '#     And append a ">" instead
]                # Close both if-statements and the map
 J               # Join the two modified parts back together
                 # (after which the result is output implicitly with trailing newline)

Python 3, 371 274 255 and 253 bytes

def f(s,n):
 for y in(a:=range(n)):
  for x in a:r=min(x,y,n+~x,n+~y);i=4*r*(n-r)-2*r+x+y+2*(2*(n+~r)-x-y)*(y>x);H=max(n+~y,y)>x>min(y-2,n-y-2);b=x+1>n/2;print(s[i:]and s[i]or"|-..''><"[H+2*((n+2*~x)*b-~x==y)+4*((n+~x-x)*b+x==n+~y)],end=" -"[H])
  print()

Try it online!

Minus 64 bytes thanks to @noodleman and another 11 bytes thanks to @Kevin Cruijssen! I then saved another 22 bytes. Most recent improvement of 6 bytes by @xnor and 13 bytes by me.

Instead of most other solutions (as far as I can tell), I tried calculating the index into the string. I dug up a post on StackOverflow that helped with this, though a lot was just throwing random equations and seeing what they do. Maybe the indexing equation can be simplified with a bit more thought, but it's surprisingly short already.

There are lots of places where -x-1 is replaced with +~x (or variants). Booleans are heavily used for indexing and cancelling parts of equations with multiplication.

Ungolfed:

def function(string: str, number: int):
  for y in range(number):
    for x in range(number):
      rounds = min(x, y, number - x - 1, number - y - 1)
        # total increments after `rounds` revolutions and fix for overcounting
      index = 4 * rounds * (number - rounds) - 2 * rounds
      index += x + y  # east + south increment
      index += 2 * (2 * (number - rounds - 1) - x - y) * (y > x)  # west + north

      # See below for explanation
      is_horizontal = max(number - y - 1, y) > x > min(y - 2, number - y - 2)
      is_right = x + 1 > number / 2
      # Both inlined
      is_up_corner = x + 1 + is_right * (number - 2 * (x + 1)) == y
      is_down_corner = x + is_right * (number - 1 - 2 * x) == number - y - 1

      # is_middle = x + 1 - number % 2 == y == number // 2
      # is_middle = is_up_corner and is_down_corner
      # is_down = y > number / 2
      # is_left_corner = y + is_down * (number - 2 * y) == x + 1
      # is_right_corner = y + is_down * (number - 1 - 2 * y) != number - x - 1

      print(
        # string[index] if index < len(string) else
        string[index:] and string[index] or \
        # See below for explanation
        "|-..''><"[is_horizontal + 2 * is_up_corner + 4 * is_down_corner],
        end=" -"[is_horizontal]
      )
    print()

Explanation of is_horizontal boolean: There are effectively four quadrants in which we need to determine whether the coordinate is a horizontal part in the snake, we can do this for the upper right corner with number - y - 1 > x. On its own this is not enough, because the lower part of the snake is now messed up. Taking y > x works for the lower right quadrant, but messes up the snake above. Combining these two equations into max(number - y - 1, y) > x leaves only the left part of the snake to fix. Creating conditions for the other corners you can combine x > min(y - 2, number - y - 2) into one large expression.

------------.                 ------------.                 ------------.
.---------. |                 .---------. |                 .---------. |
--.-----. | |                 --.-----. | |                 | .-----. | |
----.-> | | |     becomes     ----.-> | | |     becomes     | | .-> | | |
----' | ' | |                 ----'---' | |                 | | '---' | |
--' | | | ' |                 --'-------' |                 | '-------' |
' | | | | | '                 '-----------'                 '-----------'

Explanation of special character indexing into |-..''><: There are six special characters that we need to index into, which can be separated into three categories: Straight, corner and tail. By virtue of the equations, we can determine we need a tail if something is both an up- and down-corner. Obviously, if it is not a corner or tail, it should be a straight piece. Using the equation is_up_corner + 2 * is_down_corner we can differentiate the four cases straight, up-corner, down-corner, and tail. To select the correct straight and tail pieces we still need a more granular distinction. Using is_horizontal obviously works for straight pieces and additionally it works for the tail, because the left-pointing arrow has an appendage whereas the right-pointing does not. This can be combined with the previous equation by doubling the corners.

253 bytes solution

This solution outputs not a string, but a nested array/matrix of characters. This might be allowed according to the meta post: Strings and arrays of characters are interchangeable.

It's effectively the same as the above, but also returns instead of prints. It unfortunately requires *''.join() in the inner loop, because you cannot populate an array with two items per iteration. (And by doubling the iterations I only managed to golf to 255.)

lambda s,n:[[*''.join((r:=min(x,y,n+~x,n+~y),i:=4*r*(n-r)-2*r+x+y+2*(2*(n+~r)-x-y)*(y>x),H:=max(n+~y,y)>x>min(y-2,n-y-2),b:=x+1>n/2,s[i:]and s[i]or"|-..''><"[H+2*((n+2*~x)*b-~x==y)+4*((n+~x-x)*b+x==n+~y)])[4]+" -"[H]for x in range(n))]for y in range(n)]

Try it online!

Javascript, 384 371 304 288 263 235 bytes

s=(S,n)=>(t=[...S],A=Array,a=A.from(A(n),r=>A(2*n).fill(e=' ')),x=y=0,p=(b,d,f)=>(a[y][x]=x&1&&t.shift()||b[d],f?.(d)),i=2*n-1,m=d=>(z=d&2,d&1?y+=1-z:x+=1-z,--i?p('-|-|',d,m):n<2?p('>><<',d):(i=d&1?2*--n:n-1,p("..''",++d%4,m))),m(0),a)

-13 bytes saved thanks to Samathingamajig
-67 bytes by folding functions u() and d() into v(±1) and l() and r() into h(±1)
-16 bytes with only one recursive function
-25 bytes because of this
-28 bytes with more golfing

Try out

s=(S,n)=>(t=[...S],A=Array,a=A.from(A(n),r=>A(2*n).fill(e=' ')),x=y=0,p=(b,d,f)=>(a[y][x]=x&1&&t.shift()||b[d],f?.(d)),i=2*n-1,m=d=>(z=d&2,d&1?y+=1-z:x+=1-z,--i?p('-|-|',d,m):n<2?p('>><<',d):(i=d&1?2*--n:n-1,p("..''",++d%4,m))),m(0),a)

;[
  [ 'snake', 4 ],
  [ 'snakearound', 5 ],
  [ 'snakearoundandround', 6 ]
]
.forEach(([ S,n ]) => {
  console.log(s(S,n).map(r=>r.join('')).join('\n'))
})

Ungolfed

s = (S,n) => {
  t = [...S];
  a = Array.from(Array(n), r => Array(2*n).fill(e=' '));
  x = y = 0;
  p = (b,d,f) => {            // set letter in all odd positions until
                              // exhausted, otherwise use snake
    a[y][x] = x&1 && t.shift() || b[d];
    f?.(d);                   // recurse unless end
  }
  i = 2*n-1;                  // counter for going in the same direction
  m = d => {
                              // the next position is set before p is called
    z = d&2;                  // forward or back?
    if (d&1) {                // vertical or horizontal?
      y+=1-z;
    } else {
      x+=1-z;
    }
    if (--i) {               // go on
      p('-|-|', d, m);
    } else if (n<2) {        // stop
      p('>><<', d);
    } else {                 // next direction
      i = d&1 ? 2*--n : n-1; // set going length, decrement n if horizontal
      p("..''", ++d%4, m);
    }
  }
  m(0);
  return a;
}

Perl 5, 255 244 bytes

sub r{$a='';$a.=$/while s/.$/$a.=$&;''/emg;$_=$a}sub f{($s=pop.'-'x--($n=2*pop)**2)=~s/.\K/-/g;$k=local$_=($"x$n.$/)x$n;r while s@ +$|>(  )+@$_=substr$s,0,-1+length$&,'';s/^-/$k&2?"'":'.'/e;$k++&1&&y/-/|/;"$_>"@me;$n&2&&y/>/</||r&r;s/.\K.//g;r}

Try it online!

Here's with newlines for better readability:

sub r{$a='';$a.=$/while s/.$/$a.=$&;''/emg;$_=$a}
sub f{
($s=pop.'-'x--($n=2*pop)**2)=~s/.\K/-/g;
$k=local$_=($"x$n.$/)x$n;
r while s@ +$|>(  )+@
$_=substr$s,0,-1+length$&,'';
s/^-/$k&2?"'":'.'/e;
$k++&1&&y/-/|/;
"$_>"
@me;
$n&2&&y/>/</||r&r;
s/.\K.//g;
r
}

I'm sure there are glaring opportunities to golf it further, but at least, for now, it's decently competitive with other answers, considering sigils. I was more interested to play with algorithm -- the square-ish string is actually rotated while being filled. Replace r while... with print && r while... to see intermediate steps.

P.S. It's funny to watch how snake's head moves and serves as anchor where to place the next fragment.

Edit: Just a little bit shorter. Rotation subroutine is also slightly less ugly and can now handle rectangular (not just square) shapes (but of course there ought to be Perl golfing pre-fab somewhere for the purpose, 20-odd years old, so I'm re-inventing the wheel), which fact was immediately used and lead to another couple bytes off.

In fact local can be ditched (-5) and TIO's footer written so that the $_ doesn't hold read-only value while testing. And further couple bytes less if string is temporarily padded with arbitrarily large number of hyphens (1E6, say) instead of value computed from n. But I'm not that much desperate, yet :)

JavaScript (ES12), 188 bytes

Expects (string)(n). Returns a matrix of characters.

s=>n=>eval(`for(m=[k=x=y=0];~n;)for(d=-2;i=~n&&d++<2;n-=!n|d&!!y)for(;i++<(d&1?w=n*2-1:n||2);x-=d%2,y-=~-d%2)(m[y]||=[..."".padEnd(w)])[x]=d*x+1&1&&s[k++]||"|-..''< >"[i>2?d&1:d-3*~!n];m`)

Attempt This Online!

Alternate formula

I've found the following alternate formula for special characters where each entry appears only once in the lookup string. But in the end, it's just as long as the simpler one.

".<'>|-"[i > 2 ? d & 1 | 4 : !n ^ d * 3 & 2]

Commented

This is a version without eval() for readability.

// s = input string, n = size parameter
s => n => {
  for(
    // m[] is the output matrix
    // k is a pointer into s
    // (x, y) is the current position in the matrix
    m = [k = x = y = 0];
    // stop when n = -1
    ~n;
  )
    for( 
      // d is the direction
      d = -2;
      // stop when n = -1 or d = 2
      // i is the counter for the next loop
      i = ~n && d++ < 2;
      // decrement n if n = 0, or d is odd and y != 0
      n -= !n | d & !!y
    )
      for(
        ;
        // the width is 2n-1 if d is odd, or n if d is even
        // there's a special case for the last iteration
        i++ < (d & 1 ? w = n * 2 - 1 : n || 2);
        // (x, y) <- (x + dx, y + dy)
        x -= d % 2,
        y -= ~-d % 2
      )
        // if m[y] is undefined, initialize it to w spaces
        (m[y] ||= [..."".padEnd(w)])[x] =
          // if either d or x is even, attempt to get the next
          // character from s
          d * x + 1 & 1 && s[k++]
          // if falsy or undefined, use a special character:
          // - "|" or "-" for segments (i > 2)
          // - "." or "'" for corners (i = 2, n ≠ 0)
          // - "<" or ">" for the final character (i = 2, n = 0)
          || "|-..''< >"[i > 2 ? d & 1 : d - 3 * ~!n];
  return m
}

K (ngn/k), 337 331 315 290 279 231 224 211 213 bytes

{(x,w)#@[(x*w)#" ";i,*|i;:;@["|-"{1|1=x|-x}@-':i:0,+\(,/-:\1,w:-1+2*x)4!2_&s;(-2_-2+\s;!#y);:;(".''."4!!-2+#s:,/+|1(2*1+)\|!x; y:,/((#l)#("-";"")){x/y}',''l:(0,((#y)>)#+\(0,(-1+#l)#-1 1)+l:1_|1+&x#2)_y)],"<>"2!x]}

Try it online!

-6 : Save a few bytes with shorter names.
-16 : Tidying up
-25 : Cruft
-46 : Golfing gets me back under limits
-7 : Cleaner
-13 : A single statement!
+2 : handle empty string

Python3, 425 bytes

R=range
def f(n,s):
 r=[[' ']*2*n for x in R(n)];x=y=X=K=0;Y=C=1
 while 1:
  K+=1
  if s:r[x][y]=['-',s[0]][C%2or X!=0];s=s[C%2or X!=0:];C+=1
  else:r[x][y]='-|'[X!=0]
  if(y+Y==2*n-1or(0<=y+2*Y<2*n-1and' '!=r[x][y+2*Y])or Y<-y)and Y:
   if K==2:r[x][y]='<>'[Y>0];return r
   if r[x][y]in'-|':r[x][y]="'."[Y>0]
   X=Y;Y=0
  elif(x+X==n or' '!=r[x+X][y])and X:
   if r[x][y]in'-|':r[x][y]="'."[X<0]
   Y=-X;X=C=K=0
  x+=X;y+=Y

Try it online!

Pyth, 78 bytes

ju&K>*hhHH+z>s.i_Psm*Rd"-|"Qs@L_B".'"tQlz.e++b?%klGd\-@K+Hk+j\-<KH__MGStQ]@"<>

Try it online!

Charcoal, 61 bytes

F⮌S⊞υιNηF⊖η«F⊖⁻ηι⎇υ⁺⊟υ-²↷²⎇υ⊟υ§.'ιF⁻⁻ηι²⎇υ⊟υ¹↷²⎇υ⊟υ§'.ι¹»§<>η

Try it online! Link is to verbose version of code. Explanation:

F⮌S⊞υι

Get the reverse of the input into a stack, so that characters can be processed by popping from it.

Nη

Input n.

F⊖η«

Repeat n-1 times. The loop counter is taken as an ascending number for the purposes of calculating which corner character to print, but for the side lengths I show how the actual number of repetitions of the inner loops vary.

F⊖⁻ηι

Repeat from n-1 down to once...

⎇υ⁺⊟υ-²

... output the next character followed by a -, or two -s if none.

↷²

Start moving vertically.

⎇υ⊟υ§.'ι

Output the next character or . or ' appropriately if there are none left.

F⁻⁻ηι²

Repeat from n-2 down to zero...

⎇υ⊟υ¹

... output the next character or | if there are none left.

↷²

Start moving horizontally, but in the other direction.

⎇υ⊟υ§'.ι¹

Output the next character or ' or . appropriately if there are none left.

»§<>η

Output the head of the snake.