Я тут потихоньку ковыряю веб-фреймворк на scala...
От: dimgel Россия https://github.com/dimgel
Дата: 25.06.09 05:51
Оценка: 18 (1)
Всем привет.

В общем, сабж. Будет опен-сорц, public domain. Там всё очень сыро, не утрясены даже некоторые ключевые архитектурные решения. Но парой примерчиков хочется поделиться. Один сегодня, один завтра-послезавтра. То, что утрясено. Пока что в виде статей, без ссылок на скачу исходников (вылью как утрясу кое-что, если появятся заинтересованные). Всё по-английски, т.к. планирую интервенцию в scala community.

файл .../lib/web/example/hello01/Main.scala
package ru.dimgel.lib.web.example.hello01

import ru.dimgel.lib.web.core
import core.request.Request
import core.response.HTMLResponse

/**
 * DISCLAIMER ONE: I have no time yet to format scaladoc blocks appropriately.
 *                 Please read all docs in source code.
 * 
 * DISCLAIMER TWO: It's all in early development phase. Examples itself do work, 
 *                 but stuff mentioned in comments probably does not yet.
 * 
 * This is simplest "Hello world!" example. To run it standalone (outside example 
 * project), ensure your WEB-INF/web.xml looks like this:
 * 
 * <?xml version="1.0" encoding="utf-8"?>
 * <!DOCTYPE web-app PUBLIC 
 *     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 
 *     "http://java.sun.com/dtd/web-app_2_3.dtd">
 * <web-app>
 *     <servlet>
 *         <servlet-name>servlet</servlet-name>
 *         <servlet-class>ru.dimgel.lib.web.core.Servlet</servlet-class>
 *         <init-param>
 *             <param-name>main</param-name>
 *             <param-value>ru.dimgel.lib.web.example.hello01.Main</param-value>
 *         </init-param>
 *        </servlet>
 *        <servlet-mapping>
 *         <servlet-name>servlet</servlet-name>
 *         <url-pattern>/ *</url-pattern>
 *     </servlet-mapping>
 * </web-app>
 * 
 * NOTE: There must be no space between / and * chars in <url-pattern>. 
 *       I added space only to avoid them being parsed as nested comment.
 * 
 * Your subclass of lib.web.core.Main class is entry point into your web application.
 * The service() method dispatches incoming requests. For now, we'll just generate
 * simple HTMLResponse for all requests.
 * 
 * Lib.web framework heavily uses Scala's type system. Truly, the main design goal was
 * to enforce as much is possible via typization, to provide maximum support from IDE.
 * 
 * One of major consequences of this approach is using Scala's built-in XML support 
 * instead of external interpreted templates. In fact, there's no template system at all. 
 * I only plan to add some HTMLResponse transformations (e.g. head merging). Anyway,
 * you can (make and) use your own template engine and even introduce another response 
 * class for seamless integration with lib.web.
 * 
 * There are other response classes besides HTMLResponse. Actually, there are hierarchies
 * of requests and responses. The goal main goal is to enforce API restrictions via type
 * system. For example, access to request InputStream is provided only by RawRequest, 
 * but interface to "multipart/form-data" parts is provided by MultipartRequest.
 * The service() method can pattern-match request class (and request attributes) to
 * dispatch request, as demonstrated in the next example.
 * 
 * Response objects are created by application code during request processing. This is a 
 * functional approach - to look at service() method as function (Request) => Response. 
 * It allows easy use of response class hierarchy to provide different APIs for different 
 * response types. It's used in particluar by error handling (shown in the next example).
 * 
 * Oh, and by the way, I prefer factory methods to "new" operator. And have enforced it
 * too. Just coinciseness. If you try writing "new HTMLResponse(...)", you'll get
 * compiler error. =)
 */
class Main(servlet: core.Servlet) extends core.Main(servlet) {
  override def service(rq: Request) = HTMLResponse(rq,
    <html>
      <head>
        <title>{getClass.getCanonicalName}</title>
        <link rel="stylesheet" type="text/css" href="../../lib.web/example.css"/>
      </head>
      <body>
        <h1>Hello world!</h1>
        <p>This is simplest <span class="libweb">lib.web</span> example ever.</p>
      </body>
    </html>
  )
}
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: Курилка Россия http://kirya.narod.ru/
Дата: 25.06.09 06:30
Оценка:
Здравствуйте, dimgel, Вы писали:
[cut]
D>
[cut]
D>class Main(servlet: core.Servlet) extends core.Main(servlet) {
D>  override def service(rq: Request) = HTMLResponse(rq,
D>    <html>
D>      <head>
D>        <title>{getClass.getCanonicalName}</title>
D>        <link rel="stylesheet" type="text/css" href="../../lib.web/example.css"/>
D>      </head>
D>      <body>
D>        <h1>Hello world!</h1>
D>        <p>This is simplest <span class="libweb">lib.web</span> example ever.</p>
D>      </body>
D>    </html>
D>  )
D>}
D>


