text:
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "common/path.h"

#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <algorithm>
#include <cassert>
#include <filesystem>  // NOLINT [We are not in google3]

#include "absl/strings/ascii.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.h"
#include "common/buffer.h"
#include "common/errno_mapping.h"
#include "common/log.h"
#include "common/status.h"
#include "common/status_macros.h"
#include "common/util.h"

#if PLATFORM_LINUX
#include <ftw.h>     // nftw
#include <stdlib.h>  // putenv
#include <unistd.h>  // readlink
#include <utime.h>   // struct utimbuf
#include <wordexp.h>
#define __stat64 stat64
#define _chmod chmod
#endif

#if PLATFORM_WINDOWS
#include <corecrt_io.h>  // chmod
#include <direct.h>      // _rmdir

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef ReplaceFile

#include <shlobj_core.h>  // SHGetKnownFolderPath
#endif

namespace cdc_ft {
namespace {

constexpr char kPathSeparators[] = "\\/";

#if PLATFORM_WINDOWS
// Jan 1, 1970 (begin of Unix epoch) in 100 ns ticks since Jan 1, 1601 (begin
// of Windows epoch).
constexpr int64_t kUnixToWindowsEpochOffset = 0x019DB1DED53E8000;

// A Windows tick is 100 ns.
constexpr int64_t kWinTicksPerSecond = 10000000;
#endif

// Checks if the given path needs a path separator appended before the next
// component can be added.
bool NeedsPathSep(const std::string* path) {
  if (path->empty()) return false;
  return path->back() != path::PathSeparator() &&
         path->back() != path::OtherPathSeparator();
}

void JoinImpl(std::string* dest, absl::string_view path,
              absl::string_view to_append, char separator) {
  assert(dest != nullptr);
  dest->clear();
  dest->reserve(to_append.length() + path.length() + 1);
  if (!path.empty()) absl::StrAppend(dest, path);
  if (!to_append.empty()) {
    if (NeedsPathSep(dest)) dest->push_back(separator);
    absl::StrAppend(dest, to_append);
  }
}

void AppendImpl(std::string* dest, absl::string_view to_append,
                char separator) {
  if (to_append.empty()) return;
  assert(dest != nullptr);
  dest->reserve(dest->length() + to_append.length() + 1);
  if (NeedsPathSep(dest)) dest->push_back(separator);
  absl::StrAppend(dest, to_append);
}

#if PLATFORM_WINDOWS
FILETIME UnixTimeToWindowsFileTime(time_t unix_timestamp) {
  int64_t winTicks = static_cast<int64_t>(unix_timestamp) * kWinTicksPerSecond +
                     kUnixToWindowsEpochOffset;
  if (winTicks < 0) {
    // FILETIME does not support dates before 1601. Ah well, we'll get over it.
    winTicks = 0;
  }

  FILETIME ft;
  ft.dwLowDateTime = winTicks & 0xffffffff;
  ft.dwHighDateTime = winTicks >> 32;
  return ft;
}
#endif

}  // namespace

namespace path {
namespace {

// absl::StrSplit delimiter that handles both "\r\n" and "\n" newlines.
class ByNewline {
 public:
  absl::string_view Find(absl::string_view text, size_t pos) const {
    size_t found_pos = text.find("\n", pos);
    if (found_pos == absl::string_view::npos) {
      return absl::string_view(text.data() + text.size(), 0);
    }
    if (found_pos > 0 && text[found_pos - 1] == '\r') {
      return text.substr(found_pos - 1, 2);
    }
    return text.substr(found_pos, 1);
  }
};

#if PLATFORM_WINDOWS
uint64_t ToUint64(uint32_t high, uint32_t low) {
  return (((uint64_t)high) << 32) | low;
}

// Converts Windows ticks to Unix epoch. Adapted from
// https://developers.google.com/protocol-buffers/docs/reference/csharp/class/google/protobuf/well-known-types/timestamp
int64_t FileTimeToTimeT(FILETIME ft) {
  uint64_t ticks = ToUint64(ft.dwHighDateTime, ft.dwLowDateTime);
  // Divide by kWinTicksPerSecond first to avoid overlfow when
  // ticks < kUnixToWindowsEpochOffset.
  return static_cast<int64_t>(ticks / kWinTicksPerSecond) -
         static_cast<int64_t>(kUnixToWindowsEpochOffset / kWinTicksPerSecond);
}

KNOWNFOLDERID ToWinFolderId(FolderId folder_id) {
  switch (folder_id) {
    case FolderId::kRoamingAppData:
      return FOLDERID_RoamingAppData;
    case FolderId::kProgramFiles:
      return FOLDERID_ProgramFiles;
  }
  return {0};
}

#endif

}  // namespace

absl::Status GetExeDir(std::string* dir) {
  dir->clear();
#if PLATFORM_WINDOWS
  wchar_t wide_exe_path[MAX_PATH];
  if (FAILED(GetModuleFileName(nullptr, wide_exe_path, MAX_PATH))) {
    return MakeStatus("Failed to get module file name");
  }
  std::string exe_path = Util::WideToUtf8Str(wide_exe_path);
#elif PLATFORM_LINUX
  char exe_path[PATH_MAX];
  ssize_t count = readlink("/proc/self/exe", exe_path, PATH_MAX);
  if (count == -1) {
    return MakeStatus("readlink() failed");
  }
#endif

  *dir = DirName(exe_path);
  return absl::OkStatus();
}

std::string GetTempDir() {
  return std::filesystem::temp_directory_path().u8string();
}

#if PLATFORM_WINDOWS
absl::Status GetKnownFolderPath(FolderId folder_id, std::string* path) {
  path->clear();
  wchar_t* wchar_path = nullptr;
  KNOWNFOLDERID win_id = ToWinFolderId(folder_id);
  if (!SUCCEEDED(SHGetKnownFolderPath(win_id, 0, NULL, &wchar_path))) {
    return MakeStatus("Failed to get known folder path");
  }
  *path = Util::WideToUtf8Str(wchar_path);
  CoTaskMemFree(wchar_path);
  return absl::OkStatus();
}
#endif

absl::Status ExpandPathVariables(std::string* path) {
#if PLATFORM_WINDOWS
  std::wstring wchar_path = Util::Utf8ToWideStr(*path);

  DWORD size = ::ExpandEnvironmentStrings(wchar_path.c_str(), nullptr, 0);
  if (size == 0) {
    return MakeStatus("Failed to get length of expanded path");
  }

  std::wstring wchar_expanded(size, 0);
  wchar_t* out_ptr = const_cast<wchar_t*>(wchar_expanded.c_str());
  if (ExpandEnvironmentStrings(wchar_path.c_str(), out_ptr, size) != size) {
    return MakeStatus("Failed to expand path");
  }

  // wchar_expanded has two null terminators at this point. Pop one.
  wchar_expanded.pop_back();
  *path = Util::WideToUtf8Str(wchar_expanded);
  return absl::OkStatus();
#else
  wordexp_t res;
  wordexp(path->c_str(), &res, 0);
  if (res.we_wordc > 1) {
    return absl::InvalidArgumentError(
        "Path expands to multiple results (did you use * etc. ?");
  }
  *path = res.we_wordv[0];
  wordfree(&res);
  return absl::OkStatus();
#endif
}

absl::Status GetEnv(const std::string& name, std::string* value) {
  value->clear();
#if PLATFORM_WINDOWS
  // The first call returns the size INCLUDING the null terminator.
  std::wstring wchar_name = Util::Utf8ToWideStr(name);
  DWORD size = ::GetEnvironmentVariable(wchar_name.c_str(), NULL, 0);
  if (size == 0) {
    return absl::NotFoundError(
        absl::StrFormat("Environment variable '%s' not found", name.c_str()));
  }

  // The first call returns the bytes written EXCLUDING the null terminator,
  // hence the '+ 1'.
  std::wstring wstr_value(size, 0);
  wchar_t* val_ptr = const_cast<wchar_t*>(wstr_value.c_str());
  if (::GetEnvironmentVariable(wchar_name.c_str(), val_ptr, size) + 1 != size) {
    return MakeStatus("Failed to get environment variable '%s'", name);
  }

  // Pop null terminator.
  wstr_value.pop_back();

  *value = Util::WideToUtf8Str(wstr_value);
  return absl::OkStatus();
#elif PLATFORM_LINUX
  char* value_cstr = std::getenv(name.c_str());
  if (!value_cstr) {
    return absl::NotFoundError(
        absl::StrFormat("Environment variable '%s' not found", name.c_str()));
  }

  *value = value_cstr;
  return absl::OkStatus();
#endif
}

absl::Status SetEnv(const std::string& name, const std::string& value) {
#if PLATFORM_WINDOWS
  std::wstring wchar_name = Util::Utf8ToWideStr(name);
  std::wstring wchar_value = Util::Utf8ToWideStr(value);
  if (!SetEnvironmentVariable(wchar_name.c_str(), wchar_value.c_str())) {
    return MakeStatus("Failed to set environment variable '%s'", name);
  }
  return absl::OkStatus();
#elif PLATFORM_LINUX
  if (setenv(name.c_str(), value.c_str(), /*overwrite=*/1) != 0) {
    return MakeStatus("Failed to set environment variable '%s'", name);
  }
  return absl::OkStatus();
#endif
}

#if PLATFORM_WINDOWS
std::string GetDrivePrefix(const std::string& path) {
  if (path.empty()) {
    return std::string();
  }

  if (path[0] != '\\') {
    size_t pos = path.find(":");
    if (pos == std::string::npos) {
      // E.g. "\path\to\file" or "path\to\file".
      return std::string();
    }

    // E.g. "C:" or "C:\path\to\file".
    return path.substr(0, pos + 1);
  }

  // Check if it is a network share like \\computername\sharename\path\to\file.
  if (path.size() == 1 || path[1] != '\\') {
    return std::string();
  }

  // 3 since the computername should be non-empty.
  size_t third_backslash_pos = path.find('\\', 2);
  if (third_backslash_pos == std::string::npos || third_backslash_pos < 3 ||
      path.size() == third_backslash_pos + 1) {
    // E.g. "\\c" or "\\\" or "\\c\".
    return std::string();
  }

  size_t fourth_backslash_pos = path.find('\\', third_backslash_pos + 1);
  if (fourth_backslash_pos < third_backslash_pos + 2) {
    // E.g. "\\c\".
    return std::string();
  }

  // E.g. "\\c\s" or "\\c\s\path\to\file"
  return path.substr(0, fourth_backslash_pos);
}
#endif  // if PLATFORM_WINDOWS

std::string GetCwd() { return std::filesystem::current_path().u8string(); }

void SetCwd(const std::string& path) {
  std::filesystem::current_path(std::filesystem::u8path(path));
}

std::string GetFullPath(const std::string& path) {
  if (path.empty()) {
    return std::string();
  }
  std::filesystem::path fspath = std::filesystem::u8path(path);
  // Expand relative paths with the current working directory to match the
  // behavior of the Windows GetFullPathName*() API.
  if (fspath.is_relative()) {
    auto cwd = std::filesystem::current_path();
    // Special case on Windows: paths like "C:" or "C:foo", which are
    // interpreted relative to the drive's cwd. If the drive letter is different
    // from the current drive, then the directory is treated as begin absolute.
    if (fspath.has_root_name()) {
      if (fspath.root_name() != cwd.root_name()) {
        // This matches the behavior of std::filesystem::absolute() for a root
        // other than the cwd.
        fspath = std::filesystem::absolute(fspath.root_name()) / fspath;
      }
    }
    fspath = cwd / fspath;
  }
  std::error_code ec;
  std::filesystem::path cfspath = std::filesystem::weakly_canonical(fspath, ec);
  if (ec) {
    // Fall back to creating a normalized absolute path.
    cfspath = std::filesystem::absolute(fspath, ec).lexically_normal();
    if (ec) {
      LOG_WARNING("Failed to canonicalize path '%s': %s", path, ec.message());
      return fspath.u8string();
    }
  }
  return cfspath.u8string();
}

bool IsAbsolute(const std::string& path) {
  return std::filesystem::u8path(path).is_absolute();
}

std::string DirName(const std::string& path) {
  if (path.empty()) return std::string();
#if PLATFORM_WINDOWS
  // Keep the drive prefix.
  const std::string drive = GetDrivePrefix(path);
  std::string dirpart = path.substr(drive.size());
#else
  const std::string drive;
  const std::string& dirpart = path;
#endif
  size_t non_sep_pos = dirpart.find_last_not_of(kPathSeparators);
  size_t sep_pos = dirpart.find_last_of(kPathSeparators, non_sep_pos);
  if (sep_pos == std::string::npos) {
    // Handle the cases "foo", "C:", "\\computer\share".
    return drive;
  }
  // Handle the cases "/", "\\\\", "C:\\".
  if (non_sep_pos == std::string::npos) return path;
  // Handle the cases "/foo", "///foo", "C:\\foo", or "\\\\foo".
  size_t dir_end_pos = dirpart.find_last_not_of(kPathSeparators, sep_pos);
  if (dir_end_pos == std::string::npos)
    return path.substr(0, drive.size() + sep_pos + 1);
  // Handle the cases "/foo/bar", "foo/bar", "C:\\foo", "\\\\foo\\bar".
  return path.substr(0, drive.size() + dir_end_pos + 1);
}

std::string BaseName(const std::string& path) {
  if (path.empty()) return std::string();
  size_t non_sep_pos = path.find_last_not_of(kPathSeparators);
  size_t sep_pos = path.find_last_of(kPathSeparators, non_sep_pos);
  if (non_sep_pos == std::string::npos) non_sep_pos = path.size() - 1;
  if (sep_pos != std::string::npos)
    return path.substr(sep_pos + 1, non_sep_pos - sep_pos);
  return path.substr(0, non_sep_pos + 1);
}

std::string Join(absl::string_view path, absl::string_view to_append) {
  std::string dest;
  Join(&dest, path, to_append);
  return dest;
}

std::string Join(absl::string_view path, absl::string_view to_append1,
                 absl::string_view to_append2) {
  return Join(Join(path, to_append1), to_append2);
}

std::string Join(absl::string_view path, absl::string_view to_append1,
                 absl::string_view to_append2, absl::string_view to_append3) {
  return Join(Join(path, to_append1, to_append2), to_append3);
}

void Join(std::string* dest, absl::string_view path,
          absl::string_view to_append) {
  JoinImpl(dest, path, to_append, path::PathSeparator());
}

std::string JoinUnix(absl::string_view path, absl::string_view to_append) {
  std::string dest;
  JoinImpl(&dest, path, to_append, '/');
  return dest;
}

void Append(std::string* dest, absl::string_view to_append) {
  AppendImpl(dest, to_append, path::PathSeparator());
}

void AppendUnix(std::string* dest, absl::string_view to_append) {
  AppendImpl(dest, to_append, '/');
}

bool EndsWithPathSeparator(const std::string& path) {
  return !path.empty() && (path.back() == PathSeparator() ||
                           path.back() == OtherPathSeparator());
}

void EnsureEndsWithPathSeparator(std::string* path) {
  if (!EndsWithPathSeparator(*path)) {
    path->push_back(PathSeparator());
  }
}

void EnsureDoesNotEndWithPathSeparator(std::string* path) {
  while (EndsWithPathSeparator(*path)) {
    path->pop_back();
  }
}

void FixPathSeparators(std::string* path) {
  std::replace(path->begin(), path->end(), OtherPathSeparator(),
               PathSeparator());
}

std::string ToUnix(std::string path) {
  std::replace(path.begin(), path.end(), '\\', '/');
  return path;
}

std::string ToNative(std::string path) {
  std::replace(path.begin(), path.end(), OtherPathSeparator(), PathSeparator());
  return path;
}

absl::StatusOr<FILE*> OpenFile(const std::string& path, const char* mode) {
  FILE* file;
#if PLATFORM_WINDOWS
  std::wstring wchar_path = Util::Utf8ToWideStr(path);
  std::wstring wchar_mode = Util::Utf8ToWideStr(mode);

  file = _wfsopen(wchar_path.c_str(), wchar_mode.c_str(), _SH_DENYNO);
#else
  // In Linux, _SH_DENYNO is set by default.
  // Locking might be realized by lockf or fcntl.
  file = fopen(path.c_str(), mode);
#endif
  if (!file) {
    int err = errno;
    return ErrnoToCanonicalStatus(err, "Failed to open file '%s'", path);
  }
  return file;
}

#if PLATFORM_WINDOWS

void LogSearchError(const std::wstring& path) {
  uint32_t last_error = GetLastError();
  if (last_error == ERROR_FILE_NOT_FOUND || last_error == ERROR_NO_MORE_FILES) {
    // This is fine, could be an empty directory or pattern didn't find any
    // files in this directory.
    return;
  }

  std::string utf8_path = Util::WideToUtf8Str(path);

  if (last_error == ERROR_ACCESS_DENIED) {
    // Access denied is somewhat expected, so just warn and return true.
    LOG_WARNING("Failed to search '%s': Access denied", utf8_path.c_str());
    return;
  }

  LOG_ERROR("Failed to search '%s': %s", utf8_path.c_str(),
            Util::GetLastWin32Error().c_str());
  return;
}

bool IsRegularDir(const WIN32_FIND_DATA& data) {
  // Not a directory? Note that FindExSearchLimitToDirectories is just a hint.
  if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
    return false;
  }

