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

Properly Parse VML #102

Merged
merged 13 commits into from Dec 12, 2018
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -55,3 +55,6 @@ Icon
Network Trash Folder
Temporary Items
.apdisk

# leex/yecc output
src/vml_*.erl
4 changes: 2 additions & 2 deletions lib/game/command/quest.ex
Expand Up @@ -225,10 +225,10 @@ defmodule Game.Command.Quest do

false ->
response =
Format.wrap_lines([
Enum.join([
gettext("You have not completed the requirements for the quest."),
gettext("See {command}quest info %{id}{/command} for your current progress.)", id: progress.quest_id)
])
], " ")

state.socket |> @socket.echo(response)
end
Expand Down
3 changes: 1 addition & 2 deletions lib/game/command/socials.ex
Expand Up @@ -7,7 +7,6 @@ defmodule Game.Command.Socials do

import Game.Room.Helpers, only: [find_character: 2]

alias Game.Format
alias Game.Format.Socials, as: FormatSocials
alias Game.Socials

Expand Down Expand Up @@ -145,6 +144,6 @@ defmodule Game.Command.Socials do
gettext("Please make sure to enter the social command. See {command}socials{/command} for the list.")
]

state.socket |> @socket.echo(Format.wrap(Enum.join(lines, " ")))
state.socket |> @socket.echo(Enum.join(lines, " "))
end
end
50 changes: 0 additions & 50 deletions lib/game/format.ex
Expand Up @@ -71,54 +71,4 @@ defmodule Game.Format do
|> Enum.map(fn _ -> "-" end)
|> Enum.join("")
end

@doc """
Wraps lines of text
"""
@spec wrap(String.t()) :: String.t()
def wrap(string) do
string
|> String.replace("\n", "{newline}")
|> String.replace("\r", "")
|> String.split(~r/( |{[^}]*})/, include_captures: true)
|> _wrap("", "")
end

@doc """
Wraps a list of text
"""
@spec wrap_lines([String.t()]) :: String.t()
def wrap_lines(lines) do
lines
|> Enum.join(" ")
|> wrap()
end

defp _wrap([], line, string), do: join(string, line, "\n")

defp _wrap(["{newline}" | left], line, string) do
case string do
"" ->
_wrap(left, "", line)

_ ->
_wrap(left, "", Enum.join([string, line], "\n"))
end
end

defp _wrap([word | left], line, string) do
test_line = "#{line} #{word}" |> Color.strip_color() |> String.trim()

case String.length(test_line) do
len when len < 80 ->
_wrap(left, join(line, word, ""), string)

_ ->
_wrap(left, word, join(string, String.trim(line), "\n"))
end
end

defp join(str1, str2, joiner) do
Enum.join([str1, str2] |> Enum.reject(&(&1 == "")), joiner)
end
end
7 changes: 5 additions & 2 deletions lib/game/format/channels.ex
Expand Up @@ -13,11 +13,14 @@ defmodule Game.Format.Channels do
Example:

iex> Channels.channel_say(%{name: "global", color: "red"}, {:npc, %{name: "NPC"}}, %{message: "Hello"})
~s([{red}global{/red}] {npc}NPC{/npc} says, {say}"Hello"{/say})
~s(\\\\[{red}global{/red}\\\\] {npc}NPC{/npc} says, {say}"Hello"{/say})
"""
@spec channel_say(String.t(), Character.t(), map()) :: String.t()
def channel_say(channel, sender, parsed_message) do
~s([#{channel_name(channel)}] #{say(sender, parsed_message)})
context()
|> assign(:channel_name, channel_name(channel))
|> assign(:say, say(sender, parsed_message))
|> Format.template("\\[[channel_name]\\] [say]")
end

@doc """
Expand Down
1 change: 0 additions & 1 deletion lib/game/format/listen.ex
Expand Up @@ -30,6 +30,5 @@ defmodule Game.Format.Listen do
|> assign(:features, features)
|> assign(:npcs, npcs)
|> Format.template("{white}You can hear:{/white}[\nroom][\nfeatures][\nnpcs]")
|> Format.wrap()
end
end
2 changes: 1 addition & 1 deletion lib/game/format/quests.ex
Expand Up @@ -45,7 +45,7 @@ defmodule Game.Format.Quests do
#{header}
#{header |> Format.underline()}