Смешиваем хтмл и логику? Да здравствует PHP?
Re[2]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: dimgel Россия https://github.com/dimgel
Дата: 25.06.09 06:49
Оценка:
Здравствуйте, Курилка, Вы писали:

К>Смешиваем хтмл и логику?


Где ты тут увидел логику? Вообще это вопрос дисциплины программиста. Даже при наличии отдельного шаблонизатора. Я ещё месяц назад накатал огромадную доку по правилам написания шаблонов. Т.е. мой шаблонизатор не содержит библиотечного кода, но содержит большую доку.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[3]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: Курилка Россия http://kirya.narod.ru/
Дата: 25.06.09 07:10
Оценка:
Здравствуйте, dimgel, Вы писали:

D>Здравствуйте, Курилка, Вы писали:


К>>Смешиваем хтмл и логику?


D>Где ты тут увидел логику? Вообще это вопрос дисциплины программиста. Даже при наличии отдельного шаблонизатора. Я ещё месяц назад накатал огромадную доку по правилам написания шаблонов. Т.е. мой шаблонизатор не содержит библиотечного кода, но содержит большую доку.


Т.е. логики не будет?
Более вменяемый пример был бы гораздо полезней.
Re[3]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: Курилка Россия http://kirya.narod.ru/
Дата: 25.06.09 07:17
Оценка:
Здравствуйте, dimgel, Вы писали:

D>Здравствуйте, Курилка, Вы писали:


К>>Смешиваем хтмл и логику?


D>Где ты тут увидел логику? Вообще это вопрос дисциплины программиста. Даже при наличии отдельного шаблонизатора. Я ещё месяц назад накатал огромадную доку по правилам написания шаблонов. Т.е. мой шаблонизатор не содержит библиотечного кода, но содержит большую доку.


+ Вопрос — править это дело можно будет только ручками/IDE? Вход дизайнерам запрещён (со всякими там дримвиверами)?
Явное разделение на мой взгляд проще и удобнее, чем умная (возможно) но большая дока.
Re[4]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: dimgel Россия https://github.com/dimgel
Дата: 25.06.09 07:17
Оценка:
Здравствуйте, Курилка, Вы писали:

К>Т.е. логики не будет?

К>Более вменяемый пример был бы гораздо полезней.

Следующий пример будет по поводу диспетчеризации запросов и обработки ошибок. Там тоже один голый HTML. Логика будет в третьем примере, который будет предположительно через неделю. Но твою претензию по поводу разделения логики и представления я взял на заметку — во втором примере задокументирую кратенько идею своей многостраничной доки. Тем более, что в этом примере присутствует композиция шаблонов.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[4]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: dimgel Россия https://github.com/dimgel
Дата: 25.06.09 07:22
Оценка:
Здравствуйте, Курилка, Вы писали:

К>+ Вопрос — править это дело можно будет только ручками/IDE? Вход дизайнерам запрещён (со всякими там дримвиверами)?


Угу. Мой опыт фрилансера-программиста показывает, что за дизайнерами-фрилансерами всю вёрстку приходится переделывать. Ну а для более правильных пацанов я и написал в комментариях про расширяемость. Гы, я конечно понимаю, что расширяемость это одно, а готовый шаблонизатор — совсем другое, но клонирование (весьма понравившихся мне) шаблонов Lift-а у меня далеко не на первом месте.

К>Явное разделение на мой взгляд проще и удобнее, чем умная (возможно) но большая дока.


+1. Но в той доке в основном тщательно разжёванные примеры use cases, как можно встроенными средствами языка повторять конструкции шаблонизатора Lift. Я выше тебе ответил, что идею там можно описать весьма кратко. В сущности, в двух словах: шаблон — это функция (DTO) => XML.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[4]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: dimgel Россия https://github.com/dimgel
Дата: 25.06.09 07:31
Оценка: +1
Здравствуйте, Курилка, Вы писали:

К>Явное разделение на мой взгляд проще и удобнее, чем умная (возможно) но большая дока.


Кстати, насчёт удобства — не флейма ради, но аргументация моя достаточно тривиальна: во внешнем шаблоне можно опечататься в имени подствляемой переменной, а в XML-литерале — нет. Единственное, что меня печалит, — у всех объектов есть метод toString, поэтому внутрь XML-литерала я могу написать любую фигню и оно скомпилируется, например <span>{user}</span> вместо <span>{user.name}</span>.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[5]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: Курилка Россия http://kirya.narod.ru/
Дата: 25.06.09 08:40
Оценка:
Здравствуйте, dimgel, Вы писали:

D>Здравствуйте, Курилка, Вы писали:


К>>Явное разделение на мой взгляд проще и удобнее, чем умная (возможно) но большая дока.


D>Кстати, насчёт удобства — не флейма ради, но аргументация моя достаточно тривиальна: во внешнем шаблоне можно опечататься в имени подствляемой переменной, а в XML-литерале — нет. Единственное, что меня печалит, — у всех объектов есть метод toString, поэтому внутрь XML-литерала я могу написать любую фигню и оно скомпилируется, например <span>{user}</span> вместо <span>{user.name}</span>.


А toString гвоздями в компиляторе скальном пришит (сам её давно не трогал)?
Re[5]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: Mamut Швеция http://dmitriid.com
Дата: 25.06.09 13:25
Оценка:
Здравствуйте, dimgel, Вы писали:

d> К>+ Вопрос — править это дело можно будет только ручками/IDE? Вход дизайнерам запрещён (со всякими там дримвиверами)?


d> Угу. Мой опыт фрилансера-программиста показывает, что за дизайнерами-фрилансерами всю вёрстку приходится переделывать. Ну а для более правильных пацанов я и написал в комментариях про расширяемость. Гы, я конечно понимаю, что расширяемость это одно, а готовый шаблонизатор — совсем другое, но клонирование (весьма понравившихся мне) шаблонов Lift-а у меня далеко не на первом месте.


Можно взять StringTemplate


d> К>Явное разделение на мой взгляд проще и удобнее, чем умная (возможно) но большая дока.


d> +1. Но в той доке в основном тщательно разжёванные примеры use cases, как можно встроенными средствами языка повторять конструкции шаблонизатора Lift. Я выше тебе ответил, что идею там можно описать весьма кратко. В сущности, в двух словах: шаблон — это функция (DTO) => XML.


А если шаблон ну ооочень большой?
avalon 1.0rc1 rev 239, zlib 1.2.3


dmitriid.comGitHubLinkedIn
Re[6]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: dimgel Россия https://github.com/dimgel
Дата: 26.06.09 01:54
Оценка:
Здравствуйте, Курилка, Вы писали:

К>А toString гвоздями в компиляторе скальном пришит (сам её давно не трогал)?


Объявлен на классе Any (т.е. и на примитивах, и на подклассе AnyRef aka java.lang.Object). Оверрайднуть его можно, но в данном случае смысла нет.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[6]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: dimgel Россия https://github.com/dimgel
Дата: 26.06.09 01:54
Оценка:
Здравствуйте, Mamut, Вы писали:

M>А если шаблон ну ооочень большой?


Тогда загружать и парсить внешний файл — ещё более накладно (и по процессору, и по памяти) по сравнению с внедрённым в код литералом.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Пример 2: request dispatching, error handling, surrounding t
От: dimgel Россия https://github.com/dimgel
Дата: 26.06.09 06:08
Оценка: 18 (1)
Всем привет.

Сабж. Коду мало, букв в каментах много. Просьба: кроме отзывов по описанному, давать отзывы по качеству описания, ежели оное где-то хромает.

package ru.dimgel.lib.web.example.dispatch02

import java.util.regex.Pattern
import scala.xml.NodeSeq
import ru.dimgel.lib.web.core
import core.request._
import core.response._