  // Ignore "." and "..".
  if (wcscmp(data.cFileName, L".") == 0 || wcscmp(data.cFileName, L"..") == 0) {
    return false;
  }

  return true;
}

absl::Status SearchFilesWin(const std::wstring& dir,
                            const std::wstring& pattern, bool recursive,
                            SearchHandler handler, bool reset_pattern) {
  if (!recursive && pattern.empty()) {
    return absl::OkStatus();
  }
  WIN32_FIND_DATA data;
  HANDLE hFind;
  // For non-trivial patterns (!= "*"), we need a second pass to recurse into
  // subdirectories. For instance "*.txt" wouldn't find "subdir\text.txt" since
  // "subdir" doesn't match "*.txt".
  // The condition with pattern.find('*') guarantees that the same directory
  // is traversed only once, which is necessary for example for the first-level
  // directory: for sync \source\dir_to_copy /destination: dir = source, pattern
  // = dir_co_copy.
  bool need_separate_dir_pass =
      pattern != L"*" && pattern.find('*') != std::wstring::npos;
  //
  // Find all files. Also recurse into subdirs if !need_separate_dir_pass.
  //
  std::wstring path = dir + pattern;
  hFind = FindFirstFileEx(path.c_str(), FindExInfoBasic, &data,
                          FindExSearchNameMatch, nullptr,
                          FIND_FIRST_EX_LARGE_FETCH);

  if (hFind == INVALID_HANDLE_VALUE) {
    LogSearchError(path);
  } else {
    do {
      if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
        if (IsRegularDir(data)) {
          // Send information about the directory to the server, then
          // continue on processing its files recursively.
          std::string utf8_filename = Util::WideToUtf8Str(data.cFileName);
          std::string utf8_dir = Util::WideToUtf8Str(dir);
          int64_t modified_time = FileTimeToTimeT(data.ftLastWriteTime);
          absl::Status status =
              handler(utf8_dir, utf8_filename, modified_time, 0, true);
          if (!status.ok()) {
            return status;
          }
          if (recursive) {
            // Recurse into subdirectories.
            std::wstring subdir = dir + data.cFileName + L"\\";
            std::wstring new_pattern = pattern;
            if (reset_pattern) {
              new_pattern = L"*";
            }
            status =
                SearchFilesWin(subdir, new_pattern, recursive, handler, false);
            if (!status.ok()) {
              return status;
            }
          }
        }
        continue;
      }
      // Send filename, write time and file size to the handler.
      std::string utf8_dir = Util::WideToUtf8Str(dir);
      std::string utf8_filename = Util::WideToUtf8Str(data.cFileName);
      int64_t modified_time = FileTimeToTimeT(data.ftLastWriteTime);
      uint64_t size = ToUint64(data.nFileSizeHigh, data.nFileSizeLow);
      absl::Status status =
          handler(utf8_dir, utf8_filename, modified_time, size, false);
      if (!status.ok()) {
        FindClose(hFind);
        return WrapStatus(status, "Failed to search '%s': Handler failed",
                          utf8_dir + utf8_filename);
      }
    } while (FindNextFile(hFind, &data) != 0);
    LogSearchError(dir);
    FindClose(hFind);
  }