#{quest.description |> Format.wrap()}
#{quest.description}

#{steps |> Enum.join("\n")}
"""
Expand Down
4 changes: 2 additions & 2 deletions lib/game/format/rooms.ex
Expand Up @@ -51,8 +51,8 @@ defmodule Game.Format.Rooms do

context =
context()
|> assign(:room, "{green}#{room.name}{/green}")
|> assign(:zone, "{white}#{room.zone.name}{/white}")
|> assign(:room, room_name(room))
|> assign(:zone, zone_name(room.zone))
|> assign(:features, Enum.join(features(room.features), " "))

context =
Expand Down
4 changes: 2 additions & 2 deletions lib/game/format/socials.ex
Expand Up @@ -44,7 +44,7 @@ defmodule Game.Format.Socials do
def social_without_target(social, player) do
context()
|> assign(:user, Format.player_name(player))
|> Format.template("{say}#{social.without_target}{say}")
|> Format.template("{say}#{social.without_target}{/say}")
end

@doc """
Expand All @@ -54,6 +54,6 @@ defmodule Game.Format.Socials do
context()
|> assign(:user, Format.player_name(player))
|> assign(:target, Format.name(target))
|> Format.template("{say}#{social.with_target}{say}")
|> Format.template("{say}#{social.with_target}{/say}")
end
end
58 changes: 41 additions & 17 deletions lib/game/format/template.ex
Expand Up @@ -3,8 +3,6 @@ defmodule Game.Format.Template do
Template a string with variables
"""

@variable_regex ~r/\[([^\[][^\]]*)\]/

@doc """
Render a template with a context

Expand All @@ -27,26 +25,52 @@ defmodule Game.Format.Template do
|> Map.get(:assigns, %{})
|> Enum.into(%{}, fn {key, val} -> {to_string(key), val} end)

string
|> String.split(@variable_regex, include_captures: true)
|> Enum.map(&replace_variables(&1, context))
|> Enum.join()
with {:ok, ast} <- VML.parse(string) do
VML.collapse(replace_variables(ast, context))
else
{:error, _module, _error} ->
"{error}Could not parse text.{/error}"
end
end

defp replace_variables([], _context), do: []

defp replace_variables([node | nodes], context) do
[replace_variable(node, context) | replace_variables(nodes, context)]
end

defp replace_variable({:variable, space, name}, context) do
case replace_variable({:variable, name}, context) do
{:string, ""} ->
{:string, ""}

{:string, value} ->
{:string, space <> value}

value when is_list(value) ->
[{:string, space} | value]
end
end

defp replace_variables(string, context) do
case Regex.run(@variable_regex, string) do
defp replace_variable({:variable, name}, context) do
case Map.get(context, name, "") do
"" ->
{:string, ""}

nil ->
string
{:string, ""}

[_, variable] ->
key = String.trim(variable)
leading_spaces = String.replace(variable, ~r/#{key}[\s]*/, "")
value when is_list(value) ->
value

case Map.get(context, key, "") do
"" -> ""
nil -> ""
value -> Enum.join([leading_spaces, value])
end
value ->
{:string, value}
end
end

defp replace_variable({:tag, attributes, nodes}, context) do
{:tag, attributes, replace_variables(nodes, context)}
end

defp replace_variable(node, _context), do: node
end
2 changes: 1 addition & 1 deletion lib/game/session/gmcp.ex
Expand Up @@ -316,7 +316,7 @@ defmodule Game.Session.GMCP do
|> Map.take([:id, :name, :ecology, :x, :y, :map_layer])
|> Map.merge(%{
zone: zone_info(room),
description: Format.Rooms.room_description(room),
description: VML.collapse(Format.Rooms.room_description(room)),
items: render_many(items),
players: render_many(room, :players),
npcs: render_many(room, :npcs),
Expand Down