/*
 * This is the second lib.web example that demonstrates request dispatching,
 * error handling, and also surrounding template (pure Scala equivalent for Lift's 
 * <lift:surround with="..."> tag).
 
 * The example dispatches and services requests for two normal pages (/ and /about/me), 
 * forwards requests for static content to default servlet (three different use-cases 
 * shown), outputs custom 404 error page for /missing/handled/... requests, outputs 
 * default (container-provided) 404 error page for other /missing/... requests.
 *
 * NOTE ABOUT TEMPLATES
 * 
 * The technique used for implementing surrounding template is based on treating all 
 * templates as just functions (data) => NodeSeq. This is quite universal functional
 * approach, so I'm sure every template-related use-cases can be expressed this way.
 * 
 * The only problem is what can be a template input. Because templates are ordinary
 * functions just as business logic methods are, it's easy to fall into mess. I consider
 * this problem is the problem of programmer's discipline - to carefully separate logic
 * from presentation.
 * 
 * I'll probably make some another example demonstrating various use-cases on this
 * concern, including "view-first" pattern on which Lift's request processing is based,
 * but for now I just emphasized the problem by introducing nested "template" functions
 * in methods home(), aboutMe() and error404().
 */
class Main(servlet: core.Servlet) extends core.Main(servlet) {
  
  /*
   * This method is called by core.Servlet before any other request processing occurs
   * (even before creation of Request wrapper object). Parameter pathInfo is 
   * HttpServletRequest.pathInfo. If this method returns false, request is immediately 
   * forwarded to default (container-provided) servlet, without any futher processing.
   * 
   * Default implementation returns true. Implementation shown in this example states 
     * that subfolders /images/, /js/ and /css/ contain ordinary files and are to be 
     * served by default servlet.
   * 
   * Additionally, framework always forwards requests with pathInfo starting with 
   * "/lib.web/" to default servlet. This is checked before a call to willService() 
   * method. (I plan servicing jquery.js and other stuff from /lib.web/ folder.) 
   * 
   * NOTE: Currently I use single project for lib.web and examples, and this example is 
   *       mapped to /example/dispatch02/ in web.xml. File /lib.web/example.css is 
   *       served by different instance of core.Servlet which is mapped to /. That's why 
   *       in this example I use relative path ../../lib.web/example.css and it all still 
   *       works.
   */
  override def willService(pathInfo: String) =
    pathInfo == null || !Pattern.matches("^/(images|js|css)/.*$", pathInfo)
  
  /*
   * Normally, service() method would not generate response but pattern-match and
   * dispatch request to particular handlers. Request can be matched against 
   * specific request method and path (and against particular Request subclass
   * which is not shown in this example). 
   * 
   * The request.Method class is enum. I code enums in a fashion like this:
   * 
   * sealed abstract class Method(name: String) extends NotNull
   * object Method {
   *   object GET extends Method("GET")
   *   object POST extends Method("POST")
   *   ...
   * 
   *   // Factory method
   *   def apply(name: String): Method = ...
   * }
   * 
   * The request.Path class is representation of HttpServletRequest.pathInfo suitable 
   * for pattern-matching. It consists of, and can be matched against List[String] of 
   * components (split along slashes) and boolean trailingSlash. For example:
   * 
   * > Path(List("about", "me"), false) corresonds to pathInfo="/about/me",
   * > Path(List("about", "me"), true) corresonds to pathInfo="/about/me/",
   * > Path(Nil, false) corresponds to pathInfo=null.
   * 
   * The service() method implementation below matches whole request. If you need to 
   * match only path components, this can be done in a shorter way:
   * 
   * rq.path.components match {
   *   case Nil => home(rq)
   *   case List("about", "me") => aboutMe(rq)
   *   ...
   * }
   */
  override def service(rq: Request) = rq match {
    case Request(Method.GET, Path(Nil, _)) => home(rq)
    case Request(Method.GET, Path(List("about", "me"), _)) => aboutMe(rq)

    // Paths matching "^/missing(/|$)" are handled by serviceError() method.
    // ErrorResponse takes optional "message" argument which is omitted here.
    case Request(_, Path(List("missing", _*), _)) => ErrorResponse(rq, Status.NotFound)

    // Default implementation forwards request to default (container-provided) servlet. 
    // Same can be done by returning ForwardDefaultResponse(rq). Useful for servicing 
    // static content.
    case _ => super.service(rq)
    //case _ => ForwardDefaultResponse(rq)
  }
  