  //
  // Do separate directory pass to recurse into subdirectories.
  //

  if (recursive && need_separate_dir_pass) {
    hFind = FindFirstFileEx((dir + L"*").c_str(), FindExInfoBasic, &data,
                            FindExSearchLimitToDirectories, nullptr,
                            FIND_FIRST_EX_LARGE_FETCH);
    if (hFind == INVALID_HANDLE_VALUE) {
      LogSearchError(dir);
    } else {
      do {
        if (IsRegularDir(data)) {
          std::wstring subdir = dir + data.cFileName + L"\\";
          std::wstring new_pattern = pattern;
          if (reset_pattern) {
            new_pattern = L"*";
          }
          absl::Status status =
              SearchFilesWin(subdir, new_pattern, recursive, handler, false);
          if (!status.ok()) {
            return status;
          }
        }
      } while (FindNextFile(hFind, &data) != 0);

      LogSearchError(dir);
      FindClose(hFind);
    }
  }
  return absl::OkStatus();
}

#elif PLATFORM_LINUX

namespace {

// nftw() does not allow currying its handler function. Capturing lambdas don't
// cast to a plain function pointer. This is an (ugly) workaround. See e.g.
// https://stackoverflow.com/questions/7852101/c-lambda-with-captures-as-a-function-pointer
thread_local struct GlobalFtwData {
  SearchHandler handler;
  bool recursive = false;
  absl::Status status;
} global_ftw_data;

int ftw_handler(const char* filepath, const struct stat* info, int typeflag,
                struct FTW* pathinfo) {
  // Send files to the handler.
  if (typeflag == FTW_F) {
    std::string dir(filepath, pathinfo->base);
    std::string filename = filepath + pathinfo->base;
    int64_t modified_time = info->st_mtime;
    uint64_t size = info->st_size;

    // Make nftw stop iterating the file tree if the handler fails.
    global_ftw_data.status =
        global_ftw_data.handler(dir, filename, modified_time, size, false);
    if (!global_ftw_data.status.ok()) {
      return FTW_STOP;
    }
  }
  if (typeflag == FTW_D && pathinfo->level > 0) {
    std::string dir(filepath, pathinfo->base);
    std::string dirname = filepath + pathinfo->base;
    if (dirname != "." && dirname != "..") {
      int64_t modified_time = info->st_mtime;

      // Make nftw stop iterating the tree if the handler fails.
      global_ftw_data.status =
          global_ftw_data.handler(dir, dirname, modified_time, 0, true);
      if (!global_ftw_data.status.ok()) {
        return FTW_STOP;
      }
      if (!global_ftw_data.recursive) {
        return FTW_SKIP_SUBTREE;
      }
    }
  }
  return FTW_CONTINUE;
}

}  // namespace

absl::Status SearchFilesLinux(const std::string& dir, bool recursive,
                              SearchHandler handler) {
  // FTW_PHYS means "do not follow symlinks", i.e. symlinks will be overwritten
  // by client files if they have the same name. 32 is the max directory depth
  // until things get slower.
  global_ftw_data = {std::move(handler), recursive};
  int result = nftw(dir.c_str(), ftw_handler, 32, FTW_PHYS | FTW_ACTIONRETVAL);
  absl::Status status = global_ftw_data.status;
  global_ftw_data = GlobalFtwData();

  // ENOENT means the directory does not exist. This is fine.
  if (result < 0 && errno != ENOENT) {
    return MakeStatus("Failed to search '%s': nftw() failed: %s.", dir,
                      strerror(errno));
  }
  if (result > 0) {
    // Should only happen if ftw_handler returns FTW_STOP.
    assert(!status.ok());
    assert(result == FTW_STOP);
    return WrapStatus(status, "Failed to search '%s': Search handler failed",
                      dir);
  }

  return absl::OkStatus();
}
#endif

absl::Status SearchFiles(const std::string& pattern, bool recursive,
                         SearchHandler handler) {
#if PLATFORM_WINDOWS
  std::wstring wchar_pattern = Util::Utf8ToWideStr(pattern);

  // If |pattern| is a directory, search that directory. Otherwise, assume it's
  // a file or some file pattern.
  std::wstring dir = wchar_pattern;
  std::wstring filename_pattern;
  DWORD ftyp = GetFileAttributes(dir.c_str());
  bool reset_pattern = false;

  if (ftyp != INVALID_FILE_ATTRIBUTES && (ftyp & FILE_ATTRIBUTE_DIRECTORY)) {
    // It's a directory.
    if (!recursive) {
      return SearchFilesWin(dir, filename_pattern, recursive, handler,
                            reset_pattern);
    }
    if (dir.back() == L'\\') {
      filename_pattern = L"*";
    } else {
      // On Linux, source directory without trailing \\ is traversed.
      // To enable the same behavior on windows: pattern=dir_to_copy
      // and it will be reset after the first-level traversal.
      // Otherwise, server_dirs and client_dirs would differ.
      reset_pattern = true;
      while (!dir.empty() && dir.back() != L'\\') {
        dir.pop_back();
      }
      filename_pattern = wchar_pattern.substr(dir.size());
    }
  } else {
    // Assume it's a file or file pattern.
    while (!dir.empty() && dir.back() != L'\\') {
      dir.pop_back();
    }
    filename_pattern = wchar_pattern.substr(dir.size());
  }
  return SearchFilesWin(dir, filename_pattern, recursive, handler,
                        reset_pattern);
#else
  return SearchFilesLinux(pattern, recursive, handler);
#endif
}

absl::Status StreamReadFileContents(FILE* file, size_t buffer_size,
                                    StreamReadFileHandler handler) {
  Buffer buffer(buffer_size);
  return StreamReadFileContents(file, &buffer, handler);
}

absl::Status StreamReadFileContents(FILE* file, Buffer* buffer,
                                    StreamReadFileHandler handler) {
  if (!buffer->size()) {
    return absl::FailedPreconditionError("Given buffer is empty");
  }

  for (;;) {
    const size_t num_read =
        fread_nolock(buffer->data(), 1, buffer->size(), file);

    // Note: The handler might read from file, so check feof_nolock now.
    bool eof = num_read != buffer->size() && feof_nolock(file);
    if (num_read > 0) {
      absl::Status status = handler(buffer->data(), num_read);
      if (!status.ok()) {
        return WrapStatus(status, "Handler failed");
      }
    }
    if (num_read != buffer->size()) {
      if (eof) {
        // Indicate eof.
        absl::Status status = handler(nullptr, 0);
        if (!status.ok()) {
          return WrapStatus(status, "Handler failed");
        }
        return absl::OkStatus();
      }

      return MakeStatus("fread_nolock() failed");
    }
  }
}

absl::Status StreamWriteFileContents(FILE* file,
                                     StreamWriteFileHandler handler) {
  for (;;) {
    const void* data;
    size_t data_size;
    absl::Status status = handler(&data, &data_size);
    if (!status.ok()) {
      return WrapStatus(status, "Handler failed");
    }
    // By returning (nullptr, 0), the handler indicates EOF.
    if (data_size == 0) {
      assert(!data);
      return absl::OkStatus();
    }

    const size_t num_written = fwrite_nolock(data, 1, data_size, file);
    if (num_written != data_size) {
      return MakeStatus("fwrite_nolock() failed");
    }
  }
}

absl::Status ReadFile(const std::string& path, Buffer* data) {
  assert(data);
  data->clear();

  absl::StatusOr<FILE*> file = OpenFile(path, "rb");
  if (!file.ok()) {
    return file.status();
  }

  fseek64_nolock(*file, 0, SEEK_END);
  int64_t size = ftell64(*file);
  data->resize(size);
  fseek64_nolock(*file, 0, SEEK_SET);

  if (fread_nolock(data->data(), 1, data->size(), *file) != data->size()) {
    data->clear();
    return MakeStatus("Failed to read data of size %u from file '%s'",
                      data->size(), path);
  }

  fclose(*file);
  return absl::OkStatus();
}

absl::StatusOr<size_t> ReadFile(const std::string& path, void* data,
                                size_t offset, size_t len) {
  assert(data);
  if (len == 0) {
    return 0;
  }

  absl::StatusOr<FILE*> file = OpenFile(path, "rb");
  if (!file.ok()) {
    return file.status();
  }
  if (offset != 0) {
    fseek64_nolock(*file, offset, SEEK_SET);
  }
  size_t read_len = fread_nolock(data, 1, len, *file);

  if (read_len != len && !feof_nolock(*file)) {
    fclose(*file);
    return MakeStatus("fread_nolock failed: %s", Util::GetLastStrError());
  }
  fclose(*file);
  return read_len;
}

absl::StatusOr<std::string> ReadFile(const std::string& path) {
  absl::StatusOr<FILE*> file = OpenFile(path, "rb");
  if (!file.ok()) {
    return file.status();
  }

  fseek64_nolock(*file, 0, SEEK_END);
  int64_t size = ftell64(*file);
  std::string data;
  data.resize(size);
  fseek64_nolock(*file, 0, SEEK_SET);

  if (fread_nolock(data.data(), 1, data.size(), *file) != data.size()) {
    data.clear();
    return MakeStatus("Failed to read data of size %u from file '%s'",
                      data.size(), path);
  }

  fclose(*file);
  return data;
}

absl::Status ReadAllLines(const std::string& path,
                          std::vector<std::string>* lines, ReadFlags flags) {
  Buffer data;
  absl::Status status = ReadFile(path, &data);
  if (!status.ok()) {
    return status;
  }

  std::string all_lines =
      std::string(reinterpret_cast<char*>(data.data()), data.size());

  *lines = absl::StrSplit(all_lines, ByNewline());

  if ((flags & ReadFlags::kTrimWhitespace) != ReadFlags::kNone) {
    for (std::string& line : *lines) {
      absl::StripAsciiWhitespace(&line);
    }
  }

  if ((flags & ReadFlags::kRemoveEmpty) != ReadFlags::kNone) {
    lines->erase(
        std::remove_if(lines->begin(), lines->end(),
                       [](const std::string& line) { return line.empty(); }),
        lines->end());
  }

  return absl::OkStatus();
}