  /*
   * This method is called if service() returns ErrorResponse.
   * 
   * Some more notes on service(), serviceError(), ErrorRequest and ErrorResponse
   * (please read them AFTER reading serviceError() code and comments there):
   * 
   * > Remember that all response classes take request object as first argument.
   *   Attempt to create more than one response for given request will result in
   *   IllegalStateException.
   * 
   * > Attempt to return response created for different request results in
   *   AssertionError.
   * 
   * > Attempt to return ForwardDefaultResponse from serviceError() will result in
   *   AssertionError.
   * 
   * > Request and response objects are not thread-safe. All request processing
   *   must occur in same thread. The good news is that core.Servlet and core.Main
   *   classes contain no static data, so you can declare many applications in a single
   *   web.xml (as it's done in lib.web examples project).
   */
  override def serviceError(rq: ErrorRequest) = rq match {
    
    // Output custom error page for paths mathing "^/missing/handled(/|$)".
    // ErrorRequest is matched against method, path, status and message, where
    // status and message are the same as in ErrorResponse returned by service().
    case ErrorRequest(_, Path(List("missing", "handled", _*), _), Status.NotFound, _) => 
      error404(rq)
    
    /*
     * Default implementation outputs container-generated error page. Same can be done
     * by returning ErrorResponse(rq). The ErrorResponse(ErrorRequest) constructor
     * creates exactly the same object as was previously returned by service() - with 
     * same status and message. Or you can call ErrorResponse(Request, status[, message]).
     * 
     * Returning ErrorResponse from serviceError() finally results in a call to 
     * HttpServletResponse's sendError(status.code, message) if message != null, 
     * or sendError(status.code) otherwise.
     */
    case _ => super.serviceError(rq)
    //case _ => ErrorResponse(rq)
  }
  
  /**
   * Handler for home page, called by service().
   * Contains no logic, just template.
   */
  private def home(rq: Request) = {
    def template = tSurround(rq, 
      <h1>Home</h1>
      <p>This is the second example in {tLibWeb} distribution. Try main menu (looking popup hints and clicking).</p> 
    )
    HTMLResponse(rq, template)
  }
  
  /**
   * Handler for /about/me page, called by service(). 
   * Contains no logic, just template.
   */
  private def aboutMe(rq: Request) = {
    def template = tSurround(rq, 
      <h1>About me</h1>
      <p>Please see comments in the source code.</p>
    )
    HTMLResponse(rq, template)
  }

  /*
   * Error handler for /missing/handled... pages, called by serviceError().
   * Contains no logic, just template.
   * 
   * Note that here we specify additional "status" argument to HTMLResponse, same as 
   * provided by passed ErrorRequest. It would be Status.NotFound, because ErrorRequest 
   * is only created by framework from ErrorResponse returned by service() method, and 
   * there we create only ErrorResponse with Status.NotFound.
   */
  private def error404(rq: ErrorRequest) = {
    def template = tSurround(rq,
      <h1>Error 404</h1>
      <p><strong>Page not found: {rq.path}</strong></p>
      <p>This is error page is generated for paths matching <em>^/missing/handled(/|$)</em>.</p>
    )
    HTMLResponse(rq, rq.status, template)
  }
  
  /*
   * Template for lib.web logo. 
   * In this example, global templates are functions with "tXXX" name convention.
   */
  private def tLibWeb = <span class="libweb">lib.web</span>
  
  /*
   * Surrounding template.
   * In this example, global templates are functions with "tXXX" name convention.
   * 
   * Note how "content" argument is declared: function instead of value. This is
   * "lazy eval" behaviour. If you want content calculated before tSurround()
   * starts execution ("eager eval"), declare it as ordinary value parameter.
   * 
   * This template also takes Request argument, required to generate <base> tag. It 
   * is about "what can be template input" concern. Will dig it in a future examples.
   */
  private def tSurround(rq: Request, content: => NodeSeq): NodeSeq =
    <html>
      <head>
        <title>{getClass.getCanonicalName}</title>
        <base href={rq.baseUrl}/>
        <link rel="stylesheet" type="text/css" href="../../lib.web/example.css"/>
      </head>
      <body>
        <div class="mainmenu">
          <a href="../../lib.web/example.css" title="Forwarded to default servlet by framework because absolute URI starts with /lib.web/. About using relative path here, see NOTE inside willService() method comments.">/lib.web/</a>
          <a href="js/script.js" title="Forwarded to default servlet because willService() returns false for /js/ subfolder">!willService()</a>
          <a href={rq.baseUrl} title="Dispatched by service() method to home()">service(): Home</a>
          <a href="about/me" title="Dispatched by service() method to aboutMe()">service(): About me</a>
          <a href="file.txt" title="Forwarded to default servlet by service() calling super.service()">super.service()</a>
          <a href="missing/handled" title="Dispatched by serviceError() to error404()">serviceError()</a>
          <a href="missing/handled/again" title="Dispatched by serviceError() to error404()">serviceError() again</a>
          <a href="missing/unhandled" title="Displays container-provided error page by serviceError() calling super.serviceError()">super.serviceError()</a>
        </div>
        {content}
      </body>
    </html>
}
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[7]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: Mamut Швеция http://dmitriid.com
Дата: 26.06.09 07:08
Оценка:
d> M>А если шаблон ну ооочень большой?

d> Тогда загружать и парсить внешний файл — ещё более накладно (и по процессору, и по памяти) по сравнению с внедрённым в код литералом.


А если шаблон состоит из несколькизх подшаблонов?
А если шаблон один раз скомпилировать в некое промежуточное представление?
avalon 1.0rc1 rev 239, zlib 1.2.3


dmitriid.comGitHubLinkedIn
Re[8]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: Курилка Россия http://kirya.narod.ru/
Дата: 26.06.09 07:15
Оценка:
Здравствуйте, Mamut, Вы писали:

d>> M>А если шаблон ну ооочень большой?


d>> Тогда загружать и парсить внешний файл — ещё более накладно (и по процессору, и по памяти) по сравнению с внедрённым в код литералом.


M>А если шаблон состоит из несколькизх подшаблонов?

M>А если шаблон один раз скомпилировать в некое промежуточное представление?

В эту же копилку:
как реализуются циклы/условные выражения?
на "целиковой" Скале?
По-моему не зря шаблоны делают ограниченными в средствах, чтобы даже не было соблазна писать "факториалы" внутри шаблонов, и делать ту задачу, которая возложена — генерить выходной HTML.
"Разделяй и властвуй" (ц) Цезарь (?)
Re[9]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: dimgel Россия https://github.com/dimgel
Дата: 26.06.09 11:24
Оценка: 18 (1)
Здравствуйте, Курилка, Вы писали:

К>как реализуются циклы/условные выражения?


Scala-код, внедрённый внутрь XML-литерала, может содержать другие XML-литералы, также с внедрённым скала кодом, и т.д. Рекурсивно. Так что все управляющие конструкции пишутся на scala.

К>По-моему не зря шаблоны делают ограниченными в средствах


Может и не зря, но ничего кроме геморроя это в итоге не даёт. Элементарный, но часто встречающийся и вусмерть за#$%вающий пример: где форматировать дату? Или например был у меня тут заказчик на CMS, в которой bread crumbs для каталога товаров должны были верстаться по-разному в зависимости от категории товара, причём шаблон bread crumbs должен был лежать в настройках CMS (чтобы разные сателлиты-клоны могли обеспечить свою непохожесть друг на дружку). Ну невозможно уложить реальную жизнь в убогие возможности шаблонов. Думаешь, почему Smarty ругают? При использовании шаблонизаторов проникновение вёрстки в код — лишь вопрос времени.

Кстати, в Symfony (самый навороченный на данный момент PHP web framework) в качестве шаблонов используются обычные PHP-файлы. Хочешь — гони обычную разметку с <?php>-вставками (если ты дизайнер с дримвивером), хочешь — гони PHP-код с echo "<p>...</p>". Это очень правильно в том смысле, что в Symfony, в отличие от Smarty, мне не нужно беспокоиться о том, склонировали ли эти козлы PHP-функцию date(), форматирующую даты, или операторы if/for/while, и какой у этих операторов синтаксис в данном шаблонизаторе. У меня гарантированно есть все возможности PHP, и мне не нужно изучать just another piece of shit. Однако что в Symfony сделано плохо — это то, что инклудятся PHP-файлы-шаблоны внутрь библиотечной функции, предоставляющей шаблону соответствующий контекст. То есть шаблон выполняется внутри функции. Чем плохо? Я не могу объявить в шаблоне свои функции, и как следствие не могу ни сделать нормальную декомпозицию шаблона, ни организовать вывод рекурсивных данных (для этого нужно объявить рекурсивную функцию). Решается это через некий template module (не помню как он у них называется) — отдельный модуль-класс (или модуль-функция... не помню) где-то в дебрях структуры сайта, даже если нужен он для одного единственного шаблона. Заточка под дизайнеров с дримвиверами, ага.

Гы, в общем не надо мне рассказывать про "ограничение в средствах" — я этого говна сполна наелся.

Вот. Это было во-первых и в главных.