absl::Status GetStats(const std::string& path, Stats* stats) {
  struct __stat64 os_stats;
  bool result;
#if PLATFORM_WINDOWS
  result = _wstat64(Util::Utf8ToWideStr(path).c_str(), &os_stats) == 0;
#elif PLATFORM_LINUX
  result = stat64(path.c_str(), &os_stats) == 0;
#endif
  if (!result) {
    int err = errno;
    *stats = Stats();
    return ErrnoToCanonicalStatus(err, "Failed to stat '%s'", path);
  }

  stats->mode = os_stats.st_mode;
  stats->size = os_stats.st_size;
  stats->modified_time = os_stats.st_mtime;
  return absl::OkStatus();
}

absl::Status FileSize(const std::string& path, uint64_t* size) {
  Stats stats;
  absl::Status status = GetStats(path, &stats);
  if (!status.ok()) return status;
  *size = stats.size;
  return absl::OkStatus();
}

absl::Status ChangeMode(const std::string& path, uint16_t mode) {
  if (_chmod(path.c_str(), mode) != 0) {
    return MakeStatus("chmod() failed: %s", Util::GetLastStrError());
  }
  return absl::OkStatus();
}

absl::Status WriteFile(const std::string& path, const Buffer& data) {
  return WriteFile(path, data.data(), data.size());
}

absl::Status WriteFile(const std::string& path, const std::string& data) {
  return WriteFile(path, data.data(), data.size());
}

absl::Status WriteFile(const std::string& path, const void* data, size_t len) {
  absl::StatusOr<FILE*> file = OpenFile(path, "wb");
  if (!file.ok()) {
    return file.status();
  }

  if (fwrite_nolock(data, 1, len, *file) != len) {
    return MakeStatus("Failed to write data of size %u to file '%s'", len,
                      path);
  }

  fclose(*file);
  return absl::OkStatus();
}

absl::Status CreateSymlink(const std::string& target,
                           const std::string& link_path, bool is_dir) {
  std::error_code error_code;
  std::filesystem::path target_u8 = std::filesystem::u8path(target);
  std::filesystem::path link_path_u8 = std::filesystem::u8path(link_path);
  if (is_dir) {
    std::filesystem::create_symlink(target_u8, link_path_u8, error_code);
  } else {
    std::filesystem::create_directory_symlink(target_u8, link_path_u8,
                                              error_code);
  }
  return ErrorCodeToCanonicalStatus(
      error_code, "Failed to create symlink '%s' with target '%s'", link_path,
      target);
}

absl::StatusOr<std::string> GetSymlinkTarget(const std::string& link_path) {
  std::error_code error_code;
  std::filesystem::path link_path_u8 = std::filesystem::u8path(link_path);
  std::filesystem::path symlink_target =
      std::filesystem::read_symlink(link_path_u8, error_code);
  if (error_code) {
    return ErrorCodeToCanonicalStatus(error_code, "Failed to read symlink '%s'",
                                      link_path);
  }
  return symlink_target.u8string();
}

bool DirExists(const std::string& path) {
  Stats stats;
  return GetStats(path, &stats).ok() && (stats.mode & MODE_IFMT) == MODE_IFDIR;
}

bool FileExists(const std::string& path) {
  Stats stats;
  return GetStats(path, &stats).ok() && (stats.mode & MODE_IFMT) == MODE_IFREG;
}

bool Exists(const std::string& path) {
  Stats stats;
  return GetStats(path, &stats).ok();
}

absl::Status CreateDir(const std::string& path) {
  std::error_code error_code;
  std::filesystem::create_directory(std::filesystem::u8path(path), error_code);
  return ErrorCodeToCanonicalStatus(error_code,
                                    "Failed to create directory '%s'", path);
}

absl::Status CreateDirRec(const std::string& path) {
  std::error_code error_code;
  std::filesystem::create_directories(std::filesystem::u8path(path),
                                      error_code);
  return ErrorCodeToCanonicalStatus(error_code,
                                    "Failed to create directory '%s'", path);
}

absl::Status RenameFile(const std::string& from_path,
                        const std::string& to_path) {
  if (rename(from_path.c_str(), to_path.c_str()) != 0) {
    return MakeStatus("rename() failed: %s.", Util::GetLastStrError());
  }
  return absl::OkStatus();
}

absl::Status CopyFileRec(const std::string& from_path,
                         const std::string& to_path) {
  std::error_code error;
  std::filesystem::copy(std::filesystem::u8path(from_path),
                        std::filesystem::u8path(to_path),
                        std::filesystem::copy_options::recursive |
                            std::filesystem::copy_options::overwrite_existing,
                        error);
  if (error) {
    return MakeStatus("Failed to copy '%s' to '%s': '%s'", from_path, to_path,
                      error.message());
  }
  return absl::OkStatus();
}

absl::Status RemoveDirRec(const std::string& path) {
  std::error_code error;
  std::filesystem::remove_all(std::filesystem::u8path(path), error);
  if (error) {
    return MakeStatus("Failed to recursively remove directory %s: %s", path,
                      error.message());
  }
  return absl::OkStatus();
}

absl::Status RemoveFile(const std::string& path) {
#if PLATFORM_WINDOWS
  // On Linux, remove() removes also empty directories.
  // On Windows, special handling of empty-directory deletion is required.
  if (DirExists(path)) {
    if (_rmdir(path.c_str()) != 0 && errno != ENOENT) {
      return MakeStatus("_rmdir() failed: %s.", Util::GetLastStrError());
    }
    return absl::OkStatus();
  }
#endif

  if (remove(path.c_str()) != 0 && errno != ENOENT) {
    return MakeStatus("remove() failed: %s.", Util::GetLastStrError());
  }
  return absl::OkStatus();
}

absl::Status ReplaceFile(const std::string& path,
                         const std::string& replacement_path) {
  absl::Status status;

  // Delete old file.
  status = RemoveFile(path);
  if (!status.ok()) {
    return WrapStatus(status, "RemoveFile() failed for '%s'", path);
  }

  // Rename new file to old file.
  status = RenameFile(replacement_path, path);
  if (!status.ok()) {
    return WrapStatus(status, "Rename() failed from '%s' to '%s'",
                      replacement_path, path);
  }

  return absl::OkStatus();
}

absl::Status GetFileTime(const std::string& path, time_t* timestamp) {
#if PLATFORM_WINDOWS
  std::wstring wchar_path = Util::Utf8ToWideStr(path);
  HANDLE handle = CreateFileW(
      wchar_path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL);
  if (handle == INVALID_HANDLE_VALUE) {
    return MakeStatus("Failed to open '%s': %s", path,
                      Util::GetLastWin32Error());
  }

  FILETIME ft;
  int res = GetFileTime(handle, (LPFILETIME)NULL, (LPFILETIME)NULL, &ft);
  CloseHandle(handle);
  if (res == 0) {
    return MakeStatus("Failed to get file time for '%s': %s", path,
                      Util::GetLastWin32Error());
  }
  *timestamp = FileTimeToTimeT(ft);
#elif PLATFORM_LINUX
  Stats st;
  RETURN_IF_ERROR(GetStats(path, &st));
  *timestamp = static_cast<time_t>(st.modified_time);
#endif
  return absl::OkStatus();
}

absl::Status SetFileTime(const std::string& path, time_t timestamp) {
  // Would be great to use std::filesystem::last_write_time here, but
  // apparently there's no reliable conversion from time_t available in C++17,
  // see
  // https://developercommunity.visualstudio.com/t/stdfilesystemfile-time-type-does-not-allow-easy-co/251213
#if PLATFORM_WINDOWS
  HANDLE handle =
      CreateFileW(Util::Utf8ToWideStr(path).c_str(), FILE_WRITE_ATTRIBUTES,
                  FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
                  FILE_ATTRIBUTE_NORMAL, NULL);
  if (handle == INVALID_HANDLE_VALUE) {
    return MakeStatus("Failed to open '%s': %s", path,
                      Util::GetLastWin32Error());
  }

  FILETIME ft = UnixTimeToWindowsFileTime(timestamp);
  int res = SetFileTime(handle, (LPFILETIME)NULL, &ft, &ft);
  CloseHandle(handle);
  if (res == 0) {
    return MakeStatus("Failed to set file time for '%s': %s", path,
                      Util::GetLastWin32Error());
  }
#elif PLATFORM_LINUX
  // Set both actime and modtime to save a stat operation to get the actime.
  struct utimbuf utb;
  utb.actime = timestamp;
  utb.modtime = timestamp;
  if (utime(path.c_str(), &utb) != 0) {
    return MakeStatus("Failed to set file time for '%s': %s", path,
                      Util::GetLastStrError());
  }
#endif

  return absl::OkStatus();
}

std::filesystem::path ToCanonical(const std::string& path) {
  std::string str_path = GetFullPath(path);
#if PLATFORM_WINDOWS
  // Normalize drive prefix if present.
  std::string prefix = GetDrivePrefix(str_path);
  for (auto& c : prefix) {
    c = toupper(c);
  }
  str_path = prefix + str_path.substr(prefix.length());
#endif  // if PLATFORM_WINDOWS
  return std::filesystem::u8path(str_path);
}

bool AreEqual(std::string path1, std::string path2) {
  return ToCanonical(path1).compare(ToCanonical(path2)) == 0;
}
}  // namespace path
}  // namespace cdc_ft


text:
CREATE TABLE author (
  id int NOT NULL,
  name varchar(30) NOT NULL,
  web varchar(100) NOT NULL,
  born date DEFAULT NULL,
  PRIMARY KEY (id)
);

INSERT INTO author (id, name, web, born) VALUES (11, 'Jakub Vrana', 'http://www.vrana.cz/', NULL);
INSERT INTO author (id, name, web, born) VALUES (12, 'David Grudl', 'http://davidgrudl.com/', NULL);

CREATE TABLE tag (
  id int NOT NULL,
  name varchar(20) NOT NULL,
  PRIMARY KEY (id)
);