Во-вторых, добровольное использование искуственных костылей является признанием в неспособности самостоятельно придерживаться простых code conventions. Я не скажу, что это просто — это песец как трудно. Но тем не менее, по сумме аргументов (включая эффективность и поддержку от IDE) я свой выбор сделал. Имею Мнение — Хрен Оспоришь.




Кстати, шаблонизатор Lift хорош тем, что в шаблоны в принципе невозможно внедрить код. При этом шаблоны эти ну просто до безобразия гибкие, и вообще всячески достойны изучения как образец правильного подхода к разделению кода и представления. И тем не менее, в них изначально заложена официальная возможность генерирвать вёрстку из контроллеров. В сущности весь шаблонизатор построен на этом:

// Шаблон:
<table>
  <Hello:world>
        <tr><th>User name:</th><td><user:name/></td>...</tr>
    </Hello:world>
</table>

// Код :
class Hello {
  def world(content: NodeSeq) = bind("user", content, "name" -> "John Brown")
    // Метод возвращает NodeSeq с подставленным значением <user:name/>.
}


Чуете мощь? Контроллер вызывается из шаблона, контроллеру на вход передаётся кусок разметки с макросами <ns:var/> (внутренность тега <Hello:world>), он подставляет туда значения макросов с помощью bind("ns", xml, "var1" -> value1, "var2" -> value2, ...), ничего не зная о структуре разметки, и возвращает готовый NodeSeq. Циклы там делаются похожим образом. Вся диспетчеризация запросов построена на паттерне "view first": запрос мапится на шаблон, а шаблон в нужных местах инклудит контроллеры описанным образом. Очень красиво и удобно. Не нужен единый контроллер на каждую страницу, в котором нужно помнить выполнить генерацию данных для всех блоков страницы. При этом сам шаблон вместе с вызовыми контроллеров и параметрами контроллеров — голый HTML.

Тем не менее, ничто не мешает мне внедрить в шаблон вызов <Goodbye:world/> и сгенерировать в методе Goodbye.world() возвращаемый NodeSeq с нуля. Так что опять всё упирается в самодисциплину.

Кстати говоря, в PHP-шаблонах Symfony (повторюсь: наиболее продвинутый PHP web framework) тоже всё упирается в самодисциплину. Ответственный товарищ будет пользовать date() и number_format(), а безответственный — mysql_query(). Из сказанного я делаю вывод, что эволюция движется по направлению от искуственных ограничений в сторону удобств для ответственных товарищей.




Ну и в-третьих, добавлю чисто философское соображение: если язык сам по себе (включая либы, разумеется) настолько выразителен, что на нём можно сделать что-либо просто, то было бы идиотизмом наворачивать поверх лишний слой сложности. Это лишь добавит головной боли тем, кто будет этот фреймворк изучать. Это и к вопросу о наличии функции date(), и к тому, что память наша не резиновая. Одно дело — тонкий слой поверх известных вдоль и поперёк возможностей языка и Servlet API (спецы по сервлетам могли заметить, что моё решение с диспетчеризацией запросов и обработкой ошибок — это в сущности перепевка настроек web.xml с использованием pattern matching), а другое — дикие навороты, в которых чёрт ногу сломает.




В общем, единственный аргумент за внешний шаблонизатор, который я готов принять, — это дизайнеры с дримвиверами. Но как я уже говорил, мне этот use case на данный момент не интересен. (Если он мне вдруг станет интересен, я в ноль cклонирую шаблоны Lift в отдельный независимый модуль. Обеспечу совместимость, так сказать.)
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[8]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: dimgel Россия https://github.com/dimgel
Дата: 26.06.09 11:45
Оценка: 18 (1)
Здравствуйте, Mamut, Вы писали:

M>А если шаблон состоит из несколькизх подшаблонов?


Функциональная декомпозиция. В моём втором примере показан один use-case. Ты зачем поставил там оценку, если разобраться не удосужился?

M>А если шаблон один раз скомпилировать в некое промежуточное представление?


Ага, создадим себе трудности и затем успешно их преодолеем. Автор Lift как раз сейчас работает над кешированием своих шаблонов. (Интересно, cglib использует?) Я повторю свои аргументы:

(1) внешние шаблоны не дают нужной мне степени контроля со стороны IDE;
(2) внешние шаблоны лично мне не нужны, т.к. вёрстку я (почти всегда переписываю и) прикручиваю к сайту сам;
(3) поэтому для меня внешние шаблоны — это просто ещё один слой поверх языка, и так предоставляющего всё, что мне нужно;
(4) желающие могут легко заюзать свой любимый шаблонизатор: в простейшем случае можно тупо генерировать всё что нужно через шаблонизатор, а затем отдавать результат на вход HTMLResponse.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[10]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: dimgel Россия https://github.com/dimgel
Дата: 26.06.09 12:04
Оценка:
D>Кстати, в Symfony (самый навороченный на данный момент PHP web framework) в качестве шаблонов используются обычные PHP-файлы. Хочешь — гони обычную разметку с <?php>-вставками (если ты дизайнер с дримвивером), хочешь — гони PHP-код с echo "<p>...</p>". Это очень правильно в том смысле,

А ещё в том, что для PHP+HTML все известные мне HTML-редакторы обеспечивают syntax hilighting & code completion, а для Smarty-шаблонов — нет.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[10]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: Mamut Швеция http://dmitriid.com
Дата: 26.06.09 12:35
Оценка:
Здравствуйте, dimgel, Вы писали:

d> К>как реализуются циклы/условные выражения?


d> Scala-код, внедрённый внутрь XML-литерала, может содержать другие XML-литералы, также с внедрённым скала кодом, и т.д. Рекурсивно. Так что все управляющие конструкции пишутся на scala.



То есть это будет выглядеть типа

NodeSeq = 
   <html><body><p>
       {SomeClass.SomeMethod(SomeVar)}
       { 
          for...
          ....
          ....
       }
       {  while.... }
   </p></body></html>

?

d> К>По-моему не зря шаблоны делают ограниченными в средствах


d> Может и не зря, но ничего кроме геморроя это в итоге не даёт. Элементарный, но часто встречающийся и вусмерть за#$%вающий пример: где форматировать дату?


Ай. Это больно

[skip]

d>


d> Кстати, шаблонизатор Lift хорош тем, что в шаблоны в принципе невозможно внедрить код. При этом шаблоны эти ну просто до безобразия гибкие, и вообще всячески достойны изучения как образец правильного подхода к разделению кода и представления. И тем не менее, в них изначально заложена официальная возможность генерирвать вёрстку из контроллеров. В сущности весь шаблонизатор построен на этом:


d>
d> // Шаблон:
d> <table>
d>         <tr><th>User name:</th><td><user:name/></td>...</tr>
d> </table>

d> // Код :
d>   def world(content: NodeSeq) = bind("user", content, "name" -> "John Brown")
d>     // Метод возвращает NodeSeq с подставленным значением <user:name/>.
d> }
d>


d> Тем не менее, ничто не мешает мне внедрить в шаблон вызов <Goodbye:world/> и сгенерировать в методе Goodbye.world() возвращаемый NodeSeq с нуля. Так что опять всё упирается в самодисциплину.



У меня, кстати, в планах — допилить Эрланговский ErlyDTL до чего-то похожего (или тут). Потому что даже ограниченный инструмент должен бть максимально расширенным
avalon 1.0rc1 rev 239, zlib 1.2.3


dmitriid.comGitHubLinkedIn
Re[11]: Я тут потихоньку ковыряю веб-фреймворк на scala...
От: dimgel Россия https://github.com/dimgel
Дата: 26.06.09 12:53
Оценка: 18 (1)
Здравствуйте, Mamut, Вы писали:

M>То есть это будет выглядеть типа


M>
M>NodeSeq = 
M>   <html><body><p>
M>       {SomeClass.SomeMethod(SomeVar)}
M>       { 
M>          for...
M>          ....
M>          ....
M>       }
M>       {  while.... }
M>   </p></body></html>
M>

M>?

Скорее так:

<table>
  <tr><th>ID</th><th>Name</th></tr>
  {for(r <- users.getList(rq)) yield
    <tr><td>{r.id}</td>{r.name}</td></tr>
  }
</table>


То есть всё выглядит довольно пристойно и читабельно, главное здесь — со вкусом расставить скобки. :-P
Ну и какой-то контекст придётся протаскивать всюду — Request или что-то application-specific... Не плодить же ThreadLocal по каждому поводу.

Сдаётся мне, следующий пример будет "pure scala templates howto". Хотя я планировал нечто иное... оно сейчас в процессе обмозговывания, но лучше я сначала его домозгую, а потом "с высоты достигнутого" просмотрю свою доку по шаблонам ещё раз и оформлю примером.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.