INSERT INTO tag (id, name) VALUES (21, 'PHP');
INSERT INTO tag (id, name) VALUES (22, 'MySQL');
INSERT INTO tag (id, name) VALUES (23, 'JavaScript');

CREATE TABLE application (
  id int NOT NULL,
  author_id int NOT NULL,
  maintainer_id int,
  title varchar(50) NOT NULL,
  web varchar(100),
  slogan varchar(100) NOT NULL,
  PRIMARY KEY (id),
  CONSTRAINT application_author FOREIGN KEY (author_id) REFERENCES author (id),
  CONSTRAINT application_maintainer FOREIGN KEY (maintainer_id) REFERENCES author (id)
);

CREATE INDEX application_title ON application (title);

INSERT INTO application (id, author_id, maintainer_id, title, web, slogan) VALUES (1, 11, 11, 'Adminer', 'http://www.adminer.org/', 'Database management in single PHP file');
INSERT INTO application (id, author_id, maintainer_id, title, web, slogan) VALUES (2, 11, NULL, 'JUSH', 'http://jush.sourceforge.net/', 'JavaScript Syntax Highlighter');
INSERT INTO application (id, author_id, maintainer_id, title, web, slogan) VALUES (3, 12, 12, 'Nette', 'http://nettephp.com/', 'Nette Framework for PHP 5');
INSERT INTO application (id, author_id, maintainer_id, title, web, slogan) VALUES (4, 12, 12, 'Dibi', 'http://dibiphp.com/', 'Database Abstraction Library for PHP 5');

CREATE TABLE application_tag (
  application_id int NOT NULL,
  tag_id int NOT NULL,
  PRIMARY KEY (application_id, tag_id),
  CONSTRAINT application_tag_tag FOREIGN KEY (tag_id) REFERENCES tag (id),
  CONSTRAINT application_tag_application FOREIGN KEY (application_id) REFERENCES application (id) ON DELETE CASCADE
);

INSERT INTO application_tag (application_id, tag_id) VALUES (1, 21);
INSERT INTO application_tag (application_id, tag_id) VALUES (3, 21);
INSERT INTO application_tag (application_id, tag_id) VALUES (4, 21);
INSERT INTO application_tag (application_id, tag_id) VALUES (1, 22);
INSERT INTO application_tag (application_id, tag_id) VALUES (4, 22);
INSERT INTO application_tag (application_id, tag_id) VALUES (2, 23);

redpajama_set_name: RedPajamaGithub


text:
#include "OgreStableHeaders.h"
#include "OgreRotationAffector.h"
#include "OgreParticleSystem.h"
#include "OgreStringConverter.h"
#include "OgreParticle.h"

namespace Ogre {

    // init statics
	RotationAffector::CmdRotationSpeedRangeStart	RotationAffector::msRotationSpeedRangeStartCmd;
    RotationAffector::CmdRotationSpeedRangeEnd		RotationAffector::msRotationSpeedRangeEndCmd;
    RotationAffector::CmdRotationRangeStart			RotationAffector::msRotationRangeStartCmd;
    RotationAffector::CmdRotationRangeEnd			RotationAffector::msRotationRangeEndCmd;

    //-----------------------------------------------------------------------
	RotationAffector::RotationAffector(ParticleSystem* psys) :
        ParticleAffector(psys),
		mRotationSpeedRangeStart(0),
		mRotationSpeedRangeEnd(0),
		mRotationRangeStart(0),
		mRotationRangeEnd(0)
    {
		mType = "Rotator";

        // Init parameters
        if (createParamDictionary("RotationAffector"))
        {
            ParamDictionary* dict = getParamDictionary();

            dict->addParameter(ParameterDef("rotation_speed_range_start",
				"The start of a range of rotation speed to be assigned to emitted particles.", PT_REAL),
				&msRotationSpeedRangeStartCmd);

			dict->addParameter(ParameterDef("rotation_speed_range_end",
				"The end of a range of rotation speed to be assigned to emitted particles.", PT_REAL),
				&msRotationSpeedRangeEndCmd);

			dict->addParameter(ParameterDef("rotation_range_start",
				"The start of a range of rotation angles to be assigned to emitted particles.", PT_REAL),
				&msRotationRangeStartCmd);

			dict->addParameter(ParameterDef("rotation_range_end",
				"The end of a range of rotation angles to be assigned to emitted particles.", PT_REAL),
				&msRotationRangeEndCmd);
        }
    }

    //-----------------------------------------------------------------------
	void RotationAffector::_initParticle(Particle* pParticle)
	{
		pParticle->setRotation(
            mRotationRangeStart +
            (Math::UnitRandom() *
                (mRotationRangeEnd - mRotationRangeStart)));
        pParticle->rotationSpeed =
            mRotationSpeedRangeStart +
            (Math::UnitRandom() *
                (mRotationSpeedRangeEnd - mRotationSpeedRangeStart));

	}
	//-----------------------------------------------------------------------
    void RotationAffector::_affectParticles(ParticleSystem* pSystem, Real timeElapsed)
    {
        ParticleIterator pi = pSystem->_getIterator();
        Particle *p;
        Real ds;

        // Rotation adjustments by time
        ds = timeElapsed;

		Radian NewRotation;

        while (!pi.end())
        {
            p = pi.getNext();

			NewRotation = p->rotation + (ds * p->rotationSpeed);
			p->setRotation( NewRotation );
        }

    }
    //-----------------------------------------------------------------------
    const Radian& RotationAffector::getRotationSpeedRangeStart(void) const
    {
        return mRotationSpeedRangeStart;
    }
    //-----------------------------------------------------------------------
    const Radian& RotationAffector::getRotationSpeedRangeEnd(void) const
    {
        return mRotationSpeedRangeEnd;
    }
    //-----------------------------------------------------------------------
    void RotationAffector::setRotationSpeedRangeStart(const Radian& val)
    {
        mRotationSpeedRangeStart = val;
    }
    //-----------------------------------------------------------------------
    void RotationAffector::setRotationSpeedRangeEnd(const Radian& val )
    {
        mRotationSpeedRangeEnd = val;
    }
    //-----------------------------------------------------------------------
    const Radian& RotationAffector::getRotationRangeStart(void) const
    {
        return mRotationRangeStart;
    }
    //-----------------------------------------------------------------------
    const Radian& RotationAffector::getRotationRangeEnd(void) const
    {
        return mRotationRangeEnd;
    }
    //-----------------------------------------------------------------------
    void RotationAffector::setRotationRangeStart(const Radian& val)
    {
        mRotationRangeStart = val;
    }
    //-----------------------------------------------------------------------
    void RotationAffector::setRotationRangeEnd(const Radian& val )
    {
        mRotationRangeEnd = val;
    }
	//-----------------------------------------------------------------------

    //-----------------------------------------------------------------------
    //-----------------------------------------------------------------------
    // Command objects
    //-----------------------------------------------------------------------
    //-----------------------------------------------------------------------
    String RotationAffector::CmdRotationSpeedRangeEnd::doGet(const void* target) const
    {
        return StringConverter::toString(
            static_cast<const RotationAffector*>(target)->getRotationSpeedRangeEnd() );
    }
    void RotationAffector::CmdRotationSpeedRangeEnd::doSet(void* target, const String& val)
    {
        static_cast<RotationAffector*>(target)->setRotationSpeedRangeEnd(StringConverter::parseAngle(val));
    }
    //-----------------------------------------------------------------------
    String RotationAffector::CmdRotationSpeedRangeStart::doGet(const void* target) const
    {
        return StringConverter::toString(
            static_cast<const RotationAffector*>(target)->getRotationSpeedRangeStart() );
    }
    void RotationAffector::CmdRotationSpeedRangeStart::doSet(void* target, const String& val)
    {
        static_cast<RotationAffector*>(target)->setRotationSpeedRangeStart(StringConverter::parseAngle(val));
    }

	//-----------------------------------------------------------------------
    String RotationAffector::CmdRotationRangeEnd::doGet(const void* target) const
    {
        return StringConverter::toString(
            static_cast<const RotationAffector*>(target)->getRotationRangeEnd() );
    }
    void RotationAffector::CmdRotationRangeEnd::doSet(void* target, const String& val)
    {
        static_cast<RotationAffector*>(target)->setRotationRangeEnd(StringConverter::parseAngle(val));
    }
    //-----------------------------------------------------------------------
    String RotationAffector::CmdRotationRangeStart::doGet(const void* target) const
    {
        return StringConverter::toString(
            static_cast<const RotationAffector*>(target)->getRotationRangeStart() );
    }
    void RotationAffector::CmdRotationRangeStart::doSet(void* target, const String& val)
    {
        static_cast<RotationAffector*>(target)->setRotationRangeStart(StringConverter::parseAngle(val));
    }
}


text:
object ProjectSettings {
  def projectRootName = "utility-api-client"
  val specs2Version = "3.3"
}


text:
module.exports = Hyperbars = (function(Hyperbars){

	'use strict';

	var h = require('virtual-dom/h'),
		diff = require('virtual-dom/diff'),
		patch = require('virtual-dom/patch'),
		createElement = require('virtual-dom/create-element'),
		htmlparser = require("htmlparser2");

	var isObject = function(a){
		return Object.prototype.toString.call(a) === '[object Object]'
	};

	/**
	 * Parse handlebar template
	 * @param html
	 * @returns {Array}
	 */
	var parse = function(html){
		var tree = [],
			current = null;

		// Create parser
		var parser = new htmlparser.Parser({
			onopentag: function(name, attrs){
				var node = {
					type: 'tag',
					parent: current,
					name: name,
					attributes: attrs,
					children: []
				};

				if(current){
					current.children.push(node);
				}else{
					tree.push(node);
				}
				current = node;
			},
			ontext: function(text){
				// Deal with adjacent blocks and expressions
				var multiple = text.search(/{({[^{}]+})}/) > -1;
				if(multiple){
					text = text.split(/{({[^{}]+})}/g);
					text = text.map(function(item, index, array){
						if(!item || item == "") return undefined;
						if(item == "{") return "";

						if(item[0] == "{" && item.length > 1 && array[index+1] != "}"){
							item = "{"+item+"}";
						}
						if(item[0] == "{" && item.length > 1 && array[index+1] == "}"){
							item = "{{"+item+"}}";
							text[index+1] = "";
						}

						return item;
					}).filter(function(item){ return item != "{" || item != "}" });
				}else{
					text = [text];
				}

				text = text.filter(Boolean);

				text.forEach(function(text){
					var node = {
						type: 'text',
						content: text.replace(/'/g, "\\'")
					};
					if(current){
						current.children.push(node);
					}else{
						tree.push(node);
					}
				});
			},
			onclosetag: function(tagname){
				current = current ? current.parent : null;
			}
		}, {decodeEntities: true});

		// Initiate parsing
		parser.write(html);

		// Stop parser
		parser.end();

		// Return parsed html tree
		return tree;
	};

	Hyperbars.prototype = {

		'setup': function(obj){
			obj = obj || {};
			h = obj.h;
			diff = obj.diff;
			patch = obj.patch;
			createElement = obj.createElement;
			htmlparser = obj.htmlparser;
		},

		/**
		 * Register helpers
		 */
		'registerHelper': function(name, handler){
			Hyperbars.Runtime[name] = handler;
		},

		/**
		 * Compiles HTML to use with virtual-dom
		 *
		 * options params:
		 * | name  | default | description
		 * ---------------------------------
		 * | debug |  false  | outputs the js to console
		 * | raw   |  false  | returns the compiled function as a string
		 *
		 * @param template html
		 * @param options options
		 * @returns * compiled function
		 */
		'compile': function(template, options){
			var partials = this.partials;
			options = options || {};
			options.debug = options.debug || false;
			options.raw = options.raw || false;
			options.cache = options.cache || true;

			// Remove special characters
			template = template.replace(/> </g, '><')
				.replace(/> {{/g, '>{{')
				.replace(/}} </g, '}}<')
				.replace(/\n/g, '')
				.replace(/\t/g, '');

			/**
			 * Injects a pre-compiled partial into the code-generation procedure
			 * @param string handlebar partial expression body
			 * @returns {string}
			 */
			var injectPartial = function(string){
				var regex = /([\S]+="[^"]*")/g,
					parameters = string.split(regex).filter(Boolean),
					headers = parameters[0].split(' ').slice(1),
					partial = partials[headers[0]];

				if(!partial)
					throw new Error('Partial "' + headers[0] + '" is missing. Please add it to Hyperbars.partials.');

				// Partial context setup
				if(headers[1]){
					var context;
					if(headers[1].indexOf('=') > -1){
						context = "context";
						var parameter = headers[1].split('='),
							parsed = block2js(parameter[1]);
						if(parsed.indexOf("''+") == 0) parsed = parsed.slice(3);
						parameters.push(parameter[0] + "=" + parsed);
					}else{
						context = block2js(headers[1]);
						if(context.indexOf("''+") == 0) context = context.slice(3);
					}
				}

				// Partial parameters setup
				parameters = parameters.slice(1).filter(function(s){ return !!s.trim() }).map(function(s){ return s.replace('=',':')});
				return partial.toString() + "(Runtime.merge" + (context ? "(" + context: "(context") + (parameters.length ? ",{"+parameters.join(',')+"}))" : "))");
			};

			/**
			 * Returns a formatted string in javascript format based on handlebar expression
			 * @param string
			 */
			var block2js = function(string){
				if(string == "this") return 'context';
				if(string[0] == '@') return "options['"+string+"']";
				if(string[0] == '>') return injectPartial(string);
				var sanitised = string.replace(/(this).?/, '').replace(/..\//g,'parent.'),
					options = "";

				if(string.indexOf('.') > -1 && string.indexOf('..') == -1){
					var dot = sanitised.indexOf('.');
					options = sanitised.slice(dot);
					sanitised = sanitised.slice(0, dot);
				}

				// Do not encode HTML
				if(sanitised[0] == "{"){
					sanitised = sanitised.slice(1);
					return [
						"h('div',{'innerHTML':",
						"''+" + (sanitised.indexOf('parent') == 0 ? sanitised : "context['"+sanitised+"']" + options),
						"}, [])"
					].join('');
				}
				return "''+" + (sanitised.indexOf('parent') == 0 ? sanitised : "context['"+sanitised+"']" + options);
			};

			/**
			 * Places single quotes around a string.
			 * @param string
			 * @returns {string}
			 */
			var string2js = function(string){
				var open = string.indexOf('{{'),
					close = string.indexOf('}}'),
					value = string.slice(open + 2, close);
				if(open != -1 && close != -1){
					return open > 0 ? "'"+string.slice(0, open)+"'+" + block2js(value) : block2js(value);
				}else{
					return "'"+string+"'"
				}
			};

			/**
			 * Convert vnode to javascript
			 * @param vnode
			 * @returns {string}
			 */
			var node2js = function(vnode){
				if(!vnode.children || !vnode.children.length){
					vnode.children = '[]';
				}
				return 'h(' + [string2js(vnode.name), vnode.attributes, vnode.children].join(',') + ')';
			};

			/**
			 * Converts vtext node to javascript
			 * @param vtext
			 * @returns {*}
			 */
			var text2js = function(vtext){
				return string2js(vtext.content);
			};

			/**
			 * Converts handlebar expression to javascript
			 * @param expression
			 * @returns {*}
			 */
			var parsedExpressionCounter = 0; // for _identity
			var expression2js = function(expression){
				if(expression.indexOf('{{/') > -1){
					return ']})';
				}

				// Parse
				expression = expression
					.replace(/(this).?/, '')
					.replace(/..\//g,'parent.');

				// Function extraction
				// This rewrite of the previous form of whitespace detection was
				// necessary to handle helpers that don't have any attributes.
				const exprEnd  = expression.indexOf('}}'),
					exprBody   = expression.slice(3, exprEnd),
					match      = /([^\s]+)/.exec(exprBody), // grab function name even if no whitespace, e.g. {{#hello-world}}
					fn         = match[0];

				// Attribute extraction
				let unnamedParameterCount = 0;
				var regex = /([\S]+(?:=["']?[^"\s]*['"]?)?)/g,
					parameters = expression
						.substring(3 + fn.length, expression.length)
						.replace('}}', '')
						.split(regex)
						.filter(function(string){return !!string && string != " "})
						.map(function(string){
							if(string.indexOf("=") > -1){
								var s = string.trim().split("=");
								s[1] = s[1].replace(/\\\'/g,"'"); // de-esape single quotes
								if((s[1][0] != '"' && s[1].slice(-1) != '"') &&
							 	   (s[1][0] != "'" && s[1].slice(-1) != "'")){
									if(isNaN(s[1]+0) && s[1] !== 'true' && s[1] !== 'false') {
										s[1] = block2js(s[1]);
										if(s[1].indexOf("''+") == 0){
											s[1] = s[1].slice(3);
										}
									}
								}
								return `${s[0]}: ${s[1]}`;
							}else{
								string = string.trim();
								string = string.replace(/\\\'/g,"'"); // de-esape single quotes
								if((string[0] != '"' && string.slice(-1) != '"') &&
							 	   (string[0] != "'" && string.slice(-1) != "'")){
									if(isNaN(string+0) && string !== 'true' && string !== 'false') {
										string = block2js(string);
										if(string.indexOf("''+") == 0){
											string = string.slice(3);
										}
									}
								}
								return `${unnamedParameterCount ++}: ${string}`;
							}
						});

				parameters.push(`length: ${unnamedParameterCount}`);

				// Function call syntax changed to handle helper names that aren't
				// valid javascript variable names, e.x. "hello-world"
				return [
					"Runtime['",
					fn.replace(/'/g, '\\\''),
					"'](context, " + "{ " + parameters.join(', ') + ", _identity: '_" + (parsedExpressionCounter ++) + '_' + Date.now () + "' }" + ", function(context, parent, options){return ["
				].join('');
			};

			/**
			 * Converts attribute value to javascript
			 * @param attribute
			 * @returns {string}
			 */
			var attrs2js = function(attribute){
				attribute = attribute.replace(/'/g, "\\'");
				var blocks = attribute.split(/({{[^{}]+)}}/g);
				blocks = blocks.map(function(block){
					return isHandlebarExpression(block) ? expression2js(block) : block.indexOf('{{') > -1 ? block2js(block.slice(2)) : "'"+block+"'"
				}).join('+');
				return blocks.replace(/\[\+/g, "[").replace(/\[''\+/g, "[").replace(/\+['']*\]/g, "]");
			};

			/**
			 * True if the argument contains handlebar expression
			 * @param string
			 * @returns {boolean}
			 */
			var isHandlebarExpression = function(string){
				return string.indexOf('{{#') > -1 || string.indexOf('{{/') > -1;
			};

			/**
			 * Returns true if the expression is a non-block helper
			 * @param string
			 * @returns {boolean}}
			 */
			var isNonBlockHelper = function(string) {
				if(isHandlebarExpression(string))
					return false;

				// Blocks and end-block caught above, now test for custom helper
				const match = /{{([^\s}]+)/.exec(string);
				if(!match)
					return false;

				if(Hyperbars.Runtime[match[1]])
					return true;

				return false;
			}

			/**
			 * True is the argument contains handlebar expression
			 * @param string
			 * @returns {boolean}
			 */
			var isHandlebarBlock = function(string){
				return string.indexOf('{{') > -1 && string.indexOf('}}') > -1
			};

			/**
			 * Converts vnode to javascript
			 * @param node
			 */
			var toJavaScript = function(node){
				if(node.children && node.children.length){
					node.children = [
						'[', node.children.map(toJavaScript).join(','), ']'
					].join('').replace(/return \[,/g, "return [").replace(/,\]}\)\]/g, "]})]");
				}

				if(node.attributes){
					node.attributes = [
						'{',
						Object.keys(node.attributes).map(function(name){ return [string2js(name), attrs2js(node.attributes[name])].join(':') }).join(','),
						'}'
					].join('')
				}

				if(node.type == 'text'){
					if(isNonBlockHelper(node.content)) {
						// Non-block helpers look like variables from context, for example:
						// {{hello-world}}
						// We confirm that this is a helper, not a variable, by checking
						// if "hello-world" is registered in Hyperbars.Runtime. If so,
						// we trick expression2js into parsing them as the opening of a block heler.
						// This next statement almost the same thing as rewriting:
						// {{hello-world}}
						// as
						// {{#hello-world}}{{{this}}}{{/hello-world}}
						// Assuming that Hyperbars.registerHelper('hello-world',...) has been called before .compile()
						return [
							expression2js(node.content.replace('{{','{{#')), //
							// Special-case context.h to allow returning partial trees as args to h(),
							// for example: callback({h: [ 'div', {'id': 'myid' }, [] ]}, ...)
							"context.h ? h(...context.h) : ",
							// Special-case context.html to allow returning raw html,
							// for example: callback({html:"<whatever>...</whatever>"}, ...)
							"context.html ? h('div', {'innerHTML':'' + context.html}, []) : ",
							// Generic catch-all to stringify context as return value,
							// for example: callback("hello world", ...)
							"context",
							"]})",
						].join('');
					} else
					// Deal with handlebar expressions in text
					if(isHandlebarExpression(node.content)){
						return expression2js(node.content);
					}else{
						return text2js(node);
					}
				}

				if(node.type == 'tag'){
					return node2js(node);
				}
			};

			// Parse handlebar template using htmlparser
			var parsed = parse(template)[0];

			// Convert to hyperscript
			var fn = [
				'(function(state){var Runtime = Hyperbars.Runtime;var context = state;return ',
				toJavaScript(parsed),
				'}.bind({}))'
			].join('');

			// Remove those pesky line-breaks!
			fn = fn.replace(/(\r\n|\n|\r)/gm,"");

			if(options.debug || this.debug){
				console.log(fn);
			}

			// function is currently a string so eval it and return it
			return options.raw ? fn : eval(fn);
		},

		/**
		 * Dependencies
		 */
		'createElement': createElement,
		'h': h,
		'diff': diff,
		'patch': patch,
		'htmlparser': htmlparser
	};

	Hyperbars.Runtime = {
		'if': function(context, expression, callback){
			if(expression[0]){
				return callback(isObject(expression[0]) ? expression[0] : context, context);
			}
			return "";
		},
		'unless': function(context, expression, callback){
			if(!expression[0]){
				return callback(isObject(expression[0]) ? expression[0] : context, context);
			}
			return "";
		},
		'each': function(context, expression, callback){
			return expression[0].map(function (item, index, array) {
				var options = {};
				options['@index'] = index;
				options['@first'] = index == 0;
				options['@last']  = index == array.length - 1;
				return callback(item, context, options)
			})
		},
		/**
		 * credit: http://stackoverflow.com/a/8625261/5678694
		 */
		'merge': function(){
			var obj = {},
				i = 0,
				il = arguments.length,
				key;
			for (; i < il; i++) {
				for (key in arguments[i]) {
					if (arguments[i].hasOwnProperty(key)) {
						obj[key] = arguments[i][key];
					}
				}
			}
			return obj;
		}
	};

	return new Hyperbars();

})(function(){
	this.debug = false;
	this.partials = {};
});


text:
    <md-progress-circular md-mode="indeterminate"></md-progress-circular>

    Scroll right to see more challenges fast_forward

    <md-content class="md-padding" >

                    <md-button ng-click="ctrl.orderBy()">Participants</md-button>

                    No challenge available

                    <md-button ng-click="ctrl.orderBy(task.$id)">{{ task.title }}</md-button>

                    {{ task.description }}

                    More details

                    {{ctrl.taskCompletion[task.$id] | number: 0}} %

                    <md-button class="name-button"
                               ng-href="#{{ 'profile' | urlFor:{'publicId': ctrl.currentUserParticipant.$id} }}">
                        Yourself
                    </md-button>

                    <md-button class="md-icon-button md-primary small-icon" aria-label="Update"
                               ng-click="ctrl.update(ctrl.event, ctrl.tasks, ctrl.currentUserSolutions, ctrl.profile)">
                        <md-icon md-svg-icon="loop"></md-icon>
                    </md-button>

                            Completed

                            <md-button ng-if="!task.teamFormationMethod" class="md-raised md-primary"
                                       ng-click="ctrl.startMCQ(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                       target="_blank"
                                       ng-disabled="task.closedAt"
                                       aria-label="Start MCQ"
                            >Start MCQ
                            </md-button>

                            <md-button class="md-raised md-primary" ng-if="task.startIRAT"
                                       ng-click="ctrl.startMCQ(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                       target="_blank"
                                       ng-disabled="task.closedAt"
                                       aria-label="Start IRAT"
                            >Start IRAT
                            </md-button>

                            Completed

                        <md-button class="md-raised md-primary"
                                   ng-if="task.formationPattern && !ctrl.currentUserSolutions[task.$id]"
                                   ng-click="ctrl.promptForTeamFormation(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                   target="_blank"
                                   ng-disabled="task.closedAt"
                                   aria-label="Start form team"
                        >Join a Team
                        </md-button>

                            {{ctrl.currentUserSolutions[task.$id]}}
                            <md-button class="md-icon-button md-primary small-icon"
                                       ng-click="ctrl.promptForTeamFormation(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                       target="_blank"
                                       ng-disabled="task.closedAt"
                                       aria-label="Change form team"
                            >
                                <md-icon md-svg-icon="create"></md-icon>
                            </md-button>

                            <md-button class="md-raised md-primary"
                                       ng-if="task.type == 'textResponse' && ctrl.team[task.teamFormationRef].teamLeader && ctrl.team[task.teamFormationRef].teamLeader == ctrl.currentUserParticipant.$id"
                                       ng-click="ctrl.promptForTextResponse(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                       ng-disabled="task.closedAt"
                                       aria-label="Collab Text Response"
                            >Submit Response
                            </md-button>

                            <md-button class="md-raised md-primary"
                                       ng-if="task.type == 'code' && ctrl.team[task.teamFormationRef].teamLeader && ctrl.team[task.teamFormationRef].teamLeader == ctrl.currentUserParticipant.$id"
                                       ng-click="ctrl.promptForCodeResponse(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                       ng-disabled="task.closedAt"
                                       aria-label="Collab Code Challenge"
                            >Submit Code
                            </md-button>

                            <md-button class="md-raised md-primary"
                                       ng-if="task.type == 'linkPattern' && ctrl.team[task.teamFormationRef].teamLeader && ctrl.team[task.teamFormationRef].teamLeader == ctrl.currentUserParticipant.$id"
                                       ng-click="ctrl.promptForLink(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                       ng-disabled="task.closedAt"
                                       aria-label="Collab Link Pattern"
                            >Submit Link
                            </md-button>

                                Your team leader is: {{ctrl.team[task.teamFormationRef][ctrl.team[task.teamFormationRef].teamLeader].displayName}}

                            <md-button class="md-raised md-primary" ng-if="task.type == 'textResponse'"
                                       ng-click="ctrl.promptForTextResponse(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                       ng-disabled="task.closedAt"
                                       aria-label="Cooperative Text Response"
                            >Submit Response
                            </md-button>

                            <md-button class="md-raised md-primary" ng-if="task.type == 'code'"
                                       ng-click="ctrl.promptForCodeResponse(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                       ng-disabled="task.closedAt"
                                       aria-label="Cooperative Code Challenge"
                            >Submit Code
                            </md-button>

                            <md-button class="md-raised md-primary" ng-if="task.type == 'linkPattern'"
                                       ng-click="ctrl.promptForLink(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                       ng-disabled="task.closedAt"
                                       aria-label="Cooperative Link Pattern"
                            >Submit Link
                            </md-button>

                            <md-button class="md-raised md-primary" ng-if="task.startTRAT"
                                       ng-hide="ctrl.checkUser(task.teamFormationRef, ctrl.currentUserParticipant.$id) && ctrl.team[task.teamFormationRef].teamLeader != ctrl.currentUserParticipant.$id && ctrl.team[task.teamFormationRef].teamLeader != undefined"
                                       ng-click="ctrl.startTRAT(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant)"
                                       target="_blank"
                                       ng-disabled="task.closedAt"
                                       aria-label="Start trat"
                            >Start TRAT
                            </md-button>

                                Your team leader is: {{ctrl.team[task.teamFormationRef][ctrl.team[task.teamFormationRef].teamLeader].displayName}}

                            Completed

                            Completed
                            <md-button class="md-icon-button md-primary small-icon"
                                       aria-label="Update link"
                                       ng-click="ctrl.promptForLink(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                       ng-if="task.coopSubmission || (task.openedAt && ctrl.checkUser(task.teamFormationRef, ctrl.currentUserParticipant.$id) && ctrl.team[task.teamFormationRef].teamLeader == ctrl.currentUserParticipant.$id)"
                            >
                                <md-icon md-svg-icon="create"></md-icon>
                            </md-button>

                            Completed
                            <md-button class="md-icon-button md-primary small-icon"
                                                aria-label="Update link"
                                                ng-click="ctrl.promptForTextResponse(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                                ng-if="task.coopSubmission || (task.openedAt && ctrl.checkUser(task.teamFormationRef, ctrl.currentUserParticipant.$id) && ctrl.team[task.teamFormationRef].teamLeader == ctrl.currentUserParticipant.$id)"
                        >
                                <md-icon md-svg-icon="create"></md-icon>
                            </md-button>

                            Completed
                            <md-button class="md-icon-button md-primary small-icon"
                                       aria-label="Update link"
                                       ng-click="ctrl.promptForCodeResponse(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                       ng-if="task.coopSubmission || (task.openedAt && ctrl.checkUser(task.teamFormationRef, ctrl.currentUserParticipant.$id) && ctrl.team[task.teamFormationRef].teamLeader == ctrl.currentUserParticipant.$id)"
                            >
                                <md-icon md-svg-icon="create"></md-icon>
                            </md-button>

                                Completed

                                <md-button class="md-raised md-primary"
                                           ng-href="{{ctrl.startLink(task, ctrl.profile)}}"
                                           target="_blank"
                                           ng-disabled="task.closedAt"
                                           aria-label="Start Challenge"
                                >

                                        Register
                                        Start

                                </md-button>

                        {{ ctrl.currentUserSolutions[task.$id] | cmTruncate: 10 }}
                        <md-button class="md-icon-button md-primary small-icon"
                                   aria-label="Update response"
                                   ng-click="ctrl.promptForTextResponse(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                   ng-disabled="task.closedAt"
                        >
                            <md-icon md-svg-icon="create"></md-icon>
                        </md-button>

                        <md-button class="md-raised md-primary"
                                   ng-click="ctrl.promptForTextResponse(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                   ng-disabled="task.closedAt"
                                   aria-label="Collab Text Response"
                        >Submit Response
                        </md-button>

                          <md-button class="md-raised md-primary"
                                     aria-label="Update response"
                                     ng-click="ctrl.promptForVoteQuestion(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                     ng-disabled="task.closedAt"

                          > Edit Vote
                          </md-button>

                            <md-button class="md-raised md-primary"
                                       target="_blank"
                                       ng-disabled="task.closedAt"
                                       aria-label="Vote Questions"
                                       ng-click="ctrl.promptForVoteQuestion(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                            >
                            Vote
                            </md-button>

                            Completed

                            <md-button class="md-raised md-primary"
                                       target="_blank"
                                       ng-disabled="task.closedAt"
                                       aria-label="Review Question"
                                       ng-click="ctrl.promptForReviewQuestion(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                            > Review Question
                            </md-button>

                                {{ ctrl.currentUserSolutions[task.$id] | cmTruncate: 10 }}
                                <md-button class="md-icon-button md-primary small-icon"
                                           aria-label="Update response"
                                           ng-click="ctrl.promptForTextResponse(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                           ng-if="task.openedAt"
                                >
                                    <md-icon md-svg-icon="create"></md-icon>
                                </md-button>

                                    <md-button class="md-raised md-primary"
                                               aria-label="Submit response"
                                               ng-click="ctrl.promptForTextResponse(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                               ng-disabled="task.closedAt"
                                    > Submit Response
                                    </md-button>

                                {{ ctrl.currentUserSolutions[task.$id] | cmTruncate: 10 }}
                                <md-button class="md-icon-button md-primary small-icon"
                                           aria-label="Update response"
                                           ng-click="ctrl.promptForCodeResponse(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                           ng-if="task.openedAt"
                                >
                                    <md-icon md-svg-icon="create"></md-icon>
                                </md-button>

                                <md-button class="md-raised md-primary"
                                           aria-label="Submit response"
                                           ng-click="ctrl.promptForCodeResponse(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                           ng-disabled="task.closedAt"
                                >
                                    Submit Code
                                </md-button>

                                Completed
                                <md-button class="md-icon-button md-primary small-icon"
                                           aria-label="Update link"
                                           ng-click="ctrl.promptForLink(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                           ng-if="task.openedAt"
                                >
                                    <md-icon md-svg-icon="create"></md-icon>
                                </md-button>

                                    <md-button class="md-raised md-primary"
                                               aria-label="Submit link"
                                               ng-click="ctrl.promptForLink(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                               ng-disabled="task.closedAt"
                                    >
                                        Submit Link
                                    </md-button>

                            Completed

                        <md-button class="md-raised md-primary"
                                   aria-label="Submit survey"
                                   ng-click="ctrl.promptForSurvey(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant, ctrl.currentUserSolutions)"
                                   ng-disabled="task.closedAt" ng-if="!ctrl.currentUserSolutions[task.$id]">
                            Start Survey
                        </md-button>

                            <md-button class="md-raised md-primary"
                                       aria-label="Update_profile"
                                       ng-click="ctrl.editProfileInfo(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant)"
                                       ng-disabled="task.closedAt">
                                Update Profile
                            </md-button>

                            <md-button class="md-raised md-primary"
                                       aria-label="Enter_profile_info"
                                       ng-click="ctrl.editProfileInfo(ctrl.event.$id, task.$id, task, ctrl.currentUserParticipant)"
                                       ng-disabled="task.closedAt">
                                Update Profile
                            </md-button>

                        {{ctrl.solutions[ctrl.currentUserParticipant.$id][task.$id] | displayMentorship}}

                    <md-button class="name-button" ng-href="#{{ 'profile' | urlFor:{'publicId': participant.$id} }}">
                        {{ participant.user.displayName }}
                    </md-button>

                            <md-button class="md-icon-button md-primary small-icon" aria-label="Delete"
                                       ng-click="ctrl.removeParticipant($event, ctrl.event, participant)">
                                <md-icon md-svg-icon="clear"></md-icon>
                            </md-button>

                                 Completed

                                <md-menu>
                                    <md-button ng-click="$mdOpenMenu()">
                                        {{ ctrl.solutions[participant.$id][task.$id] | cmTruncate: 10 }}
                                    </md-button>

                                    <md-menu-content>

{{ ctrl.solutions[participant.$id][task.$id]}}

                                    </md-menu-content>
                                </md-menu>

                                <md-button aria-label="viewCode"
                                           ng-click="ctrl.viewCodeResponse(task, ctrl.solutions[participant.$id][task.$id])">
                                    View Code
                                </md-button>

{{ctrl.solutions[participant.$id][task.$id] | showSchool}}

                                {{ctrl.solutions[participant.$id][task.$id]}}

                                Completed

                            {{ctrl.solutions[participant.$id][task.$id] | displayMentorship}}
                            <md-button class="md-icon-button md-primary small-icon" aria-label="Update"
                                       ng-click="ctrl.reassignMentorPairing(task.taskFrom, task.$id, ctrl.solutions[participant.$id][task.$id], task.mentorAssignmentMethod)"
                                       ng-if="ctrl.profile.$id == ctrl.event.owner.publicId && participant.$id != ctrl.profile.$id">
                                <md-icon md-svg-icon="loop"></md-icon>
                            </md-button>

{{ctrl.solutions[participant.$id][task.teamFormationRef]}} : {{ctrl.coopTeam[task.teamFormationRef][ctrl.teamId[task.teamFormationRef][participant.$id]].status[1]}} / {{ctrl.coopTeam[task.teamFormationRef][ctrl.teamId[task.teamFormationRef][participant.$id]].status[0]}}

                No other participants

    </md-content>

    <clm-pager options="ctrl.pagerOptions" class="md-padding"></clm-pager>


text:
Bestsellers

                {foreach from=$DanhSach item=Sach}

                            HinhAnhSach)}" style="width:200px;height:200px;" class="img-responsive" />

{$Sach->TenSach}

 $123.20

                            <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                <button type="button" data-toggle="tooltip" title="Add to Wish List" onClick=""></button>
                                <button type="button" data-toggle="tooltip" title="Compare this Product" onClick=""></button>

                {/foreach}

Electronics - view all

Laptops

Desktops

Cameras

Mobile Phones

TV & Home Audio

MP3 Players

Aspire Ultrabook Laptop

 $230.00 $241.99 -5%

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

 Strategies for Acquiring Your Own Laptop

 $1,400.00 $1,900.00 -26%

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Laptop Silver black

 $1,142.00 $1,202.00 -5%

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Ideapad Yoga 13-59341124 Laptop

 $2.00

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Hp Pavilion G6 2314ax Notebok Laptop

 $122.00

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Samsung Galaxy S4

 $62.00 $122.00 -50%

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Hp Pavilion G6 2314ax Notebok Laptop

 $122.00

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

FinePix S8400W Long Zoom Camera

 $122.00

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Digital Camera for Elderly

 $92.00 $98.00 -6%

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Aspire Ultrabook Laptop

 $230.00 $241.99 -5%

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

iPhone5

 $123.20

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Samsung Galaxy S4

 $62.00 $122.00 -50%

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

HTC M7 with Stunning Looks

 $337.99

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Aspire Ultrabook Laptop

 $230.00 $241.99 -5%

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Portable Mp3 Player

 $122.00

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

 Strategies for Acquiring Your Own Laptop

 $1,400.00 $1,900.00 -26%

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Laptop Silver black

 $1,142.00 $1,202.00 -5%

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Ideapad Yoga 13-59341124 Laptop

 $2.00

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Mp3 Player

 $122.00

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

FinePix S8400W Long Zoom Camera

 $122.00

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Hp Pavilion G6 2314ax Notebok Laptop

 $122.00

                                        <button class="btn-primary" type="button" onClick="cart.add('34');">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick="wishlist.add('34');"></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick="compare.add('34');"></button>

Samsung Galaxy S4

 $62.00 $122.00 -50%

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Digital Camera for Elderly

 $92.00 $98.00 -6%

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Portable Mp3 Player

 $122.00

                                        <button class="btn-primary" type="button" onClick="cart.add('48');">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Mp3 Player

 $122.00

                                        <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                            <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                            <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Health & Beauty - view all

Hair Care Cream for Men

 $134.00

                            <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Hair Care Products

 $66.80 $90.80 -27%

                            <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Bed Head Foxy Curls Contour Cream

 $88.00

                            <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Shower Gel Perfume for Women

 $95.00 $99.00 -4%

                            <button class="btn-primary" type="button" onClick="cart.add('61');">Add to Cart</button>

                                <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick="wishlist.add('61');"></button>
                                <button type="button" data-toggle="tooltip" title="Add to compare" onClick="compare.add('61');"></button>

Perfumes for Women

 $85.00

                            <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Make Up for Naturally Beautiful Better

 $123.00

                            <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Pnina Tornai Perfume

 $110.00

                            <button class="btn-primary" type="button" onClick="">Add to Cart</button>

                                <button type="button" data-toggle="tooltip" title="Add to wishlist" onClick=""></button>
                                <button type="button" data-toggle="tooltip" title="Add to compare" onClick=""></button>

Free Shipping

Free shipping on order over $1000

Gift Cards

Give the special perfect gift

Reward Points

Earn and spend with ease


text:
module Process

  # This overrides the default version because it is broken if it
  # exists.

  if respond_to? :daemon
    class << self
      remove_method :daemon
    end
  end

  def self.daemon(nochdir=false, noclose=false)
    exit if fork                     # Parent exits, child continues.

    Process.setsid                   # Become session leader.

    exit if fork                     # Zap session leader. See [1].

    Dir.chdir "/" unless nochdir     # Release old working directory.

    if !noclose
      STDIN.reopen File.open("/dev/null", "r")

      null_out = File.open "/dev/null", "w"
      STDOUT.reopen null_out
      STDERR.reopen null_out
    end

    0
  end
end